lsfg-vk/lsfg-vk-layer/src/entrypoint.cpp
PancakeTAS b2928e4ce6
fix: layer: clean up layer info
emplacing a value into an unordered map does not override, so if the swapchain handles were identical (which they are commonly), then it would try to continue with the swapchain images from the old swapchain.

ironically, the swapchain images are also mostly identical. in fact, when using validation layers they are intentionally-unintentionally identical.. so I never caught this bug.

either way, this fixes a bunch of weird behavior:
- swapchain creation failing
- the application hanging on a present
- the infamous "assertion failed!" wine warning
- segmentation faults in the NVIDIA driver
2025-12-25 20:23:22 +01:00

492 lines
19 KiB
C++

/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "instance.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 <cstddef>
#include <cstdint>
#include <exception>
#include <iostream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
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;
Root root;
}* layer_info;
// instance-wide info initialized at instance creation
struct InstanceInfo {
VkInstance handle;
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;
// create instance
VkResult myvkCreateInstance(
const VkInstanceCreateInfo* info,
const VkAllocationCallbacks* alloc,
VkInstance* instance) {
// 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, "
"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, "
"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, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
layerInfo->u.pLayerInfo = linkInfo->pNext; // advance for next layer
// create instance
auto* vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(
layer_info->GetInstanceProcAddr(nullptr, "vkCreateInstance"));
if (!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;
}
try {
VkInstanceCreateInfo newInfo = *info;
layer_info->root.modifyInstanceCreateInfo(newInfo,
[=, newInfo = &newInfo]() {
auto res = vkCreateInstance(newInfo, alloc, instance);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateInstance() failed");
}
);
instance_info = new InstanceInfo{
.handle = *instance,
.funcs = vk::initVulkanInstanceFuncs(*instance, layer_info->GetInstanceProcAddr, true),
.devices = {}
};
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";
return e.error();
}
}
// create device
VkResult myvkCreateDevice(
VkPhysicalDevice physdev,
const VkDeviceCreateInfo* info,
const VkAllocationCallbacks* alloc,
VkDevice* device) {
// 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, "
"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, "
"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, "
"the previous layer does not follow spec\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
layerInfo->u.pLayerInfo = linkInfo->pNext; // advance for next layer
// 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";
return VK_ERROR_INITIALIZATION_FAILED;
}
auto* setLoaderData = layerInfo->u.pfnSetDeviceLoaderData;
if (!setLoaderData) {
std::cerr << "lsfg-vk: instance loader data function is null.\n";
return VK_ERROR_INITIALIZATION_FAILED;
}
// create device
try {
VkDeviceCreateInfo newInfo = *info;
layer_info->root.modifyDeviceCreateInfo(newInfo,
[=, newInfo = &newInfo]() {
auto res = instance_info->funcs.CreateDevice(physdev, newInfo, alloc, device);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateDevice() failed");
}
);
} 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";
return e.error();
}
// create layer instance
try {
instance_info->devices.emplace(
*device,
vk::Vulkan(
instance_info->handle, *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';
}
return VK_SUCCESS;
}
// 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);
// 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, "
"the previous layer does not follow spec\n";
return;
}
vkDestroyDevice(device, alloc);
}
// destroy instance
void myvkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* alloc) {
// destroy instance info
delete instance_info;
instance_info = nullptr;
// 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, "
"the previous layer does not follow spec\n";
return;
}
vkDestroyInstance(instance, alloc);
// destroy layer info
// NOTE: there's no real way of unloading the layer without a deconstructor.
// multiple instances just aren't common enough to worry about it.
// NOTE2: it doesn't really matter anyways, because the myvkDestroyDevice code
// freezes the entire thing anyways.
delete layer_info;
layer_info = nullptr;
}
// 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())
return it->second;
return nullptr;
}
// get instance-level function pointers
PFN_vkVoidFunction myvkGetInstanceProcAddr(VkInstance instance, const char* name) {
if (!name) return nullptr;
if (std::string(name) == "vkCreateInstance") // pre-instance function
return reinterpret_cast<PFN_vkVoidFunction>(myvkCreateInstance);
auto func = getProcAddr(name);
if (func) return func;
if (!layer_info->GetInstanceProcAddr) return nullptr;
return layer_info->GetInstanceProcAddr(instance, name);
}
// get device-level function pointers
PFN_vkVoidFunction myvkGetDeviceProcAddr(VkDevice device, const char* name) {
if (!name) return nullptr;
auto func = getProcAddr(name);
if (func) return func;
if (!instance_info->funcs.GetDeviceProcAddr) return nullptr;
return instance_info->funcs.GetDeviceProcAddr(device, name);
}
}
namespace {
VkResult myvkCreateSwapchainKHR(
VkDevice device,
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;
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);
const auto& mapping = instance_info->swapchains.find(info->oldSwapchain);
if (mapping != instance_info->swapchains.end())
instance_info->swapchains.erase(mapping);
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);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateSwapchainKHR() failed");
}
);
// get all swapchain images
uint32_t imageCount{};
auto res = it->second.df().GetSwapchainImagesKHR(device, *swapchain,
&imageCount, nullptr);
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;
} catch (const ls::vulkan_error& e) {
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 << "- " << e.what() << '\n';
return VK_ERROR_INITIALIZATION_FAILED;
}
}
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{};
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';
}
}
// present each swapchain
for (size_t i = 0; i < info->swapchainCount; i++) {
const auto& swapchain = info->pSwapchains[i]; // NOLINT (array index)
const auto& it = instance_info->swapchains.find(swapchain);
if (it == instance_info->swapchains.end())
return VK_ERROR_INITIALIZATION_FAILED;
try {
std::vector<VkSemaphore> waitSemaphores;
waitSemaphores.reserve(info->waitSemaphoreCount);
for (size_t j = 0; j < info->waitSemaphoreCount; j++)
waitSemaphores.push_back(info->pWaitSemaphores[j]); // NOLINT (array index)
auto& context = layer_info->root.getSwapchainContext(swapchain);
result = context.present(it->second,
queue, swapchain,
const_cast<void*>(info->pNext),
info->pImageIndices[i], // NOLINT (array index)
{ waitSemaphores.begin(), waitSemaphores.end() }
);
} 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
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';
result = VK_ERROR_UNKNOWN;
}
if (result != VK_SUCCESS && info->pResults)
info->pResults[i] = result; // NOLINT (array index)
}
return result;
#pragma clang diagnostic pop
}
void myvkDestroySwapchainKHR(
VkDevice device,
VkSwapchainKHR swapchain,
const VkAllocationCallbacks* alloc) {
const auto& it = instance_info->devices.find(device);
if (it == instance_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);
}
}
/// Vulkan layer entrypoint
__attribute__((visibility("default")))
VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVersionStruct) {
// ensure loader compatibility
if (!pVersionStruct
|| pVersionStruct->sType != LAYER_NEGOTIATE_INTERFACE_STRUCT
|| pVersionStruct->loaderLayerInterfaceVersion < 2)
return VK_ERROR_INITIALIZATION_FAILED;
// load the layer configuration
try {
layer_info = new LayerInfo {
.map = {
#define VKPTR(name) reinterpret_cast<PFN_vkVoidFunction>(name)
{ "vkCreateInstance", VKPTR(myvkCreateInstance) },
{ "vkCreateDevice", VKPTR(myvkCreateDevice) },
{ "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
delete layer_info;
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';
return VK_ERROR_INITIALIZATION_FAILED;
}
// emplace function pointers/version
pVersionStruct->loaderLayerInterfaceVersion = 2;
pVersionStruct->pfnGetPhysicalDeviceProcAddr = nullptr;
pVersionStruct->pfnGetDeviceProcAddr = myvkGetDeviceProcAddr;
pVersionStruct->pfnGetInstanceProcAddr = myvkGetInstanceProcAddr;
return VK_SUCCESS;
}