diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 45f5ad3ed..2f656b83e 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -81,6 +81,7 @@ set(GLAD_EXTENSIONS ) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND GLAD_EXTENSIONS + VK_EXT_external_memory_host VK_KHR_wayland_surface VK_KHR_xcb_surface VK_KHR_xlib_surface diff --git a/examples/09-depth/generic_depth_addon.cpp b/examples/09-depth/generic_depth_addon.cpp index 89be4475e..9dddb9c78 100644 --- a/examples/09-depth/generic_depth_addon.cpp +++ b/examples/09-depth/generic_depth_addon.cpp @@ -1475,6 +1475,11 @@ static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_l if (!device->create_resource_view(depth_stencil_backup->backup_texture, resource_usage::shader_resource, srv_desc, &data.selected_shader_resource)) break; } + else + { + // Untracking the previous depth-stencil above cleared the view, but it still points to the revived backup texture + data.selected_shader_resource = prev_shader_resource; + } data.using_backup_texture = true; } diff --git a/examples/09-depth/generic_depth_detection.hpp b/examples/09-depth/generic_depth_detection.hpp index e0425008e..b0fd4f388 100644 --- a/examples/09-depth/generic_depth_detection.hpp +++ b/examples/09-depth/generic_depth_detection.hpp @@ -5,7 +5,7 @@ namespace depth_detection { // Bit masks permit lossless merging across command lists. Unknown is an - // explicit bit, so unsupported state cannot accidentally vote for normal Z. + // explicit bit, so unsupported state is never mistaken for normal Z. enum evidence : uint8_t { none = 0, normal = 1, reversed = 2, unknown = 4 }; constexpr uint8_t clear_evidence(float depth) { @@ -37,8 +37,10 @@ namespace depth_detection candidate = none; consistent_frames = 0; } - const uint8_t vote = (clears == normal && comparisons == normal) ? normal : - (clears == reversed && comparisons == reversed) ? reversed : none; + // Unknown evidence (always/equal tests, unresolved dynamic state) says nothing about the + // convention, so it only abstains. Contradicting evidence still blocks the vote. + const uint8_t clear_direction = clears & (normal | reversed); + const uint8_t vote = (clear_direction == normal || clear_direction == reversed) && clear_direction == (comparisons & (normal | reversed)) ? clear_direction : none; if (vote == none || vote != candidate) { candidate = vote; diff --git a/include/reshade_api_resource.hpp b/include/reshade_api_resource.hpp index 9e2b37e1f..094d09329 100644 --- a/include/reshade_api_resource.hpp +++ b/include/reshade_api_resource.hpp @@ -244,6 +244,12 @@ namespace reshade::api /// Resource is backed by sparse memory. /// sparse_binding = 0x40000, + /// + /// Used together with on a buffer to import existing host memory instead of allocating: the "shared_handle" parameter of points to a variable that holds the host pointer. + /// The pointer and the buffer size must satisfy the device's minimum imported host pointer alignment. The memory must stay valid until the resource is destroyed. + /// This flag is only supported in Vulkan, with VK_EXT_external_memory_host. + /// + shared_host = 0x100000, }; RESHADE_DEFINE_ENUM_FLAG_OPERATORS(resource_flags); diff --git a/source/addon_manager.cpp b/source/addon_manager.cpp index 2edaadc0e..3dafcdab1 100644 --- a/source/addon_manager.cpp +++ b/source/addon_manager.cpp @@ -47,9 +47,9 @@ namespace { void *get_module_handle(const void *address) { - Dl_info info = {}; - struct link_map *map = nullptr; - return address != nullptr && dladdr1(address, &info, reinterpret_cast(&map), RTLD_DL_LINKMAP) != 0 ? map : nullptr; + // Called for every add-on callback of every event, so this must not take the loader lock like dladdr1 does + dl_find_object result; + return address != nullptr && _dl_find_object(const_cast(address), &result) == 0 ? result.dlfo_link_map : nullptr; } void *const g_module_handle = get_module_handle(reinterpret_cast(&get_module_handle)); diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index 0b22e0ee6..01bfe2111 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -282,6 +282,13 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, false); #endif +#if VK_EXT_external_memory_host + // Lets add-ons import host memory shared with another process as a buffer (resource_flags::shared_host) + // The extension depends on external memory, which is core in Vulkan 1.1 + if (instance.api_version >= VK_API_VERSION_1_1) + add_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, false); +#endif + #if VK_KHR_external_memory_win32 add_extension(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, false); #endif @@ -535,7 +542,7 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi 0 == std::strcmp(name_without_prefix, "SubmitDebugUtilsMessageEXT") || 0 == std::strcmp(name_without_prefix, "CreateDebugUtilsMessengerEXT") || 0 == std::strcmp(name_without_prefix, "DestroyDebugUtilsMessengerEXT") || - (std::strstr(name_without_prefix, "Properties") != nullptr && std::strstr(name_without_prefix, "AccelerationStructures") == nullptr && std::strstr(name_without_prefix, "Handle") == nullptr) || + (std::strstr(name_without_prefix, "Properties") != nullptr && std::strstr(name_without_prefix, "AccelerationStructures") == nullptr && std::strstr(name_without_prefix, "Handle") == nullptr && std::strstr(name_without_prefix, "HostPointer") == nullptr) || (std::strstr(name_without_prefix, "Surface") != nullptr && std::strstr(name_without_prefix, "DeviceGroupSurface") == nullptr) || (std::strstr(name_without_prefix, "PhysicalDevice") != nullptr)) return reinterpret_cast(device.dispatch_table.GetInstanceProcAddr(device.instance_handle, name)); diff --git a/source/vulkan/vulkan_hooks_swapchain.cpp b/source/vulkan/vulkan_hooks_swapchain.cpp index e6843d44e..a71f0290f 100644 --- a/source/vulkan/vulkan_hooks_swapchain.cpp +++ b/source/vulkan/vulkan_hooks_swapchain.cpp @@ -76,7 +76,8 @@ VkResult VKAPI_CALL vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreat if (device_impl->_primary_graphics_queue != nullptr) { // Add required usage flags to create info - create_info.imageUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + // Transfer destination lets add-ons write results back into the back buffer with a copy (e.g. frames processed out of process) + create_info.imageUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; #if VK_KHR_swapchain_mutable_format // Add required format variants, so e.g. both linear and sRGB views can be created for the swap chain images diff --git a/source/vulkan/vulkan_impl_device.cpp b/source/vulkan/vulkan_impl_device.cpp index 2995061fc..0945c30b9 100644 --- a/source/vulkan/vulkan_impl_device.cpp +++ b/source/vulkan/vulkan_impl_device.cpp @@ -399,6 +399,11 @@ bool reshade::vulkan::device_impl::create_resource(const api::resource_desc &des { *out_resource = { 0 }; +#if VK_EXT_external_memory_host + if ((desc.flags & api::resource_flags::shared_host) != 0) + return shared_handle != nullptr && import_host_buffer(desc, *shared_handle, out_resource); +#endif + assert((desc.usage & initial_state) == initial_state || initial_state == api::resource_usage::general || initial_state == api::resource_usage::cpu_access); VmaAllocation allocation = VMA_NULL; @@ -731,6 +736,64 @@ bool reshade::vulkan::device_impl::create_resource(const api::resource_desc &des return false; } +#if VK_EXT_external_memory_host +bool reshade::vulkan::device_impl::import_host_buffer(const api::resource_desc &desc, void *host_pointer, api::resource *out_resource) +{ + if (!vk.EXT_external_memory_host || desc.type != api::resource_type::buffer || host_pointer == nullptr) + return false; + + VkMemoryHostPointerPropertiesEXT host_properties { VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT }; + if (vk.GetMemoryHostPointerPropertiesEXT(_orig, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host_pointer, &host_properties) != VK_SUCCESS || host_properties.memoryTypeBits == 0) + { + reshade::log::message(reshade::log::level::error, "Host pointer %p cannot be imported (check its alignment).", host_pointer); + return false; + } + + VkBufferCreateInfo create_info { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + convert_resource_desc(desc, create_info); + VkExternalMemoryBufferCreateInfo external_memory_info { VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO }; + external_memory_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; + create_info.pNext = &external_memory_info; + + VkImportMemoryHostPointerInfoEXT import_info { VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT }; + import_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; + import_info.pHostPointer = host_pointer; + + // A dedicated pool carries the import structure into the allocation, like the Win32 import path in create_resource, so that destroy_resource frees it the usual way + VmaAllocationCreateInfo alloc_info = {}; + alloc_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + alloc_info.memoryTypeBits = host_properties.memoryTypeBits; + VmaPoolCreateInfo pool_info = {}; + pool_info.pMemoryAllocateNext = &import_info; + if (vmaFindMemoryTypeIndexForBufferInfo(_alloc, &create_info, &alloc_info, &pool_info.memoryTypeIndex) != VK_SUCCESS || + vmaCreatePool(_alloc, &pool_info, &alloc_info.pool) != VK_SUCCESS) + return false; + + VkBuffer object = VK_NULL_HANDLE; + VmaAllocation allocation = VMA_NULL; + VmaAllocationInfo allocation_info = {}; + if (vmaCreateBuffer(_alloc, &create_info, &alloc_info, &object, &allocation, &allocation_info) != VK_SUCCESS) + { + reshade::log::message(reshade::log::level::error, "Failed to import host memory at %p as a %llu byte buffer.", host_pointer, static_cast(desc.buffer.size)); + vmaDestroyPool(_alloc, alloc_info.pool); + return false; + } + + object_data data; + data.allocation = allocation; + data.pool = alloc_info.pool; + data.memory = allocation_info.deviceMemory; + data.memory_offset = allocation_info.offset; + data.create_info = create_info; + data.create_info.pNext = nullptr; // Clear out structure chain pointer, since it becomes invalid once leaving the current scope + + register_object(object, std::move(data)); + + *out_resource = { (uint64_t)object }; + return true; +} +#endif + void reshade::vulkan::device_impl::destroy_resource(api::resource resource) { if (resource == 0) diff --git a/source/vulkan/vulkan_impl_device.hpp b/source/vulkan/vulkan_impl_device.hpp index 809f2691a..e005150b0 100644 --- a/source/vulkan/vulkan_impl_device.hpp +++ b/source/vulkan/vulkan_impl_device.hpp @@ -161,6 +161,9 @@ namespace reshade::vulkan const VkPhysicalDeviceFeatures _enabled_features; private: +#if VK_EXT_external_memory_host + bool import_host_buffer(const api::resource_desc &desc, void *host_pointer, api::resource *out_resource); +#endif bool create_descriptor_set_layout(const api::pipeline_layout_param ¶m, VkDescriptorSetLayout *out_set_layout, std::vector &embedded_samplers); VmaAllocator _alloc = nullptr; diff --git a/tests/linux_portability.cpp b/tests/linux_portability.cpp index bad0c5b5e..88fecfb3b 100644 --- a/tests/linux_portability.cpp +++ b/tests/linux_portability.cpp @@ -741,11 +741,23 @@ static void test_depth_detection() assert(state.finished); assert(state.observe(1, 120, convention, convention) == none); } - for (uint8_t clears : { uint8_t(none), uint8_t(normal), uint8_t(reversed), uint8_t(normal | reversed), uint8_t(unknown) }) - for (uint8_t comparisons : { uint8_t(none), uint8_t(normal), uint8_t(reversed), uint8_t(normal | reversed), uint8_t(unknown) }) + // Draws with state that gives no ordering evidence (always/equal tests, unresolved dynamic state) + // are common next to the real geometry and must not veto it, which is what Suzerain's UI and sky do + for (uint8_t convention : { uint8_t(normal), uint8_t(reversed) }) + for (uint8_t noise : { uint8_t(none), uint8_t(unknown) }) + { + observation state; + for (uint64_t frame = 0; frame < 119; ++frame) + assert(state.observe(1, frame, convention | noise, convention | noise) == none); + assert(state.observe(1, 119, convention | noise, convention | noise) == convention); + } + for (uint8_t clears : { uint8_t(none), uint8_t(normal), uint8_t(reversed), uint8_t(normal | reversed), uint8_t(unknown), uint8_t(normal | unknown), uint8_t(reversed | unknown), uint8_t(normal | reversed | unknown) }) + for (uint8_t comparisons : { uint8_t(none), uint8_t(normal), uint8_t(reversed), uint8_t(normal | reversed), uint8_t(unknown), uint8_t(normal | unknown), uint8_t(reversed | unknown), uint8_t(normal | reversed | unknown) }) { - if ((clears == normal || clears == reversed) && clears == comparisons) + const uint8_t clear_direction = clears & (normal | reversed), compare_direction = comparisons & (normal | reversed); + if ((clear_direction == normal || clear_direction == reversed) && clear_direction == compare_direction) continue; + // Contradictory or missing evidence never votes, however long it is observed observation state; for (uint64_t frame = 0; frame < 600; ++frame) assert(state.observe(1, frame, clears, comparisons) == none);