You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
375 lines
13 KiB
375 lines
13 KiB
/*
|
|
* Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
#ifndef NV_VK_SWAPCHAIN_INCLUDED
|
|
#define NV_VK_SWAPCHAIN_INCLUDED
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <vulkan/vulkan_core.h>
|
|
|
|
namespace nvvk {
|
|
|
|
/**
|
|
\class nvvk::SwapChain
|
|
|
|
\brief nvvk::SwapChain is a helper to handle swapchain setup and use
|
|
|
|
In Vulkan, we have to use `VkSwapchainKHR` to request a swap chain
|
|
(front and back buffers) from the operating system and manually
|
|
synchronize our and OS's access to the images within the swap chain.
|
|
This helper abstracts that process.
|
|
|
|
For each swap chain image there is an ImageView, and one read and write
|
|
semaphore synchronizing it (see `SwapChainAcquireState`).
|
|
|
|
To start, you need to call `init`, then `update` with the window's
|
|
initial framebuffer size (for example, use `glfwGetFramebufferSize`).
|
|
Then, in your render loop, you need to call `acquire()` to get the
|
|
swap chain image to draw to, draw your frame (waiting and signalling
|
|
the appropriate semaphores), and call `present()`.
|
|
|
|
Sometimes, the swap chain needs to be re-created (usually due to
|
|
window resizes). `nvvk::SwapChain` detects this automatically and
|
|
re-creates the swap chain for you. Every new swap chain is assigned a
|
|
unique ID (`getChangeID()`), allowing you to detect swap chain
|
|
re-creations. This usually triggers a `VkDeviceWaitIdle`; however, if
|
|
this is not appropriate, see `setWaitQueue()`.
|
|
|
|
Finally, there is a utility function to setup the image transitions
|
|
from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
|
which is the format an image must be in before it is presented.
|
|
|
|
Example in combination with nvvk::Context :
|
|
|
|
* get the window handle
|
|
* create its related surface
|
|
* make sure the Queue is the one we need to render in this surface
|
|
|
|
\code{.cpp}
|
|
// could {.cpp}be arguments of a function/method :
|
|
nvvk::Context ctx;
|
|
NVPWindow win;
|
|
...
|
|
|
|
// get the surface of the window in which to render
|
|
VkWin32SurfaceCreateInfoKHR createInfo = {};
|
|
... populate the fields of createInfo ...
|
|
createInfo.hwnd = glfwGetWin32Window(win.m_internal);
|
|
result = vkCreateWin32SurfaceKHR(ctx.m_instance, &createInfo, nullptr, &m_surface);
|
|
|
|
...
|
|
// make sure we assign the proper Queue to m_queueGCT, from what the surface tells us
|
|
ctx.setGCTQueueWithPresent(m_surface);
|
|
\endcode
|
|
|
|
The initialization can happen now :
|
|
|
|
\code{.cpp}
|
|
m_swapChain.init(ctx.m_device, ctx.m_physicalDevice, ctx.m_queueGCT, ctx.m_queueGCT.familyIndex,
|
|
m_surface, VK_FORMAT_B8G8R8A8_UNORM);
|
|
...
|
|
// after init or update you also have to setup the image layouts at some point
|
|
VkCommandBuffer cmd = ...
|
|
m_swapChain.cmdUpdateBarriers(cmd);
|
|
\endcode
|
|
|
|
During a resizing of a window, you can update the swapchain as well :
|
|
|
|
\code{.cpp}
|
|
bool WindowSurface::resize(int w, int h)
|
|
{
|
|
...
|
|
m_swapChain.update(w, h);
|
|
// be cautious to also transition the image layouts
|
|
...
|
|
}
|
|
\endcode
|
|
|
|
|
|
A typical renderloop would look as follows:
|
|
|
|
\code{.cpp}
|
|
// handles vkAcquireNextImageKHR and setting the active image
|
|
// w,h only needed if update(w,h) not called reliably.
|
|
int w, h;
|
|
bool recreated;
|
|
glfwGetFramebufferSize(window, &w, &h);
|
|
if(!m_swapChain.acquire(w, h, &recreated, [, optional SwapChainAcquireState ptr]))
|
|
{
|
|
... handle acquire error (shouldn't happen)
|
|
}
|
|
|
|
VkCommandBuffer cmd = ...
|
|
|
|
// acquire might have recreated the swap chain: respond if needed here.
|
|
// NOTE: you can also check the recreated variable above, but this
|
|
// only works if the swap chain was recreated this frame.
|
|
if (m_swapChain.getChangeID() != lastChangeID){
|
|
// after init or resize you have to setup the image layouts
|
|
m_swapChain.cmdUpdateBarriers(cmd);
|
|
|
|
lastChangeID = m_swapChain.getChangeID();
|
|
}
|
|
|
|
// do render operations either directly using the imageview
|
|
VkImageView swapImageView = m_swapChain.getActiveImageView();
|
|
|
|
// or you may always render offline int your own framebuffer
|
|
// and then simply blit into the backbuffer. NOTE: use
|
|
// m_swapChain.getWidth() / getHeight() to get blit dimensions,
|
|
// actual swap chain image size may differ from requested width/height.
|
|
VkImage swapImage = m_swapChain.getActiveImage();
|
|
vkCmdBlitImage(cmd, ... swapImage ...);
|
|
|
|
// setup submit
|
|
VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &cmd;
|
|
|
|
// we need to ensure to wait for the swapchain image to have been read already
|
|
// so we can safely blit into it
|
|
|
|
VkSemaphore swapchainReadSemaphore = m_swapChain->getActiveReadSemaphore();
|
|
VkPipelineStageFlags swapchainReadFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = &swapchainReadSemaphore;
|
|
submitInfo.pWaitDstStageMask = &swapchainReadFlags);
|
|
|
|
// once this submit completed, it means we have written the swapchain image
|
|
VkSemaphore swapchainWrittenSemaphore = m_swapChain->getActiveWrittenSemaphore();
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &swapchainWrittenSemaphore;
|
|
|
|
// submit it
|
|
vkQueueSubmit(m_queue, 1, &submitInfo, fence);
|
|
|
|
// present via a queue that supports it
|
|
// this will also setup the dependency for the appropriate written semaphore
|
|
// and bump the semaphore cycle
|
|
m_swapChain.present(m_queue);
|
|
\endcode
|
|
|
|
*/
|
|
|
|
// What SwapChain::acquire produces: a swap chain image plus
|
|
// semaphores protecting it.
|
|
struct SwapChainAcquireState
|
|
{
|
|
// The image and its view and index in the swap chain.
|
|
VkImage image;
|
|
VkImageView view;
|
|
uint32_t index;
|
|
// MUST wait on this semaphore before writing to the image. ("The
|
|
// system" signals this semaphore when it's done presenting the
|
|
// image and can safely be reused).
|
|
VkSemaphore waitSem;
|
|
// MUST signal this semaphore when done writing to the image, and
|
|
// before presenting it. (The system waits for this before presenting).
|
|
VkSemaphore signalSem;
|
|
};
|
|
|
|
|
|
class SwapChain
|
|
{
|
|
private:
|
|
struct Entry
|
|
{
|
|
VkImage image{};
|
|
VkImageView imageView{};
|
|
// be aware semaphore index may not match active image index
|
|
VkSemaphore readSemaphore{};
|
|
VkSemaphore writtenSemaphore{};
|
|
};
|
|
|
|
VkDevice m_device = VK_NULL_HANDLE;
|
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
|
|
|
VkQueue m_queue{};
|
|
VkQueue m_waitQueue{}; // See waitIdle and setWaitQueue.
|
|
uint32_t m_queueFamilyIndex{0};
|
|
|
|
VkSurfaceKHR m_surface{};
|
|
VkFormat m_surfaceFormat{};
|
|
VkColorSpaceKHR m_surfaceColor{};
|
|
|
|
uint32_t m_imageCount{0};
|
|
VkSwapchainKHR m_swapchain{};
|
|
|
|
std::vector<Entry> m_entries;
|
|
std::vector<VkImageMemoryBarrier> m_barriers;
|
|
|
|
// index for current image, returned by vkAcquireNextImageKHR
|
|
// vk spec: The order in which images are acquired is implementation-dependent,
|
|
// and may be different than the order the images were presented
|
|
uint32_t m_currentImage{0};
|
|
// index for current semaphore, incremented by `SwapChain::present`
|
|
uint32_t m_currentSemaphore{0};
|
|
// incremented by `SwapChain::update`, use to update other resources or track changes
|
|
uint32_t m_changeID{0};
|
|
// surface
|
|
VkExtent2D m_extent{0, 0};
|
|
// requested on update
|
|
uint32_t m_updateWidth{0};
|
|
uint32_t m_updateHeight{0};
|
|
// if the swap operation is sync'ed with monitor
|
|
bool m_vsync = false;
|
|
// if vsync is off which mode to prefer
|
|
VkPresentModeKHR m_preferredVsyncOffMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
// usage flags for swapchain images
|
|
VkImageUsageFlags m_imageUsage{};
|
|
|
|
VkResult waitIdle()
|
|
{
|
|
if(m_waitQueue)
|
|
return vkQueueWaitIdle(m_waitQueue);
|
|
else
|
|
return vkDeviceWaitIdle(m_device);
|
|
}
|
|
|
|
// triggers device/queue wait idle
|
|
void deinitResources();
|
|
|
|
public:
|
|
SwapChain(SwapChain const&) = delete;
|
|
SwapChain& operator=(SwapChain const&) = delete;
|
|
|
|
SwapChain() {}
|
|
|
|
static constexpr VkImageUsageFlags s_defaultImageUsage =
|
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
|
|
SwapChain(VkDevice device,
|
|
VkPhysicalDevice physicalDevice,
|
|
VkQueue queue,
|
|
uint32_t queueFamilyIndex,
|
|
VkSurfaceKHR surface,
|
|
VkFormat format = VK_FORMAT_B8G8R8A8_UNORM,
|
|
VkImageUsageFlags imageUsage = s_defaultImageUsage)
|
|
{
|
|
init(device, physicalDevice, queue, queueFamilyIndex, surface, format, imageUsage);
|
|
}
|
|
~SwapChain() { deinit(); }
|
|
|
|
bool init(VkDevice device,
|
|
VkPhysicalDevice physicalDevice,
|
|
VkQueue queue,
|
|
uint32_t queueFamilyIndex,
|
|
VkSurfaceKHR surface,
|
|
VkFormat format = VK_FORMAT_B8G8R8A8_UNORM,
|
|
VkImageUsageFlags imageUsage = s_defaultImageUsage);
|
|
|
|
// triggers queue/device wait idle
|
|
void deinit();
|
|
|
|
// update the swapchain configuration
|
|
// (must be called at least once after init)
|
|
// triggers queue/device wait idle
|
|
// returns actual swapchain dimensions, which may differ from requested
|
|
VkExtent2D update(int width, int height, bool vsync);
|
|
VkExtent2D update(int width, int height) { return update(width, height, m_vsync); }
|
|
|
|
// Returns true on success.
|
|
//
|
|
// Sets active index to the next swap chain image to draw to.
|
|
// The handles and semaphores for this image are optionally written to *pOut.
|
|
//
|
|
// `acquire` and `acquireAutoResize` use getActiveReadSemaphore();
|
|
// `acquireCustom` allows you to provide your own semaphore.
|
|
//
|
|
// If the swap chain was invalidated (window resized, etc.), the
|
|
// swap chain will be recreated, which triggers queue/device wait
|
|
// idle. If you are not calling `update` manually on window resize,
|
|
// you must pass the new swap image size explicitly.
|
|
//
|
|
// WARNING: The actual swap image size might not match what is
|
|
// requested; use getWidth/getHeight to check actual swap image
|
|
// size.
|
|
//
|
|
// If the swap chain was recreated, *pRecreated is set to true (if
|
|
// pRecreated != nullptr); otherwise, set to false.
|
|
//
|
|
// WARNING the swap chain could be spontaneously recreated, even if
|
|
// you are calling `update` whenever the window is resized.
|
|
bool acquire(bool* pRecreated = nullptr, SwapChainAcquireState* pOut = nullptr);
|
|
bool acquireAutoResize(int width, int height, bool* pRecreated, SwapChainAcquireState* pOut = nullptr);
|
|
|
|
// Can be made public if this functionality is needed again.
|
|
private:
|
|
bool acquireCustom(VkSemaphore semaphore, bool* pRecreated = nullptr, SwapChainAcquireState* pOut = nullptr);
|
|
bool acquireCustom(VkSemaphore semaphore, int width, int height, bool* pRecreated, SwapChainAcquireState* pOut = nullptr);
|
|
|
|
public:
|
|
// all present functions bump semaphore cycle
|
|
|
|
// present on provided queue
|
|
void present(VkQueue queue);
|
|
// present using a default queue from init time
|
|
void present() { present(m_queue); }
|
|
// present via a custom function
|
|
// (e.g. when extending via VkDeviceGroupPresentInfoKHR)
|
|
// fills in defaults for provided presentInfo
|
|
// with getActiveImageIndex()
|
|
// and getActiveWrittenSemaphore()
|
|
void presentCustom(VkPresentInfoKHR& outPresentInfo);
|
|
|
|
VkSemaphore getActiveReadSemaphore() const;
|
|
VkSemaphore getActiveWrittenSemaphore() const;
|
|
VkImage getActiveImage() const;
|
|
VkImageView getActiveImageView() const;
|
|
uint32_t getActiveImageIndex() const { return m_currentImage; }
|
|
|
|
uint32_t getImageCount() const { return m_imageCount; }
|
|
VkImage getImage(uint32_t i) const;
|
|
VkImageView getImageView(uint32_t i) const;
|
|
VkFormat getFormat() const { return m_surfaceFormat; }
|
|
|
|
// Get the actual size of the swap chain images.
|
|
uint32_t getWidth() const { return m_extent.width; }
|
|
uint32_t getHeight() const { return m_extent.height; }
|
|
VkExtent2D getExtent() const { return m_extent; }
|
|
|
|
// Get the requested size of the swap chain images. THIS IS RARELY USEFUL.
|
|
uint32_t getUpdateWidth() const { return m_updateWidth; }
|
|
uint32_t getUpdateHeight() const { return m_updateHeight; }
|
|
|
|
bool getVsync() const { return m_vsync; }
|
|
VkSwapchainKHR getSwapchain() const { return m_swapchain; }
|
|
|
|
// does a vkCmdPipelineBarrier for VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
|
|
// must apply resource transitions after update calls
|
|
void cmdUpdateBarriers(VkCommandBuffer cmd) const;
|
|
|
|
uint32_t getChangeID() const;
|
|
|
|
// Ordinarily, `SwapChain` calls vkDeviceWaitIdle before recreating
|
|
// the swap chain. However, if setWaitQueue is called with a
|
|
// non-null queue, we only wait for that queue instead of the whole
|
|
// device. This may be needed if you are using queues in other CPU
|
|
// threads that are not synchronized to the render loop.
|
|
void setWaitQueue(VkQueue waitQueue = VK_NULL_HANDLE) { m_waitQueue = waitQueue; }
|
|
|
|
// typically either VK_PRESENT_MODE_MAILBOX_KHR or VK_PRESENT_MODE_IMMEDIATE_KHR
|
|
void setPreferredVsyncOffMode(VkPresentModeKHR mode) { m_preferredVsyncOffMode = mode; }
|
|
};
|
|
} // namespace nvvk
|
|
#endif
|
|
|