feat(bindless): Implement updated backend in lsfg-vk-layer

This commit is contained in:
PancakeTAS 2026-04-25 23:32:59 +02:00
parent fd0746c049
commit 50c3895c62
No known key found for this signature in database
7 changed files with 241 additions and 227 deletions

View file

@ -67,6 +67,10 @@ vk::PhysicalDevice vkhelper::findPhysicalDevice(
auto& props{info.properties};
// Check first if id is not given
if (id.empty())
return physdev;
// Compare device name
props.deviceName.back() = '\0'; // Ensure null-termination
if (id == std::string(props.deviceName))

View file

@ -29,4 +29,3 @@ Checks:
# Vulkan layers often require C-style memory access
- -cppcoreguidelines-pro-bounds-pointer-arithmetic
- -cppcoreguidelines-pro-type-union-access
- -clang-diagnostic-unsafe-buffer-usage

View file

@ -22,17 +22,17 @@
using namespace lsfgvk::layer;
namespace {
// global layer info initialized at layer negotiation
/// Global layer info initialized at layer negotiation
struct LayerInfo {
std::unordered_map<std::string, PFN_vkVoidFunction> map; //!< function pointer override map
std::unordered_map<std::string, PFN_vkVoidFunction> map; //!< Function pointer override map
PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
Root root;
}* layer_info; // NOLINT (global variable)
// instance-wide info initialized at instance creation(s)
/// Instance-wide info initialized at instance creation(s)
struct InstanceInfo {
std::vector<VkInstance> handles; // there may be several instances
std::vector<VkInstance> handles; // There may be several instances
vk::VulkanInstanceFuncs funcs;
std::unordered_map<VkDevice, vk::Vulkan> devices;
@ -40,44 +40,44 @@ namespace {
std::unordered_map<VkSwapchainKHR, SwapchainInfo> swapchainInfos;
}* instance_info; // NOLINT (global variable)
// create instance
/// Create instance
VkResult myvkCreateInstance(
const VkInstanceCreateInfo* info,
const VkAllocationCallbacks* alloc,
VkInstance* instance) {
// apply layer chaining
// Apply layer chaining
auto* layerInfo = reinterpret_cast<VkLayerInstanceCreateInfo*>(const_cast<void*>(info->pNext));
while (layerInfo && (layerInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
|| layerInfo->function != VK_LAYER_LINK_INFO)) {
layerInfo = reinterpret_cast<VkLayerInstanceCreateInfo*>(const_cast<void*>(layerInfo->pNext));
}
if (!layerInfo) {
std::cerr << "lsfg-vk: no layer info found in pNext chain, "
std::cerr << "lsfg-vk: No layer info found in pNext chain, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
auto* linkInfo = layerInfo->u.pLayerInfo;
if (!linkInfo) {
std::cerr << "lsfg-vk: link info is null, "
std::cerr << "lsfg-vk: Link info is null, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
layer_info->GetInstanceProcAddr = linkInfo->pfnNextGetInstanceProcAddr;
if (!layer_info->GetInstanceProcAddr) {
std::cerr << "lsfg-vk: next layer's vkGetInstanceProcAddr is null, "
std::cerr << "lsfg-vk: Next layer's vkGetInstanceProcAddr is null, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
layerInfo->u.pLayerInfo = linkInfo->pNext; // advance for next layer
layerInfo->u.pLayerInfo = linkInfo->pNext; // Advance for next layer
// create instance
// Create instance
auto* vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(
layer_info->GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"));
if (!vkCreateInstance) {
std::cerr << "lsfg-vk: failed to get next layer's vkCreateInstance, "
std::cerr << "lsfg-vk: Failed to get next layer's vkCreateInstance, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
@ -103,64 +103,64 @@ namespace {
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. "
std::cerr << "lsfg-vk: Required Vulkan instance extensions are not present. "
"Your GPU driver is not supported.\n";
return e.error();
}
}
// create device
/// Create device
VkResult myvkCreateDevice(
VkPhysicalDevice physdev,
const VkDeviceCreateInfo* info,
const VkAllocationCallbacks* alloc,
VkDevice* device) {
// apply layer chaining
// Apply layer chaining
auto* layerInfo = reinterpret_cast<VkLayerDeviceCreateInfo*>(const_cast<void*>(info->pNext));
while (layerInfo && (layerInfo->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
|| layerInfo->function != VK_LAYER_LINK_INFO)) {
layerInfo = reinterpret_cast<VkLayerDeviceCreateInfo*>(const_cast<void*>(layerInfo->pNext));
}
if (!layerInfo) {
std::cerr << "lsfg-vk: no layer info found in pNext chain, "
std::cerr << "lsfg-vk: No layer info found in pNext chain, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
auto* linkInfo = layerInfo->u.pLayerInfo;
if (!linkInfo) {
std::cerr << "lsfg-vk: link info is null, "
std::cerr << "lsfg-vk: Link info is null, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
instance_info->funcs.GetDeviceProcAddr = linkInfo->pfnNextGetDeviceProcAddr;
if (!linkInfo->pfnNextGetDeviceProcAddr) {
std::cerr << "lsfg-vk: next layer's vkGetDeviceProcAddr is null, "
std::cerr << "lsfg-vk: Next layer's vkGetDeviceProcAddr is null, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
layerInfo->u.pLayerInfo = linkInfo->pNext; // advance for next layer
layerInfo->u.pLayerInfo = linkInfo->pNext; // Advance for next layer
// fetch device loader functions
// Fetch device loader functions
layerInfo = reinterpret_cast<VkLayerDeviceCreateInfo*>(const_cast<void*>(info->pNext));
while (layerInfo && (layerInfo->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
|| layerInfo->function != VK_LOADER_DATA_CALLBACK)) {
layerInfo = reinterpret_cast<VkLayerDeviceCreateInfo*>(const_cast<void*>(layerInfo->pNext));
}
if (!layerInfo) {
std::cerr << "lsfg-vk: no layer loader data found in pNext chain.\n";
std::cerr << "lsfg-vk: No layer loader data found in pNext chain.\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
auto* setLoaderData = layerInfo->u.pfnSetDeviceLoaderData;
if (!setLoaderData) {
std::cerr << "lsfg-vk: instance loader data function is null.\n";
std::cerr << "lsfg-vk: Instance loader data function is null.\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
// create device
// Create device
try {
VkDeviceCreateInfo newInfo = *info;
layer_info->root.modifyDeviceCreateInfo(newInfo,
@ -172,12 +172,12 @@ namespace {
);
} 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. "
std::cerr << "lsfg-vk: Required Vulkan device extensions are not present. "
"Your GPU driver is not supported.\n";
return e.error();
}
// create layer instance
// Create layer instance
try {
instance_info->devices.emplace(
*device,
@ -189,25 +189,25 @@ namespace {
)
);
} catch (const std::exception& e) {
std::cerr << "lsfg-vk: something went wrong during lsfg-vk initialization:\n";
std::cerr << "lsfg-vk: Something went wrong during lsfg-vk initialization:\n";
std::cerr << "- " << e.what() << '\n';
}
return VK_SUCCESS;
}
// destroy device
/// Destroy device
void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* alloc) {
// destroy layer instance
// Destroy layer instance
auto it = instance_info->devices.find(device);
if (it != instance_info->devices.end())
instance_info->devices.erase(it);
// destroy device
// Destroy device
auto vkDestroyDevice = reinterpret_cast<PFN_vkDestroyDevice>(
instance_info->funcs.GetDeviceProcAddr(device, "vkDestroyDevice"));
if (!vkDestroyDevice) {
std::cerr << "lsfg-vk: failed to get next layer's vkDestroyDevice, "
std::cerr << "lsfg-vk: Failed to get next layer's vkDestroyDevice, "
"the previous layer does not follow spec\n";
return;
}
@ -215,24 +215,24 @@ namespace {
vkDestroyDevice(device, alloc);
}
// destroy instance
/// Destroy instance
void myvkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* alloc) {
// remove instance handle
// Remove instance handle
auto it = std::ranges::find(instance_info->handles, instance);
if (it != instance_info->handles.end())
instance_info->handles.erase(it);
// destroy instance info if no handles remain
// Destroy instance info if no handles remain
if (instance_info->handles.empty()) {
delete instance_info; // NOLINT (memory management)
instance_info = nullptr;
}
// destroy instance
// Destroy instance
auto vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(
layer_info->GetInstanceProcAddr(instance, "vkDestroyInstance"));
if (!vkDestroyInstance) {
std::cerr << "lsfg-vk: failed to get next layer's vkDestroyInstance, "
std::cerr << "lsfg-vk: Failed to get next layer's vkDestroyInstance, "
"the previous layer does not follow spec\n";
return;
}
@ -240,7 +240,7 @@ namespace {
vkDestroyInstance(instance, alloc);
}
// get optional function pointer override
/// Get optional function pointer override
PFN_vkVoidFunction getProcAddr(const std::string& name) {
auto it = layer_info->map.find(name);
if (it != layer_info->map.end())
@ -248,7 +248,7 @@ namespace {
return nullptr;
}
// get instance-level function pointers
/// Get instance-level function pointers
PFN_vkVoidFunction myvkGetInstanceProcAddr(VkInstance instance, const char* name) {
if (!name) return nullptr;
@ -259,7 +259,7 @@ namespace {
return layer_info->GetInstanceProcAddr(instance, name);
}
// get device-level function pointers
/// Get device-level function pointers
PFN_vkVoidFunction myvkGetDeviceProcAddr(VkDevice device, const char* name) {
if (!name) return nullptr;
@ -282,7 +282,7 @@ namespace {
return VK_ERROR_INITIALIZATION_FAILED;
try {
// retire old swapchain
// Retire old swapchain
if (info->oldSwapchain) {
const auto& info_mapping = instance_info->swapchainInfos.find(info->oldSwapchain);
if (info_mapping != instance_info->swapchainInfos.end())
@ -295,9 +295,9 @@ namespace {
layer_info->root.removeSwapchainContext(info->oldSwapchain);
}
layer_info->root.update(); // ensure config is up to date
layer_info->root.update(); // Ensure config is up to date
// create swapchain
// Create swapchain
VkSwapchainCreateInfoKHR newInfo = *info;
layer_info->root.modifySwapchainCreateInfo(it->second, newInfo,
[=, newInfo = &newInfo]() {
@ -308,7 +308,7 @@ namespace {
}
);
// get all swapchain images
// Get all swapchain images
uint32_t imageCount{};
auto res = it->second.df().GetSwapchainImagesKHR(device, *swapchain,
&imageCount, VK_NULL_HANDLE);
@ -329,7 +329,7 @@ namespace {
.presentMode = newInfo.presentMode
}).first->second;
// create lsfg-vk swapchain
// Create lsfg-vk swapchain
layer_info->root.createSwapchainContext(it->second, *swapchain, info);
instance_info->swapchains.emplace(*swapchain,
@ -337,11 +337,11 @@ namespace {
return res;
} catch (const ls::vulkan_error& e) {
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain creation:\n";
std::cerr << "lsfg-vk: Something went wrong during lsfg-vk swapchain creation:\n";
std::cerr << "- " << 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 << "lsfg-vk: Something went wrong during lsfg-vk swapchain creation:\n";
std::cerr << "- " << e.what() << '\n';
return VK_ERROR_INITIALIZATION_FAILED;
}
@ -352,12 +352,12 @@ namespace {
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
VkResult result = VK_SUCCESS;
// ensure layer config is up to date
// Ensure layer config is up to date
bool reload{};
try {
reload = layer_info->root.update();
} catch (const std::exception&) {
reload = false; // ignore parse errors
reload = false; // Ignore parse errors
}
if (reload) {
@ -376,7 +376,7 @@ namespace {
}
}
// present each swapchain
// Present each swapchain
for (size_t i = 0; i < info->swapchainCount; i++) {
const auto& swapchain = info->pSwapchains[i];
@ -402,7 +402,7 @@ namespace {
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
} // Silently swallow out-of-date errors
result = e.error();
} catch (const std::exception& e) {
@ -437,7 +437,7 @@ namespace {
layer_info->root.removeSwapchainContext(swapchain);
// destroy swapchain
// Destroy swapchain
it->second.df().DestroySwapchainKHR(device, swapchain, alloc);
}
}
@ -445,13 +445,13 @@ namespace {
/// Vulkan layer entrypoint
__attribute__((visibility("default")))
VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVersionStruct) {
// ensure loader compatibility
// Ensure loader compatibility
if (!pVersionStruct
|| pVersionStruct->sType != LAYER_NEGOTIATE_INTERFACE_STRUCT
|| pVersionStruct->loaderLayerInterfaceVersion < 2)
return VK_ERROR_INITIALIZATION_FAILED;
// if the layer has already been initialized, skip
// If the layer has already been initialized, skip
if (layer_info) {
pVersionStruct->loaderLayerInterfaceVersion = 2;
pVersionStruct->pfnGetPhysicalDeviceProcAddr = nullptr;
@ -460,7 +460,7 @@ VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVers
return VK_SUCCESS;
}
// load the layer configuration
// Load the layer configuration
try {
layer_info = new LayerInfo { // NOLINT (memory management)
.map = {
@ -477,20 +477,20 @@ VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVers
.root = Root()
};
if (!layer_info->root.active()) { // skip inactive
if (!layer_info->root.active()) { // 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 << "lsfg-vk: Something went wrong during lsfg-vk layer initialization:\n";
std::cerr << "- " << e.what() << '\n';
return VK_ERROR_INITIALIZATION_FAILED;
}
// emplace function pointers/version
// Emplace function pointers/version
pVersionStruct->loaderLayerInterfaceVersion = 2;
pVersionStruct->pfnGetPhysicalDeviceProcAddr = nullptr;
pVersionStruct->pfnGetDeviceProcAddr = myvkGetDeviceProcAddr;

View file

@ -2,10 +2,10 @@
#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 "swapchain.hpp"
#include <algorithm>
#include <cstdint>
@ -25,7 +25,7 @@ using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
/// helper function to add required extensions
/// 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);
@ -45,14 +45,14 @@ namespace {
}
Root::Root() {
// find active profile
// 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 << "' ";
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";
@ -167,10 +167,10 @@ void Root::modifySwapchainCreateInfo(const vk::Vulkan& vk, VkSwapchainCreateInfo
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");
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
if (!this->backend.has_value()) { // Emplace backend late, due to loader bug
const auto& global = this->config.get().global();
setenv("DISABLE_LSFGVK", "1", 1);
@ -183,23 +183,12 @@ void Root::createSwapchainContext(const vk::Vulkan& vk,
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);
},
profile.gpu.value_or(""),
dll, global.allow_fp16
);
} catch (const std::exception& e) {
unsetenv("DISABLE_LSFGVK");
throw ls::error("failed to create backend instance", e);
throw ls::error("Failed to create backend instance", e);
}
unsetenv("DISABLE_LSFGVK");

View file

@ -2,13 +2,14 @@
#pragma once
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "lsfg-vk/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 <functional>
#include <optional>
#include <unordered_map>
@ -16,49 +17,75 @@
namespace lsfgvk::layer {
/// root context of the lsfg-vk layer
///
/// Root context of the lsfg-vk layer
///
class Root {
public:
/// create the lsfg-vk root context
///
/// Create the lsfg-vk root context
///
/// @throws ls::error on failure
///
Root();
/// check if the layer is active
/// @return true if active
///
/// 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
///
/// 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
///
/// 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
///
/// 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
///
/// 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
///
/// 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
///
/// 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())
@ -66,14 +93,17 @@ namespace lsfgvk::layer {
return it->second;
}
/// remove swapchain context
/// @param swapchain swapchain handle
///
/// 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;
ls::lazy<lsfgvk::Instance> backend;
std::unordered_map<VkSwapchainKHR, Swapchain> swapchains;
};

View file

@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "swapchain.hpp"
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "lsfg-vk/lsfgvk.hpp"
#include "lsfg-vk-common/configuration/config.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
@ -10,11 +10,10 @@
#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 <memory>
#include <optional>
#include <utility>
#include <vector>
@ -25,6 +24,7 @@ using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
/// Barrier helper
VkImageMemoryBarrier barrierHelper(VkImage handle,
VkAccessFlags srcAccessMask,
VkAccessFlags dstAccessMask,
@ -66,66 +66,45 @@ void layer::context_ModifySwapchainCreateInfo(const ls::GameConf& profile, uint3
}
}
Swapchain::Swapchain(const vk::Vulkan& vk, backend::Instance& backend,
Swapchain::Swapchain(const vk::Vulkan& vk, lsfgvk::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);
}
this->ctx = std::make_unique<lsfgvk::Context>(
backend,
extent.width, extent.height,
this->profile.flow_scale,
this->profile.performance_mode
);
backend::makeLeaking(); // don't worry about it :3
this->total = static_cast<uint32_t>(this->profile.multiplier) - 1;
} catch (const std::exception& e) {
throw ls::error("failed to create swapchain context", e);
throw ls::error("Failed to create swapchain context", e);
}
const auto exportedFds = this->ctx->exportFds();
this->sourceImage.emplace(vk,
extent, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
exportedFds.sourceFd, std::nullopt, 2);
this->destinationImage.emplace(vk,
extent, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
exportedFds.destinationFd, std::nullopt, 2); // FIXME: Should be 1
this->syncSemaphore.emplace(vk, 0, exportedFds.syncFd);
this->renderCommandBuffer.emplace(vk);
this->renderFence.emplace(vk);
for (size_t i = 0; i < this->destinationImages.size(); i++) {
this->finalSemaphore.emplace(vk);
for (size_t i = 0; i < this->total; i++) {
this->passes.emplace_back(RenderPass {
.commandBuffer = vk::CommandBuffer(vk),
.acquireSemaphore = vk::Semaphore(vk)
.acquireSemaphore = vk::Semaphore(vk),
.copySemaphore = 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,
@ -133,19 +112,18 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
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);
const auto sourceImageIdx{static_cast<uint32_t>(this->iteration) % 2};
// schedule frame generation
// Schedule frame generation
try {
this->instance.get().scheduleFrames(this->ctx.get());
this->ctx->dispatch(this->total);
} catch (const std::exception& e) {
throw ls::error("failed to schedule frames", e);
throw ls::error("Failed to schedule frames", e);
}
// update present mode when not using pacing
// 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) {
@ -160,12 +138,12 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
#pragma clang diagnostic pop
}
// wait for completion of previous frame
if (this->fidx && !this->renderFence->wait(vk, 150ULL * 1000 * 1000))
// Wait for completion of previous frame
if (this->iteration && !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
// Copy swapchain image into backend source image
const auto& cmdbuf = *this->renderCommandBuffer;
cmdbuf.begin(vk);
@ -177,15 +155,15 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
),
barrierHelper(sourceImage.handle(),
barrierHelper(this->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(),
{ swapchainImage, this->sourceImage->handle() },
this->sourceImage->getExtent(),
{
barrierHelper(swapchainImage,
VK_ACCESS_TRANSFER_READ_BIT,
@ -193,39 +171,40 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
),
}
},
0, sourceImageIdx
);
cmdbuf.end(vk);
cmdbuf.submit(vk,
semaphores, VK_NULL_HANDLE, 0,
{}, this->syncSemaphore->handle(), this->idx++
{}, this->syncSemaphore->handle(), this->syncValue
);
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);
for (size_t i = 0; i < this->passes.size(); i++) {
auto& pass{this->passes.at(i)};
const bool last{i == (this->passes.size() - 1)};
// acquire swapchain image
uint32_t aqImageIdx{};
// Acquire swapchain image
uint32_t swapchainImageIdx{};
auto res = vk.df().AcquireNextImageKHR(vk.dev(), swapchain,
UINT64_MAX, pass.acquireSemaphore.handle(),
VK_NULL_HANDLE,
&aqImageIdx
&swapchainImageIdx
);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw ls::vulkan_error(res, "vkAcquireNextImageKHR() failed");
const auto& aquiredSwapchainImage = this->info.images.at(aqImageIdx);
const auto& aquiredSwapchainImage = this->info.images.at(swapchainImageIdx);
// copy backend destination image into swapchain image
// Copy backend destination image into swapchain image
auto& cmdbuf = pass.commandBuffer;
cmdbuf.begin(vk);
cmdbuf.blitImage(vk,
{
barrierHelper(destinationImage.handle(),
barrierHelper(this->destinationImage->handle(),
VK_ACCESS_NONE,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
@ -238,8 +217,8 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
),
},
{ destinationImage.handle(), aquiredSwapchainImage },
destinationImage.getExtent(),
{ this->destinationImage->handle(), aquiredSwapchainImage },
this->destinationImage->getExtent(),
{
barrierHelper(aquiredSwapchainImage,
VK_ACCESS_TRANSFER_WRITE_BIT,
@ -250,48 +229,43 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
}
);
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);
std::vector<VkSemaphore> signalSemaphores{ pass.copySemaphore.handle() };
if (last)
signalSemaphores.push_back(this->finalSemaphore->handle());
this->syncValue++;
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
{ pass.acquireSemaphore.handle() }, this->syncSemaphore->handle(), this->syncValue,
signalSemaphores, last ? nullptr : this->syncSemaphore->handle(), this->syncValue + 1,
last ? this->renderFence->handle() : VK_NULL_HANDLE
);
// present swapchain image
this->syncValue++;
// Present swapchain image
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = i ? nullptr : next_chain,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &pcs.first.handle(),
.pWaitSemaphores = &pass.copySemaphore.handle(),
.swapchainCount = 1,
.pSwapchains = &swapchain,
.pImageIndices = &aqImageIdx,
.pImageIndices = &swapchainImageIdx,
};
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());
// Present original swapchain image
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &lastPCS.second.handle(),
.pWaitSemaphores = &this->finalSemaphore->handle(),
.swapchainCount = 1,
.pSwapchains = &swapchain,
.pImageIndices = &imageIdx,
@ -300,6 +274,7 @@ VkResult Swapchain::present(const vk::Vulkan& vk,
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw ls::vulkan_error(res, "vkQueuePresentKHR() failed");
this->fidx++;
this->iteration++;
return res;
}

View file

@ -2,7 +2,7 @@
#pragma once
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "lsfg-vk/lsfgvk.hpp"
#include "lsfg-vk-common/configuration/config.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
@ -12,15 +12,18 @@
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <cstdint>
#include <utility>
#include <memory>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace lsfgvk::layer {
/// swapchain info struct
///
/// Swapchain info struct
///
struct SwapchainInfo {
std::vector<VkImage> images;
VkFormat format;
@ -29,53 +32,67 @@ namespace lsfgvk::layer {
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
///
/// 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
///
/// Swapchain context for a layer instance
///
class Swapchain {
public:
/// create a new swapchain context
/// @param vk vulkan instance
///
/// 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,
/// @param profile Active game profile
/// @param info Swapchain info
///
Swapchain(const vk::Vulkan& vk, lsfgvk::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
///
/// Present a frame
///
/// @param vk Vulkan instance
/// @param queue Presentation queue
/// @param next_chain next chain pointer for the present info (WARNING: shared!)
/// @param imageIdx Swapchain image index to present to
/// @param semaphores Semaphores to wait on before presenting
/// @throws ls::vulkan_error on vulkan error
///
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::Image> sourceImage;
ls::lazy<vk::Image> destinationImage;
ls::lazy<vk::TimelineSemaphore> syncSemaphore;
ls::lazy<vk::CommandBuffer> renderCommandBuffer;
ls::lazy<vk::Fence> renderFence;
ls::lazy<vk::Semaphore> finalSemaphore;
struct RenderPass {
vk::CommandBuffer commandBuffer;
vk::Semaphore acquireSemaphore;
vk::Semaphore copySemaphore;
};
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::R<lsfgvk::Instance> instance;
std::unique_ptr<lsfgvk::Context> ctx;
uint32_t total{};
size_t iteration{0};
size_t syncValue{1};
ls::GameConf profile;
SwapchainInfo info;