mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2026-03-25 20:51:36 +00:00
feat(frame-pacing): implement basic virtual swapchain
This commit is contained in:
parent
35933799c9
commit
111295af8c
9 changed files with 649 additions and 237 deletions
|
|
@ -5,7 +5,8 @@
|
|||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace ls {
|
||||
/// helper alias for std::reference_wrapper
|
||||
|
|
@ -25,8 +26,7 @@ namespace ls {
|
|||
/// @throws std::logic_error if value already present
|
||||
template<typename... Args>
|
||||
T& emplace(Args&&... args) {
|
||||
if (this->opt.has_value())
|
||||
throw std::logic_error("lazy: value already present");
|
||||
assert(!this->opt.has_value() && "lazy: value already present");
|
||||
|
||||
this->opt.emplace(std::forward<Args>(args)...);
|
||||
return *this->opt;
|
||||
|
|
@ -40,8 +40,7 @@ namespace ls {
|
|||
/// @return reference to value
|
||||
/// @throws std::logic_error if no value present
|
||||
const T& operator*() const {
|
||||
if (!this->opt.has_value())
|
||||
throw std::logic_error("lazy: no value present");
|
||||
assert(this->opt.has_value() && "lazy: no value present");
|
||||
return *this->opt;
|
||||
}
|
||||
|
||||
|
|
@ -49,8 +48,7 @@ namespace ls {
|
|||
/// @return pointer to value
|
||||
/// @throws std::logic_error if no value present
|
||||
const T* operator->() const {
|
||||
if (!this->opt.has_value())
|
||||
throw std::logic_error("lazy: no value present");
|
||||
assert(this->opt.has_value() && "lazy: no value present");
|
||||
return &(*this->opt);
|
||||
}
|
||||
|
||||
|
|
@ -58,8 +56,7 @@ namespace ls {
|
|||
/// @return mutable reference to value
|
||||
/// @throws std::logic_error if no value present
|
||||
T& mut() {
|
||||
if (!this->opt.has_value())
|
||||
throw std::logic_error("lazy: no value present");
|
||||
assert(this->opt.has_value() && "lazy: no value present");
|
||||
return *this->opt;
|
||||
}
|
||||
private:
|
||||
|
|
@ -130,4 +127,21 @@ namespace ls {
|
|||
T* ptr{};
|
||||
std::function<void(T&)> deleter{};
|
||||
};
|
||||
|
||||
/// find a structure in a pNext chain
|
||||
/// @param type the structure type to find
|
||||
/// @param chain the pNext chain
|
||||
/// @return pointer to the found structure, or nullptr
|
||||
template<typename T>
|
||||
T* find_structure(VkStructureType type, const void* chain) noexcept {
|
||||
auto* next = reinterpret_cast<VkBaseInStructure*>(const_cast<void*>(chain));
|
||||
while (next) {
|
||||
if (next->sType == type)
|
||||
return reinterpret_cast<T*>(next);
|
||||
|
||||
next = const_cast<VkBaseInStructure*>(next->pNext);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,13 +20,16 @@ namespace vk {
|
|||
/// @param usage usage flags
|
||||
/// @param importFd optional file descriptor for shared memory
|
||||
/// @param exportFd optional pointer to an integer where the file descriptor will be stored
|
||||
/// @param flags additional flags for image creation
|
||||
/// @param chain optional pNext chain for image creation
|
||||
/// @throws ls::vulkan_error on failure
|
||||
Image(const vk::Vulkan& vk,
|
||||
VkExtent2D extent,
|
||||
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VkImageUsageFlags usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
std::optional<int> importFd = std::nullopt,
|
||||
std::optional<int*> exportFd = std::nullopt);
|
||||
std::optional<int*> exportFd = std::nullopt,
|
||||
VkImageCreateFlags flags = 0, const void* chain = nullptr);
|
||||
|
||||
/// get the image handle
|
||||
/// @return the image handle
|
||||
|
|
|
|||
|
|
@ -117,6 +117,10 @@ namespace vk {
|
|||
PFN_vkAcquireNextImageKHR AcquireNextImageKHR;
|
||||
PFN_vkQueuePresentKHR QueuePresentKHR;
|
||||
PFN_vkDestroySwapchainKHR DestroySwapchainKHR;
|
||||
|
||||
// optional extension functions
|
||||
PFN_vkWaitForPresentKHR WaitForPresentKHR;
|
||||
PFN_vkWaitForPresent2KHR WaitForPresent2KHR;
|
||||
};
|
||||
|
||||
/// initialize vulkan device function pointers
|
||||
|
|
|
|||
|
|
@ -16,16 +16,18 @@ namespace {
|
|||
/// create a image
|
||||
ls::owned_ptr<VkImage> createImage(const vk::Vulkan& vk,
|
||||
VkExtent2D extent, VkFormat format, VkImageUsageFlags usage,
|
||||
bool external) {
|
||||
bool external, VkImageCreateFlags flags, const void* chain) {
|
||||
VkImage handle{};
|
||||
|
||||
const VkExternalMemoryImageCreateInfo externalInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.pNext = chain,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
|
||||
};
|
||||
const VkImageCreateInfo imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = external ? &externalInfo : nullptr,
|
||||
.pNext = external ? &externalInfo : chain,
|
||||
.flags = flags,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {
|
||||
|
|
@ -155,10 +157,12 @@ Image::Image(const vk::Vulkan& vk,
|
|||
VkFormat format,
|
||||
VkImageUsageFlags usage,
|
||||
std::optional<int> importFd,
|
||||
std::optional<int*> exportFd) :
|
||||
std::optional<int*> exportFd,
|
||||
VkImageCreateFlags flags, const void* chain) :
|
||||
image(createImage(vk,
|
||||
extent, format, usage,
|
||||
importFd.has_value() || exportFd.has_value()
|
||||
importFd.has_value() || exportFd.has_value(),
|
||||
flags, chain
|
||||
)),
|
||||
memory(allocateMemory(vk,
|
||||
*this->image,
|
||||
|
|
|
|||
|
|
@ -388,7 +388,12 @@ VulkanDeviceFuncs vk::initVulkanDeviceFuncs(PFN_vkGetDeviceProcAddr f, VkDevice
|
|||
.QueuePresentKHR = graphical ?
|
||||
dpa<PFN_vkQueuePresentKHR>(f, d, "vkQueuePresentKHR") : nullptr,
|
||||
.DestroySwapchainKHR = graphical ?
|
||||
dpa<PFN_vkDestroySwapchainKHR>(f, d, "vkDestroySwapchainKHR") : nullptr
|
||||
dpa<PFN_vkDestroySwapchainKHR>(f, d, "vkDestroySwapchainKHR") : nullptr,
|
||||
|
||||
.WaitForPresentKHR = reinterpret_cast<PFN_vkWaitForPresentKHR>(
|
||||
f(d, "vkWaitForPresentKHR")),
|
||||
.WaitForPresent2KHR = reinterpret_cast<PFN_vkWaitForPresent2KHR>(
|
||||
f(d, "vkWaitForPresent2KHR"))
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,3 +28,5 @@ Checks:
|
|||
- -cppcoreguidelines-pro-bounds-pointer-arithmetic
|
||||
- -cppcoreguidelines-pro-type-union-access
|
||||
- -clang-diagnostic-unsafe-buffer-usage
|
||||
# Vulkan layers override methods, where not all parameters are needed
|
||||
- -readability-named-parameter
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@
|
|||
#include "hooks/layer.hpp"
|
||||
#include "hooks/swapchain.hpp"
|
||||
#include "lsfg-vk-common/helpers/errors.hpp"
|
||||
#include "lsfg-vk-common/helpers/pointers.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
|
@ -318,11 +320,78 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
VkResult myvkGetSwapchainImagesKHR(
|
||||
VkDevice,
|
||||
VkSwapchainKHR swapchain,
|
||||
uint32_t* count,
|
||||
VkImage* images) {
|
||||
const auto& it = layer_info->swapchains.find(swapchain);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
return it->second->GetSwapchainImagesKHR(count, images);
|
||||
}
|
||||
|
||||
VkResult myvkAcquireNextImageKHR(
|
||||
VkDevice,
|
||||
VkSwapchainKHR swapchain,
|
||||
uint64_t timeout,
|
||||
VkSemaphore semaphore,
|
||||
VkFence fence,
|
||||
uint32_t* idx) {
|
||||
const auto& it = layer_info->swapchains.find(swapchain);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
return it->second->AcquireNextImageKHR(timeout, semaphore, fence, idx);
|
||||
}
|
||||
|
||||
VkResult myvkAcquireNextImage2KHR(
|
||||
VkDevice,
|
||||
const VkAcquireNextImageInfoKHR* info,
|
||||
uint32_t* idx) {
|
||||
const auto& it = layer_info->swapchains.find(info->swapchain);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
return it->second->AcquireNextImage2KHR(info, idx);
|
||||
}
|
||||
|
||||
VkResult myvkReleaseSwapchainImagesKHR(
|
||||
VkDevice,
|
||||
const VkReleaseSwapchainImagesInfoKHR* info) {
|
||||
const auto& it = layer_info->swapchains.find(info->swapchain);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
return it->second->ReleaseSwapchainImagesKHR(info);
|
||||
}
|
||||
|
||||
VkResult myvkWaitForPresentKHR(
|
||||
VkDevice,
|
||||
VkSwapchainKHR swapchain,
|
||||
uint64_t id,
|
||||
uint64_t timeout) {
|
||||
const auto& it = layer_info->swapchains.find(swapchain);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
return it->second->WaitForPresentKHR(id, timeout);
|
||||
}
|
||||
|
||||
VkResult myvkWaitForPresent2KHR(
|
||||
VkDevice,
|
||||
VkSwapchainKHR swapchain,
|
||||
const VkPresentWait2InfoKHR* info) {
|
||||
const auto& it = layer_info->swapchains.find(swapchain);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
return it->second->WaitForPresent2KHR(info);
|
||||
}
|
||||
|
||||
VkResult myvkQueuePresentKHR(VkQueue queue,
|
||||
const VkPresentInfoKHR* info) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
|
||||
VkResult result = VK_SUCCESS;
|
||||
|
||||
// re-create out-of-date managed swapchains
|
||||
|
|
@ -337,6 +406,49 @@ namespace {
|
|||
// ignore error: return VK_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
// check for VK_KHR_swapchain_maintenance1
|
||||
std::vector<VkFence> fences(info->swapchainCount, VK_NULL_HANDLE);
|
||||
std::vector<std::optional<VkPresentModeKHR>> modes(info->swapchainCount, std::nullopt);
|
||||
|
||||
const auto* fenceInfo = ls::find_structure<VkSwapchainPresentFenceInfoKHR>(
|
||||
VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_KHR,
|
||||
info->pNext
|
||||
);
|
||||
if (fenceInfo) {
|
||||
for (uint32_t i = 0; i < fenceInfo->swapchainCount; i++)
|
||||
fences[i] = fenceInfo->pFences[i];
|
||||
}
|
||||
|
||||
const auto* presentModeInfo = ls::find_structure<VkSwapchainPresentModeInfoKHR>(
|
||||
VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_KHR,
|
||||
info->pNext
|
||||
);
|
||||
if (presentModeInfo) {
|
||||
for (uint32_t i = 0; i < presentModeInfo->swapchainCount; i++)
|
||||
modes[i] = presentModeInfo->pPresentModes[i];
|
||||
}
|
||||
|
||||
// check for VK_KHR_present_id(2)
|
||||
std::vector<std::optional<uint64_t>> presentIds(info->swapchainCount, std::nullopt);
|
||||
|
||||
const auto* presentIdInfo = ls::find_structure<VkPresentIdKHR>(
|
||||
VK_STRUCTURE_TYPE_PRESENT_ID_KHR,
|
||||
info->pNext
|
||||
);
|
||||
if (presentIdInfo) {
|
||||
for (uint32_t i = 0; i < presentIdInfo->swapchainCount; i++)
|
||||
presentIds[i] = presentIdInfo->pPresentIds[i];
|
||||
}
|
||||
|
||||
const auto* presentId2Info = ls::find_structure<VkPresentId2KHR>(
|
||||
VK_STRUCTURE_TYPE_PRESENT_ID_2_KHR,
|
||||
info->pNext
|
||||
);
|
||||
if (presentId2Info) {
|
||||
for (uint32_t i = 0; i < presentId2Info->swapchainCount; i++)
|
||||
presentIds[i] = presentId2Info->pPresentIds[i];
|
||||
}
|
||||
|
||||
// collect semaphores and values
|
||||
std::vector<uint64_t> waitValues(info->waitSemaphoreCount);
|
||||
std::vector<VkSemaphore> signalSemaphores;
|
||||
|
|
@ -354,6 +466,42 @@ namespace {
|
|||
signalValues.push_back(sync.second);
|
||||
}
|
||||
|
||||
// present all managed swapchains
|
||||
for (uint32_t i = 0; i < info->swapchainCount; i++) {
|
||||
const auto& handle = info->pSwapchains[i];
|
||||
|
||||
const auto& it = layer_info->swapchains.find(handle);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
try {
|
||||
const auto& fence = fences[i];
|
||||
const auto& present_mode = modes[i];
|
||||
const auto& present_id = presentIds[i];
|
||||
|
||||
const MyVkPresentInfo mypresentinfo{
|
||||
.idx = info->pImageIndices[i],
|
||||
.fence = fence,
|
||||
.present_mode = present_mode,
|
||||
.id = present_id
|
||||
};
|
||||
result = it->second->partial_QueuePresentKHR(mypresentinfo);
|
||||
} catch (const ls::vulkan_error& e) {
|
||||
if (e.error() != VK_ERROR_OUT_OF_DATE_KHR) { // swallow out-of-date errors
|
||||
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n"
|
||||
"- " << e.what() << '\n';
|
||||
}
|
||||
result = e.error();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n"
|
||||
"- " << e.what() << '\n';
|
||||
result = VK_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
if (result != VK_SUCCESS && info->pResults)
|
||||
info->pResults[i] = result;
|
||||
}
|
||||
|
||||
// submit the present operation
|
||||
const VkTimelineSemaphoreSubmitInfo timelineInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
|
||||
|
|
@ -380,37 +528,7 @@ namespace {
|
|||
return res;
|
||||
}
|
||||
|
||||
// present all managed swapchains
|
||||
for (uint32_t i = 0; i < info->swapchainCount; i++) {
|
||||
const auto& handle = info->pSwapchains[i];
|
||||
|
||||
const auto& it = layer_info->swapchains.find(handle);
|
||||
if (it == layer_info->swapchains.end())
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
|
||||
try {
|
||||
result = it->second->present(queue,
|
||||
const_cast<void*>(info->pNext),
|
||||
info->pImageIndices[i]
|
||||
);
|
||||
} catch (const ls::vulkan_error& e) {
|
||||
if (e.error() != VK_ERROR_OUT_OF_DATE_KHR) { // swallow out-of-date errors
|
||||
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n"
|
||||
"- " << e.what() << '\n';
|
||||
}
|
||||
result = e.error();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n"
|
||||
"- " << e.what() << '\n';
|
||||
result = VK_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
if (result != VK_SUCCESS && info->pResults)
|
||||
info->pResults[i] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void myvkDestroySwapchainKHR(
|
||||
|
|
@ -461,6 +579,13 @@ VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVers
|
|||
{ "vkDestroyDevice", VKPTR(myvkDestroyDevice) },
|
||||
{ "vkDestroyInstance", VKPTR(myvkDestroyInstance) },
|
||||
{ "vkCreateSwapchainKHR", VKPTR(myvkCreateSwapchainKHR) },
|
||||
{ "vkGetSwapchainImagesKHR", VKPTR(myvkGetSwapchainImagesKHR) },
|
||||
{ "vkAcquireNextImageKHR", VKPTR(myvkAcquireNextImageKHR) },
|
||||
{ "vkAcquireNextImage2KHR", VKPTR(myvkAcquireNextImage2KHR) },
|
||||
{ "vkReleaseSwapchainImagesKHR", VKPTR(myvkReleaseSwapchainImagesKHR) },
|
||||
{ "vkReleaseSwapchainImagesEXT", VKPTR(myvkReleaseSwapchainImagesKHR) },
|
||||
{ "vkWaitForPresentKHR", VKPTR(myvkWaitForPresentKHR) },
|
||||
{ "vkWaitForPresent2KHR", VKPTR(myvkWaitForPresent2KHR) },
|
||||
{ "vkQueuePresentKHR", VKPTR(myvkQueuePresentKHR) },
|
||||
{ "vkDestroySwapchainKHR", VKPTR(myvkDestroySwapchainKHR) }
|
||||
#undef VKPTR
|
||||
|
|
|
|||
|
|
@ -4,35 +4,33 @@
|
|||
#include "instance.hpp"
|
||||
#include "layer.hpp"
|
||||
#include "lsfg-vk-common/helpers/errors.hpp"
|
||||
#include "lsfg-vk-common/helpers/pointers.hpp"
|
||||
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
|
||||
#include "lsfg-vk-common/vulkan/fence.hpp"
|
||||
#include "lsfg-vk-common/vulkan/semaphore.hpp"
|
||||
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
|
||||
#include "lsfg-vk-common/vulkan/vulkan.hpp"
|
||||
#include "swapchain.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <bits/time.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
using namespace lsfgvk;
|
||||
using namespace lsfgvk::layer;
|
||||
|
||||
namespace {
|
||||
/// helper to acquire the max number of images
|
||||
uint32_t getMaxImages(const vk::Vulkan& vk, VkSurfaceKHR surface) {
|
||||
VkSurfaceCapabilitiesKHR caps{};
|
||||
auto res = vk.fi().GetPhysicalDeviceSurfaceCapabilitiesKHR(vk.physdev(), surface, &caps);
|
||||
if (res != VK_SUCCESS)
|
||||
throw ls::vulkan_error(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR() failed");
|
||||
|
||||
return caps.maxImageCount;
|
||||
}
|
||||
/// helper to get swapchain images
|
||||
std::vector<VkImage> getSwapchainImages(const vk::Vulkan& vk, VkSwapchainKHR swapchain) {
|
||||
uint32_t imageCount = 0;
|
||||
|
|
@ -53,167 +51,409 @@ MyVkSwapchain::MyVkSwapchain(MyVkLayer& layer, MyVkInstance& instance, MyVkDevic
|
|||
VkSwapchainCreateInfoKHR info,
|
||||
const std::function<VkSwapchainKHR(VkSwapchainCreateInfoKHR*)>& createFunc) :
|
||||
layer(std::ref(layer)), instance(std::ref(instance)), device(std::ref(device)),
|
||||
extent(info.imageExtent), format(info.imageFormat) {
|
||||
// modify create info
|
||||
const uint32_t maxImageCount = getMaxImages(this->device.get().vkd(), info.surface);
|
||||
info.minImageCount += layer.profile().multiplier;
|
||||
if (maxImageCount && info.minImageCount > maxImageCount)
|
||||
info.minImageCount = maxImageCount;
|
||||
presentSemaphore(device.vkd(), 0), presentIndex(1) {
|
||||
const auto& vk = device.vkd();
|
||||
|
||||
info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
// ensure underlying swapchain supports transfer operations
|
||||
info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
// create swapchain
|
||||
this->handle = createFunc(&info);
|
||||
this->images = getSwapchainImages(this->device.get().vkd(), this->handle);
|
||||
|
||||
this->reinitialize();
|
||||
}
|
||||
|
||||
void MyVkSwapchain::reinitialize() {
|
||||
const auto& vk = this->device.get().vkd();
|
||||
this->generator.emplace(this->layer, this->device,
|
||||
this->extent,
|
||||
this->format
|
||||
// check for VK_KHR_swapchain_mutable_format
|
||||
VkImageCreateFlags flags{};
|
||||
if (info.flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR)
|
||||
flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||
const auto* chain = ls::find_structure<VkImageFormatListCreateInfo>(
|
||||
VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
|
||||
info.pNext
|
||||
);
|
||||
|
||||
this->waitSemaphore.emplace(vk, 0);
|
||||
this->waitValue = 1;
|
||||
// create underlying swapchain
|
||||
this->handle = createFunc(&info);
|
||||
this->swapchainImages = getSwapchainImages(vk, this->handle);
|
||||
|
||||
this->commandBuffer.emplace(vk);
|
||||
this->fence.emplace(vk);
|
||||
// create virtual swapchain images
|
||||
this->images.reserve(this->swapchainImages.size());
|
||||
this->availableImages = std::vector<bool>(this->swapchainImages.size(), true);
|
||||
for (size_t i = 0; i < this->swapchainImages.size(); i++) {
|
||||
this->images.emplace_back(vk,
|
||||
info.imageExtent,
|
||||
info.imageFormat,
|
||||
info.imageUsage,
|
||||
std::nullopt, std::nullopt,
|
||||
flags,
|
||||
reinterpret_cast<const void*>(chain)
|
||||
);
|
||||
}
|
||||
|
||||
const size_t max_flight = std::max(this->images.size(), this->passes.size() + 2);
|
||||
// create thread
|
||||
this->doneSemaphore.emplace(vk, 0);
|
||||
this->thread = std::thread(&MyVkSwapchain::thread_main, this);
|
||||
|
||||
this->passes.clear();
|
||||
for (size_t i = 0; i < max_flight; i++) {
|
||||
this->passes.emplace_back(RenderPass {
|
||||
.commandBuffer = vk::CommandBuffer(vk),
|
||||
// this->reinitialize();
|
||||
}
|
||||
|
||||
// void MyVkSwapchain::reinitialize() {
|
||||
// // ...
|
||||
// }
|
||||
|
||||
MyVkSwapchain::~MyVkSwapchain() noexcept {
|
||||
this->running.store(false);
|
||||
if (this->thread.joinable())
|
||||
this->thread.join();
|
||||
}
|
||||
|
||||
/* Virtual swapchain logic */
|
||||
|
||||
std::pair<VkSemaphore, uint64_t> MyVkSwapchain::sync() {
|
||||
return { this->presentSemaphore.handle(), this->presentIndex++ };
|
||||
}
|
||||
|
||||
#define FILL_BARRIER(handle) \
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, \
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, \
|
||||
.image = (handle), \
|
||||
.subresourceRange = { \
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \
|
||||
.levelCount = 1, \
|
||||
.layerCount = 1 \
|
||||
}
|
||||
|
||||
void MyVkSwapchain::thread_main() noexcept {
|
||||
const auto& vk = this->device.get().vkd();
|
||||
auto& offload = this->device.get().offload();
|
||||
|
||||
struct Pass {
|
||||
vk::Semaphore acquireSemaphore;
|
||||
vk::CommandBuffer commandBuffer;
|
||||
vk::Fence copyFence;
|
||||
vk::Semaphore presentSemaphore;
|
||||
};
|
||||
|
||||
std::vector<Pass> passes;
|
||||
passes.reserve(this->swapchainImages.size() + 1);
|
||||
for (size_t i = 0; i < this->swapchainImages.size() + 1; i++) {
|
||||
passes.emplace_back(Pass {
|
||||
.acquireSemaphore = vk::Semaphore(vk),
|
||||
.pcs = {
|
||||
vk::Semaphore(vk),
|
||||
vk::Semaphore(vk)
|
||||
}
|
||||
.commandBuffer = vk::CommandBuffer(vk),
|
||||
.copyFence = vk::Fence(vk),
|
||||
.presentSemaphore = vk::Semaphore(vk)
|
||||
});
|
||||
}
|
||||
|
||||
this->idx = 0;
|
||||
uint64_t counter{1};
|
||||
while (this->running.load()) {
|
||||
// wait for present signal and fetch the image index
|
||||
if (!this->presentSemaphore.wait(vk, counter, 100'1000))
|
||||
continue; // timeout after 100us
|
||||
counter++;
|
||||
|
||||
if (this->presents.empty()) {
|
||||
// NOTE: the timeline semaphore is only hooked up
|
||||
// after the partial present call, which means this
|
||||
// queue must be filled. no mutex is necessary.
|
||||
|
||||
std::cerr << "lsfg-vk: virtual swapchain encountered an impossible state\n";
|
||||
break;
|
||||
}
|
||||
|
||||
const auto ppi = this->presents.front();
|
||||
this->presents.pop();
|
||||
|
||||
// acquire a real swapchain image
|
||||
const auto& pass = passes[counter % passes.size()];
|
||||
|
||||
uint32_t real_idx{};
|
||||
{
|
||||
const std::scoped_lock<std::mutex> lock(this->swapchainMutex);
|
||||
|
||||
auto res = vk.df().AcquireNextImageKHR(vk.dev(),
|
||||
this->handle, UINT64_MAX,
|
||||
pass.acquireSemaphore.handle(), VK_NULL_HANDLE,
|
||||
&real_idx
|
||||
);
|
||||
if (res != VK_SUCCESS) {
|
||||
status.store(res);
|
||||
|
||||
if (res != VK_SUBOPTIMAL_KHR)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// copy virtual image into real swapchain image
|
||||
const auto& cmdbuf = pass.commandBuffer;
|
||||
cmdbuf.begin(vk);
|
||||
|
||||
auto& virtualImage = this->images.at(ppi.idx);
|
||||
auto& swapchainImage = this->swapchainImages.at(real_idx);
|
||||
|
||||
cmdbuf.blitImage(vk,
|
||||
{
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_NONE,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
FILL_BARRIER(virtualImage.handle())
|
||||
},
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_NONE,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
FILL_BARRIER(swapchainImage)
|
||||
},
|
||||
},
|
||||
{ virtualImage.handle(), swapchainImage },
|
||||
virtualImage.getExtent(),
|
||||
{
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
FILL_BARRIER(swapchainImage)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
cmdbuf.end(vk);
|
||||
|
||||
{
|
||||
const std::scoped_lock<std::mutex> lock(offload.mutex);
|
||||
cmdbuf.submit(vk,
|
||||
{ pass.acquireSemaphore.handle() }, VK_NULL_HANDLE, 0,
|
||||
{ pass.presentSemaphore.handle() }, VK_NULL_HANDLE, 0,
|
||||
pass.copyFence.handle(), offload.queue
|
||||
);
|
||||
}
|
||||
|
||||
// present the real swapchain image
|
||||
const uint64_t presentId = ppi.id.value_or(0);
|
||||
const VkPresentIdKHR presentIdInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR,
|
||||
.swapchainCount = 1,
|
||||
.pPresentIds = &presentId
|
||||
};
|
||||
const auto mode = ppi.present_mode.value_or(VK_PRESENT_MODE_FIFO_KHR);
|
||||
const VkSwapchainPresentModeInfoKHR presentModeInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_KHR,
|
||||
.pNext = ppi.id.has_value() ? &presentIdInfo : nullptr,
|
||||
.swapchainCount = 1,
|
||||
.pPresentModes = &mode
|
||||
};
|
||||
|
||||
const void* chain{};
|
||||
if (ppi.present_mode.has_value())
|
||||
chain = &presentModeInfo;
|
||||
else if (ppi.id.has_value())
|
||||
chain = &presentIdInfo;
|
||||
const VkPresentInfoKHR presentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.pNext = chain,
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &pass.presentSemaphore.handle(),
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &this->handle,
|
||||
.pImageIndices = &real_idx,
|
||||
};
|
||||
{
|
||||
const std::scoped_lock<std::mutex> lock(offload.mutex);
|
||||
const std::scoped_lock<std::mutex> lock2(this->swapchainMutex);
|
||||
auto res = vk.df().QueuePresentKHR(offload.queue, &presentInfo);
|
||||
if (res != VK_SUCCESS) {
|
||||
status.store(res);
|
||||
|
||||
if (res != VK_SUBOPTIMAL_KHR)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ppi.id.has_value()) {
|
||||
this->doneSemaphore->signal(vk, presentId + 1);
|
||||
}
|
||||
|
||||
// wait for the copy to finish
|
||||
if (!pass.copyFence.wait(vk, UINT64_MAX)) {
|
||||
std::cerr << "lsfg-vk: virtual swapchain encountered an impossible timeout\n";
|
||||
break;
|
||||
}
|
||||
pass.copyFence.reset(vk);
|
||||
|
||||
// mark image as available again
|
||||
{
|
||||
const std::scoped_lock<std::mutex> lock(this->availabilityMutex);
|
||||
this->availableImages.at(ppi.idx) = true;
|
||||
}
|
||||
|
||||
// signal the fence
|
||||
if (ppi.fence != VK_NULL_HANDLE) {
|
||||
auto res = vk.df().QueueSubmit(offload.queue, 0, nullptr, ppi.fence);
|
||||
if (res != VK_SUCCESS)
|
||||
status.store(res);
|
||||
}
|
||||
}
|
||||
|
||||
if (offload.mutex.try_lock()) { // must be in vkDeviceWaitIdle if locked
|
||||
vk.df().QueueWaitIdle(offload.queue);
|
||||
offload.mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/* Reimplementations of Vulkan functions */
|
||||
|
||||
VkResult MyVkSwapchain::GetSwapchainImagesKHR(uint32_t *count, VkImage *images) noexcept {
|
||||
if (images == nullptr) {
|
||||
*count = static_cast<uint32_t>(this->images.size());
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VkResult res = VK_SUCCESS;
|
||||
auto limit = static_cast<uint32_t>(this->images.size());
|
||||
|
||||
if (*count < limit) {
|
||||
limit = *count;
|
||||
res = VK_INCOMPLETE;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < limit; i++)
|
||||
images[i] = this->images[i].handle();
|
||||
|
||||
*count = limit;
|
||||
return res;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// helper to acquire the next swapchain image
|
||||
uint32_t acquireNextImageKHR(const vk::Vulkan& vk, VkSwapchainKHR swapchain,
|
||||
const vk::Semaphore& semaphore) {
|
||||
uint32_t imageIdx{};
|
||||
/// find and mark an available image
|
||||
std::optional<uint32_t> mark_available(std::vector<bool>& avail, std::mutex& mutex) noexcept {
|
||||
const std::scoped_lock<std::mutex> lock(mutex);
|
||||
for (size_t i = 0; i < avail.size(); i++) {
|
||||
if (!avail[i])
|
||||
continue;
|
||||
|
||||
auto res = vk.df().AcquireNextImageKHR(vk.dev(), swapchain, UINT64_MAX,
|
||||
semaphore.handle(), VK_NULL_HANDLE, &imageIdx);
|
||||
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
|
||||
throw ls::vulkan_error(res, "vkAcquireNextImageKHR() failed");
|
||||
|
||||
return imageIdx;
|
||||
avail[i] = false;
|
||||
return static_cast<uint32_t>(i);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
/// helper to present a swapchain image
|
||||
VkResult queuePresentKHR(const vk::Vulkan& vk, VkQueue queue,
|
||||
VkSwapchainKHR swapchain, uint32_t imageIdx,
|
||||
const vk::Semaphore& waitSemaphore, void* next) {
|
||||
const VkPresentInfoKHR presentInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.pNext = next,
|
||||
.waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &waitSemaphore.handle(),
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &swapchain,
|
||||
.pImageIndices = &imageIdx,
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// find now in microseconds
|
||||
uint64_t nowInUs() noexcept {
|
||||
timespec ts{};
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
|
||||
return static_cast<uint64_t>(ts.tv_sec) * 1'000'000
|
||||
+ static_cast<uint64_t>(ts.tv_nsec) / 1'000;
|
||||
}
|
||||
/// signal a semaphore and fence
|
||||
void signalSemaphoreAndFence(const vk::Vulkan& vk,
|
||||
VkSemaphore semaphore, VkFence fence) noexcept {
|
||||
const VkSubmitInfo info{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.signalSemaphoreCount = semaphore != VK_NULL_HANDLE,
|
||||
.pSignalSemaphores = semaphore != VK_NULL_HANDLE ? &semaphore : nullptr
|
||||
};
|
||||
auto res = vk.df().QueuePresentKHR(queue, &presentInfo);
|
||||
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
|
||||
throw ls::vulkan_error(res, "vkQueuePresentKHR() failed");
|
||||
|
||||
return res;
|
||||
vk.df().QueueSubmit(vk.queue(), 1, &info, fence);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<VkSemaphore, uint64_t> MyVkSwapchain::sync() {
|
||||
return { this->waitSemaphore->handle(), this->waitValue };
|
||||
}
|
||||
|
||||
VkResult MyVkSwapchain::present(VkQueue queue, void* next, uint32_t imageIdx) {
|
||||
VkResult MyVkSwapchain::AcquireNextImageKHR(uint64_t timeout,
|
||||
VkSemaphore semaphore, VkFence fence, uint32_t *idx) noexcept {
|
||||
const auto& vk = this->device.get().vkd();
|
||||
|
||||
// wait for completion of copy
|
||||
if (this->waitValue > 1 && !this->fence->wait(vk, 150ULL * 1000 * 1000))
|
||||
throw ls::vulkan_error(VK_TIMEOUT, "vkWaitForFences() failed");
|
||||
this->fence->reset(vk);
|
||||
// fast path: try to get an available image without blocking
|
||||
if (auto optIdx = mark_available(this->availableImages, this->availabilityMutex)) {
|
||||
*idx = *optIdx;
|
||||
|
||||
// copy swapchain image into backend source image
|
||||
auto& cmdbuf = this->commandBuffer.mut();
|
||||
cmdbuf.begin(vk);
|
||||
|
||||
const auto& sync = this->generator.mut().prepare(cmdbuf,
|
||||
this->images.at(imageIdx)
|
||||
);
|
||||
|
||||
cmdbuf.end(vk);
|
||||
cmdbuf.submit(vk,
|
||||
{}, this->waitSemaphore->handle(), this->waitValue++,
|
||||
{}, sync.first, sync.second,
|
||||
this->fence->handle()
|
||||
);
|
||||
|
||||
// schedule frame generation
|
||||
this->generator.mut().schedule();
|
||||
|
||||
// present generated frames
|
||||
for (size_t i = 0; i < this->generator->count(); i++) {
|
||||
auto& pass = this->passes.at(this->idx % this->passes.size());
|
||||
|
||||
// acquire swapchain image
|
||||
const uint32_t passImageIdx = acquireNextImageKHR(vk, this->handle,
|
||||
pass.acquireSemaphore
|
||||
);
|
||||
|
||||
// copy backend destination image into swapchain image
|
||||
auto& passCmdbuf = pass.commandBuffer;
|
||||
passCmdbuf.begin(vk);
|
||||
|
||||
const auto& sync = this->generator.mut().obtain(passCmdbuf,
|
||||
this->images.at(passImageIdx)
|
||||
);
|
||||
|
||||
passCmdbuf.end(vk);
|
||||
|
||||
std::vector<VkSemaphore> waitSemaphores{ pass.acquireSemaphore.handle() };
|
||||
if (i > 0) { // non-first pass
|
||||
const auto& prev_pass = this->passes.at((this->idx - 1) % this->passes.size());
|
||||
waitSemaphores.push_back(prev_pass.pcs.second.handle());
|
||||
}
|
||||
|
||||
const std::vector<VkSemaphore> signalSemaphores{
|
||||
pass.pcs.first.handle(),
|
||||
pass.pcs.second.handle()
|
||||
};
|
||||
|
||||
passCmdbuf.submit(vk,
|
||||
waitSemaphores, sync.first, sync.second,
|
||||
signalSemaphores, VK_NULL_HANDLE, 0
|
||||
);
|
||||
|
||||
// present swapchain image
|
||||
queuePresentKHR(vk, queue, this->handle,
|
||||
passImageIdx,
|
||||
pass.pcs.first,
|
||||
next
|
||||
);
|
||||
|
||||
next = nullptr; // should only be set for first present
|
||||
this->idx++;
|
||||
signalSemaphoreAndFence(vk, semaphore, fence);
|
||||
return this->status.load();
|
||||
}
|
||||
|
||||
// present original swapchain image
|
||||
const auto& prev_pass = this->passes.at((this->idx - 1) % this->passes.size());
|
||||
return queuePresentKHR(vk, queue, this->handle,
|
||||
imageIdx,
|
||||
prev_pass.pcs.second,
|
||||
next
|
||||
// if call is non-blocking: return not ready
|
||||
if (timeout == 0)
|
||||
return VK_NOT_READY;
|
||||
|
||||
// otherwise: repeat fetch and delay 100us
|
||||
const uint64_t start = nowInUs();
|
||||
timeout *= 1'000; // to microseconds
|
||||
|
||||
while (nowInUs() - start < timeout) {
|
||||
auto res = this->status.load();
|
||||
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
|
||||
return res;
|
||||
|
||||
if (auto optIdx = mark_available(this->availableImages, this->availabilityMutex)) {
|
||||
*idx = *optIdx;
|
||||
|
||||
signalSemaphoreAndFence(vk, semaphore, fence);
|
||||
return res;
|
||||
}
|
||||
|
||||
usleep(100);
|
||||
}
|
||||
|
||||
return VK_TIMEOUT;
|
||||
}
|
||||
|
||||
VkResult MyVkSwapchain::AcquireNextImage2KHR(const VkAcquireNextImageInfoKHR* info,
|
||||
uint32_t* idx) noexcept {
|
||||
return this->AcquireNextImageKHR(
|
||||
info->timeout,
|
||||
info->semaphore,
|
||||
info->fence,
|
||||
idx
|
||||
);
|
||||
}
|
||||
|
||||
VkResult MyVkSwapchain::partial_QueuePresentKHR(const MyVkPresentInfo& info) noexcept {
|
||||
this->presents.emplace(info);
|
||||
return this->status.load();
|
||||
}
|
||||
|
||||
VkResult MyVkSwapchain::ReleaseSwapchainImagesKHR(
|
||||
const VkReleaseSwapchainImagesInfoKHR* info) noexcept {
|
||||
for (uint32_t i = 0; i < info->imageIndexCount; i++) {
|
||||
const uint32_t idx = info->pImageIndices[i];
|
||||
|
||||
const std::scoped_lock<std::mutex> lock(this->availabilityMutex);
|
||||
this->availableImages.at(idx) = true;
|
||||
}
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VkResult MyVkSwapchain::WaitForPresentKHR(uint64_t id, uint64_t timeout) noexcept {
|
||||
const auto& vk = this->device.get().vkd();
|
||||
|
||||
if (!this->doneSemaphore->wait(vk, id + 1, timeout))
|
||||
return VK_TIMEOUT;
|
||||
|
||||
const std::scoped_lock<std::mutex> lock(this->swapchainMutex);
|
||||
const auto& df = vk.df();
|
||||
|
||||
if (df.WaitForPresentKHR) {
|
||||
return vk.df().WaitForPresentKHR(vk.dev(), this->handle, id, timeout);
|
||||
}
|
||||
|
||||
if (df.WaitForPresent2KHR) {
|
||||
const VkPresentWait2InfoKHR info{
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_WAIT_2_INFO_KHR,
|
||||
.presentId = id,
|
||||
.timeout = timeout
|
||||
};
|
||||
return vk.df().WaitForPresent2KHR(vk.dev(), this->handle, &info);
|
||||
}
|
||||
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT; // should never happen
|
||||
}
|
||||
|
||||
VkResult MyVkSwapchain::WaitForPresent2KHR(
|
||||
const VkPresentWait2InfoKHR* info) noexcept {
|
||||
return this->WaitForPresentKHR(
|
||||
info->presentId,
|
||||
info->timeout
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@
|
|||
|
||||
#include "device.hpp"
|
||||
#include "instance.hpp"
|
||||
#include "../generator.hpp"
|
||||
#include "lsfg-vk-common/helpers/pointers.hpp"
|
||||
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
|
||||
#include "lsfg-vk-common/vulkan/fence.hpp"
|
||||
#include "lsfg-vk-common/vulkan/semaphore.hpp"
|
||||
#include "lsfg-vk-common/vulkan/image.hpp"
|
||||
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -20,7 +22,17 @@
|
|||
|
||||
namespace lsfgvk::layer {
|
||||
|
||||
/// swapchain wrapper class
|
||||
/// helper struct for partial present information
|
||||
struct MyVkPresentInfo {
|
||||
uint32_t idx{};
|
||||
// VK_KHR_swapchain_maintenance1
|
||||
VkFence fence{};
|
||||
std::optional<VkPresentModeKHR> present_mode;
|
||||
// VK_KHR_present_id(2)
|
||||
std::optional<uint64_t> id;
|
||||
};
|
||||
|
||||
/// swapchain wrapper (and virtual) class
|
||||
class MyVkSwapchain {
|
||||
public:
|
||||
/// create a vulkan swapchain wrapper
|
||||
|
|
@ -33,61 +45,64 @@ namespace lsfgvk::layer {
|
|||
MyVkSwapchain(MyVkLayer& layer, MyVkInstance& instance, MyVkDevice& device,
|
||||
VkSwapchainCreateInfoKHR info,
|
||||
const std::function<VkSwapchainKHR(VkSwapchainCreateInfoKHR*)>& createFunc);
|
||||
|
||||
/// reinitialize the swapchain resources
|
||||
/// reinitialize the contained swapchain resources
|
||||
/// @throws ls::vulkan_error on vulkan errors
|
||||
void reinitialize();
|
||||
|
||||
/// get the preset synchronization info
|
||||
/// @return pair of wait and signal semaphores
|
||||
std::pair<VkSemaphore, uint64_t> sync();
|
||||
/// get the synchronization pair for presentation
|
||||
/// @return semaphore and value pair
|
||||
[[nodiscard]] std::pair<VkSemaphore, uint64_t> sync();
|
||||
|
||||
/// present a frame
|
||||
/// @param queue presentation queue
|
||||
/// @param next pNext chain pointer
|
||||
/// @param imageIdx swapchain image index to present to
|
||||
/// @throws ls::vulkan_error on vulkan errors
|
||||
VkResult present(VkQueue queue, void* next, uint32_t imageIdx);
|
||||
/// reimplement vkGetSwapchainImagesKHR
|
||||
VkResult GetSwapchainImagesKHR(uint32_t* count, VkImage* images) noexcept;
|
||||
/// reimplement vkAcquireNextImageKHR
|
||||
VkResult AcquireNextImageKHR(uint64_t timeout,
|
||||
VkSemaphore semaphore, VkFence fence, uint32_t* idx) noexcept;
|
||||
/// reimplement vkAcquireNextImage2KHR
|
||||
VkResult AcquireNextImage2KHR(const VkAcquireNextImageInfoKHR* info,
|
||||
uint32_t* idx) noexcept;
|
||||
/// reimplement vkQueuePresentKHR
|
||||
VkResult partial_QueuePresentKHR(const MyVkPresentInfo& info) noexcept;
|
||||
/// reimplement vkReleaseSwapchainImagesKHR
|
||||
VkResult ReleaseSwapchainImagesKHR(const VkReleaseSwapchainImagesInfoKHR* info) noexcept;
|
||||
/// reimplement vkWaitForPresentKHR
|
||||
VkResult WaitForPresentKHR(uint64_t id, uint64_t timeout) noexcept;
|
||||
/// reimplement vkWaitForPresent2KHR
|
||||
VkResult WaitForPresent2KHR(const VkPresentWait2InfoKHR* info) noexcept;
|
||||
|
||||
/// get swapchain extent
|
||||
/// @return swapchain extent
|
||||
[[nodiscard]] VkExtent2D swapchainExtent() const { return this->extent; }
|
||||
/// get swapchain format
|
||||
/// @return swapchain format
|
||||
[[nodiscard]] VkFormat swapchainFormat() const { return this->format; }
|
||||
/// get the underlying real swapchain handle
|
||||
/// @return real swapchain handle
|
||||
[[nodiscard]] auto swapchain() const noexcept { return this->handle; }
|
||||
|
||||
// non-moveable, non-copyable
|
||||
MyVkSwapchain(const MyVkSwapchain&) = delete;
|
||||
MyVkSwapchain& operator=(const MyVkSwapchain&) = delete;
|
||||
MyVkSwapchain(MyVkSwapchain&&) = delete;
|
||||
MyVkSwapchain& operator=(MyVkSwapchain&&) = delete;
|
||||
~MyVkSwapchain() = default;
|
||||
~MyVkSwapchain() noexcept;
|
||||
private:
|
||||
ls::R<MyVkLayer> layer;
|
||||
ls::R<MyVkInstance> instance;
|
||||
ls::R<MyVkDevice> device;
|
||||
|
||||
VkSwapchainKHR handle;
|
||||
VkExtent2D extent;
|
||||
VkFormat format;
|
||||
std::vector<VkImage> images;
|
||||
vk::TimelineSemaphore presentSemaphore;
|
||||
uint64_t presentIndex;
|
||||
|
||||
ls::lazy<Generator> generator;
|
||||
std::mutex swapchainMutex;
|
||||
VkSwapchainKHR handle; // from real swapchain
|
||||
std::vector<VkImage> swapchainImages;
|
||||
|
||||
ls::lazy<vk::TimelineSemaphore> waitSemaphore;
|
||||
uint64_t waitValue{1};
|
||||
std::vector<vk::Image> images; // virtual swapchain images
|
||||
std::mutex availabilityMutex;
|
||||
std::vector<bool> availableImages;
|
||||
//std::mutex presentationMutex; -- should not be necessary!
|
||||
std::queue<MyVkPresentInfo> presents;
|
||||
ls::lazy<vk::TimelineSemaphore> doneSemaphore;
|
||||
|
||||
ls::lazy<vk::CommandBuffer> commandBuffer;
|
||||
ls::lazy<vk::Fence> fence;
|
||||
|
||||
struct RenderPass {
|
||||
vk::CommandBuffer commandBuffer;
|
||||
vk::Semaphore acquireSemaphore;
|
||||
|
||||
std::pair<vk::Semaphore, vk::Semaphore> pcs;
|
||||
};
|
||||
std::vector<RenderPass> passes;
|
||||
size_t idx{0};
|
||||
std::atomic_bool running{true};
|
||||
std::atomic<VkResult> status{VK_SUCCESS};
|
||||
std::thread thread;
|
||||
void thread_main() noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue