Search code examples
openglglsltexturesmemory-barriers

Reading texture data with glGetTexImage after writing from compute shader with imageStore


I'm generating noise into 3D texture in compute shader and then building mesh out of it on CPU. It works fine when I do that in the main loop, but I noticed that I'm only getting ~1% of noise filled on the first render. Here is minimal example, where I'm trying to fill 3D texture with ones in shader, but getting zeroes or noise in return:

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <stdio.h>
#include <stdlib.h>

void error(const char* message) { ... }

const char* slurp_file(const char* filename) { ... }

int main() {
  glfwInit();

  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  GLFWwindow* window = glfwCreateWindow(100, 100, "", NULL, NULL);
  glfwMakeContextCurrent(window);
  gladLoadGL();
  
  // create texture
  GLuint texture;
  glGenTextures(1, &texture);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_3D, texture);

  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

  // allocate it
  size_t size = 4;
  glTexImage3D(GL_TEXTURE_3D, 0, GL_R32F, size, size, size, 0, GL_RED, GL_FLOAT, NULL);

  // create shader
  const char* shader_source = slurp_file("shaders/example.comp");
  GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
  glShaderSource(shader, 1, &shader_source, NULL);
  glCompileShader(shader);

  // check it
  int code;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &code);
  if (!code) error("shader compilation failed");

  // create program
  int program = glCreateProgram();
  glAttachShader(program, shader);
  glLinkProgram(program);

  // check it
  glGetProgramiv(program, GL_LINK_STATUS, &code);
  if (!code) error("program linkage failed");

  // run shader
  glUseProgram(program);
  // i don't think it's needed, as 0 would be a default value
  int uniform_loc = glGetUniformLocation(program, "img_output");
  glUniform1i(uniform_loc, 0);

  // dispatch and sync
  glDispatchCompute(size, size, size);
  // originally i had GL_TEXTURE_FETCH_BARRIER_BIT here
  glMemoryBarrier(GL_ALL_BARRIER_BITS);

  // read texture data
  float* data = malloc(size * size * size * sizeof(float));
  glBindImageTexture(0, texture, 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32F);
  glGetTexImage(GL_TEXTURE_3D, 0, GL_RED, GL_FLOAT, data);

  // prints 0s or noise, but i expect all 1s
  for (int i = 0; i < size * size * size; i++)
    printf("%f ", data[i]);
}

Here is the shader:

#version 430

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0, r32f) writeonly uniform image3D img_output;

void main() {
  ivec3 texture_coords = ivec3(gl_GlobalInvocationID.xyz);

  vec4  pixel = vec4(1, 0, 0, 0);

  imageStore(img_output, texture_coords, pixel);
}

Just adding sleep after glDispatchCompute does nothing, so I think I'm missing some locking? Or maybe texture should be configured in some other way?


Solution

  • You need to bind the texture to the image unit before executing the compute shader. The binding between the texture object and the shader is established through the texture image unit. The shader knows the unit because you set the unit variable or specify the binding point with a layout qualifier, but you also need to bind the object to the unit:

    glBindImageTexture(0, texture, 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32F);
    glDispatchCompute(size, size, size);