lovr icon indicating copy to clipboard operation
lovr copied to clipboard

Readonly Depth Attachments

Open bjornbytes opened this issue 2 months ago • 1 comments

Vulkan lets you mark the depth attachment as readonly, which can enable some optimizations. It's useful if you've already rendered the depth buffer and just want to use it for depth tests, without writing to it during the pass. LOVR could add support for this:

pass:setCanvas({
  depth = {
    texture = t,
    readonly = true
  }
})

I think in this case the depth clear would be forced to false, and depth write would be forced to false as well, with Pass:setDepthWrite erroring if you try to set it to true.

It gets a little more complicated due to the stencil buffer. Technically you can separately set each "aspect" (depth vs. stencil) as readonly, if you wanted to do some stencil buffer writes/tests during the pass while keeping the depth readonly, or vice versa. Maybe we can have a separate stencilreadonly flag or something...

Another question is whether this would let you sample the depth buffer in a shader. Since you're not writing to it anymore, it should be safe. We could validate that the depth is readonly if you try to send the depth attachment to a shader uniform. I think the code that writes the descriptor would probably still work -- it would use the VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL layout (so it would require both aspects to be readonly, unless you used a texture view or something).

Synchronization would need to be modified to understand that readonly depth attachments are not being written to, only read from.

Related to #869

bjornbytes avatar Oct 21 '25 02:10 bjornbytes

local lg = lovr.graphics
local w, h = 128, 128

local Tex1 = lg.newTexture(w, h, 1, {
  format = 'rgba8', mipmaps = false, samples = 1, usage = { 'render', 'sample' }, label = "Tex1"
})
local DepthTex = lg.newTexture(w, h, 1, {
  format = 'd32f', mipmaps = false, usage = { 'render', 'sample' },
  linear = true, samples = 1, label = 'DepthTex'
})
local pass1 = lg.newPass({ Tex1, depth = DepthTex, samples = 1, label = 'Pas1' })

local Tex2 = lg.newTexture(w, h, 1, {
  format = 'rg11b10f', mipmaps = false, samples = 1, usage = { 'render', 'sample' }, label = "Tex2"
})
local pass2 = lg.newPass({ Tex2, depth = DepthTex, samples = 1, label = 'Pass2' })

local shader = lg.newShader('normal', [[
uniform texture2DArray DepthTex;

vec4 lovrmain() {
  return getPixel(DepthTex, UV, ViewIndex);
}
]])

function lovr.update(dt)
end

function lovr.draw(pass)
  -- depth write
  pass1:reset()
  pass1:sphere(vec3(0, 1.5, -5), vec3(0.2))

  -- depth readonly with depth test
  pass2:reset()
  pass2:setShader(shader)
  pass2:setDepthWrite(false)
  pass2:send('DepthTex', DepthTex)
  pass2:sphere(vec3(0, 1.5, -6), vec3(0.4))

  pass:draw(Tex1, vec3(-3, 1.5, -5))
  pass:draw(Tex2, vec3(3, 1.5, -5))

  return lg.submit(pass1, pass2, pass)
end

Example of current image layout conflict:

Validation Error: [ VUID-VkDescriptorImageInfo-imageLayout-00344 ] | MessageID = 0xde55a405 vkCmdDrawIndexed(): Cannot use VkImage 0x1290000000129[DepthTex] (layer 0, mip 0) with specific layout VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL (specified by sampled image descriptor [VkDescriptorSet 0x3530000000353, Set 2, Binding 0, Index 0, variable "DepthTex"]) that doesn't match the previous known layout VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL. The Vulkan spec states: imageLayout must match the actual VkImageLayout of each subresource accessible from imageView at the time this descriptor is accessed as defined by the image layout matching rules (https://vulkan.lunarg.com/doc/view/1.4.328.1/windows/antora/spec/latest/chapters/descriptorsets.html#VUID-VkDescriptorImageInfo-imageLayout-00344) Objects: 5 [0] VkCommandBuffer 0x2e9a8611a40[Pass2] [1] VkPipeline 0x54b000000054b [2] VkDescriptorSet 0x3530000000353 [3] VkImage 0x1290000000129[DepthTex] [4] VkImageView 0x12a000000012a

bjornbytes avatar Oct 21 '25 02:10 bjornbytes