feat(frame-pacing): separate instance & swapchain into several objects

This commit is contained in:
PancakeTAS 2025-12-31 11:12:00 +01:00
parent c93e18399d
commit 35933799c9
No known key found for this signature in database
16 changed files with 1208 additions and 876 deletions

View file

@ -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})

View file

@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <utility>
@ -24,27 +24,25 @@ using namespace lsfgvk::layer;
namespace {
// global layer info initialized at layer negotiation
struct LayerInfo {
std::unordered_map<std::string, PFN_vkVoidFunction> map; //!< function pointer override map
PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
std::unordered_map<std::string, PFN_vkVoidFunction> 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<VkInstance, std::unique_ptr<MyVkInstance>> instances;
std::unordered_map<VkDevice, std::unique_ptr<MyVkDevice>> devices;
std::unordered_map<VkSwapchainKHR, std::unique_ptr<MyVkSwapchain>> swapchains;
}* layer_info; // NOLINT (global variable)
// instance-wide info initialized at instance creation(s)
struct InstanceInfo {
std::vector<VkInstance> handles; // there may be several instances
vk::VulkanInstanceFuncs funcs;
std::unordered_map<VkDevice, vk::Vulkan> devices;
std::unordered_map<VkSwapchainKHR, ls::R<vk::Vulkan>> swapchains;
std::unordered_map<VkSwapchainKHR, SwapchainInfo> 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<VkLayerInstanceCreateInfo*>(const_cast<void*>(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<MyVkInstance>(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<VkLayerDeviceCreateInfo*>(const_cast<void*>(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<PFN_vkCreateDevice>(
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<MyVkDevice>(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<std::mutex> 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<PFN_vkDestroyDevice>(
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<PFN_vkDestroyInstance>(
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<MyVkSwapchain>(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<VkImage> 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<vk::Vulkan>(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<uint64_t> waitValues(info->waitSemaphoreCount);
std::vector<VkSemaphore> signalSemaphores;
std::vector<uint64_t> 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<uint32_t>(waitValues.size()),
.pWaitSemaphoreValues = waitValues.data(),
.signalSemaphoreValueCount = static_cast<uint32_t>(signalValues.size()),
.pSignalSemaphoreValues = signalValues.data()
};
std::vector<VkPipelineStageFlags> 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<uint32_t>(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<VkSemaphore> 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<void*>(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<PFN_vkVoidFunction>(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;
}

View file

@ -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 <cstdint>
#include <exception>
#include <functional>
#include <optional>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
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<int> sourceFds(2);
std::vector<int> 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<ls::R<backend::Context>>(
new ls::R<backend::Context>(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<backend::Context>& 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<VkSemaphore, uint64_t> 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<VkSemaphore, uint64_t> 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++ };
}

View file

@ -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 <cstdint>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
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<VkSemaphore, uint64_t> 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<VkSemaphore, uint64_t> 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<backend::Instance> instance;
ls::R<const vk::Vulkan> vk;
std::vector<vk::Image> sourceImages;
std::vector<vk::Image> destinationImages;
uint64_t frameIdx{0}; // real frames-only
uint64_t generatedIdx{0}; // generated frames-only
ls::lazy<vk::TimelineSemaphore> syncSemaphore;
uint64_t syncValue{1};
ls::owned_ptr<ls::R<backend::Context>> ctx;
};
}

View file

@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
/// helper function to add required extensions
std::vector<const char*> add_extensions(const char* const* existingExtensions, size_t count,
const std::vector<const char*>& requiredExtensions) {
std::vector<const char*> 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<VkQueueFamilyProperties> 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<VkDevice(VkDeviceCreateInfo*)>& 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<uint32_t>(extensions.size());
info.ppEnabledExtensionNames = extensions.data();
// enable timeline semaphores
bool isFeatureEnabled = false;
auto* featureInfo = reinterpret_cast<VkBaseInStructure*>(const_cast<void*>(info.pNext));
while (featureInfo) {
if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES) {
auto* features = reinterpret_cast<VkPhysicalDeviceVulkan12Features*>(featureInfo);
features->timelineSemaphore = VK_TRUE;
isFeatureEnabled = true;
} else if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES) {
auto* features = reinterpret_cast<VkPhysicalDeviceTimelineSemaphoreFeatures*>(featureInfo);
features->timelineSemaphore = VK_TRUE;
isFeatureEnabled = true;
}
featureInfo = const_cast<VkBaseInStructure*>(featureInfo->pNext);
}
VkPhysicalDeviceTimelineSemaphoreFeatures timelineFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
.pNext = const_cast<void*>(info.pNext),
.timelineSemaphore = VK_TRUE
};
if (!isFeatureEnabled)
info.pNext = &timelineFeatures;
// append a graphics queue
std::vector<VkDeviceQueueCreateInfo> 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<uint32_t> 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<uint32_t>(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);
}

View file

@ -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 <functional>
#include <mutex>
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
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<VkDevice(VkDeviceCreateInfo*)>& 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<MyVkLayer> layer;
ls::R<MyVkInstance> instance;
VkDevice handle;
vk::VulkanDeviceFuncs dfuncs{};
ls::lazy<vk::Vulkan> vk;
OffloadQueue offloadQueue{};
};
}

View file

@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
/// helper function to add required extensions
std::vector<const char*> add_extensions(const char* const* existingExtensions, size_t count,
const std::vector<const char*>& requiredExtensions) {
std::vector<const char*> 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<VkInstance(VkInstanceCreateInfo*)>& 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<uint32_t>(extensions.size());
info.ppEnabledExtensionNames = extensions.data();
// create instance
this->handle = createFunc(&info); // NOLINT (prefer member initializer)
this->ifuncs = vk::initVulkanInstanceFuncs(this->handle, addr, true);
}

View file

@ -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 <functional>
#include <vulkan/vulkan_core.h>
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<VkInstance(VkInstanceCreateInfo*)>& 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<MyVkLayer> layer;
VkInstance handle;
vk::VulkanInstanceFuncs ifuncs{};
};
}

View file

@ -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 <exception>
#include <iostream>
#include <optional>
#include <string>
#include <utility>
#include <stdlib.h>
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<const std::string&, const std::string&> ids,
const std::optional<std::string>& 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();
}

View file

@ -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 <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
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<ls::GameConf> current_profile;
ls::lazy<backend::Instance> backend_instance;
};
}

View file

@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#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;
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<VkImage> 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<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;
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<VkSemaphore, uint64_t> 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<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++;
}
// 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
);
}

View file

@ -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 <cstdint>
#include <functional>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
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<VkSwapchainKHR(VkSwapchainCreateInfoKHR*)>& 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<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);
/// 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<MyVkLayer> layer;
ls::R<MyVkInstance> instance;
ls::R<MyVkDevice> device;
VkSwapchainKHR handle;
VkExtent2D extent;
VkFormat format;
std::vector<VkImage> images;
ls::lazy<Generator> generator;
ls::lazy<vk::TimelineSemaphore> waitSemaphore;
uint64_t waitValue{1};
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};
};
}

View file

@ -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 <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <functional>
#include <iostream>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <stdlib.h>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
/// helper function to add required extensions
std::vector<const char*> add_extensions(const char* const* existingExtensions, size_t count,
const std::vector<const char*>& requiredExtensions) {
std::vector<const char*> 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<void(void)>& 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<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
finish();
}
void Root::modifyDeviceCreateInfo(VkDeviceCreateInfo& createInfo,
const std::function<void(void)>& 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<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
bool isFeatureEnabled = false;
auto* featureInfo = reinterpret_cast<VkBaseInStructure*>(const_cast<void*>(createInfo.pNext));
while (featureInfo) {
if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES) {
auto* features = reinterpret_cast<VkPhysicalDeviceVulkan12Features*>(featureInfo);
features->timelineSemaphore = VK_TRUE;
isFeatureEnabled = true;
} else if (featureInfo->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES) {
auto* features = reinterpret_cast<VkPhysicalDeviceTimelineSemaphoreFeatures*>(featureInfo);
features->timelineSemaphore = VK_TRUE;
isFeatureEnabled = true;
}
featureInfo = const_cast<VkBaseInStructure*>(featureInfo->pNext);
}
VkPhysicalDeviceTimelineSemaphoreFeatures timelineFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
.pNext = const_cast<void*>(createInfo.pNext),
.timelineSemaphore = VK_TRUE
};
if (!isFeatureEnabled)
createInfo.pNext = &timelineFeatures;
finish();
}
void Root::modifySwapchainCreateInfo(const vk::Vulkan& vk, VkSwapchainCreateInfoKHR& createInfo,
const std::function<void(void)>& 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<const std::string&, const std::string&> ids,
const std::optional<std::string>& 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);
}

View file

@ -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 <optional>
#include <unordered_map>
#include <vulkan/vulkan_core.h>
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<void(void)>& 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<void(void)>& 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<void(void)>& 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<ls::GameConf> active_profile;
ls::lazy<backend::Instance> backend;
std::unordered_map<VkSwapchainKHR, Swapchain> swapchains;
};
}

View file

@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <functional>
#include <optional>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
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<int> sourceFds(2);
std::vector<int> 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<ls::R<backend::Context>>(
new ls::R<backend::Context>(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<backend::Context>& 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<VkSemaphore>& 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<VkSwapchainPresentModeInfoEXT*>(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<VkPresentModeKHR*>(info->pPresentModes)[i] =
VK_PRESENT_MODE_FIFO_KHR;
}
info = reinterpret_cast<VkSwapchainPresentModeInfoEXT*>(const_cast<void*>(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<VkSemaphore> 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<VkSemaphore> 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;
}

View file

@ -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 <cstdint>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace lsfgvk::layer {
/// swapchain info struct
struct SwapchainInfo {
std::vector<VkImage> 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<VkSemaphore>& semaphores);
private:
std::vector<vk::Image> sourceImages;
std::vector<vk::Image> destinationImages;
ls::lazy<vk::TimelineSemaphore> syncSemaphore;
ls::lazy<vk::CommandBuffer> renderCommandBuffer;
ls::lazy<vk::Fence> renderFence;
struct RenderPass {
vk::CommandBuffer commandBuffer;
vk::Semaphore acquireSemaphore;
};
std::vector<RenderPass> passes;
std::vector<std::pair<vk::Semaphore, vk::Semaphore>> postCopySemaphores;
ls::R<backend::Instance> instance;
ls::owned_ptr<ls::R<backend::Context>> ctx;
size_t idx{1};
size_t fidx{0}; // real frame index
ls::GameConf profile;
SwapchainInfo info;
};
}