From 35933799c91b4af95a5b4b26beccf662cbf7898f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 31 Dec 2025 11:12:00 +0100 Subject: [PATCH] feat(frame-pacing): separate instance & swapchain into several objects --- lsfg-vk-layer/CMakeLists.txt | 9 +- lsfg-vk-layer/src/entrypoint.cpp | 369 +++++++++++++------------- lsfg-vk-layer/src/generator.cpp | 169 ++++++++++++ lsfg-vk-layer/src/generator.hpp | 69 +++++ lsfg-vk-layer/src/hooks/device.cpp | 144 ++++++++++ lsfg-vk-layer/src/hooks/device.hpp | 71 +++++ lsfg-vk-layer/src/hooks/instance.cpp | 58 ++++ lsfg-vk-layer/src/hooks/instance.hpp | 48 ++++ lsfg-vk-layer/src/hooks/layer.cpp | 98 +++++++ lsfg-vk-layer/src/hooks/layer.hpp | 54 ++++ lsfg-vk-layer/src/hooks/swapchain.cpp | 219 +++++++++++++++ lsfg-vk-layer/src/hooks/swapchain.hpp | 93 +++++++ lsfg-vk-layer/src/instance.cpp | 214 --------------- lsfg-vk-layer/src/instance.hpp | 80 ------ lsfg-vk-layer/src/swapchain.cpp | 305 --------------------- lsfg-vk-layer/src/swapchain.hpp | 84 ------ 16 files changed, 1208 insertions(+), 876 deletions(-) create mode 100644 lsfg-vk-layer/src/generator.cpp create mode 100644 lsfg-vk-layer/src/generator.hpp create mode 100644 lsfg-vk-layer/src/hooks/device.cpp create mode 100644 lsfg-vk-layer/src/hooks/device.hpp create mode 100644 lsfg-vk-layer/src/hooks/instance.cpp create mode 100644 lsfg-vk-layer/src/hooks/instance.hpp create mode 100644 lsfg-vk-layer/src/hooks/layer.cpp create mode 100644 lsfg-vk-layer/src/hooks/layer.hpp create mode 100644 lsfg-vk-layer/src/hooks/swapchain.cpp create mode 100644 lsfg-vk-layer/src/hooks/swapchain.hpp delete mode 100644 lsfg-vk-layer/src/instance.cpp delete mode 100644 lsfg-vk-layer/src/instance.hpp delete mode 100644 lsfg-vk-layer/src/swapchain.cpp delete mode 100644 lsfg-vk-layer/src/swapchain.hpp diff --git a/lsfg-vk-layer/CMakeLists.txt b/lsfg-vk-layer/CMakeLists.txt index 0413479..ce4fd59 100644 --- a/lsfg-vk-layer/CMakeLists.txt +++ b/lsfg-vk-layer/CMakeLists.txt @@ -1,7 +1,10 @@ set(LAYER_SOURCES - "src/entrypoint.cpp" - "src/instance.cpp" - "src/swapchain.cpp") + "src/hooks/device.cpp" + "src/hooks/instance.cpp" + "src/hooks/layer.cpp" + "src/hooks/swapchain.cpp" + "src/generator.cpp" + "src/entrypoint.cpp") add_library(lsfg-vk-layer SHARED ${LAYER_SOURCES}) diff --git a/lsfg-vk-layer/src/entrypoint.cpp b/lsfg-vk-layer/src/entrypoint.cpp index 6256b9f..80eb7f6 100644 --- a/lsfg-vk-layer/src/entrypoint.cpp +++ b/lsfg-vk-layer/src/entrypoint.cpp @@ -1,16 +1,16 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include "instance.hpp" +#include "hooks/device.hpp" +#include "hooks/instance.hpp" +#include "hooks/layer.hpp" +#include "hooks/swapchain.hpp" #include "lsfg-vk-common/helpers/errors.hpp" -#include "lsfg-vk-common/helpers/pointers.hpp" -#include "lsfg-vk-common/vulkan/vulkan.hpp" -#include "swapchain.hpp" -#include -#include #include #include #include +#include +#include #include #include #include @@ -24,27 +24,25 @@ using namespace lsfgvk::layer; namespace { // global layer info initialized at layer negotiation struct LayerInfo { - std::unordered_map map; //!< function pointer override map - PFN_vkGetInstanceProcAddr GetInstanceProcAddr; + std::unordered_map map; // layer override map - Root root; + PFN_vkGetInstanceProcAddr GetInstanceProcAddr; // next layer functions + PFN_vkGetDeviceProcAddr GetDeviceProcAddr; + PFN_vkQueueSubmit QueueSubmit; + + MyVkLayer layer; // managed instances + std::unordered_map> instances; + std::unordered_map> devices; + std::unordered_map> swapchains; }* layer_info; // NOLINT (global variable) - // instance-wide info initialized at instance creation(s) - struct InstanceInfo { - std::vector handles; // there may be several instances - vk::VulkanInstanceFuncs funcs; - - std::unordered_map devices; - std::unordered_map> swapchains; - std::unordered_map swapchainInfos; - }* instance_info; // NOLINT (global variable) - // create instance VkResult myvkCreateInstance( const VkInstanceCreateInfo* info, const VkAllocationCallbacks* alloc, VkInstance* instance) { + auto& myvk_layer = layer_info->layer; + // apply layer chaining auto* layerInfo = reinterpret_cast(const_cast(info->pNext)); while (layerInfo && (layerInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO @@ -83,29 +81,32 @@ namespace { } try { - VkInstanceCreateInfo newInfo = *info; - layer_info->root.modifyInstanceCreateInfo(newInfo, - [=, newInfo = &newInfo]() { - auto res = vkCreateInstance(newInfo, alloc, instance); + auto myvk_instance = std::make_unique(myvk_layer, + *info, + layer_info->GetInstanceProcAddr, + [=](VkInstanceCreateInfo* info) { + auto res = vkCreateInstance(info, alloc, instance); if (res != VK_SUCCESS) throw ls::vulkan_error(res, "vkCreateInstance() failed"); + + return *instance; } ); - - if (!instance_info) - instance_info = new InstanceInfo{ // NOLINT (memory management) - .funcs = vk::initVulkanInstanceFuncs(*instance, - layer_info->GetInstanceProcAddr, true), - }; - - instance_info->handles.push_back(*instance); + layer_info->instances.emplace(*instance, std::move(myvk_instance)); return VK_SUCCESS; } catch (const ls::vulkan_error& e) { if (e.error() == VK_ERROR_EXTENSION_NOT_PRESENT) std::cerr << "lsfg-vk: required Vulkan instance extensions are not present. " "Your GPU driver is not supported.\n"; + else + std::cerr << "lsfg-vk: something went wrong during lsfg-vk instance initialization:\n" + "- " << e.what() << '\n'; return e.error(); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: something went wrong during lsfg-vk instance initialization:\n" + "- " << e.what() << '\n'; + return VK_ERROR_INITIALIZATION_FAILED; } } @@ -115,6 +116,9 @@ namespace { const VkDeviceCreateInfo* info, const VkAllocationCallbacks* alloc, VkDevice* device) { + auto& myvk_layer = layer_info->layer; + auto& myvk_instance = *layer_info->instances.begin()->second; + // apply layer chaining auto* layerInfo = reinterpret_cast(const_cast(info->pNext)); while (layerInfo && (layerInfo->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO @@ -134,7 +138,7 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } - instance_info->funcs.GetDeviceProcAddr = linkInfo->pfnNextGetDeviceProcAddr; + layer_info->GetDeviceProcAddr = linkInfo->pfnNextGetDeviceProcAddr; if (!linkInfo->pfnNextGetDeviceProcAddr) { std::cerr << "lsfg-vk: next layer's vkGetDeviceProcAddr is null, " "the previous layer does not follow spec\n"; @@ -161,51 +165,59 @@ namespace { } // create device + auto* vkCreateDevice = reinterpret_cast( + layer_info->GetInstanceProcAddr(myvk_instance.instance(), "vkCreateDevice")); + if (!vkCreateDevice) { + std::cerr << "lsfg-vk: failed to get next layer's vkCreateDevice, " + "the previous layer does not follow spec\n"; + return VK_ERROR_INITIALIZATION_FAILED; + } + try { - VkDeviceCreateInfo newInfo = *info; - layer_info->root.modifyDeviceCreateInfo(newInfo, - [=, newInfo = &newInfo]() { - auto res = instance_info->funcs.CreateDevice(physdev, newInfo, alloc, device); + auto myvk_device = std::make_unique(myvk_layer, myvk_instance, + physdev, *info, + layer_info->GetDeviceProcAddr, setLoaderData, + [=](VkDeviceCreateInfo* info) { + auto res = vkCreateDevice(physdev, info, alloc, device); if (res != VK_SUCCESS) throw ls::vulkan_error(res, "vkCreateDevice() failed"); + + return *device; } ); + layer_info->devices.emplace(*device, std::move(myvk_device)); + + return VK_SUCCESS; } catch (const ls::vulkan_error& e) { if (e.error() == VK_ERROR_EXTENSION_NOT_PRESENT) std::cerr << "lsfg-vk: required Vulkan device extensions are not present. " "Your GPU driver is not supported.\n"; + else + std::cerr << "lsfg-vk: something went wrong during lsfg-vk device initialization:\n" + "- " << e.what() << '\n'; return e.error(); - } - - // create layer instance - try { - instance_info->devices.emplace( - *device, - vk::Vulkan( - instance_info->handles.front(), *device, physdev, - instance_info->funcs, vk::initVulkanDeviceFuncs(instance_info->funcs, *device, - true), - true, setLoaderData - ) - ); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: something went wrong during lsfg-vk initialization:\n"; - std::cerr << "- " << e.what() << '\n'; + std::cerr << "lsfg-vk: something went wrong during lsfg-vk device initialization:\n" + "- " << e.what() << '\n'; + return VK_ERROR_INITIALIZATION_FAILED; } + } - return VK_SUCCESS; + VkResult myvkDeviceWaitIdle(VkDevice device) { + auto it = layer_info->devices.find(device); + if (it == layer_info->devices.end()) + return VK_ERROR_DEVICE_LOST; + + const std::scoped_lock lock(it->second->offload().mutex); + return it->second->funcs().DeviceWaitIdle(device); } // destroy device void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* alloc) { - // destroy layer instance - auto it = instance_info->devices.find(device); - if (it != instance_info->devices.end()) - instance_info->devices.erase(it); + layer_info->devices.erase(device); - // destroy device auto vkDestroyDevice = reinterpret_cast( - instance_info->funcs.GetDeviceProcAddr(device, "vkDestroyDevice")); + layer_info->GetDeviceProcAddr(device, "vkDestroyDevice")); if (!vkDestroyDevice) { std::cerr << "lsfg-vk: failed to get next layer's vkDestroyDevice, " "the previous layer does not follow spec\n"; @@ -217,18 +229,8 @@ namespace { // destroy instance void myvkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* alloc) { - // remove instance handle - auto it = std::ranges::find(instance_info->handles, instance); - if (it != instance_info->handles.end()) - instance_info->handles.erase(it); + layer_info->instances.erase(instance); - // destroy instance info if no handles remain - if (instance_info->handles.empty()) { - delete instance_info; // NOLINT (memory management) - instance_info = nullptr; - } - - // destroy instance auto vkDestroyInstance = reinterpret_cast( layer_info->GetInstanceProcAddr(instance, "vkDestroyInstance")); if (!vkDestroyInstance) { @@ -266,8 +268,8 @@ namespace { auto func = getProcAddr(name); if (func) return func; - if (!instance_info->funcs.GetDeviceProcAddr) return nullptr; - return instance_info->funcs.GetDeviceProcAddr(device, name); + if (!layer_info->GetDeviceProcAddr) return nullptr; + return layer_info->GetDeviceProcAddr(device, name); } } @@ -277,138 +279,129 @@ namespace { const VkSwapchainCreateInfoKHR* info, const VkAllocationCallbacks* alloc, VkSwapchainKHR* swapchain) { - const auto& it = instance_info->devices.find(device); - if (it == instance_info->devices.end()) - return VK_ERROR_INITIALIZATION_FAILED; + auto& myvk_layer = layer_info->layer; + auto& myvk_instance = *layer_info->instances.begin()->second; + auto& myvk_device = *layer_info->devices.find(device)->second; + + auto* vkCreateSwapchainKHR = myvk_device.funcs().CreateSwapchainKHR; + layer_info->QueueSubmit = myvk_device.funcs().QueueSubmit; try { - // retire old swapchain - if (info->oldSwapchain) { - const auto& info_mapping = instance_info->swapchainInfos.find(info->oldSwapchain); - if (info_mapping != instance_info->swapchainInfos.end()) - instance_info->swapchainInfos.erase(info_mapping); + myvk_layer.update(); // ensure config is up to date - const auto& mapping = instance_info->swapchains.find(info->oldSwapchain); - if (mapping != instance_info->swapchains.end()) - instance_info->swapchains.erase(mapping); + // remove old managed swapchain + if (info->oldSwapchain) + layer_info->swapchains.erase(info->oldSwapchain); - layer_info->root.removeSwapchainContext(info->oldSwapchain); - } - - layer_info->root.update(); // ensure config is up to date - - // create swapchain - VkSwapchainCreateInfoKHR newInfo = *info; - layer_info->root.modifySwapchainCreateInfo(it->second, newInfo, - [=, newInfo = &newInfo]() { - auto res = it->second.df().CreateSwapchainKHR( - device, newInfo, alloc, swapchain); + // create managed swapchain + auto myvk_swapchain = std::make_unique(myvk_layer, myvk_instance, myvk_device, + *info, + [=](VkSwapchainCreateInfoKHR* info) { + auto res = vkCreateSwapchainKHR(device, info, alloc, swapchain); if (res != VK_SUCCESS) throw ls::vulkan_error(res, "vkCreateSwapchainKHR() failed"); + + return *swapchain; } ); + layer_info->swapchains.emplace(*swapchain, std::move(myvk_swapchain)); - // get all swapchain images - uint32_t imageCount{}; - auto res = it->second.df().GetSwapchainImagesKHR(device, *swapchain, - &imageCount, VK_NULL_HANDLE); - if (res != VK_SUCCESS || imageCount == 0) - throw ls::vulkan_error(res, "vkGetSwapchainImagesKHR() failed"); - - std::vector swapchainImages(imageCount); - res = it->second.df().GetSwapchainImagesKHR(device, *swapchain, - &imageCount, swapchainImages.data()); - if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "vkGetSwapchainImagesKHR() failed"); - - auto& info = instance_info->swapchainInfos.emplace(*swapchain, SwapchainInfo { - .images = std::move(swapchainImages), - .format = newInfo.imageFormat, - .colorSpace = newInfo.imageColorSpace, - .extent = newInfo.imageExtent, - .presentMode = newInfo.presentMode - }).first->second; - - // create lsfg-vk swapchain - layer_info->root.createSwapchainContext(it->second, *swapchain, info); - - instance_info->swapchains.emplace(*swapchain, - ls::R(it->second)); - - return res; + return VK_SUCCESS; } catch (const ls::vulkan_error& e) { - std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain creation:\n"; - std::cerr << "- " << e.what() << '\n'; + std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain creation:\n" + "- " << e.what() << '\n'; return e.error(); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain creation:\n"; - std::cerr << "- " << e.what() << '\n'; - return VK_ERROR_INITIALIZATION_FAILED; + std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain creation:\n" + "- " << e.what() << '\n'; + return VK_ERROR_UNKNOWN; } } - VkResult myvkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* 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; - // ensure layer config is up to date - bool reload{}; + // re-create out-of-date managed swapchains try { - reload = layer_info->root.update(); - } catch (const std::exception&) { - reload = false; // ignore parse errors - } - - if (reload) { - try { - for (const auto& [swapchain, vk] : instance_info->swapchains) { - auto& info = instance_info->swapchainInfos.at(swapchain); - - layer_info->root.removeSwapchainContext(swapchain); - layer_info->root.createSwapchainContext(vk, swapchain, info); - } - - std::cerr << "lsfg-vk: updated lsfg-vk configuration\n"; - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: something went wrong during lsfg-vk configuration update:\n"; - std::cerr << "- " << e.what() << '\n'; + if (layer_info->layer.update()) { + for (auto& [handle, myswapchain] : layer_info->swapchains) + myswapchain->reinitialize(); } + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain re-creation:\n" + "- " << e.what() << '\n'; + // ignore error: return VK_ERROR_UNKNOWN; } - // present each swapchain - for (size_t i = 0; i < info->swapchainCount; i++) { - const auto& swapchain = info->pSwapchains[i]; + // collect semaphores and values + std::vector waitValues(info->waitSemaphoreCount); + std::vector signalSemaphores; + std::vector signalValues; - const auto& it = instance_info->swapchains.find(swapchain); - if (it == instance_info->swapchains.end()) - return VK_ERROR_INITIALIZATION_FAILED; + 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; + + const auto& sync = it->second->sync(); + signalSemaphores.push_back(sync.first); + signalValues.push_back(sync.second); + } + + // submit the present operation + const VkTimelineSemaphoreSubmitInfo timelineInfo{ + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + .waitSemaphoreValueCount = static_cast(waitValues.size()), + .pWaitSemaphoreValues = waitValues.data(), + .signalSemaphoreValueCount = static_cast(signalValues.size()), + .pSignalSemaphoreValues = signalValues.data() + }; + std::vector stages(info->waitSemaphoreCount, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + const VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = &timelineInfo, + .waitSemaphoreCount = info->waitSemaphoreCount, + .pWaitSemaphores = info->pWaitSemaphores, + .pWaitDstStageMask = stages.data(), + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphores.data() + }; + auto res = layer_info->QueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + if (res != VK_SUCCESS) { + std::cerr << "lsfg-vk: something went wrong during lsfg-vk present submission:\n" + "- vkQueueSubmit() failed with error " << res << '\n'; + 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 { - std::vector waitSemaphores; - waitSemaphores.reserve(info->waitSemaphoreCount); - - for (size_t j = 0; j < info->waitSemaphoreCount; j++) - waitSemaphores.push_back(info->pWaitSemaphores[j]); - - auto& context = layer_info->root.getSwapchainContext(swapchain); - result = context.present(it->second, - queue, swapchain, + result = it->second->present(queue, const_cast(info->pNext), - info->pImageIndices[i], - { waitSemaphores.begin(), waitSemaphores.end() } + info->pImageIndices[i] ); } catch (const ls::vulkan_error& e) { - if (e.error() != VK_ERROR_OUT_OF_DATE_KHR) { - std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n"; - std::cerr << "- " << e.what() << '\n'; - } // silently swallow out-of-date errors - + 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"; - std::cerr << "- " << e.what() << '\n'; + std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n" + "- " << e.what() << '\n'; result = VK_ERROR_UNKNOWN; } @@ -424,25 +417,21 @@ namespace { VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* alloc) { - const auto& it = instance_info->devices.find(device); - if (it == instance_info->devices.end()) + layer_info->swapchains.erase(swapchain); + + auto it = layer_info->devices.find(device); + if (it == layer_info->devices.end()) return; - const auto& info_mapping = instance_info->swapchainInfos.find(swapchain); - if (info_mapping != instance_info->swapchainInfos.end()) - instance_info->swapchainInfos.erase(info_mapping); - - const auto& mapping = instance_info->swapchains.find(swapchain); - if (mapping != instance_info->swapchains.end()) - instance_info->swapchains.erase(mapping); - - layer_info->root.removeSwapchainContext(swapchain); - - // destroy swapchain - it->second.df().DestroySwapchainKHR(device, swapchain, alloc); + auto vkDestroySwapchainKHR = it->second->funcs().DestroySwapchainKHR; + vkDestroySwapchainKHR(device, swapchain, alloc); } } +namespace { + +} + /// Vulkan layer entrypoint __attribute__((visibility("default"))) VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVersionStruct) { @@ -468,25 +457,25 @@ VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVers #define VKPTR(name) reinterpret_cast(name) { "vkCreateInstance", VKPTR(myvkCreateInstance) }, { "vkCreateDevice", VKPTR(myvkCreateDevice) }, + { "vkDeviceWaitIdle", VKPTR(myvkDeviceWaitIdle) }, { "vkDestroyDevice", VKPTR(myvkDestroyDevice) }, { "vkDestroyInstance", VKPTR(myvkDestroyInstance) }, { "vkCreateSwapchainKHR", VKPTR(myvkCreateSwapchainKHR) }, { "vkQueuePresentKHR", VKPTR(myvkQueuePresentKHR) }, { "vkDestroySwapchainKHR", VKPTR(myvkDestroySwapchainKHR) } #undef VKPTR - }, - .root = Root() + } }; - if (!layer_info->root.active()) { // skip inactive + if (!layer_info->layer.isActive()) { // skip inactive delete layer_info; // NOLINT (memory management) layer_info = nullptr; return VK_ERROR_INITIALIZATION_FAILED; } } catch (const std::exception& e) { - std::cerr << "lsfg-vk: something went wrong during lsfg-vk layer initialization:\n"; - std::cerr << "- " << e.what() << '\n'; + std::cerr << "lsfg-vk: something went wrong during lsfg-vk layer initialization:\n" + "- " << e.what() << '\n'; return VK_ERROR_INITIALIZATION_FAILED; } diff --git a/lsfg-vk-layer/src/generator.cpp b/lsfg-vk-layer/src/generator.cpp new file mode 100644 index 0000000..141ade2 --- /dev/null +++ b/lsfg-vk-layer/src/generator.cpp @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "generator.hpp" +#include "hooks/device.hpp" +#include "hooks/layer.hpp" +#include "lsfg-vk-backend/lsfgvk.hpp" +#include "lsfg-vk-common/configuration/config.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/image.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace lsfgvk; +using namespace lsfgvk::layer; + +Generator::Generator(MyVkLayer& layer, MyVkDevice& device, + VkExtent2D extent, VkFormat format) : + instance(std::ref(layer.backend())), vk(std::ref(device.vkd())) { + const auto& profile = layer.profile(); + const bool hdr = format > 57; + + // create shared objects + std::vector sourceFds(2); + std::vector destinationFds(profile.multiplier - 1); + + this->sourceImages.reserve(sourceFds.size()); + for (int& fd : sourceFds) + this->sourceImages.emplace_back(vk, + extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + std::nullopt, &fd); + + this->destinationImages.reserve(destinationFds.size()); + for (int& fd : destinationFds) + this->destinationImages.emplace_back(vk, + extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + std::nullopt, &fd); + + int syncFd{}; + this->syncSemaphore.emplace(vk, 0, std::nullopt, &syncFd); + + // create backend context + try { + this->ctx = ls::owned_ptr>( + new ls::R(instance.get().openContext( + { sourceFds.at(0), sourceFds.at(1) }, destinationFds, syncFd, + extent.width, extent.height, + hdr, 1.0F / profile.flow_scale, profile.performance_mode + )), + [instance = &instance.get()](ls::R& ctx) { + instance->closeContext(ctx); + } + ); + + backend::makeLeaking(); // don't worry about it :3 + } catch (const std::exception& e) { + throw ls::error("failed to create frame generation context", e); + } +} + +#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 \ + } + +std::pair Generator::prepare(vk::CommandBuffer& cmdbuf, + VkImage swapchainImage) { + const auto& sourceImage = this->sourceImages.at(this->frameIdx % 2); + + cmdbuf.blitImage(this->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(swapchainImage) + }, + { + .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(sourceImage.handle()) + }, + }, + { swapchainImage, sourceImage.handle() }, + sourceImage.getExtent(), + { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + FILL_BARRIER(swapchainImage) + } + } + ); + + return { this->syncSemaphore->handle(), this->syncValue++ }; +} + +void Generator::schedule() { + try { + this->instance.get().scheduleFrames(this->ctx.get()); + } catch (const std::exception& e) { + throw ls::error("failed to schedule frames", e); + } + + this->frameIdx++; +} + +std::pair Generator::obtain(vk::CommandBuffer& cmdbuf, + VkImage swapchainImage) { + const auto& destinationImage = this->destinationImages.at(this->generatedIdx++ % this->destinationImages.size()); + + cmdbuf.blitImage(this->vk, + { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + FILL_BARRIER(destinationImage.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) + }, + }, + { destinationImage.handle(), swapchainImage }, + destinationImage.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) + } + } + ); + + return { this->syncSemaphore->handle(), this->syncValue++ }; +} diff --git a/lsfg-vk-layer/src/generator.hpp b/lsfg-vk-layer/src/generator.hpp new file mode 100644 index 0000000..4bd3cea --- /dev/null +++ b/lsfg-vk-layer/src/generator.hpp @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#include "hooks/device.hpp" +#include "hooks/layer.hpp" +#include "lsfg-vk-backend/lsfgvk.hpp" +#include "lsfg-vk-common/helpers/pointers.hpp" +#include "lsfg-vk-common/vulkan/command_buffer.hpp" +#include "lsfg-vk-common/vulkan/image.hpp" +#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp" +#include "lsfg-vk-common/vulkan/vulkan.hpp" + +#include +#include +#include + +#include + +namespace lsfgvk::layer { + + /// frame generation context wrapper + class Generator { + public: + /// create a new frame generation context + /// @param layer parent layer + /// @param device parent device + /// @param extent swapchain extent + /// @param format swapchain format + Generator(MyVkLayer& layer, MyVkDevice& device, + VkExtent2D extent, VkFormat format); + + /// copy a swapchain image into the backend source image + /// @param cmdbuf command buffer to record into + /// @param swapchainImage swapchain image to copy from + /// @return timeline pair to signal for frame generation to start + /// @throws ls::vulkan_error on vulkan errors + std::pair prepare(vk::CommandBuffer& cmdbuf, VkImage swapchainImage); + + /// schedule frame generation and increment frame index + /// @throws ls::error on scheduling errors + void schedule(); + + /// copy a backend destination image into a swapchain image + /// @param cmdbuf command buffer to record into + /// @param swapchainImage swapchain image to copy into + /// @return timeline pair to wait on for frame generation completion + /// @throws ls::vulkan_error on vulkan errors + std::pair obtain(vk::CommandBuffer& cmdbuf, VkImage swapchainImage); + + /// return the amount of generated frames + /// @return generated frames count + [[nodiscard]] uint64_t count() const { return this->destinationImages.size(); } + private: + ls::R instance; + ls::R vk; + + std::vector sourceImages; + std::vector destinationImages; + uint64_t frameIdx{0}; // real frames-only + uint64_t generatedIdx{0}; // generated frames-only + + ls::lazy syncSemaphore; + uint64_t syncValue{1}; + + ls::owned_ptr> ctx; + }; + +} diff --git a/lsfg-vk-layer/src/hooks/device.cpp b/lsfg-vk-layer/src/hooks/device.cpp new file mode 100644 index 0000000..0b6ccb6 --- /dev/null +++ b/lsfg-vk-layer/src/hooks/device.cpp @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "device.hpp" +#include "instance.hpp" +#include "layer.hpp" +#include "lsfg-vk-common/helpers/errors.hpp" +#include "lsfg-vk-common/vulkan/vulkan.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace lsfgvk; +using namespace lsfgvk::layer; + +namespace { + /// helper function to add required extensions + std::vector add_extensions(const char* const* existingExtensions, size_t count, + const std::vector& requiredExtensions) { + std::vector extensions(count); + std::copy_n(existingExtensions, count, extensions.data()); + + for (const auto& requiredExtension : requiredExtensions) { + auto it = std::ranges::find_if(extensions, + [requiredExtension](const char* extension) { + return std::string(extension) == std::string(requiredExtension); + }); + if (it == extensions.end()) + extensions.push_back(requiredExtension); + } + + return extensions; + } + /// helper function for finding a graphics queue family + uint32_t find_qfi(VkPhysicalDevice physdev, const vk::VulkanInstanceFuncs& funcs) { + uint32_t queueFamilyCount = 0; + funcs.GetPhysicalDeviceQueueFamilyProperties(physdev, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + funcs.GetPhysicalDeviceQueueFamilyProperties(physdev, &queueFamilyCount, queueFamilies.data()); + + for (uint32_t i = 0; i < queueFamilyCount; ++i) + if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + return i; + + throw ls::error("no graphics queue family found"); + } +} + +MyVkDevice::MyVkDevice(MyVkLayer& layer, MyVkInstance& instance, + VkPhysicalDevice physdev, VkDeviceCreateInfo info, + PFN_vkGetDeviceProcAddr addr, PFN_vkSetDeviceLoaderData loader_addr, + const std::function& createFunc) : + layer(std::ref(layer)), instance(std::ref(instance)) { + // add required extensions + auto extensions = add_extensions( + info.ppEnabledExtensionNames, + info.enabledExtensionCount, + { + "VK_KHR_external_memory", + "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore", + "VK_KHR_external_semaphore_fd", + "VK_KHR_timeline_semaphore" + } + ); + info.enabledExtensionCount = static_cast(extensions.size()); + info.ppEnabledExtensionNames = extensions.data(); + + // enable timeline semaphores + bool isFeatureEnabled = false; + auto* featureInfo = reinterpret_cast(const_cast(info.pNext)); + while (featureInfo) { + if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES) { + auto* features = reinterpret_cast(featureInfo); + features->timelineSemaphore = VK_TRUE; + isFeatureEnabled = true; + } else if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES) { + auto* features = reinterpret_cast(featureInfo); + features->timelineSemaphore = VK_TRUE; + isFeatureEnabled = true; + } + + featureInfo = const_cast(featureInfo->pNext); + } + + VkPhysicalDeviceTimelineSemaphoreFeatures timelineFeatures{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, + .pNext = const_cast(info.pNext), + .timelineSemaphore = VK_TRUE + }; + if (!isFeatureEnabled) + info.pNext = &timelineFeatures; + + // append a graphics queue + std::vector queues; + queues.reserve(info.queueCreateInfoCount + 1); + for (uint32_t i = 0; i < info.queueCreateInfoCount; ++i) + queues.push_back(info.pQueueCreateInfos[i]); + + const uint32_t qfi = find_qfi(physdev, instance.funcs()); + std::optional queueIdx{0}; + for (auto& queueInfo : queues) { + if (queueInfo.queueFamilyIndex == qfi) { + queueIdx.emplace(queueInfo.queueCount); + queueInfo.queueCount++; // we pray it doesn't exceed the max + break; + } + } + + if (!queueIdx.has_value()) { + const VkDeviceQueueCreateInfo queueInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = qfi, + .queueCount = 2, + .pQueuePriorities = new float[1]{ 1.0F } + }; + queues.push_back(queueInfo); + } + + info.queueCreateInfoCount = static_cast(queues.size()); + info.pQueueCreateInfos = queues.data(); + + // create device + this->handle = createFunc(&info); + this->dfuncs = vk::initVulkanDeviceFuncs(addr, this->handle, true); + this->vk.emplace(vk::Vulkan( + instance.instance(), this->handle, physdev, + instance.funcs(), this->dfuncs, + true, + loader_addr + )); + + // extract the graphics queues + this->dfuncs.GetDeviceQueue(this->handle, qfi, queueIdx.value_or(0), &this->offloadQueue.queue); + loader_addr(this->handle, this->offloadQueue.queue); +} diff --git a/lsfg-vk-layer/src/hooks/device.hpp b/lsfg-vk-layer/src/hooks/device.hpp new file mode 100644 index 0000000..3ae1feb --- /dev/null +++ b/lsfg-vk-layer/src/hooks/device.hpp @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#include "instance.hpp" +#include "lsfg-vk-common/helpers/pointers.hpp" +#include "lsfg-vk-common/vulkan/vulkan.hpp" + +#include + +#include +#include +#include + +namespace lsfgvk::layer { + + /// simple struct for multithreaded queue access + struct OffloadQueue { + VkQueue queue; + std::mutex mutex; + }; + + /// device wrapper class + class MyVkDevice { + public: + /// create a vulkan device wrapper + /// @param layer layer reference + /// @param instance parent vulkan instance + /// @param info base device info + /// @param physdev physical device to create the device for + /// @param addr function to get device proc addresses + /// @param loader_addr function to set loader data + /// @param createFunc function to create the device + /// @throws ls::vulkan_error on vulkan errors + MyVkDevice(MyVkLayer& layer, MyVkInstance& instance, + VkPhysicalDevice physdev, VkDeviceCreateInfo info, + PFN_vkGetDeviceProcAddr addr, PFN_vkSetDeviceLoaderData loader_addr, + const std::function& createFunc); + + /// get the referenced vulkan device + /// @return vulkan device + [[nodiscard]] const auto& device() const { return handle; } + /// get the vulkan device functions + /// @return vulkan device functions + [[nodiscard]] const auto& funcs() const { return dfuncs; } + + /// get the vulkan instance + /// @return vulkan instance + [[nodiscard]] const auto& vkd() const { return *vk; } + /// get the additional queue + /// @return vulkan queue + [[nodiscard]] auto& offload() { return offloadQueue; } + + // non-moveable, non-copyable + MyVkDevice(const MyVkDevice&) = delete; + MyVkDevice& operator=(const MyVkDevice&) = delete; + MyVkDevice(MyVkDevice&&) = delete; + MyVkDevice& operator=(MyVkDevice&&) = delete; + ~MyVkDevice() = default; + private: + ls::R layer; + ls::R instance; + + VkDevice handle; + vk::VulkanDeviceFuncs dfuncs{}; + + ls::lazy vk; + OffloadQueue offloadQueue{}; + }; + +} diff --git a/lsfg-vk-layer/src/hooks/instance.cpp b/lsfg-vk-layer/src/hooks/instance.cpp new file mode 100644 index 0000000..b75e51d --- /dev/null +++ b/lsfg-vk-layer/src/hooks/instance.cpp @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "instance.hpp" +#include "layer.hpp" +#include "lsfg-vk-common/vulkan/vulkan.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace lsfgvk; +using namespace lsfgvk::layer; + +namespace { + /// helper function to add required extensions + std::vector add_extensions(const char* const* existingExtensions, size_t count, + const std::vector& requiredExtensions) { + std::vector extensions(count); + std::copy_n(existingExtensions, count, extensions.data()); + + for (const auto& requiredExtension : requiredExtensions) { + auto it = std::ranges::find_if(extensions, + [requiredExtension](const char* extension) { + return std::string(extension) == std::string(requiredExtension); + }); + if (it == extensions.end()) + extensions.push_back(requiredExtension); + } + + return extensions; + } +} + +MyVkInstance::MyVkInstance(MyVkLayer& layer, + VkInstanceCreateInfo info, PFN_vkGetInstanceProcAddr addr, + const std::function& createFunc) : + layer(std::ref(layer)) { + auto extensions = add_extensions( + info.ppEnabledExtensionNames, + info.enabledExtensionCount, + { + "VK_KHR_get_physical_device_properties2", + "VK_KHR_external_memory_capabilities", + "VK_KHR_external_semaphore_capabilities" + } + ); + info.enabledExtensionCount = static_cast(extensions.size()); + info.ppEnabledExtensionNames = extensions.data(); + + // create instance + this->handle = createFunc(&info); // NOLINT (prefer member initializer) + this->ifuncs = vk::initVulkanInstanceFuncs(this->handle, addr, true); +} diff --git a/lsfg-vk-layer/src/hooks/instance.hpp b/lsfg-vk-layer/src/hooks/instance.hpp new file mode 100644 index 0000000..6efb6cf --- /dev/null +++ b/lsfg-vk-layer/src/hooks/instance.hpp @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#include "layer.hpp" +#include "lsfg-vk-common/helpers/pointers.hpp" +#include "lsfg-vk-common/vulkan/vulkan.hpp" + +#include + +#include + +namespace lsfgvk::layer { + + /// instance wrapper class + class MyVkInstance { + public: + /// create a vulkan instance wrapper + /// @param layer layer reference + /// @param info base instance info + /// @param addr function to get instance proc addresses + /// @param createFunc function to create the instance + /// @throws ls::vulkan_error on vulkan errors + MyVkInstance(MyVkLayer& layer, + VkInstanceCreateInfo info, PFN_vkGetInstanceProcAddr addr, + const std::function& createFunc); + + /// get the referenced vulkan instance + /// @return vulkan instance + [[nodiscard]] const auto& instance() const { return handle; } + /// get the vulkan instance functions + /// @return vulkan instance functions + [[nodiscard]] const auto& funcs() const { return ifuncs; } + + // non-moveable, non-copyable + MyVkInstance(const MyVkInstance&) = delete; + MyVkInstance& operator=(const MyVkInstance&) = delete; + MyVkInstance(MyVkInstance&&) = delete; + MyVkInstance& operator=(MyVkInstance&&) = delete; + ~MyVkInstance() = default; + private: + ls::R layer; + + VkInstance handle; + vk::VulkanInstanceFuncs ifuncs{}; + }; + +} diff --git a/lsfg-vk-layer/src/hooks/layer.cpp b/lsfg-vk-layer/src/hooks/layer.cpp new file mode 100644 index 0000000..17c028d --- /dev/null +++ b/lsfg-vk-layer/src/hooks/layer.cpp @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "layer.hpp" +#include "lsfg-vk-backend/lsfgvk.hpp" +#include "lsfg-vk-common/configuration/detection.hpp" +#include "lsfg-vk-common/helpers/errors.hpp" +#include "lsfg-vk-common/helpers/paths.hpp" + +#include +#include +#include +#include +#include + +#include + +using namespace lsfgvk; +using namespace lsfgvk::layer; + +MyVkLayer::MyVkLayer() { + const auto& profile = ls::findProfile(this->config.get(), ls::identify()); + if (!profile.has_value()) + return; + + std::cerr << "lsfg-vk: using profile with name '" << profile->second.name << "' "; + switch (profile->first) { + case ls::IdentType::OVERRIDE: + std::cerr << "(identified via override)\n"; + break; + case ls::IdentType::EXECUTABLE: + std::cerr << "(identified via executable)\n"; + break; + case ls::IdentType::WINE_EXECUTABLE: + std::cerr << "(identified via wine executable)\n"; + break; + case ls::IdentType::PROCESS_NAME: + std::cerr << "(identified via process name)\n"; + break; + } + + this->current_profile.emplace(profile->second); +} + +bool MyVkLayer::update() { + if (!this->config.update()) + return false; + + const auto& profile = findProfile(this->config.get(), ls::identify()); + if (profile.has_value()) + this->current_profile = profile->second; + else + this->current_profile = std::nullopt; + + return true; +} + +backend::Instance& MyVkLayer::backend() { + if (this->backend_instance.has_value()) + return this->backend_instance.mut(); + + if (!this->current_profile.has_value()) + throw ls::error("attempted to get backend instance while layer is inactive"); + const auto& global = this->config.get().global(); + const auto& profile = *this->current_profile; + + setenv("DISABLE_LSFGVK", "1", 1); + + try { + std::string dll{}; + if (global.dll.has_value()) + dll = *global.dll; + else + dll = ls::findShaderDll(); + + this->backend_instance.emplace( + [gpu = profile.gpu]( + const std::string& deviceName, + std::pair ids, + const std::optional& pci + ) { + if (!gpu) + return true; + + return (deviceName == *gpu) + || (ids.first + ":" + ids.second == *gpu) + || (pci && *pci == *gpu); + }, + dll, global.allow_fp16 + ); + } catch (const std::exception& e) { + unsetenv("DISABLE_LSFGVK"); + throw ls::error("failed to create backend instance", e); + } + + unsetenv("DISABLE_LSFGVK"); + + return this->backend_instance.mut(); +} diff --git a/lsfg-vk-layer/src/hooks/layer.hpp b/lsfg-vk-layer/src/hooks/layer.hpp new file mode 100644 index 0000000..4495e8b --- /dev/null +++ b/lsfg-vk-layer/src/hooks/layer.hpp @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#include "lsfg-vk-backend/lsfgvk.hpp" +#include "lsfg-vk-common/configuration/config.hpp" +#include "lsfg-vk-common/helpers/pointers.hpp" + +#include +#include + +namespace lsfgvk::layer { + + /// device wrapper class + class MyVkLayer { + public: + /// create a vulkan device wrapper + /// @throws ls::vulkan_error on failure + MyVkLayer(); + + /// ensure the layer is up-to-date + /// @return true if the configuration was updated + bool update(); + + /// check if there is an active profile + /// @return true if there is an active profile + [[nodiscard]] bool isActive() const { return this->current_profile.has_value(); } + + /// get the global configuration + /// @return configuration reference + [[nodiscard]] const auto& global() const { return this->config.get().global(); } + /// get the active profile, if any + /// @return profile optional + [[nodiscard]] const auto& profile() const { return *this->current_profile; } + + /// get or create the backend instance + /// @return backend instance reference + /// @throws ls::error if an error occured during backend creation + [[nodiscard]] backend::Instance& backend(); + + // non-moveable, non-copyable + MyVkLayer(const MyVkLayer&) = delete; + MyVkLayer& operator=(const MyVkLayer&) = delete; + MyVkLayer(MyVkLayer&&) = delete; + MyVkLayer& operator=(MyVkLayer&&) = delete; + ~MyVkLayer() = default; + private: + ls::WatchedConfig config; + std::optional current_profile; + + ls::lazy backend_instance; + }; + +} diff --git a/lsfg-vk-layer/src/hooks/swapchain.cpp b/lsfg-vk-layer/src/hooks/swapchain.cpp new file mode 100644 index 0000000..12c58ad --- /dev/null +++ b/lsfg-vk-layer/src/hooks/swapchain.cpp @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "device.hpp" +#include "instance.hpp" +#include "layer.hpp" +#include "lsfg-vk-common/helpers/errors.hpp" +#include "lsfg-vk-common/vulkan/command_buffer.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 +#include +#include +#include +#include +#include +#include + +#include + +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 getSwapchainImages(const vk::Vulkan& vk, VkSwapchainKHR swapchain) { + uint32_t imageCount = 0; + auto res = vk.df().GetSwapchainImagesKHR(vk.dev(), swapchain, &imageCount, nullptr); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "vkGetSwapchainImagesKHR() failed to get image count"); + + std::vector images(imageCount); + res = vk.df().GetSwapchainImagesKHR(vk.dev(), swapchain, &imageCount, images.data()); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "vkGetSwapchainImagesKHR() failed to get images"); + + return images; + } +} + +MyVkSwapchain::MyVkSwapchain(MyVkLayer& layer, MyVkInstance& instance, MyVkDevice& device, + VkSwapchainCreateInfoKHR info, + const std::function& 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; + + info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | 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 + ); + + this->waitSemaphore.emplace(vk, 0); + this->waitValue = 1; + + this->commandBuffer.emplace(vk); + this->fence.emplace(vk); + + const size_t max_flight = std::max(this->images.size(), this->passes.size() + 2); + + this->passes.clear(); + for (size_t i = 0; i < max_flight; i++) { + this->passes.emplace_back(RenderPass { + .commandBuffer = vk::CommandBuffer(vk), + .acquireSemaphore = vk::Semaphore(vk), + .pcs = { + vk::Semaphore(vk), + vk::Semaphore(vk) + } + }); + } + + this->idx = 0; +} + +namespace { + /// helper to acquire the next swapchain image + uint32_t acquireNextImageKHR(const vk::Vulkan& vk, VkSwapchainKHR swapchain, + const vk::Semaphore& semaphore) { + uint32_t imageIdx{}; + + 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; + } + /// 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, + }; + auto res = vk.df().QueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw ls::vulkan_error(res, "vkQueuePresentKHR() failed"); + + return res; + } +} + +std::pair MyVkSwapchain::sync() { + return { this->waitSemaphore->handle(), this->waitValue }; +} + +VkResult MyVkSwapchain::present(VkQueue queue, void* next, uint32_t imageIdx) { + 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); + + // 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 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 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++; + } + + // 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 + ); +} diff --git a/lsfg-vk-layer/src/hooks/swapchain.hpp b/lsfg-vk-layer/src/hooks/swapchain.hpp new file mode 100644 index 0000000..5facd1d --- /dev/null +++ b/lsfg-vk-layer/src/hooks/swapchain.hpp @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#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/timeline_semaphore.hpp" + +#include +#include +#include +#include + +#include + +namespace lsfgvk::layer { + + /// swapchain wrapper class + class MyVkSwapchain { + public: + /// create a vulkan swapchain wrapper + /// @param layer layer reference + /// @param instance parent vulkan instance + /// @param device parent vulkan device + /// @param info base swapchain info + /// @param createFunc function to create the swapchain + /// @throws ls::vulkan_error on vulkan errors + MyVkSwapchain(MyVkLayer& layer, MyVkInstance& instance, MyVkDevice& device, + VkSwapchainCreateInfoKHR info, + const std::function& createFunc); + + /// reinitialize the 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 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); + + /// 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; } + + // non-moveable, non-copyable + MyVkSwapchain(const MyVkSwapchain&) = delete; + MyVkSwapchain& operator=(const MyVkSwapchain&) = delete; + MyVkSwapchain(MyVkSwapchain&&) = delete; + MyVkSwapchain& operator=(MyVkSwapchain&&) = delete; + ~MyVkSwapchain() = default; + private: + ls::R layer; + ls::R instance; + ls::R device; + + VkSwapchainKHR handle; + VkExtent2D extent; + VkFormat format; + std::vector images; + + ls::lazy generator; + + ls::lazy waitSemaphore; + uint64_t waitValue{1}; + + ls::lazy commandBuffer; + ls::lazy fence; + + struct RenderPass { + vk::CommandBuffer commandBuffer; + vk::Semaphore acquireSemaphore; + + std::pair pcs; + }; + std::vector passes; + size_t idx{0}; + }; + +} diff --git a/lsfg-vk-layer/src/instance.cpp b/lsfg-vk-layer/src/instance.cpp deleted file mode 100644 index 8708638..0000000 --- a/lsfg-vk-layer/src/instance.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ - -#include "instance.hpp" -#include "lsfg-vk-common/helpers/paths.hpp" -#include "swapchain.hpp" -#include "lsfg-vk-common/configuration/detection.hpp" -#include "lsfg-vk-common/helpers/errors.hpp" -#include "lsfg-vk-common/vulkan/vulkan.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace lsfgvk; -using namespace lsfgvk::layer; - -namespace { - /// helper function to add required extensions - std::vector add_extensions(const char* const* existingExtensions, size_t count, - const std::vector& requiredExtensions) { - std::vector extensions(count); - std::copy_n(existingExtensions, count, extensions.data()); - - for (const auto& requiredExtension : requiredExtensions) { - auto it = std::ranges::find_if(extensions, - [requiredExtension](const char* extension) { - return std::string(extension) == std::string(requiredExtension); - }); - if (it == extensions.end()) - extensions.push_back(requiredExtension); - } - - return extensions; - } -} - -Root::Root() { - // find active profile - const auto& profile = findProfile(this->config.get(), ls::identify()); - if (!profile.has_value()) - return; - - this->active_profile = profile->second; - - std::cerr << "lsfg-vk: using profile with name '" << this->active_profile->name << "' "; - switch (profile->first) { - case ls::IdentType::OVERRIDE: - std::cerr << "(identified via override)\n"; - break; - case ls::IdentType::EXECUTABLE: - std::cerr << "(identified via executable)\n"; - break; - case ls::IdentType::WINE_EXECUTABLE: - std::cerr << "(identified via wine executable)\n"; - break; - case ls::IdentType::PROCESS_NAME: - std::cerr << "(identified via process name)\n"; - break; - } -} - -bool Root::update() { - if (!this->config.update()) - return false; - - const auto& profile = findProfile(this->config.get(), ls::identify()); - if (profile.has_value()) - this->active_profile = profile->second; - else - this->active_profile = std::nullopt; - - return true; -} - -void Root::modifyInstanceCreateInfo(VkInstanceCreateInfo& createInfo, - const std::function& finish) const { - if (!this->active_profile.has_value()) - return; - - auto extensions = add_extensions( - createInfo.ppEnabledExtensionNames, - createInfo.enabledExtensionCount, - { - "VK_KHR_get_physical_device_properties2", - "VK_KHR_external_memory_capabilities", - "VK_KHR_external_semaphore_capabilities" - } - ); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - finish(); -} - -void Root::modifyDeviceCreateInfo(VkDeviceCreateInfo& createInfo, - const std::function& finish) const { - if (!this->active_profile.has_value()) - return; - - auto extensions = add_extensions( - createInfo.ppEnabledExtensionNames, - createInfo.enabledExtensionCount, - { - "VK_KHR_external_memory", - "VK_KHR_external_memory_fd", - "VK_KHR_external_semaphore", - "VK_KHR_external_semaphore_fd", - "VK_KHR_timeline_semaphore" - } - ); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - bool isFeatureEnabled = false; - auto* featureInfo = reinterpret_cast(const_cast(createInfo.pNext)); - while (featureInfo) { - if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES) { - auto* features = reinterpret_cast(featureInfo); - features->timelineSemaphore = VK_TRUE; - isFeatureEnabled = true; - } else if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES) { - auto* features = reinterpret_cast(featureInfo); - features->timelineSemaphore = VK_TRUE; - isFeatureEnabled = true; - } - - featureInfo = const_cast(featureInfo->pNext); - } - - VkPhysicalDeviceTimelineSemaphoreFeatures timelineFeatures{ - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, - .pNext = const_cast(createInfo.pNext), - .timelineSemaphore = VK_TRUE - }; - if (!isFeatureEnabled) - createInfo.pNext = &timelineFeatures; - - finish(); -} - -void Root::modifySwapchainCreateInfo(const vk::Vulkan& vk, VkSwapchainCreateInfoKHR& createInfo, - const std::function& finish) const { - if (!this->active_profile.has_value()) - return; - - VkSurfaceCapabilitiesKHR caps{}; - auto res = vk.fi().GetPhysicalDeviceSurfaceCapabilitiesKHR( - vk.physdev(), createInfo.surface, &caps); - if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR() failed"); - - context_ModifySwapchainCreateInfo(*this->active_profile, caps.maxImageCount, createInfo); - - finish(); -} - -void Root::createSwapchainContext(const vk::Vulkan& vk, - VkSwapchainKHR swapchain, const SwapchainInfo& info) { - if (!this->active_profile.has_value()) - throw ls::error("attempted to create swapchain context while layer is inactive"); - const auto& profile = *this->active_profile; - - if (!this->backend.has_value()) { // emplace backend late, due to loader bug - const auto& global = this->config.get().global(); - - setenv("DISABLE_LSFGVK", "1", 1); - - try { - std::string dll{}; - if (global.dll.has_value()) - dll = *global.dll; - else - dll = ls::findShaderDll(); - - this->backend.emplace( - [gpu = profile.gpu]( - const std::string& deviceName, - std::pair ids, - const std::optional& pci - ) { - if (!gpu) - return true; - - return (deviceName == *gpu) - || (ids.first + ":" + ids.second == *gpu) - || (pci && *pci == *gpu); - }, - dll, global.allow_fp16 - ); - } catch (const std::exception& e) { - unsetenv("DISABLE_LSFGVK"); - throw ls::error("failed to create backend instance", e); - } - - unsetenv("DISABLE_LSFGVK"); - } - - this->swapchains.emplace(swapchain, - Swapchain(vk, this->backend.mut(), profile, info)); -} - -void Root::removeSwapchainContext(VkSwapchainKHR swapchain) { - this->swapchains.erase(swapchain); -} diff --git a/lsfg-vk-layer/src/instance.hpp b/lsfg-vk-layer/src/instance.hpp deleted file mode 100644 index 35083b6..0000000 --- a/lsfg-vk-layer/src/instance.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ - -#pragma once - -#include "lsfg-vk-backend/lsfgvk.hpp" -#include "lsfg-vk-common/configuration/config.hpp" -#include "lsfg-vk-common/helpers/errors.hpp" -#include "lsfg-vk-common/helpers/pointers.hpp" -#include "lsfg-vk-common/vulkan/vulkan.hpp" -#include "swapchain.hpp" - -#include -#include - -#include - -namespace lsfgvk::layer { - - /// root context of the lsfg-vk layer - class Root { - public: - /// create the lsfg-vk root context - /// @throws ls::error on failure - Root(); - - /// check if the layer is active - /// @return true if active - [[nodiscard]] bool active() const { return this->active_profile.has_value(); } - - /// ensure the layer is up-to-date - /// @return true if the configuration was updated - bool update(); - - /// modify instance create info - /// @param createInfo original create info - /// @param finish function to call after modification - void modifyInstanceCreateInfo(VkInstanceCreateInfo& createInfo, - const std::function& finish) const; - /// modify device create info - /// @param createInfo original create info - /// @param finish function to call after modification - void modifyDeviceCreateInfo(VkDeviceCreateInfo& createInfo, - const std::function& finish) const; - - /// modify swapchain create info - /// @param vk vulkan instance - /// @param createInfo original create info - /// @param finish function to call after modification - void modifySwapchainCreateInfo(const vk::Vulkan& vk, VkSwapchainCreateInfoKHR& createInfo, - const std::function& finish) const; - /// create swapchain context - /// @param vk vulkan instance - /// @param swapchain swapchain handle - /// @param info swapchain info - /// @throws ls::error on failure - void createSwapchainContext(const vk::Vulkan& vk, VkSwapchainKHR swapchain, - const SwapchainInfo& info); - /// get swapchain context - /// @param swapchain swapchain handle - /// @return swapchain context - /// @throws ls::error if not found - [[nodiscard]] Swapchain& getSwapchainContext(VkSwapchainKHR swapchain) { - const auto& it = this->swapchains.find(swapchain); - if (it == this->swapchains.end()) - throw ls::error("swapchain context not found"); - - return it->second; - } - /// remove swapchain context - /// @param swapchain swapchain handle - void removeSwapchainContext(VkSwapchainKHR swapchain); - private: - ls::WatchedConfig config; - std::optional active_profile; - - ls::lazy backend; - std::unordered_map swapchains; - }; - -} diff --git a/lsfg-vk-layer/src/swapchain.cpp b/lsfg-vk-layer/src/swapchain.cpp deleted file mode 100644 index 85033ae..0000000 --- a/lsfg-vk-layer/src/swapchain.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ - -#include "swapchain.hpp" -#include "lsfg-vk-backend/lsfgvk.hpp" -#include "lsfg-vk-common/configuration/config.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/image.hpp" -#include "lsfg-vk-common/vulkan/semaphore.hpp" -#include "lsfg-vk-common/vulkan/vulkan.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace lsfgvk; -using namespace lsfgvk::layer; - -namespace { - VkImageMemoryBarrier barrierHelper(VkImage handle, - VkAccessFlags srcAccessMask, - VkAccessFlags dstAccessMask, - VkImageLayout oldLayout, - VkImageLayout newLayout) { - return VkImageMemoryBarrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = srcAccessMask, - .dstAccessMask = dstAccessMask, - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = handle, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - } -} - -void layer::context_ModifySwapchainCreateInfo(const ls::GameConf& profile, uint32_t maxImages, - VkSwapchainCreateInfoKHR& createInfo) { - createInfo.imageUsage |= - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - - switch (profile.pacing) { - case ls::Pacing::None: - createInfo.minImageCount += profile.multiplier; - if (maxImages && createInfo.minImageCount > maxImages) - createInfo.minImageCount = maxImages; - - createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; - break; - } -} - -Swapchain::Swapchain(const vk::Vulkan& vk, backend::Instance& backend, - ls::GameConf profile, SwapchainInfo info) : - instance(backend), - profile(std::move(profile)), info(std::move(info)) { - const VkExtent2D extent = this->info.extent; - const bool hdr = this->info.format > 57; - - std::vector sourceFds(2); - std::vector destinationFds(this->profile.multiplier - 1); - - this->sourceImages.reserve(sourceFds.size()); - for (int& fd : sourceFds) - this->sourceImages.emplace_back(vk, - extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - std::nullopt, &fd); - - this->destinationImages.reserve(destinationFds.size()); - for (int& fd : destinationFds) - this->destinationImages.emplace_back(vk, - extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - std::nullopt, &fd); - - int syncFd{}; - this->syncSemaphore.emplace(vk, 0, std::nullopt, &syncFd); - - try { - this->ctx = ls::owned_ptr>( - new ls::R(backend.openContext( - { sourceFds.at(0), sourceFds.at(1) }, destinationFds, syncFd, - extent.width, extent.height, - hdr, 1.0F / this->profile.flow_scale, this->profile.performance_mode - )), - [backend = &backend](ls::R& ctx) { - backend->closeContext(ctx); - } - ); - - backend::makeLeaking(); // don't worry about it :3 - } catch (const std::exception& e) { - throw ls::error("failed to create swapchain context", e); - } - - this->renderCommandBuffer.emplace(vk); - this->renderFence.emplace(vk); - for (size_t i = 0; i < this->destinationImages.size(); i++) { - this->passes.emplace_back(RenderPass { - .commandBuffer = vk::CommandBuffer(vk), - .acquireSemaphore = vk::Semaphore(vk) - }); - } - - const size_t frames = std::max(this->info.images.size(), this->destinationImages.size() + 2); - for (size_t i = 0; i < frames; i++) { - this->postCopySemaphores.emplace_back( - vk::Semaphore(vk), - vk::Semaphore(vk) - ); - } -} - -VkResult Swapchain::present(const vk::Vulkan& vk, - VkQueue queue, VkSwapchainKHR swapchain, - void* next_chain, uint32_t imageIdx, - const std::vector& semaphores) { - const auto& swapchainImage = this->info.images.at(imageIdx); - const auto& sourceImage = this->sourceImages.at(this->fidx % 2); - - // schedule frame generation - try { - this->instance.get().scheduleFrames(this->ctx.get()); - } catch (const std::exception& e) { - throw ls::error("failed to schedule frames", e); - } - - // update present mode when not using pacing - if (this->profile.pacing == ls::Pacing::None) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunknown-warning-option" -#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" - auto* info = reinterpret_cast(next_chain); - while (info) { - if (info->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT) { - for (size_t i = 0; i < info->swapchainCount; i++) - const_cast(info->pPresentModes)[i] = - VK_PRESENT_MODE_FIFO_KHR; - } - - info = reinterpret_cast(const_cast(info->pNext)); - } -#pragma clang diagnostic pop - } - - // wait for completion of previous frame - if (this->fidx && !this->renderFence->wait(vk, 150ULL * 1000 * 1000)) - throw ls::vulkan_error(VK_TIMEOUT, "vkWaitForFences() failed"); - this->renderFence->reset(vk); - - // copy swapchain image into backend source image - const auto& cmdbuf = *this->renderCommandBuffer; - cmdbuf.begin(vk); - - cmdbuf.blitImage(vk, - { - barrierHelper(swapchainImage, - VK_ACCESS_NONE, - VK_ACCESS_TRANSFER_READ_BIT, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL - ), - barrierHelper(sourceImage.handle(), - VK_ACCESS_NONE, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - ), - }, - { swapchainImage, sourceImage.handle() }, - sourceImage.getExtent(), - { - barrierHelper(swapchainImage, - VK_ACCESS_TRANSFER_READ_BIT, - VK_ACCESS_MEMORY_READ_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR - ), - } - ); - - cmdbuf.end(vk); - cmdbuf.submit(vk, - semaphores, VK_NULL_HANDLE, 0, - {}, this->syncSemaphore->handle(), this->idx++ - ); - - for (size_t i = 0; i < this->destinationImages.size(); i++) { - auto& pcs = this->postCopySemaphores.at(this->idx % this->postCopySemaphores.size()); - auto& destinationImage = this->destinationImages.at(i); - auto& pass = this->passes.at(i); - - // acquire swapchain image - uint32_t aqImageIdx{}; - auto res = vk.df().AcquireNextImageKHR(vk.dev(), swapchain, - UINT64_MAX, pass.acquireSemaphore.handle(), - VK_NULL_HANDLE, - &aqImageIdx - ); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - throw ls::vulkan_error(res, "vkAcquireNextImageKHR() failed"); - - const auto& aquiredSwapchainImage = this->info.images.at(aqImageIdx); - - // copy backend destination image into swapchain image - auto& cmdbuf = pass.commandBuffer; - cmdbuf.begin(vk); - - cmdbuf.blitImage(vk, - { - barrierHelper(destinationImage.handle(), - VK_ACCESS_NONE, - VK_ACCESS_TRANSFER_READ_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL - ), - barrierHelper(aquiredSwapchainImage, - VK_ACCESS_NONE, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - ), - }, - { destinationImage.handle(), aquiredSwapchainImage }, - destinationImage.getExtent(), - { - barrierHelper(aquiredSwapchainImage, - VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_MEMORY_READ_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR - ), - } - ); - - std::vector waitSemaphores{ pass.acquireSemaphore.handle() }; - if (i) { // non-first pass - const auto& prevPCS = this->postCopySemaphores.at((this->idx - 1) % this->postCopySemaphores.size()); - waitSemaphores.push_back(prevPCS.second.handle()); - } - - const std::vector signalSemaphores{ - pcs.first.handle(), - pcs.second.handle() - }; - - cmdbuf.end(vk); - cmdbuf.submit(vk, - waitSemaphores, this->syncSemaphore->handle(), this->idx, - signalSemaphores, VK_NULL_HANDLE, 0, - i == this->destinationImages.size() - 1 ? this->renderFence->handle() : VK_NULL_HANDLE - ); - - // present swapchain image - const VkPresentInfoKHR presentInfo{ - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = i ? nullptr : next_chain, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &pcs.first.handle(), - .swapchainCount = 1, - .pSwapchains = &swapchain, - .pImageIndices = &aqImageIdx, - }; - res = vk.df().QueuePresentKHR(queue, - &presentInfo); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - throw ls::vulkan_error(res, "vkQueuePresentKHR() failed"); - - this->idx++; - } - - // present original swapchain image - auto& lastPCS = this->postCopySemaphores.at((this->idx - 1) % this->postCopySemaphores.size()); - const VkPresentInfoKHR presentInfo{ - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &lastPCS.second.handle(), - .swapchainCount = 1, - .pSwapchains = &swapchain, - .pImageIndices = &imageIdx, - }; - auto res = vk.df().QueuePresentKHR(queue, &presentInfo); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - throw ls::vulkan_error(res, "vkQueuePresentKHR() failed"); - - this->fidx++; - return res; -} diff --git a/lsfg-vk-layer/src/swapchain.hpp b/lsfg-vk-layer/src/swapchain.hpp deleted file mode 100644 index 3c711a0..0000000 --- a/lsfg-vk-layer/src/swapchain.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ - -#pragma once - -#include "lsfg-vk-backend/lsfgvk.hpp" -#include "lsfg-vk-common/configuration/config.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/image.hpp" -#include "lsfg-vk-common/vulkan/semaphore.hpp" -#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp" -#include "lsfg-vk-common/vulkan/vulkan.hpp" - -#include -#include -#include - -#include - -namespace lsfgvk::layer { - - /// swapchain info struct - struct SwapchainInfo { - std::vector images; - VkFormat format; - VkColorSpaceKHR colorSpace; - VkExtent2D extent; - VkPresentModeKHR presentMode; - }; - - /// modify the swapchain create info based on the profile pre-swapchain creation - /// @param profile active game profile - /// @param maxImages maximum number of images supported by the surface - /// @param createInfo swapchain create info to modify - void context_ModifySwapchainCreateInfo(const ls::GameConf& profile, uint32_t maxImages, - VkSwapchainCreateInfoKHR& createInfo); - - /// swapchain context for a layer instance - class Swapchain { - public: - /// create a new swapchain context - /// @param vk vulkan instance - /// @param backend lsfg-vk backend instance - /// @param profile active game profile - /// @param info swapchain info - Swapchain(const vk::Vulkan& vk, backend::Instance& backend, - ls::GameConf profile, SwapchainInfo info); - - /// present a frame - /// @param vk vulkan instance - /// @param queue presentation queue - /// @param next_chain next chain pointer for the present info (WARN: shared!) - /// @param imageIdx swapchain image index to present to - /// @param semaphores semaphores to wait on before presenting - /// @throws ls::vulkan_error on vulkan errors - VkResult present(const vk::Vulkan& vk, - VkQueue queue, VkSwapchainKHR swapchain, - void* next_chain, uint32_t imageIdx, - const std::vector& semaphores); - private: - std::vector sourceImages; - std::vector destinationImages; - ls::lazy syncSemaphore; - - ls::lazy renderCommandBuffer; - ls::lazy renderFence; - struct RenderPass { - vk::CommandBuffer commandBuffer; - vk::Semaphore acquireSemaphore; - }; - std::vector passes; - std::vector> postCopySemaphores; - - ls::R instance; - ls::owned_ptr> ctx; - size_t idx{1}; - size_t fidx{0}; // real frame index - - ls::GameConf profile; - SwapchainInfo info; - }; - -}