mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2026-04-22 02:11:43 +00:00
feat(frame-pacing): separate instance & swapchain into several objects
This commit is contained in:
parent
c93e18399d
commit
35933799c9
16 changed files with 1208 additions and 876 deletions
|
|
@ -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})
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
169
lsfg-vk-layer/src/generator.cpp
Normal file
169
lsfg-vk-layer/src/generator.cpp
Normal 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++ };
|
||||
}
|
||||
69
lsfg-vk-layer/src/generator.hpp
Normal file
69
lsfg-vk-layer/src/generator.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
144
lsfg-vk-layer/src/hooks/device.cpp
Normal file
144
lsfg-vk-layer/src/hooks/device.cpp
Normal 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);
|
||||
}
|
||||
71
lsfg-vk-layer/src/hooks/device.hpp
Normal file
71
lsfg-vk-layer/src/hooks/device.hpp
Normal 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{};
|
||||
};
|
||||
|
||||
}
|
||||
58
lsfg-vk-layer/src/hooks/instance.cpp
Normal file
58
lsfg-vk-layer/src/hooks/instance.cpp
Normal 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);
|
||||
}
|
||||
48
lsfg-vk-layer/src/hooks/instance.hpp
Normal file
48
lsfg-vk-layer/src/hooks/instance.hpp
Normal 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{};
|
||||
};
|
||||
|
||||
}
|
||||
98
lsfg-vk-layer/src/hooks/layer.cpp
Normal file
98
lsfg-vk-layer/src/hooks/layer.cpp
Normal 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();
|
||||
}
|
||||
54
lsfg-vk-layer/src/hooks/layer.hpp
Normal file
54
lsfg-vk-layer/src/hooks/layer.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
219
lsfg-vk-layer/src/hooks/swapchain.cpp
Normal file
219
lsfg-vk-layer/src/hooks/swapchain.cpp
Normal 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
|
||||
);
|
||||
}
|
||||
93
lsfg-vk-layer/src/hooks/swapchain.hpp
Normal file
93
lsfg-vk-layer/src/hooks/swapchain.hpp
Normal 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};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue