I'm currently struggling with an issue and couldn't find if there's a workaround for it on Vulkan.
I have some pre-recorded secondary command buffers that are used for all the static draw commands in our game and the viewport/scissor are dynamic - so they have to be updated for each command buffer.
Now the problem is that since the game also uses dynamic resolution, the viewport/scissor recorded on those secondary command buffers might not match the one currently used on the current frame.
It would be great if the restrictions (setting the dynamic states) applied only to the primary command buffer instead, since secondary CBs can't be executed directly anyway.
We could then do something like (and for the record that's basically what we do on DX12):
allocate secondary command buffer
begin secondary command buffer
set scissor
set viewport
end secondary command buffer
if (updateStaticCommandBuffer)
{
begin secondary command buffer
draw
end secondary command buffer
}
execute commands (pass both secondary command buffers)
But currently the dynamic states need to be set on every command buffer, forcing us to recreate the static CBs every time the resolution changes.
@danjtg, this is a known limitation of Vulkan today, and right now we don't have any workaround for it - you just have to re-record the command buffer. You aren't the first person to ask about this, so we were already tracking it internally as a feature request. But it really helps to have a clear explanation of your use case and preferred workaround - thanks for that. We'll leave this open with the enhancement label for the time being.
I wanted to ask a similar question. VK_EXT_descriptor_indexing allows update-after-bind. Would some implementations be trivially capable of similar semantics on the dynamic state? E.g. something like:
```
VkViewportDescriptor vp_descriptor = vkCreateViewportDescriptor();
vkBeginCommandBuffer();
vkCmdBindViewportDescriptor(vp_descriptor);
vkCmdDrawWhatever();
vkEndCommandBuffer();
VkViewport vp = {...};
vkUpdateDynamicState( vp_descriptor, vp );
@krOoze, the implementations we know of that could easily use an update-after-bind state object as you describe, are the ones that expose the state in question as dynamic. What would be the advantage of the state object over calling vkCmdSetViewport on the command buffer? That seems simpler and easier to manage.
@TomOlson Same as for update-after-bind states. It would not require rebuilding the whole command buffer. I.e. it might have near zero CPU usage; one of the main goals of Vulkan.
I think a solution for viewports/scissor boxes is the wrong way to go here. The problem boils down to one of philosophy. More specifically, what does a secondary command buffer mean as a concept.
Direct3D 12 has a concept like secondary command buffers; they call them "bundles". The OP of this thread essentially wants to treat a secondary command buffer the way D3D treats bundles. But that rubs up against two APIs with different philosophies on how rendering has to work.
The core fundamental difference really comes down to render passes. Bear with me.
D3D12 has no render passes. As such, there is no need for a distinction between primary and secondary command buffers. Instead, there is a distinction between direct command lists and "bundle" command lists. And the differences between them are that bundles can be executed into other command lists, and that bundles inherit state from prior commands in the lists they're injected into. The only state they don't inherit is the pipeline state object itself (and primitive topology).
The thinking here is that, when building command lists across threads, each thread (or task) has its own direct command list. They stick commands into these lists. Bundles are just pre-processed packets of command state that presumably don't change often. As such, they're not much different from any other command that you inject into a CB. Once all of the threads have built their direct command lists, the lists are gathered together and submitted to the queue.
Vulkan has two levels of command buffers, but it does completely different things with them.
The primary/secondary command buffer distinction exists to provide a place for render pass begin/end operations (the primary CB), while still providing a way for threads to pour commands into specific subpasses of that render pass (the secondary CBs). Each thread (or task assigned to a thread) builds a large secondary CB that contains all of its commands. Once all of the threads are done, the main thread builds a primary CB, begins a render pass, pours the various secondary CBs into the appropriate subpasses thereof, and submits the primary CB to the queue.
Secondary command buffers do not inherit any state except for render pass state (which must be specified at CB creation time) and query counter state. Secondary command buffers, like direct command lists in D3D12, are expected to be logically isolated from one another.
What the OP is describing is effectively what happens when people try (and fail) to use secondary command buffers like D3D12 bundles. That's why I think a targeted solution for the OP's specific problem of viewports&scissor boxes is wrong.
If it is important for viewport and scissors to be mutable like this, even within secondary CBs, then maybe the Vulkan developers should look into a way to package commands that can inherit state across CB boundaries. Secondary command buffers are the wrong tool for that, obviously, but it wouldn't be unreasonable for Vulkan to create some third type of CB that can handle such a thing. An Inheriting Command Buffer. They would work mostly like secondary CBs, except that you can submit their commands to secondary CBs as well.
I think such a CB type is far more usable for static creation than our current system. It'd certainly be more general than trying to pick and choose what kind of state to mutate in our immutable command buffers...
@NicolBolas I agree that the concepts of both APIs and the way they approach this problem are different, however the issue remains the same: there is currently no way in Vulkan to pre-record a command buffer and re-use it in an environment where only the scissor/viewport are updated.
I mentioned DX12 because when you work on a multi-platform environment I guess we (I?) always try to find common ground and compare features. There were similar limitations on the other side; there was a workaround for it though; unfortunately that workaround doesn't work in Vulkan.
The way I see it, there are multiple ways this issue can be approached.
Secondary command buffers don't inherit any state from the primary command buffer except render pass and framebuffer. What about specifying this dynamic state in VkCommandBufferInheritanceInfo so that it can be inherited too?
@danjtg
What about specifying this dynamic state in VkCommandBufferInheritanceInfo so that it can be inherited too?
It wouldn't be enough. Remember: when you begin a subpass, you must specify a VkSubpassContents type, which can be inline or secondary CBs, not both. And since all state is reset at subpass boundaries, there wouldn't be a place to set the viewport state before submitting secondary CBs.
In order to make more general inheriting of state work, you'd have to either add a new kind of VkSubpassContents type that allows mixed content (both inline and secondary CBs) or just make a third kind of CB. And personally, I think the latter would be a better way to handle it, since the work can be done in the secondary CB thread, rather than in the thread building the primary CB. This also allows secondary CBs to remain essentially isolated, with these other CBs being specifically for these kinds of state-inheriting data.
Most helpful comment
I think a solution for viewports/scissor boxes is the wrong way to go here. The problem boils down to one of philosophy. More specifically, what does a secondary command buffer mean as a concept.
Direct3D 12 has a concept like secondary command buffers; they call them "bundles". The OP of this thread essentially wants to treat a secondary command buffer the way D3D treats bundles. But that rubs up against two APIs with different philosophies on how rendering has to work.
The core fundamental difference really comes down to render passes. Bear with me.
D3D12 has no render passes. As such, there is no need for a distinction between primary and secondary command buffers. Instead, there is a distinction between direct command lists and "bundle" command lists. And the differences between them are that bundles can be executed into other command lists, and that bundles inherit state from prior commands in the lists they're injected into. The only state they don't inherit is the pipeline state object itself (and primitive topology).
The thinking here is that, when building command lists across threads, each thread (or task) has its own direct command list. They stick commands into these lists. Bundles are just pre-processed packets of command state that presumably don't change often. As such, they're not much different from any other command that you inject into a CB. Once all of the threads have built their direct command lists, the lists are gathered together and submitted to the queue.
Vulkan has two levels of command buffers, but it does completely different things with them.
The primary/secondary command buffer distinction exists to provide a place for render pass begin/end operations (the primary CB), while still providing a way for threads to pour commands into specific subpasses of that render pass (the secondary CBs). Each thread (or task assigned to a thread) builds a large secondary CB that contains all of its commands. Once all of the threads are done, the main thread builds a primary CB, begins a render pass, pours the various secondary CBs into the appropriate subpasses thereof, and submits the primary CB to the queue.
Secondary command buffers do not inherit any state except for render pass state (which must be specified at CB creation time) and query counter state. Secondary command buffers, like direct command lists in D3D12, are expected to be logically isolated from one another.
What the OP is describing is effectively what happens when people try (and fail) to use secondary command buffers like D3D12 bundles. That's why I think a targeted solution for the OP's specific problem of viewports&scissor boxes is wrong.
If it is important for viewport and scissors to be mutable like this, even within secondary CBs, then maybe the Vulkan developers should look into a way to package commands that can inherit state across CB boundaries. Secondary command buffers are the wrong tool for that, obviously, but it wouldn't be unreasonable for Vulkan to create some third type of CB that can handle such a thing. An Inheriting Command Buffer. They would work mostly like secondary CBs, except that you can submit their commands to secondary CBs as well.
I think such a CB type is far more usable for static creation than our current system. It'd certainly be more general than trying to pick and choose what kind of state to mutate in our immutable command buffers...