by David Zhao Akeley
This sample provides an example of how indirect draw commands and the
optional
VK_NV_inherited_viewport_scissor
extension
can be used to maximize secondary command buffer re-use. The indirect draw
commands allow a pre-recorded subpass secondary command buffer to deal with
varying model counts and positions, while the extension helps deal with
changing viewport sizes (due to the user resizing the window, etc.).
The extension is not required to run the sample; the sample can still be
used in this case as a simple example of secondary command buffer re-use.
In core Vulkan (at time of writing), secondary command buffers do not inherit any state from the calling primary command buffer except for the active render pass and subpass. This means that secondary command buffers must set the dynamic viewport and scissor state themselves, and typically must be re-recorded with new state when the window is resized, even though all the other commands are unchanged. The extension provides a way to avoid this inefficiency.
inherited.cpp
Without the extension enabled, the sample is able to re-use a
secondary subpass command buffer only when the viewport that the
command buffer is meant to draw to has not been resized. A command
buffer is paired with the VkViewport
state it was recorded with in
the SubpassSecondary
struct. The App::needNewSecondaryCmdBuf
function detects when the secondary command buffer must be
re-recorded, which is when:
minDepth
/maxDepth
bounds changed.
As each of the four viewports on screen needs different viewport/scissor
state, one secondary command buffer is needed for each one; they are
identical except for different vkCmdSetViewport
and
vkCmdSetScissor
commands. These secondary command buffers are stored
in App::m_subpassSecondaryArray
.
The extension defines the following extension structure for
VkCommandBufferInheritanceInfo
:
// Provided by VK_NV_inherited_viewport_scissor
typedef struct VkCommandBufferInheritanceViewportScissorInfoNV {
VkStructureType sType;
const void* pNext;
VkBool32 viewportScissor2D;
uint32_t viewportDepthCount;
const VkViewport* pViewportDepths;
} VkCommandBufferInheritanceViewportScissorInfoNV;
If the extension structure is present with viewportScissor2D
true,
then the restriction on inheriting viewport and scissor state is
relaxed. The
spec
describes the requirements precisely, but basically, the viewport and
scissor state is that set by the most-recently executed viewport/scissor
setting command, subject to the restrictions:
vkCmdExecuteCommands
, and
after binding a pipeline that defines the state statically.
(This is all true in core Vulkan as well).
minDepth
and
maxDepth
values as the i^{th} entry of pViewportDepths
, unless
this viewport is not consumed by any draw commands. Thus useful
inheritance is effectively limited to the 2D rectangle.Additionally, secondary command buffers that enable viewport/scissor inheritance are specifically forbidden from setting the inherited state themselves.
pViewportDepths
are used only for their
minDepth
and maxDepth
values; x
, y
, width
, and height
are ignored. We decided to re-use the VkViewport
struct (rather than
defining a new “depth only” viewport structure) as that allows more
code reuse between the VK_NV_inherited_viewport_scissor
code path
and the fallback code path.
Start by checking if the extension is supported by checking for the
VK_NV_INHERITED_VIEWPORT_SCISSOR_EXTENSION_NAME
string and placing a
VkPhysicalDeviceInheritedViewportScissorFeaturesNV
structure on the
pNext
chain of VkPhysicalDeviceFeatures2
, as usual.
To enable viewport/scissor inheritance for a secondary command buffer,
add a
VkCommandBufferInheritanceViewportScissorInfoNV
structure to the pNext
chain of
VkCommandBufferInheritanceInfo
with viewportScissor2D == VK_TRUE
. This is done in
App::replaceSecondaryCmdBuf
in the sample.
// The extension struct needed to enable inheriting 2D viewport+scisors.
VkCommandBufferInheritanceViewportScissorInfoNV inheritViewportInfo {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_VIEWPORT_SCISSOR_INFO_NV,
nullptr, // pNext
VK_TRUE, // viewportScissor2D
1, // viewportDepthCount
&viewport // pViewportDepths
};
When this inheritance is done, we also must not set the viewport and scissor state in the secondary command buffer, as would otherwise usually be done.
The sample records the fact that this inheritance is enabled for the
command buffer in SubpassSecondary::inheritViewportScissor
; if this is
true, there is no longer any need to match the target viewport's 2D size,
and we skip the matching-2D-rectangle check in App::needNewSecondaryCmdBuf
.
pViewportDepth
-matching requirement discussed earlier.
Finally, in App::doFrame
, when viewport/scissor inheritance is enabled, we:
The sample does this in the calling primary command buffer
(re-recorded every frame), but, it is also allowed to set the state in
an earlier secondary command buffer in the same vkCmdExecuteCommands
call.
That's about it. When the extension is supported, you can tap the i
key to toggle viewport/scissor inheritance. The sample logs when it
recreates the framebuffer or re-records the secondary command
buffer. You can see that when inheritance is disabled, both the
secondary command buffer and framebuffer are recreated every time the
window changes size, but when inheritance is enabled, only the
framebuffer is recreated upon window resize: