feat(frame-pacing): split swapchain thread into helper functions

This commit is contained in:
PancakeTAS 2026-01-09 15:57:53 +01:00
parent 111295af8c
commit 18a39ce5e5
No known key found for this signature in database
2 changed files with 149 additions and 89 deletions

View file

@ -15,6 +15,7 @@
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <exception>
#include <functional>
#include <iostream>
#include <mutex>
@ -140,50 +141,24 @@ void MyVkSwapchain::thread_main() noexcept {
});
}
try { // FIXME: indentation and stuff
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))
const auto ppi = this->virtual_FetchUPresent(100'1000, counter);
if (!ppi.has_value())
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;
}
}
const uint32_t real_idx = this->virtual_AcquireNext(pass.acquireSemaphore);
// copy virtual image into real swapchain image
const auto& cmdbuf = pass.commandBuffer;
cmdbuf.begin(vk);
auto& virtualImage = this->images.at(ppi.idx);
auto& virtualImage = this->images.at(ppi->idx);
auto& swapchainImage = this->swapchainImages.at(real_idx);
cmdbuf.blitImage(vk,
@ -231,69 +206,20 @@ void MyVkSwapchain::thread_main() noexcept {
}
// 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);
}
this->virtual_PresentLinked(*ppi, pass.presentSemaphore, real_idx);
// 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;
}
if (!pass.copyFence.wait(vk, UINT64_MAX))
throw ls::error("virtual swapchain copy fence wait timed out");
pass.copyFence.reset(vk);
// mark image as available again
{
const std::scoped_lock<std::mutex> lock(this->availabilityMutex);
this->availableImages.at(ppi.idx) = true;
}
this->virtual_CompleteUPresent(*ppi);
}
// 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);
}
} catch (const std::exception& e) {
std::cerr << "lsfg-vk: virtual swapchain encountered an error:\n"
"- " << e.what() << "\n";
}
if (offload.mutex.try_lock()) { // must be in vkDeviceWaitIdle if locked
@ -302,6 +228,115 @@ void MyVkSwapchain::thread_main() noexcept {
}
}
std::optional<MyVkPresentInfo> MyVkSwapchain::virtual_FetchUPresent(uint64_t timeout,
uint64_t& counter) {
const auto& vk = this->device.get().vkd();
if (!this->presentSemaphore.wait(vk, counter, timeout))
return std::nullopt;
counter++;
if (this->presents.empty())
throw ls::error("virtual_FetchUPresent() encountered an impossible state");
const auto info = this->presents.front();
this->presents.pop();
return info;
}
uint32_t MyVkSwapchain::virtual_AcquireNext(const vk::Semaphore& semaphore) {
const auto& vk = this->device.get().vkd();
uint32_t idx{};
{
const std::scoped_lock<std::mutex> lock(this->swapchainMutex);
auto res = vk.df().AcquireNextImageKHR(vk.dev(),
this->handle, UINT64_MAX,
semaphore.handle(), VK_NULL_HANDLE,
&idx
);
if (res != VK_SUCCESS) {
this->status.store(res);
if (res != VK_SUBOPTIMAL_KHR)
throw ls::error("vkAcquireNextImageKHR() failed");
}
}
return idx;
}
void MyVkSwapchain::virtual_PresentLinked(const MyVkPresentInfo& original_info,
const vk::Semaphore& semaphore, uint32_t idx) {
const auto& vk = this->device.get().vkd();
const uint64_t presentId = original_info.id.value_or(0);
const VkPresentIdKHR presentIdInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR,
.swapchainCount = 1,
.pPresentIds = &presentId
};
const auto mode = original_info.present_mode.value_or(VK_PRESENT_MODE_FIFO_KHR);
const VkSwapchainPresentModeInfoKHR presentModeInfo{
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_KHR,
.pNext = original_info.id.has_value() ? &presentIdInfo : nullptr,
.swapchainCount = 1,
.pPresentModes = &mode
};
const void* chain{};
if (original_info.present_mode.has_value())
chain = &presentModeInfo;
else if (original_info.id.has_value())
chain = &presentIdInfo;
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = chain,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &semaphore.handle(),
.swapchainCount = 1,
.pSwapchains = &this->handle,
.pImageIndices = &idx,
};
{
auto& offload = this->device.get().offload();
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) {
this->status.store(res);
if (res != VK_SUBOPTIMAL_KHR)
throw ls::error("vkQueuePresentKHR() failed");
}
}
if (original_info.id.has_value())
this->doneSemaphore->signal(vk, presentId + 1);
}
void MyVkSwapchain::virtual_CompleteUPresent(const MyVkPresentInfo& info) {
const auto& vk = this->device.get().vkd();
{
const std::scoped_lock<std::mutex> lock(this->availabilityMutex);
this->availableImages.at(info.idx) = true;
}
if (info.fence != VK_NULL_HANDLE) {
auto& offload = this->device.get().offload();
auto res = vk.df().QueueSubmit(offload.queue, 0, nullptr, info.fence);
if (res != VK_SUCCESS)
this->status.store(res);
}
}
/* Reimplementations of Vulkan functions */
VkResult MyVkSwapchain::GetSwapchainImagesKHR(uint32_t *count, VkImage *images) noexcept {

View file

@ -6,6 +6,7 @@
#include "instance.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/semaphore.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include <atomic>
@ -80,6 +81,30 @@ namespace lsfgvk::layer {
MyVkSwapchain(MyVkSwapchain&&) = delete;
MyVkSwapchain& operator=(MyVkSwapchain&&) = delete;
~MyVkSwapchain() noexcept;
protected: // for use exclusively inside the offload thread
/// wait for a present from the underlying swapchain, incrementing the counter on success
/// @param timeout timeout in nanoseconds
/// @return the optional present information
/// @throws ls::error on critical failure
std::optional<MyVkPresentInfo> virtual_FetchUPresent(uint64_t timeout, uint64_t& counter);
/// acquire a swapchain image from the virtual swapchain
/// @param semaphore semaphore to signal when the image is available
/// @return the index of the acquired image
/// @throws ls::error on critical failure
uint32_t virtual_AcquireNext(const vk::Semaphore& semaphore);
/// present an image to the virtual swapchain linked to the original present call
/// @param original_info original present information
/// @param semaphore semaphore to wait on before presenting
/// @param idx index of the image to present
/// @throws ls::error on critical failure
void virtual_PresentLinked(const MyVkPresentInfo& original_info,
const vk::Semaphore& semaphore, uint32_t idx);
/// mark a present from the underlying swapchain as complete
/// @param info present information
void virtual_CompleteUPresent(const MyVkPresentInfo& info);
private:
ls::R<MyVkLayer> layer;
ls::R<MyVkInstance> instance;