Skip to content

Can’t write to GL_RGBA32UI FBO texture on OpenGL ES

I have two GL_RGBA32UI FBO textures, which I use to store current state of particle positions/velocities per texel.

The first I fill with data like this only once:

Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_RGBA32UI, width, height, 0, GL30.GL_RGBA_INTEGER, GL20.GL_UNSIGNED_INT, buffer);

Per render loop the second one is written to via a shader while the first is used as texture and the second as target. I do that by drawing a quad of from [-1, -1] to [1, 1] while the viewport is set between [0, 0] and [textureSize, textureSize]. This way, in the fragment shader I have a shader run per texel. In each run I read the first texture as input, update it and write it out to the second texture.

Then I render the second FBO’s texture to the screen using a different shader and mesh, where every texel would be represented by one vertex in the mesh. This way I can extract the particle position from the texture and set gl_Position accordingly in the vertex shader.

After that I switch the first and second FBO and continue with the next render loop. This means that the two FBOs are used as a GPU based storage for render data.

This works totally fine on the desktop app and even in the Android emulator. It fails on real Android devices though: The second FBO’s texture of the particular loop has always the values [0, 0, 0, 0] after update on real Android devices only. It totally works fine when just rendering the data from the first buffer, though.

Any idea?

My update shaders (take first FBO’s texture and render it to the second’s) are as follows.

Vertex shader:

#version 300 es

precision mediump float;

in vec2 a_vertex;
out vec2 v_texCoords;

void main()
{
    v_texCoords = a_vertex / 2.0 + 0.5;
    gl_Position = vec4(a_vertex, 0, 1);
}

Fragment shader:

#version 300 es

precision mediump float;
precision mediump usampler2D;

uniform usampler2D u_positionTexture;
uniform float u_delta;

in vec2 v_texCoords;
out uvec4 fragColor;

void main()
{
    uvec4 position_raw = texture(u_positionTexture, v_texCoords);

    vec2 position = vec2(
        uintBitsToFloat(position_raw.x),
        uintBitsToFloat(position_raw.y)
    );
    vec2 velocity = vec2(
        uintBitsToFloat(position_raw.z),
        uintBitsToFloat(position_raw.w)
    );

    // Usually I would alter position and velocity vector here and write it back
    // like this:
    // position += (velocity * u_delta);
    //
    // fragColor = uvec4(
    //  floatBitsToUint(position.x),
    //  floatBitsToUint(position.y),
    //  floatBitsToUint(velocity.x),
    //  floatBitsToUint(velocity.y));

    // Even with this the output is 0 on all channels:
    fragColor = uvec4(
        floatBitsToUint(50.0),
        floatBitsToUint(50.0),
        floatBitsToUint(0.0),
        floatBitsToUint(0.0));

    // Even writing the input directly would not make the correct values appear in the texture pixels:
    // fragColor = position_raw;
}

How I update the textures (from fbo1 to fbo2):

private void updatePositions(float delta) {
  fbo2.begin();
  updateShader.bind();
  Gdx.gl20.glViewport(0, 0, textureSize, textureSize);
  fbo1.getColorBufferTexture().bind(0);
  updateShader.setUniformf("u_delta", delta);
  updateShader.setUniformi("u_positionTexture", 0);
  Gdx.gl20.glDisable(GL20.GL_BLEND);
  Gdx.gl20.glBlendFunc(GL20.GL_ONE, GL20.GL_ZERO);
  updateMesh.render(updateShader, GL20.GL_TRIANGLE_STRIP);
  fbo2.end();
}

Answer

If you are reading a 32-bit per component texture you need a highp sampler and you need to store the result in a highp variable.

Currently you are specifying a mediump for usample2D and the default int precision is also mediump. For integers mediump is specified as “at least” 16-bit, so either of these may result in your 32-bit value being truncated.

Note the “at least” – it’s legal for an implementation to store this at a higher precision – so you may find “it happens to work” on some implementations (like the emulator) because that implementation chooses to use a wider type.