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 --- .clang-format | 12 ++ CMakeLists.txt | 41 +++++ README.md | 1 + README.txt | 1 - low_latency_layer.json | 21 +++ src/layer.cc | 408 +++++++++++++++++++++++++++++++++++++++++++++++++ src/layer.hh | 22 +++ 7 files changed, 505 insertions(+), 1 deletion(-) create mode 100644 .clang-format create mode 100644 CMakeLists.txt create mode 100644 README.md delete mode 100644 README.txt create mode 100644 low_latency_layer.json create mode 100644 src/layer.cc create mode 100644 src/layer.hh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3552c88 --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +--- +BasedOnStyle: LLVM + +IndentWidth: 4 +TabWidth: 4 +UseTab: Never + +--- +Language: Cpp +DerivePointerAlignment: false +PointerAlignment: Left +ReferenceAlignment: Left \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..793e637 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.10) +project(low_latency_layer + VERSION 0.01 + LANGUAGES CXX +) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +file(GLOB_RECURSE CXX_SOURCES src/*.cc) + +set(LIBRARY_NAME VkLayer_NJ3AHXAC_LowLatency) +add_library(${LIBRARY_NAME} SHARED + ${CXX_SOURCES} +) + +find_package(Vulkan REQUIRED) +target_link_libraries(${LIBRARY_NAME} + ${Vulkan_LIBRARIES} +) + +# Copy layer into out/ +set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/out") +add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_DIR}" +) + +add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "${OUTPUT_DIR}/" +) + +add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/low_latency_layer.json" + "${OUTPUT_DIR}/" +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8026076 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +initial commit diff --git a/README.txt b/README.txt deleted file mode 100644 index 8026076..0000000 --- a/README.txt +++ /dev/null @@ -1 +0,0 @@ -initial commit diff --git a/low_latency_layer.json b/low_latency_layer.json new file mode 100644 index 0000000..17392b1 --- /dev/null +++ b/low_latency_layer.json @@ -0,0 +1,21 @@ +{ + "file_format_version": "1.0.0", + "layer": { + "name": "VK_LAYER_NJ3AHXAC_LowLatency", + "type": "GLOBAL", + "library_path": "./libVkLayer_NJ3AHXAC_LowLatency.so", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "WIP Sample layer from https://renderdoc.org/vulkan-layer-guide.html", + "functions": { + "vkGetInstanceProcAddr": "LowLatency_GetInstanceProcAddr", + "vkGetDeviceProcAddr": "LowLatency_GetDeviceProcAddr" + }, + "enable_environment": { + "ENABLE_LOW_LATENCY_LAYER": "1" + }, + "disable_environment": { + "DISABLE_LOW_LATENCY_LAYER": "1" + } + } +} \ No newline at end of file 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