diff options
| -rw-r--r-- | .clang-format | 12 | ||||
| -rw-r--r-- | CMakeLists.txt | 41 | ||||
| -rw-r--r-- | README.md (renamed from README.txt) | 0 | ||||
| -rw-r--r-- | low_latency_layer.json | 21 | ||||
| -rw-r--r-- | src/layer.cc | 408 | ||||
| -rw-r--r-- | src/layer.hh | 22 |
6 files changed, 504 insertions, 0 deletions
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 + "$<TARGET_FILE:${LIBRARY_NAME}>" + "${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/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 <vulkan/utility/vk_dispatch_table.h> +#include <vulkan/vk_layer.h> +#include <vulkan/vk_platform.h> +#include <vulkan/vulkan.h> +#include <vulkan/vulkan.hpp> +#include <vulkan/vulkan_core.h> + +#include <cstring> +#include <iostream> +#include <mutex> +#include <string_view> +#include <unordered_map> + +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<VkCommandBuffer, command_stats> + commandbuffer_to_stats{}; +static std::unordered_map<void*, VkuInstanceDispatchTable> instance_dispatch; +static std::unordered_map<void*, VkuDeviceDispatchTable> device_dispatch; + +template <typename T> +concept DispatchableType = + std::same_as<std::remove_cvref_t<T>, VkQueue> || + std::same_as<std::remove_cvref_t<T>, VkCommandBuffer> || + std::same_as<std::remove_cvref_t<T>, VkInstance> || + std::same_as<std::remove_cvref_t<T>, VkDevice> || + std::same_as<std::remove_cvref_t<T>, VkPhysicalDevice>; +template <DispatchableType T> void* get_key(const T& inst) { + return *reinterpret_cast<void**>(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<const VkBaseInStructure*>(pCreateInfo->pNext); + base; base = base->pNext) { + + if (base->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO) { + continue; + } + + const auto info = + reinterpret_cast<const VkLayerInstanceCreateInfo*>(base); + if (info->function != VK_LAYER_LINK_INFO) { + continue; + } + return const_cast<VkLayerInstanceCreateInfo*>(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<PFN_vkCreateInstance>( + 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<PFN_vkDestroyInstance>( + next_gipa(*pInstance, "vkDestroyInstance")), + .GetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>( + next_gipa(*pInstance, "vkGetInstanceProcAddr")), + .EnumerateDeviceExtensionProperties = + reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>( + 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<const VkBaseInStructure*>(pCreateInfo->pNext); + base; base = base->pNext) { + + if (base->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO) { + continue; + } + + const auto info = + reinterpret_cast<const VkLayerDeviceCreateInfo*>(base); + + if (info->function != VK_LAYER_LINK_INFO) { + continue; + } + + return const_cast<VkLayerDeviceCreateInfo*>(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<PFN_vkCreateDevice>( + 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<PFN_vkGetDeviceProcAddr>( + next_gdpa(*pDevice, "vkGetDeviceProcAddr")), + .DestroyDevice = reinterpret_cast<PFN_vkDestroyDevice>( + next_gdpa(*pDevice, "vkDestroyDevice")), + .BeginCommandBuffer = reinterpret_cast<PFN_vkBeginCommandBuffer>( + next_gdpa(*pDevice, "vkBeginCommandBuffer")), + .EndCommandBuffer = reinterpret_cast<PFN_vkEndCommandBuffer>( + next_gdpa(*pDevice, "vkEndCommandBuffer")), + .CmdDraw = reinterpret_cast<PFN_vkCmdDraw>( + next_gdpa(*pDevice, "vkCmdDraw")), + .CmdDrawIndexed = reinterpret_cast<PFN_vkCmdDrawIndexed>( + 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<std::string_view, const PFN_vkVoidFunction>{ + {"vkGetInstanceProcAddr", + reinterpret_cast<PFN_vkVoidFunction>(LowLatency_GetInstanceProcAddr)}, + + {"vkEnumerateInstanceLayerProperties", + reinterpret_cast<PFN_vkVoidFunction>( + low_latency::EnumerateInstanceLayerProperties)}, + {"vkEnumerateInstanceExtensionProperties", + reinterpret_cast<PFN_vkVoidFunction>( + low_latency::EnumerateInstanceExtensionProperties)}, + + {"vkCreateInstance", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::CreateInstance)}, + {"vkDestroyInstance", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::DestroyInstance)}, + }; + +static const auto device_functions = + std::unordered_map<std::string_view, const PFN_vkVoidFunction>{ + {"vkGetDeviceProcAddr", + reinterpret_cast<PFN_vkVoidFunction>(LowLatency_GetDeviceProcAddr)}, + + {"vkEnumerateDeviceLayerProperties", + reinterpret_cast<PFN_vkVoidFunction>( + low_latency::EnumerateDeviceLayerProperties)}, + {"vkEnumerateDeviceExtensionProperties", + reinterpret_cast<PFN_vkVoidFunction>( + low_latency::EnumerateDeviceExtensionProperties)}, + + {"vkCreateDevice", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::CreateDevice)}, + {"vkDestroyDevice", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::DestroyDevice)}, + + {"vkCmdDraw", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::CmdDraw)}, + {"vkCmdDrawIndexed", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::CmdDrawIndexed)}, + + {"vkBeginCommandBuffer", + reinterpret_cast<PFN_vkVoidFunction>(low_latency::BeginCommandBuffer)}, + {"vkEndCommandBuffer", + reinterpret_cast<PFN_vkVoidFunction>(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 <vulkan/vk_platform.h> +#include <vulkan/vulkan.hpp> + +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 |
