Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions examples/09-depth/generic_depth_addon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
8 changes: 5 additions & 3 deletions examples/09-depth/generic_depth_detection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions include/reshade_api_resource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ namespace reshade::api
/// Resource is backed by sparse memory.
/// </summary>
sparse_binding = 0x40000,
/// <summary>
/// Used together with <see cref="shared"/> on a buffer to import existing host memory instead of allocating: the "shared_handle" parameter of <see cref="device::create_resource"/> 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.
/// </summary>
shared_host = 0x100000,
};
RESHADE_DEFINE_ENUM_FLAG_OPERATORS(resource_flags);

Expand Down
6 changes: 3 additions & 3 deletions source/addon_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void **>(&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<void *>(address), &result) == 0 ? result.dlfo_link_map : nullptr;
}

void *const g_module_handle = get_module_handle(reinterpret_cast<const void *>(&get_module_handle));
Expand Down
9 changes: 8 additions & 1 deletion source/vulkan/vulkan_hooks_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<GLADapiproc>(device.dispatch_table.GetInstanceProcAddr(device.instance_handle, name));
Expand Down
3 changes: 2 additions & 1 deletion source/vulkan/vulkan_hooks_swapchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 63 additions & 0 deletions source/vulkan/vulkan_impl_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<unsigned long long>(desc.buffer.size));
vmaDestroyPool(_alloc, alloc_info.pool);
return false;
}

object_data<VK_OBJECT_TYPE_BUFFER> 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<VK_OBJECT_TYPE_BUFFER>(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)
Expand Down
3 changes: 3 additions & 0 deletions source/vulkan/vulkan_impl_device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &param, VkDescriptorSetLayout *out_set_layout, std::vector<VkSampler> &embedded_samplers);

VmaAllocator _alloc = nullptr;
Expand Down
18 changes: 15 additions & 3 deletions tests/linux_portability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down