Renderer
Renderer
class includes all drawing commands used by an application and it contains all the Vulkan components we have seen so far.
Scene
is our base class which only contains a Renderer and a timer. Renderer
will be subclassed and the timer in Update reports frames per second per two seconds.
EmptyScene
inherits from Scene
and assigns all its custom implementations to function
s of EventLoop
. We defer the Renderer
creation to the APP_CMD_INIT_WINDOW
event as ANativeWindow
is invalid until then. On APP_CMD_CONFIG_CHANGED
event ANativeWindow_getWidth
and ANativeWindow_getHeight
return the old width and height before orientation change so we swap them as the new resolution.
EmptySceneRenderer
inherits Renderer
and initialize and assembles all the Vulkan components to make our scene work properly.
How all the components are constructed and interact with each other is specified in the constructor, like layers and extensions to use and dependencies of objects, etc. ColorDestinationRenderPass
is chosen for our simple purpose and resettable command pools are created since we are going to record and reset(implicitly) command buffers every frame and therefore, we should allocate command buffers from pools with VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
flag bit.
To get better performance, RenderImpl
have multiple frames being rendered. The picture lists cases of a various number of concurrent frames.
The pipelined effect becomes minor when the application is CPU bottleneck. While the more application is GPU bottleneck, the more concurrent frame count we would choose to be. However, it is not always the right choice. The more concurrent frames, the more input delays. For instance, we may make keypress cause the model to play some animation. Though the keypress effects on the model apply to the offscreen frame with no delay it will be several frames later to be presented.
The actual workflow is a little bit of complex so we split it into the following parts.
- We only deal with the case where queue families of presentation and graphics are identical which is also a much simpler and more performant way.
- If device orientation changes, we must recreate the swapchain as widow surface and resolution become invalid.
vkWaitForFences
waits for the fence at positioncurrentFrameIndex
to complete andvkResetFences
resets it to unsignaled state.
In the beginning n loop: fences are created at signaled state sovkWaitForFences
does not wait at all. (n isswapchain->ConcurrentFramesCount()
)
Others: It’ll wait until the corresponding command is completed.currentFrameToImageindex
keepscurrentFrameIndex
toimageIndex
mappings.
In the first loop: It is empty so the conditional expression is false.
Others: Release the corresponding framebuffer since we are going to create a new one for the current frame and erase the mapping, then.vkAcquireNextImageKHR
acquire an image from the swapchain, along with the position(imageIndex
) in swapchain at which the image resides, and signalsimageAvailableSemaphores[currentFrameIndex]
when it completes.imageIndexToCurrentFrame
keepsimageIndex
tocurrentFrameIndex
mappings.
In the first loop: It is empty so the conditional expression is false.
Others: A concept I didn’t notice is that the image acquired byvkAcquireNextImageKHR
is not necessarily to be totally free. It may still be being rendered at the time being returned byvkAcquireNextImageKHR
so we have to check the corresponding fence as well.- Once retrieving
imageIndex
we createVkFramebuffer
offramebuffers[imageIndex]
withswapchain->ImageViews()[imageIndex]
and the render pass. Then we keep a mapping betweencurrentFrameIndex
andimageIndex
. What we want to do is that we want to know what framebuffer to deal with when we find a particular fence is completed in the future frames. - Build a command buffer at the position
imageIndex
ofcommandBuffers
. - Submit the command buffer created to the graphics queue. The submission will wait at stage
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT-_BIT
forimageAvailableSemaphores[currentFrameIndex]
to be signaled, and signalcommandsCompleteSemaphores[currentFrameIndex]
andmultiFrameFences[currentFrameIndex]
when it completes. - Wait for
commandsCompleteSemaphores[currentFrameIndex]
to be signaled and present the image to the screen. - Make
currentFrameIndex
cycle around the range [0,swapchain->ConcurrentFramesCount()
] to update the semaphore and fences to use in the next loop.
Note
We might have seen some Vulkan sample create and record command buffer and framebuffer before render loop begins and that is totally fine as long as our rendering data does not change during loops, like only changes of transformation matrices. However, we want to present a more generic way and check what the performance will be.