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);