From c7363b6165a7795d10a8989c241dcdec84d0c7d7 Mon Sep 17 00:00:00 2001 From: Nicolas James Date: Thu, 15 Jan 2026 18:58:37 +1100 Subject: Add implementation of renderdoc's vulkan layer tutorial --- src/layer.cc | 408 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/layer.hh | 22 ++++ 2 files changed, 430 insertions(+) create mode 100644 src/layer.cc create mode 100644 src/layer.hh (limited to 'src') diff --git a/src/layer.cc b/src/layer.cc new file mode 100644 index 0000000..24cc519 --- /dev/null +++ b/src/layer.cc @@ -0,0 +1,408 @@ +#include "layer.hh" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace low_latency { + +static auto mutex = std::mutex{}; + +struct command_stats { + std::uint32_t num_draws; + std::uint32_t num_instances; + std::uint32_t num_verts; +}; +static std::unordered_map + commandbuffer_to_stats{}; +static std::unordered_map instance_dispatch; +static std::unordered_map device_dispatch; + +template +concept DispatchableType = + std::same_as, VkQueue> || + std::same_as, VkCommandBuffer> || + std::same_as, VkInstance> || + std::same_as, VkDevice> || + std::same_as, VkPhysicalDevice>; +template void* get_key(const T& inst) { + return *reinterpret_cast(inst); +} + +static VKAPI_ATTR VkResult VKAPI_CALL +BeginCommandBuffer(VkCommandBuffer command_buffer, + const VkCommandBufferBeginInfo* begin_info) { + const auto lock = std::scoped_lock{mutex}; + commandbuffer_to_stats[command_buffer] = {}; + return device_dispatch[get_key(command_buffer)].BeginCommandBuffer( + command_buffer, begin_info); +} + +static VKAPI_ATTR void VKAPI_CALL CmdDraw(VkCommandBuffer command_buffer, + std::uint32_t vertex_count, + std::uint32_t instance_count, + std::uint32_t first_vertex, + std::uint32_t first_instance) { + + const auto lock = std::scoped_lock{mutex}; + + if (const auto it = commandbuffer_to_stats.find(command_buffer); + it != std::end(commandbuffer_to_stats)) { + + auto& stats = it->second; + stats.num_draws++; + stats.num_instances += instance_count; + stats.num_verts += instance_count * vertex_count; + } + + device_dispatch[get_key(command_buffer)].CmdDraw( + command_buffer, vertex_count, instance_count, first_vertex, + first_instance); +} + +static VKAPI_ATTR void VKAPI_CALL CmdDrawIndexed(VkCommandBuffer command_buffer, + uint32_t index_count, + uint32_t instance_count, + uint32_t first_index, + int32_t vertex_offset, + uint32_t first_instance) { + + const auto lock = std::scoped_lock{mutex}; + + if (const auto it = commandbuffer_to_stats.find(command_buffer); + it != std::end(commandbuffer_to_stats)) { + + auto& stats = it->second; + stats.num_draws++; + stats.num_instances += instance_count; + stats.num_verts += instance_count * index_count; + } + + device_dispatch[get_key(command_buffer)].CmdDrawIndexed( + command_buffer, index_count, instance_count, first_index, vertex_offset, + first_instance); +} + +static VKAPI_ATTR VkResult VKAPI_CALL +EndCommandBuffer(VkCommandBuffer command_buffer) { + + const auto lock = std::scoped_lock{mutex}; + + const auto& s = commandbuffer_to_stats[command_buffer]; + + std::cout << std::format("Command buffer ended with {} draws, {} " + "instances and {} vertices\n", + s.num_draws, s.num_instances, s.num_verts); + + const auto it = device_dispatch.find(get_key(command_buffer)); + if (it == std::end(device_dispatch)) { + return VK_ERROR_DEVICE_LOST; + } + return it->second.EndCommandBuffer(command_buffer); +} + +static VKAPI_ATTR VkResult VKAPI_CALL +CreateInstance(const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { + + // Iterate through list starting at pNext until we see create_info and + // link_info. + auto layer_create_info = [&]() -> VkLayerInstanceCreateInfo* { + for (auto base = + reinterpret_cast(pCreateInfo->pNext); + base; base = base->pNext) { + + if (base->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO) { + continue; + } + + const auto info = + reinterpret_cast(base); + if (info->function != VK_LAYER_LINK_INFO) { + continue; + } + return const_cast(info); + } + return nullptr; + }(); + + if (!layer_create_info || !layer_create_info->u.pLayerInfo) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Store our get instance proc addr function and pop it off our list + + // advance the list so future layers know what to call. + const auto next_gipa = + layer_create_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + if (!next_gipa) { + return VK_ERROR_INITIALIZATION_FAILED; + } + layer_create_info->u.pLayerInfo = layer_create_info->u.pLayerInfo->pNext; + + // Call our create instance func, and store vkDestroyInstance, and + // vkCreateDevice as well. + const auto create_instance_func = reinterpret_cast( + next_gipa(VK_NULL_HANDLE, "vkCreateInstance")); + if (!create_instance_func) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + if (const auto result = + create_instance_func(pCreateInfo, pAllocator, pInstance); + result != VK_SUCCESS) { + + return result; + } + + const auto lock = std::scoped_lock{mutex}; + instance_dispatch.emplace( + get_key(*pInstance), + VkuInstanceDispatchTable{ + .DestroyInstance = reinterpret_cast( + next_gipa(*pInstance, "vkDestroyInstance")), + .GetInstanceProcAddr = reinterpret_cast( + next_gipa(*pInstance, "vkGetInstanceProcAddr")), + .EnumerateDeviceExtensionProperties = + reinterpret_cast( + next_gipa(*pInstance, + "vkEnumerateDeviceExtensionProperties")), + } + + ); + + return VK_SUCCESS; +} + +static VKAPI_ATTR void VKAPI_CALL +DestroyInstance(VkInstance instance, const VkAllocationCallbacks* allocator) { + + const auto lock = std::scoped_lock{mutex}; + instance_dispatch.erase(get_key(instance)); +} + +static VKAPI_ATTR VkResult VKAPI_CALL CreateDevice( + VkPhysicalDevice physical_device, const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { + + auto layer_create_info = [&]() -> VkLayerDeviceCreateInfo* { + for (auto base = + reinterpret_cast(pCreateInfo->pNext); + base; base = base->pNext) { + + if (base->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO) { + continue; + } + + const auto info = + reinterpret_cast(base); + + if (info->function != VK_LAYER_LINK_INFO) { + continue; + } + + return const_cast(info); + } + return nullptr; + }(); + + if (!layer_create_info || !layer_create_info->u.pLayerInfo) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + const auto next_gipa = + layer_create_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + const auto next_gdpa = + layer_create_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; + if (!next_gipa || !next_gdpa) { + return VK_ERROR_INITIALIZATION_FAILED; + } + layer_create_info->u.pLayerInfo = layer_create_info->u.pLayerInfo->pNext; + + const auto create_func = reinterpret_cast( + next_gipa(VK_NULL_HANDLE, "vkCreateDevice")); + if (!create_func) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + if (const auto result = + create_func(physical_device, pCreateInfo, pAllocator, pDevice); + result != VK_SUCCESS) { + return result; + } + + const auto lock = std::scoped_lock{mutex}; + device_dispatch.emplace( + get_key(*pDevice), + VkuDeviceDispatchTable{ + .GetDeviceProcAddr = reinterpret_cast( + next_gdpa(*pDevice, "vkGetDeviceProcAddr")), + .DestroyDevice = reinterpret_cast( + next_gdpa(*pDevice, "vkDestroyDevice")), + .BeginCommandBuffer = reinterpret_cast( + next_gdpa(*pDevice, "vkBeginCommandBuffer")), + .EndCommandBuffer = reinterpret_cast( + next_gdpa(*pDevice, "vkEndCommandBuffer")), + .CmdDraw = reinterpret_cast( + next_gdpa(*pDevice, "vkCmdDraw")), + .CmdDrawIndexed = reinterpret_cast( + next_gdpa(*pDevice, "vkCmdDrawIndexed")), + }); + + return VK_SUCCESS; +} + +static VKAPI_ATTR void VKAPI_CALL +DestroyDevice(VkDevice device, const VkAllocationCallbacks* allocator) { + + const auto lock = std::scoped_lock{mutex}; + device_dispatch.erase(get_key(device)); +} + +// These are wrong, the tutorial isn't correct afaik. +static VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceLayerProperties( + std::uint32_t* pPropertyCount, VkLayerProperties* pProperties) { + + if (pPropertyCount) { + *pPropertyCount = 1; + } + + if (pProperties) { + std::strcpy(pProperties->layerName, LAYER_NAME); + std::strcpy(pProperties->description, "Low Latency Layer"); + pProperties->implementationVersion = 1; + pProperties->specVersion = VK_API_VERSION_1_3; + } + + return VK_SUCCESS; +} + +static VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceLayerProperties( + VkPhysicalDevice physical_device, uint32_t* pPropertyCount, + VkLayerProperties* pProperties) { + + return EnumerateInstanceLayerProperties(pPropertyCount, pProperties); +} + +static VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties( + const char* pLayerName, uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + + if (!pLayerName || std::string_view{pLayerName} != LAYER_NAME) { + + return VK_ERROR_LAYER_NOT_PRESENT; + } + + if (pPropertyCount) { + *pPropertyCount = 0; + } + return VK_SUCCESS; +} + +static VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties( + VkPhysicalDevice physical_device, const char* pLayerName, + uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { + + if (!pLayerName || std::string_view{pLayerName} != LAYER_NAME) { + + if (physical_device == VK_NULL_HANDLE) { + return VK_SUCCESS; + } + + const auto lock = std::scoped_lock{mutex}; + return instance_dispatch[get_key(physical_device)] + .EnumerateDeviceExtensionProperties(physical_device, pLayerName, + pPropertyCount, pProperties); + } + + if (pPropertyCount) { + *pPropertyCount = 0; + } + return VK_SUCCESS; +} + +} // namespace low_latency + +static const auto instance_functions = + std::unordered_map{ + {"vkGetInstanceProcAddr", + reinterpret_cast(LowLatency_GetInstanceProcAddr)}, + + {"vkEnumerateInstanceLayerProperties", + reinterpret_cast( + low_latency::EnumerateInstanceLayerProperties)}, + {"vkEnumerateInstanceExtensionProperties", + reinterpret_cast( + low_latency::EnumerateInstanceExtensionProperties)}, + + {"vkCreateInstance", + reinterpret_cast(low_latency::CreateInstance)}, + {"vkDestroyInstance", + reinterpret_cast(low_latency::DestroyInstance)}, + }; + +static const auto device_functions = + std::unordered_map{ + {"vkGetDeviceProcAddr", + reinterpret_cast(LowLatency_GetDeviceProcAddr)}, + + {"vkEnumerateDeviceLayerProperties", + reinterpret_cast( + low_latency::EnumerateDeviceLayerProperties)}, + {"vkEnumerateDeviceExtensionProperties", + reinterpret_cast( + low_latency::EnumerateDeviceExtensionProperties)}, + + {"vkCreateDevice", + reinterpret_cast(low_latency::CreateDevice)}, + {"vkDestroyDevice", + reinterpret_cast(low_latency::DestroyDevice)}, + + {"vkCmdDraw", + reinterpret_cast(low_latency::CmdDraw)}, + {"vkCmdDrawIndexed", + reinterpret_cast(low_latency::CmdDrawIndexed)}, + + {"vkBeginCommandBuffer", + reinterpret_cast(low_latency::BeginCommandBuffer)}, + {"vkEndCommandBuffer", + reinterpret_cast(low_latency::EndCommandBuffer)}, + }; + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL +LowLatency_GetDeviceProcAddr(VkDevice device, const char* const pName) { + + if (const auto it = device_functions.find(pName); + it != std::end(device_functions)) { + + return it->second; + } + + const auto lock = std::scoped_lock{low_latency::mutex}; + return low_latency::device_dispatch[low_latency::get_key(device)] + .GetDeviceProcAddr(device, pName); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL +LowLatency_GetInstanceProcAddr(VkInstance instance, const char* const pName) { + + for (const auto& functions : {device_functions, instance_functions}) { + const auto it = functions.find(pName); + if (it == std::end(functions)) { + continue; + } + return it->second; + } + + const auto lock = std::scoped_lock{low_latency::mutex}; + return low_latency::instance_dispatch[low_latency::get_key(instance)] + .GetInstanceProcAddr(instance, pName); +} \ No newline at end of file diff --git a/src/layer.hh b/src/layer.hh new file mode 100644 index 0000000..5633c63 --- /dev/null +++ b/src/layer.hh @@ -0,0 +1,22 @@ +#ifndef LAYER_HH_ +#define LAYER_HH_ + +#include +#include + +extern "C" { + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL +LowLatency_GetInstanceProcAddr(VkInstance instance, const char* const pname); + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL +LowLatency_GetDeviceProcAddr(VkDevice device, const char* pName); +} + +namespace low_latency { + +static constexpr auto LAYER_NAME = "VK_LAYER_NJ3AHXAC_LowLatency"; + +} + +#endif \ No newline at end of file -- cgit v1.2.3