lovr
lovr copied to clipboard
Running a compute shader which writes to a ShaderBlock breaks rendering on Quest
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
Confirmed that this works on desktop GL, so it seems to be GLES specific. Or driver specific.
GL_DEBUG_OUTPUT says nothing
Barriers are indeed happening when expected, changing them to GL_ALL_BARRIERS_BITS doesn't change anything.
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).
Disabling multisampling fixes this sample but still results in rendering bugs in more complex projects (each eye renders a different thing). Likely multiview related?
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.
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?
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.
Discoveries:
- Making the block
readonlydoesn't change anything - Disabling MSAA fixes the issue (
t.headset.msaacan 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...
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