I'm writing a graphics engine in Metal and I'm using the stencil buffer to mask the volumes covered by the Spherical Harmonic lights in the scene. I use two shaders for that, and I need 3 draw calls per light: one for the back faces, another for the front faces, and a final draw call with a different shader to actually render the light.
But, if I understood well Metal documentation, you need to define all your passes "statically", that is, you need a different Render Command Encoder for every shader and render surfaces configurations that you use. Is this correct?
That means that I ended up creating this loop for my lights, which feels quite horrible because I'm creating lots of encoders,
for l in shLights {
let descStencil = createLightAccumulationRenderPass()
guard let encoderStencil = commandBuffer.makeRenderCommandEncoder(descriptor: descStencil) else {
continue
}
drawSHLightStencil(l, encoder: encoderStencil)
encoderStencil.endEncoding()
let descColor = createLightAccumulationRenderPass()
guard let encoderColor = commandBuffer.makeRenderCommandEncoder(descriptor: descColor) else {
continue
}
drawSHLight(l, encoder: encoderColor)
encoderColor.endEncoding()
}
The full code is here: https://github.com/endavid/VidEngine/blob/master/VidFramework/VidFramework/sdk/gfx/plugins/DeferredLightingPlugin.swift (drawSHLights
function)
And if you need more context on how this is used, please check this blogpost: http://endavid.com/index.php?entry=85
I also tried reusing the encoders, but if you don't call endEncoding
, Metal crashes on the next call of makeRenderCommandEncoder
.
Is it possible to combine those encoders in any way?
Edit: I've taken a GPU capture so it's easier to see the whole render pipeline. Here's a screenshot,
It's quite small, but I've put some labels on top. The white labels correspond to the stuff in the loop. There are 3 lights in the scene, and 3 spheres are being lit by them.
But, if I understood well Metal documentation, you need to define all your passes "statically", that is, you need a different Render Command Encoder for every shader and render surfaces configurations that you use. Is this correct?
No, that's not entirely correct. You'll notice that there are some attributes of a render command encoder that are specified via the MTLRenderPassDescriptor
at the time you create the encoder, and there are other attributes that are set via accessors on the encoder after it has been created. The former are immutable for the lifetime of the encoder. The latter can be changed.
So, you do need a new command encoder if you change the render targets (attachments). But you do not need a new command encoder to change the shaders. The shaders are specified by the render pipeline state and can be changed on an existing command encoder using setRenderPipelineState(_:)
.
It is definitely true that you should create your render pipeline state objects once in the lifetime of the app, if at all possible. But you can reuse them as often as needed after that.
Finally, I would not worry too much about creating multiple render command encoders. They are designed to be relatively cheap to create. So, while expending a bit of effort to consolidate all work which can be done with a given encoder together is fine, don't bend over backwards to try to make something "simpler" when it runs against the grain of how things work.