lovr icon indicating copy to clipboard operation
lovr copied to clipboard

Running a compute shader which writes to a ShaderBlock breaks rendering on Quest

Open porglezomp opened this issue 5 years ago • 9 comments

Here's a minimal case that demonstrates the issue. This case renders two cubes, one green, using a default shader, and one blue, reading an initially-zero red value from a ShaderBlock.

Expected: If you uncomment the lovr.graphics.compute line, the blue cube should turn purple. Actual: If you uncomment the lovr.graphics.compute line, both cubes disappear.

Interestingly, you can uncomment that line but comment out the write to the variable, and everything works, so it's only caused by compute shaders that perform writes, and not just binding the compute shader etc.

Code:

local shaders = {}

function lovr.load()
    local block = lovr.graphics.newShaderBlock("compute", {
        value = "float",
    }, { readable = true, writable = true })
    local blockCode = block:getShaderCode("block")
    shaders.compute = lovr.graphics.newComputeShader(
         blockCode .. [[
            layout(local_size_x = 1) in;
            void compute() {
                // comment out this line with compute enabled below for success??
                value = 1.0;
            }
        ]])
    shaders.compute:sendBlock("block", block)
    shaders.graphics = lovr.graphics.newShader([[
        vec4 position(mat4 projection, mat4 transform, vec4 vertex) {
            return projection * transform * vertex;
        }
    ]], blockCode .. [[
        vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) {
            return vec4(value, 0.0, 0.5, 1.0);
        }
    ]])
    shaders.graphics:sendBlock("block", block)
end

function lovr.draw()
    lovr.graphics.clear()
    -- uncomment this line to see the failure:
    -- lovr.graphics.compute(shaders.compute)
    lovr.graphics.setShader(shaders.graphics)
    lovr.graphics.cube("fill", 1, 1, -3)
    lovr.graphics.setShader(nil)
    lovr.graphics.setColor(0, 0.5, 0)
    lovr.graphics.cube("fill", -1, 1, -3)
end

porglezomp avatar Jan 31 '20 13:01 porglezomp

Confirmed that this works on desktop GL, so it seems to be GLES specific. Or driver specific.

bjornbytes avatar Feb 01 '20 02:02 bjornbytes

GL_DEBUG_OUTPUT says nothing

bjornbytes avatar Feb 01 '20 02:02 bjornbytes

Barriers are indeed happening when expected, changing them to GL_ALL_BARRIERS_BITS doesn't change anything.

bjornbytes avatar Feb 01 '20 02:02 bjornbytes

Reading data out of the buffer (print(block:read('value')[1])) after the compute also fixes it, I suspect maybe because it maps the buffer.

(I also made a note to fix the fact that ShaderBlock:read is returning a 1-element table if you read a scalar field).

bjornbytes avatar Feb 01 '20 03:02 bjornbytes

Disabling multisampling fixes this sample but still results in rendering bugs in more complex projects (each eye renders a different thing). Likely multiview related?

bjornbytes avatar Feb 22 '20 19:02 bjornbytes

Maybe making the block read-only in the vertex/fragment shader would help. Mobile implementations aren't great at storage writes in graphics stages, so maybe the fact that the buffer can be written to in this shader is causing everything to fall over.

bjornbytes avatar Dec 02 '20 02:12 bjornbytes

How can I request that? Looking at the current API it seems like there's no writable anymore (if there ever was?) and I'm not sure if that controlled the mapping—does this require a change in the engine to try out?

porglezomp avatar Dec 02 '20 07:12 porglezomp

Yeah, it will require a change to test (probably ShaderBlock:getShaderCode takes an optional "access" hint that it uses to decorate the block with readonly/writeonly/readwrite). I'll try this out once I have a spare moment.

bjornbytes avatar Dec 02 '20 07:12 bjornbytes

Discoveries:

  • Making the block readonly doesn't change anything
  • Disabling MSAA fixes the issue (t.headset.msaa can be set to 1)
  • There is now a mysterious GL debug message, but I'm not sure what it means: Punting to direct with MSAA downsample on store optimization enabled

At this point I'm sort of hoping that the pending vulkan backend will either provide more validation feedback or just eliminate the problem entirely...

bjornbytes avatar Dec 02 '20 21:12 bjornbytes

Confirmed working now, here's the updated example:

local shaders = {}

function lovr.load()
    buffer = lovr.graphics.newBuffer(1, 'float')

    shaders.compute = lovr.graphics.newShader([[
        layout(local_size_x = 1) in;
        layout(set = 0, binding = 0) buffer Block { float value; };
        void lovrmain() {
            // comment out this line with compute enabled below for success??
            value = 1.0;
        }
    ]])

    shaders.graphics = lovr.graphics.newShader('unlit', [[
        layout(set = 2, binding = 0) buffer Block { float value; };
        vec4 lovrmain() {
            return vec4(value, 0.0, 0.5, 1.0);
        }
    ]])
end

function lovr.draw(pass)
    local computer = lovr.graphics.getPass('compute')
    computer:setShader(shaders.compute)
    computer:send('Block', buffer)
    -- uncomment this line to see the failure:
    computer:compute()

    pass:setShader(shaders.graphics)
    pass:send('Block', buffer)
    pass:cube(1, 1, -3)
    pass:setShader(nil)
    pass:setColor(0, 0.5, 0)
    pass:cube(-1, 1, -3)

    return lovr.graphics.submit(computer, pass)
end

bjornbytes avatar Sep 22 '22 05:09 bjornbytes