mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2026-03-26 05:01:33 +00:00
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
492 lines
19 KiB
C++
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;
|
|
}
|