cleanup and refactoring
This commit is contained in:
parent
2302158928
commit
76f6bf62a4
1285 changed files with 757994 additions and 8 deletions
385
raytracer/nvpro_core/nvvk/swapchain_vk.hpp
Normal file
385
raytracer/nvpro_core/nvvk/swapchain_vk.hpp
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
/** @DOC_START
|
||||
# class nvvk::SwapChain
|
||||
|
||||
> 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
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
The initialization can happen now :
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
During a resizing of a window, you can update the swapchain as well :
|
||||
|
||||
```cpp
|
||||
bool WindowSurface::resize(int w, int h)
|
||||
{
|
||||
...
|
||||
m_swapChain.update(w, h);
|
||||
// be cautious to also transition the image layouts
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
A typical renderloop would look as follows:
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
@DOC_END */
|
||||
|
||||
// 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{};
|
||||
};
|
||||
|
||||
struct SemaphoreEntry
|
||||
{
|
||||
// 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<SemaphoreEntry> m_semaphores;
|
||||
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 VkFormat s_defaultImageFormat = VK_FORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
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 = s_defaultImageFormat,
|
||||
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 = s_defaultImageFormat,
|
||||
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);
|
||||
|
||||
// add one to avoid accidentally missing proper fence wait prior acquire
|
||||
uint32_t getSemaphoreCycleCount() const { return m_imageCount + 1; }
|
||||
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue