From e4769697a5b53c25ec62402737ed0ee7cf88494a Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Fri, 2 Oct 2026 01:09:34 +0200 Subject: [PATCH 01/11] Linux Vulkan: add-on device interop and present flushes that wait for the back buffer - ReShadeVulkanRequestDeviceExtension (include/reshade_vulkan_interop.h): add-ons ask, from AddonInit, for device extensions that ReShade enables at vkCreateDevice when supported. "VK_KHR_buffer_device_address" also enables the feature, editing the game's own feature structure when it has one. - ReShadeVulkanGetDeviceInterop: the instance, physical device, the next layer's vkGet*ProcAddr, the API version and what was enabled, for add-ons that drive the game's device natively. - During the present event, the first flush of the immediate command list waits on the present's semaphores, so add-ons can flush and wait for the back buffer (ReShadePresentEventFlushWaits). Co-Authored-By: Claude Opus 5.5 --- include/reshade_vulkan_interop.h | 42 +++++++ source/vulkan/vulkan_hooks_device.cpp | 124 +++++++++++++++++++- source/vulkan/vulkan_hooks_swapchain.cpp | 30 +++-- source/vulkan/vulkan_impl_command_queue.cpp | 9 +- source/vulkan/vulkan_impl_command_queue.hpp | 5 + 5 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 include/reshade_vulkan_interop.h diff --git a/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h new file mode 100644 index 000000000..92deb13b0 --- /dev/null +++ b/include/reshade_vulkan_interop.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause OR MIT + */ + +#pragma once + +// Linux Vulkan layer only: lets an add-on drive the game's VkDevice with native Vulkan calls (for +// example a library that records its own commands), beyond what the ReShade API covers. +// Vulkan types are passed as void pointers so that this header does not need the Vulkan headers. + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Asks ReShade to enable a device extension on devices created from now on, when the driver supports it. +/// Call this from AddonInit: add-ons are loaded when the instance is created, before its devices. +/// Requesting "VK_KHR_buffer_device_address" also enables the bufferDeviceAddress feature (core since Vulkan 1.2). +void ReShadeVulkanRequestDeviceExtension(const char *name); + +struct ReShadeVulkanDeviceInterop +{ + uint32_t size; ///< Set by the caller to sizeof(ReShadeVulkanDeviceInterop) + void *instance; ///< VkInstance + void *physical_device; ///< VkPhysicalDevice + /// The next layer's vkGetInstanceProcAddr and vkGetDeviceProcAddr: calls through these do not come back through ReShade. + void *(*get_instance_proc_addr)(void *instance, const char *name); + void *(*get_device_proc_addr)(void *device, const char *name); + uint32_t api_version; ///< The instance's Vulkan API version + uint32_t enabled_extension_count; + const char *const *enabled_extensions; ///< Valid until the device is destroyed + uint32_t buffer_device_address; ///< Whether the bufferDeviceAddress feature is enabled +}; + +/// Describes a device ReShade hooked (the VkDevice of api::device::get_native()). Returns 0 for any other device. +uint32_t ReShadeVulkanGetDeviceInterop(void *device, struct ReShadeVulkanDeviceInterop *out); + +#ifdef __cplusplus +} +#endif diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index 01bfe2111..a39cbfb41 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -17,6 +17,9 @@ #include "process_environment.hpp" #include // std::strcmp, std::strncmp #include // std::find_if, std::min +#include +#include +#include // Set during Vulkan device creation and presentation, to avoid hooking internal D3D devices created e.g. by NVIDIA Ansel, Optimus or layered DXGI swap chain extern thread_local bool g_in_dxgi_runtime; @@ -24,6 +27,67 @@ extern thread_local bool g_in_dxgi_runtime; extern lockfree_linear_map g_vulkan_instances; lockfree_linear_map g_vulkan_devices; +#if defined(__linux__) +#include "reshade_vulkan_interop.h" + +// Device extensions add-ons asked for (see ReShadeVulkanRequestDeviceExtension), and what each device +// got, for add-ons that drive the device with native Vulkan calls +namespace +{ + struct device_interop + { + VkInstance instance; + VkPhysicalDevice physical_device; + PFN_vkGetInstanceProcAddr get_instance_proc_addr; + PFN_vkGetDeviceProcAddr get_device_proc_addr; + uint32_t api_version; + std::vector extension_names; + std::vector extensions; + bool buffer_device_address; + }; + + std::mutex s_interop_mutex; + std::vector s_requested_device_extensions; + std::unordered_map s_device_interop; + + std::vector requested_device_extensions() + { + const std::lock_guard lock(s_interop_mutex); + return s_requested_device_extensions; + } +} + +extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRequestDeviceExtension(const char *name) +{ + if (name == nullptr) + return; + const std::lock_guard lock(s_interop_mutex); + // Add-ons are loaded again for every instance, and ask again: keep each name once + if (std::find(s_requested_device_extensions.begin(), s_requested_device_extensions.end(), name) == s_requested_device_extensions.end()) + s_requested_device_extensions.emplace_back(name); +} + +extern "C" __attribute__((visibility("default"))) uint32_t ReShadeVulkanGetDeviceInterop(void *device, ReShadeVulkanDeviceInterop *out) +{ + if (out == nullptr || out->size < sizeof(ReShadeVulkanDeviceInterop)) + return 0; + const std::lock_guard lock(s_interop_mutex); + const auto it = s_device_interop.find(static_cast(device)); + if (it == s_device_interop.end()) + return 0; + const device_interop &interop = it->second; + out->instance = interop.instance; + out->physical_device = interop.physical_device; + out->get_instance_proc_addr = reinterpret_cast(interop.get_instance_proc_addr); + out->get_device_proc_addr = reinterpret_cast(interop.get_device_proc_addr); + out->api_version = interop.api_version; + out->enabled_extension_count = static_cast(interop.extensions.size()); + out->enabled_extensions = interop.extensions.data(); + out->buffer_device_address = interop.buffer_device_address ? 1 : 0; + return 1; +} +#endif + #if RESHADE_ADDON void create_default_view(reshade::vulkan::device_impl *device_impl, VkImage image) { @@ -141,6 +205,11 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi std::vector enabled_extensions; enabled_extensions.reserve(pCreateInfo->enabledExtensionCount); + bool force_buffer_device_address = false; +#if defined(__linux__) + // Outlives the call below: enabled_extensions points into it + const std::vector requested_extensions = requested_device_extensions(); +#endif for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) enabled_extensions.push_back(pCreateInfo->ppEnabledExtensionNames[i]); @@ -318,6 +387,36 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi add_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, true); add_extension(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME, true); #endif + +#if defined(__linux__) + // Extensions add-ons requested, when supported and not already enabled + for (const std::string &name : requested_extensions) + { + if (name == VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) + { + // The feature: core in Vulkan 1.2, the extension before + VkPhysicalDeviceBufferDeviceAddressFeatures supported_bda { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES }; + VkPhysicalDeviceFeatures2 supported { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &supported_bda }; + const auto get_features2 = instance.dispatch_table.GetPhysicalDeviceFeatures2 != nullptr ? instance.dispatch_table.GetPhysicalDeviceFeatures2 : instance.dispatch_table.GetPhysicalDeviceFeatures2KHR; + if (get_features2 != nullptr) + get_features2(physicalDevice, &supported); + if (!supported_bda.bufferDeviceAddress) + { + reshade::log::message(reshade::log::level::warning, "Add-on requested buffer device address, which this device does not support."); + continue; + } + if (instance.api_version < VK_API_VERSION_1_2 && !add_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, false)) + continue; + force_buffer_device_address = true; + continue; + } + if (std::find_if(enabled_extensions.cbegin(), enabled_extensions.cend(), + [&name](const char *enabled) { return name == enabled; }) != enabled_extensions.cend()) + continue; + if (add_extension(name.c_str(), false)) + reshade::log::message(reshade::log::level::info, "Enabled device extension \"%s\" requested by an add-on.", name.c_str()); + } +#endif } } @@ -345,6 +444,8 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi // Force enable timeline semaphore support (used for effect runtime present/graphics queue synchronization in case of present from compute, e.g. in Indiana Jones and the Great Circle and DOOM Eternal) ext.timeline_semaphore = const_cast(existing_vulkan_12_features)->timelineSemaphore = VK_TRUE; ext.descriptor_indexing = existing_vulkan_12_features->descriptorIndexing; + if (force_buffer_device_address) + const_cast(existing_vulkan_12_features)->bufferDeviceAddress = VK_TRUE; ext.buffer_device_address = existing_vulkan_12_features->bufferDeviceAddress; } else @@ -374,10 +475,13 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi if (const auto existing_buffer_device_address_features = find_in_structure_chain( pCreateInfo->pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES)) { + if (force_buffer_device_address) + const_cast(existing_buffer_device_address_features)->bufferDeviceAddress = VK_TRUE; ext.buffer_device_address = existing_buffer_device_address_features->bufferDeviceAddress; } - else if (ext.buffer_device_address) + else if (ext.buffer_device_address || force_buffer_device_address) { + ext.buffer_device_address = 1; append_to_structure_chain(&create_info, &ext.buffer_device_address_features); ext.buffer_device_address_features.bufferDeviceAddress = VK_TRUE; } @@ -676,6 +780,17 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi } #pragma endregion +#if defined(__linux__) + { + device_interop interop { instance.handle, physicalDevice, get_instance_proc_addr, get_device_proc_addr, instance.api_version, {}, {}, ext.buffer_device_address != 0 }; + interop.extension_names.assign(enabled_extensions.begin(), enabled_extensions.end()); + for (const std::string &name : interop.extension_names) + interop.extensions.push_back(name.c_str()); + const std::lock_guard lock(s_interop_mutex); + s_device_interop[device.handle] = std::move(interop); + } +#endif + // Initialize per-device data const auto device_impl = new reshade::vulkan::device_impl( device.handle, @@ -779,6 +894,13 @@ void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks // Finally destroy the device delete device_impl; +#if defined(__linux__) + { + const std::lock_guard lock(s_interop_mutex); + s_device_interop.erase(device); + } +#endif + trampoline(device, pAllocator); } diff --git a/source/vulkan/vulkan_hooks_swapchain.cpp b/source/vulkan/vulkan_hooks_swapchain.cpp index a71f0290f..8afccf4e6 100644 --- a/source/vulkan/vulkan_hooks_swapchain.cpp +++ b/source/vulkan/vulkan_hooks_swapchain.cpp @@ -530,6 +530,15 @@ VkResult VKAPI_CALL vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swap return trampoline(device, swapchain, pSwapchainImageCount, pSwapchainImages); } +#if defined(__linux__) +// Lets add-ons check that flushing the immediate command list during the present event waits for the +// back buffer to be complete (see 'command_queue_impl::_present_wait_info'). Returns the behavior's version. +extern "C" __attribute__((visibility("default"))) uint32_t ReShadePresentEventFlushWaits() +{ + return 1; +} +#endif + VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo) { assert(pPresentInfo != nullptr); @@ -545,6 +554,17 @@ VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPr else queue_impl->_mutex.lock(); + // The present's waits, taken over by the first flush of the immediate command list (see '_present_wait_info') + temp_mem wait_stages(present_info.waitSemaphoreCount); + std::fill_n(wait_stages.p, present_info.waitSemaphoreCount, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + + VkSubmitInfo submit_info { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submit_info.waitSemaphoreCount = present_info.waitSemaphoreCount; + submit_info.pWaitSemaphores = present_info.pWaitSemaphores; + submit_info.pWaitDstStageMask = wait_stages.p; + + queue_impl->_present_wait_info = &submit_info; + for (uint32_t i = 0; i < pPresentInfo->swapchainCount; ++i) { reshade::vulkan::object_data *const swapchain_impl = device_impl->get_private_data_for_object(pPresentInfo->pSwapchains[i]); @@ -662,16 +682,10 @@ VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPr reshade::present_effect_runtime(swapchain_impl); } + queue_impl->_present_wait_info = nullptr; + // Synchronize immediate command list flush { - temp_mem wait_stages(present_info.waitSemaphoreCount); - std::fill_n(wait_stages.p, present_info.waitSemaphoreCount, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - - VkSubmitInfo submit_info { VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submit_info.waitSemaphoreCount = present_info.waitSemaphoreCount; - submit_info.pWaitSemaphores = present_info.pWaitSemaphores; - submit_info.pWaitDstStageMask = wait_stages.p; - queue_impl->flush_immediate_command_list(&submit_info); // If the application is presenting with a different queue than rendering, synchronize these two queues diff --git a/source/vulkan/vulkan_impl_command_queue.cpp b/source/vulkan/vulkan_impl_command_queue.cpp index a83855364..2191010fb 100644 --- a/source/vulkan/vulkan_impl_command_queue.cpp +++ b/source/vulkan/vulkan_impl_command_queue.cpp @@ -82,8 +82,13 @@ void reshade::vulkan::command_queue_impl::flush_immediate_command_list() const } void reshade::vulkan::command_queue_impl::flush_immediate_command_list(VkSubmitInfo *wait_semaphore_info) const { - if (_immediate_cmd_list != nullptr) - _immediate_cmd_list->flush(wait_semaphore_info); + if (_immediate_cmd_list == nullptr) + return; + // During the present event the first flush takes over the present's waits: the back buffer is only + // complete once they are signaled. The present then waits on that flush's semaphore instead. + if (_present_wait_info != nullptr && wait_semaphore_info != _present_wait_info && _present_wait_info->waitSemaphoreCount != 0) + _immediate_cmd_list->flush(_present_wait_info); + _immediate_cmd_list->flush(wait_semaphore_info); } void reshade::vulkan::command_queue_impl::begin_debug_event(const char *label, const float color[4]) diff --git a/source/vulkan/vulkan_impl_command_queue.hpp b/source/vulkan/vulkan_impl_command_queue.hpp index eea279f30..694b55f15 100644 --- a/source/vulkan/vulkan_impl_command_queue.hpp +++ b/source/vulkan/vulkan_impl_command_queue.hpp @@ -40,6 +40,11 @@ namespace reshade::vulkan mutable std::recursive_mutex _mutex; + // Set by 'vkQueuePresentKHR' while the present event runs: the semaphores the present waits on. + // Any flush of the immediate command list during the event waits on them first, so add-ons can + // submit work that reads the back buffer and wait for it before present. + VkSubmitInfo *_present_wait_info = nullptr; + protected: device_impl *const _device; From e47a0dc4a87f324280975672ede5326d8d313480 Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Fri, 2 Oct 2026 02:11:18 +0200 Subject: [PATCH 02/11] Linux Vulkan: queues for add-ons, optical flow, timeline semaphores everywhere - ReShadeVulkanRequestQueue(flags): add-ons ask, from AddonInit, for a queue of their own with the given capabilities. ReShade adds it at vkCreateDevice in the family with the fewest other capabilities that has room, after the game's queues, and reports it in ReShadeVulkanDeviceInterop::queues together with the device's queue families (for concurrent sharing). - Requesting VK_NV_optical_flow also enables its opticalFlow feature, when supported (the bundled headers predate the extension: its constants are defined locally). - Timeline semaphores are enabled on every Vulkan 1.2+ device, not only when the game passes VkPhysicalDeviceVulkan12Features: create_fence failed on devices such as Dolphin's. Co-Authored-By: Claude Opus 5.5 --- include/reshade_vulkan_interop.h | 20 ++- source/vulkan/vulkan_hooks_device.cpp | 173 +++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 5 deletions(-) diff --git a/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h index 92deb13b0..7cba2cdc7 100644 --- a/include/reshade_vulkan_interop.h +++ b/include/reshade_vulkan_interop.h @@ -17,9 +17,22 @@ extern "C" { /// Asks ReShade to enable a device extension on devices created from now on, when the driver supports it. /// Call this from AddonInit: add-ons are loaded when the instance is created, before its devices. -/// Requesting "VK_KHR_buffer_device_address" also enables the bufferDeviceAddress feature (core since Vulkan 1.2). +/// Requesting "VK_KHR_buffer_device_address" also enables the bufferDeviceAddress feature (core since Vulkan 1.2), +/// and "VK_NV_optical_flow" the opticalFlow feature (the extension is only enabled when the feature is supported). void ReShadeVulkanRequestDeviceExtension(const char *name); +/// Asks ReShade to give devices created from now on one more queue with at least 'queue_flags' (VkQueueFlags), +/// for the add-on's own work. The family with the fewest other capabilities is preferred, so that the work can run +/// beside the game's. The game never sees this queue. Call this from AddonInit. +/// Returns the queue's index in ReShadeVulkanDeviceInterop::queues; asking again for the same flags returns the same index. +uint32_t ReShadeVulkanRequestQueue(uint32_t queue_flags); + +struct ReShadeVulkanQueue +{ + void *queue; ///< VkQueue, or null when no family had room + uint32_t family_index; +}; + struct ReShadeVulkanDeviceInterop { uint32_t size; ///< Set by the caller to sizeof(ReShadeVulkanDeviceInterop) @@ -32,6 +45,11 @@ struct ReShadeVulkanDeviceInterop uint32_t enabled_extension_count; const char *const *enabled_extensions; ///< Valid until the device is destroyed uint32_t buffer_device_address; ///< Whether the bufferDeviceAddress feature is enabled + // Filled only when 'size' covers them: + uint32_t queue_count; + const struct ReShadeVulkanQueue *queues; ///< Added for ReShadeVulkanRequestQueue, in request order; valid until the device is destroyed + uint32_t queue_family_count; ///< The queue families the device was created with (for concurrent sharing) + const uint32_t *queue_family_indices; ///< Valid until the device is destroyed }; /// Describes a device ReShade hooked (the VkDevice of api::device::get_native()). Returns 0 for any other device. diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index a39cbfb41..f52fac4e1 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -30,10 +30,21 @@ lockfree_linear_map g_vulkan_devices; #if defined(__linux__) #include "reshade_vulkan_interop.h" -// Device extensions add-ons asked for (see ReShadeVulkanRequestDeviceExtension), and what each device +// Device extensions and queues add-ons asked for (see reshade_vulkan_interop.h), and what each device // got, for add-ons that drive the device with native Vulkan calls namespace { + // From the Vulkan registry: newer than the bundled headers (generated without VK_NV_optical_flow) + constexpr VkQueueFlags queue_video_decode_bit = 0x20, queue_video_encode_bit = 0x40, queue_optical_flow_bit = 0x100; + constexpr const char optical_flow_extension_name[] = "VK_NV_optical_flow"; + constexpr VkStructureType structure_type_optical_flow_features = static_cast(1000464000); + struct optical_flow_features // VkPhysicalDeviceOpticalFlowFeaturesNV + { + VkStructureType sType; + void *pNext; + VkBool32 opticalFlow; + }; + struct device_interop { VkInstance instance; @@ -44,10 +55,13 @@ namespace std::vector extension_names; std::vector extensions; bool buffer_device_address; + std::vector queues; // in request order + std::vector queue_families; }; std::mutex s_interop_mutex; std::vector s_requested_device_extensions; + std::vector s_requested_queues; // VkQueueFlags, in request order std::unordered_map s_device_interop; std::vector requested_device_extensions() @@ -55,6 +69,34 @@ namespace const std::lock_guard lock(s_interop_mutex); return s_requested_device_extensions; } + std::vector requested_queues() + { + const std::lock_guard lock(s_interop_mutex); + return s_requested_queues; + } + + // The family for a queue with `required` capabilities: the one with room that has the fewest other + // capabilities (graphics counting most), so that the work runs beside the game's. `used` counts the + // queues already asked for per family, and is updated. + bool choose_queue_family(const std::vector &families, VkQueueFlags required, std::vector &used, const std::vector &special, uint32_t &out_family, uint32_t &out_index) + { + uint32_t best = std::numeric_limits::max(), best_score = std::numeric_limits::max(); + for (uint32_t family = 0; family < families.size(); ++family) + { + const VkQueueFlags flags = families[family].queueFlags; + if ((flags & required) != required || special[family] || used[family] >= families[family].queueCount) + continue; + const VkQueueFlags extra = flags & ~required & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | queue_video_decode_bit | queue_video_encode_bit | queue_optical_flow_bit); + const uint32_t score = static_cast(__builtin_popcount(extra)) + ((extra & VK_QUEUE_GRAPHICS_BIT) != 0 ? 8u : 0u); + if (score < best_score) + best = family, best_score = score; + } + if (best == std::numeric_limits::max()) + return false; + out_family = best; + out_index = used[best]++; + return true; + } } extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRequestDeviceExtension(const char *name) @@ -67,9 +109,20 @@ extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRequestDevic s_requested_device_extensions.emplace_back(name); } +extern "C" __attribute__((visibility("default"))) uint32_t ReShadeVulkanRequestQueue(uint32_t queue_flags) +{ + const std::lock_guard lock(s_interop_mutex); + // Asked again on every load of the add-on: the same request keeps its index + const auto it = std::find(s_requested_queues.begin(), s_requested_queues.end(), queue_flags); + if (it != s_requested_queues.end()) + return static_cast(it - s_requested_queues.begin()); + s_requested_queues.push_back(queue_flags); + return static_cast(s_requested_queues.size() - 1); +} + extern "C" __attribute__((visibility("default"))) uint32_t ReShadeVulkanGetDeviceInterop(void *device, ReShadeVulkanDeviceInterop *out) { - if (out == nullptr || out->size < sizeof(ReShadeVulkanDeviceInterop)) + if (out == nullptr || out->size < offsetof(ReShadeVulkanDeviceInterop, queue_count)) return 0; const std::lock_guard lock(s_interop_mutex); const auto it = s_device_interop.find(static_cast(device)); @@ -84,6 +137,13 @@ extern "C" __attribute__((visibility("default"))) uint32_t ReShadeVulkanGetDevic out->enabled_extension_count = static_cast(interop.extensions.size()); out->enabled_extensions = interop.extensions.data(); out->buffer_device_address = interop.buffer_device_address ? 1 : 0; + if (out->size >= sizeof(ReShadeVulkanDeviceInterop)) + { + out->queue_count = static_cast(interop.queues.size()); + out->queues = interop.queues.data(); + out->queue_family_count = static_cast(interop.queue_families.size()); + out->queue_family_indices = interop.queue_families.data(); + } return 1; } #endif @@ -206,6 +266,7 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi std::vector enabled_extensions; enabled_extensions.reserve(pCreateInfo->enabledExtensionCount); bool force_buffer_device_address = false; + bool force_optical_flow = false; #if defined(__linux__) // Outlives the call below: enabled_extensions points into it const std::vector requested_extensions = requested_device_extensions(); @@ -413,6 +474,21 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi if (std::find_if(enabled_extensions.cbegin(), enabled_extensions.cend(), [&name](const char *enabled) { return name == enabled; }) != enabled_extensions.cend()) continue; + if (name == optical_flow_extension_name) + { + // The extension is useless without its feature + optical_flow_features supported_flow { structure_type_optical_flow_features }; + VkPhysicalDeviceFeatures2 supported { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &supported_flow }; + const auto get_features2 = instance.dispatch_table.GetPhysicalDeviceFeatures2 != nullptr ? instance.dispatch_table.GetPhysicalDeviceFeatures2 : instance.dispatch_table.GetPhysicalDeviceFeatures2KHR; + if (get_features2 != nullptr) + get_features2(physicalDevice, &supported); + if (!supported_flow.opticalFlow) + continue; + force_optical_flow = add_extension(name.c_str(), false); + if (force_optical_flow) + reshade::log::message(reshade::log::level::info, "Enabled device extension \"%s\" requested by an add-on.", name.c_str()); + continue; + } if (add_extension(name.c_str(), false)) reshade::log::message(reshade::log::level::info, "Enabled device extension \"%s\" requested by an add-on.", name.c_str()); } @@ -424,6 +500,63 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi create_info.enabledExtensionCount = static_cast(enabled_extensions.size()); create_info.ppEnabledExtensionNames = enabled_extensions.data(); +#if defined(__linux__) + // Queues for add-ons' own work, when they asked and ReShade runs on this device. The game's queues + // keep their indices: added queues come after them in their family. + std::vector queue_create_infos(pCreateInfo->pQueueCreateInfos, pCreateInfo->pQueueCreateInfos + pCreateInfo->queueCreateInfoCount); + std::vector> queue_priorities; + std::vector added_queues; // family and index for now, the VkQueue after creation + std::vector added_queue_indices; + if (graphics_queue_family_index != std::numeric_limits::max()) + { + std::vector used(queue_families.size(), 0); + std::vector special(queue_families.size(), false); + for (const VkDeviceQueueCreateInfo &info : queue_create_infos) + used[info.queueFamilyIndex] += info.queueCount, special[info.queueFamilyIndex] = special[info.queueFamilyIndex] || info.flags != 0; + std::vector added_per_family(queue_families.size(), 0); + for (const uint32_t flags : requested_queues()) + { + uint32_t family = 0, index = 0; + if (!choose_queue_family(queue_families, flags, used, special, family, index)) + { + reshade::log::message(reshade::log::level::warning, "No queue family has room for a queue (flags 0x%x) requested by an add-on.", flags); + added_queues.push_back({ nullptr, std::numeric_limits::max() }); + added_queue_indices.push_back(0); + continue; + } + added_queues.push_back({ nullptr, family }); + added_queue_indices.push_back(index); + added_per_family[family] += 1; + reshade::log::message(reshade::log::level::info, "Adding a queue (flags 0x%x, family %u, index %u) requested by an add-on.", flags, family, index); + } + for (uint32_t family = 0; family < added_per_family.size(); ++family) + { + if (added_per_family[family] == 0) + continue; + const auto existing = std::find_if(queue_create_infos.begin(), queue_create_infos.end(), + [family](const VkDeviceQueueCreateInfo &info) { return info.queueFamilyIndex == family; }); + if (existing != queue_create_infos.end()) + { + queue_priorities.emplace_back(existing->pQueuePriorities, existing->pQueuePriorities + existing->queueCount); + queue_priorities.back().resize(existing->queueCount + added_per_family[family], 1.0f); + existing->queueCount += added_per_family[family]; + existing->pQueuePriorities = queue_priorities.back().data(); + } + else + { + queue_priorities.emplace_back(added_per_family[family], 1.0f); + VkDeviceQueueCreateInfo info { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + info.queueFamilyIndex = family; + info.queueCount = added_per_family[family]; + info.pQueuePriorities = queue_priorities.back().data(); + queue_create_infos.push_back(info); + } + } + create_info.queueCreateInfoCount = static_cast(queue_create_infos.size()); + create_info.pQueueCreateInfos = queue_create_infos.data(); + } +#endif + VkDevicePrivateDataCreateInfo private_data_info { VK_STRUCTURE_TYPE_DEVICE_PRIVATE_DATA_CREATE_INFO }; private_data_info.privateDataSlotRequestCount = 1; append_to_structure_chain(&create_info, &private_data_info); @@ -466,10 +599,12 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi { ext.timeline_semaphore = const_cast(existing_timeline_semaphore_features)->timelineSemaphore = VK_TRUE; } - else if (ext.timeline_semaphore) + else if (ext.timeline_semaphore || instance.api_version >= VK_API_VERSION_1_2) { + // Core in Vulkan 1.2 but still off unless enabled: force it here too, as for the Vulkan 1.2 features structure above append_to_structure_chain(&create_info, &ext.timeline_semaphore_features); ext.timeline_semaphore_features.timelineSemaphore = VK_TRUE; + ext.timeline_semaphore = 1; } if (const auto existing_buffer_device_address_features = find_in_structure_chain( @@ -563,6 +698,22 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi } } +#if defined(__linux__) + optical_flow_features flow_features { structure_type_optical_flow_features }; + if (force_optical_flow) + { + if (const auto existing_flow_features = find_in_structure_chain(pCreateInfo->pNext, structure_type_optical_flow_features)) + { + const_cast(existing_flow_features)->opticalFlow = VK_TRUE; + } + else + { + append_to_structure_chain(&create_info, &flow_features); + flow_features.opticalFlow = VK_TRUE; + } + } +#endif + // Enable Vulkan memory model device scope if it is not, since it is required by atomics in generated SPIR-V code for effects if (const auto existing_memory_model_features = find_in_structure_chain( pCreateInfo->pNext, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES)) @@ -782,7 +933,21 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi #if defined(__linux__) { - device_interop interop { instance.handle, physicalDevice, get_instance_proc_addr, get_device_proc_addr, instance.api_version, {}, {}, ext.buffer_device_address != 0 }; + for (size_t i = 0; i < added_queues.size(); ++i) + { + if (added_queues[i].family_index == std::numeric_limits::max()) + continue; + VkQueue queue = VK_NULL_HANDLE; + device.dispatch_table.GetDeviceQueue(device.handle, added_queues[i].family_index, added_queue_indices[i], &queue); + // As for the game's queues below: the loader did not set the dispatch pointer on these + if (queue != VK_NULL_HANDLE) + *reinterpret_cast(queue) = *reinterpret_cast(device.handle); + added_queues[i].queue = queue; + } + device_interop interop { instance.handle, physicalDevice, get_instance_proc_addr, get_device_proc_addr, instance.api_version, {}, {}, ext.buffer_device_address != 0, added_queues, {} }; + for (uint32_t i = 0; i < create_info.queueCreateInfoCount; ++i) + if (std::find(interop.queue_families.begin(), interop.queue_families.end(), create_info.pQueueCreateInfos[i].queueFamilyIndex) == interop.queue_families.end()) + interop.queue_families.push_back(create_info.pQueueCreateInfos[i].queueFamilyIndex); interop.extension_names.assign(enabled_extensions.begin(), enabled_extensions.end()); for (const std::string &name : interop.extension_names) interop.extensions.push_back(name.c_str()); From 201644413bb7b1ad7e2d1c4e5a74246176a61789 Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Wed, 7 Oct 2026 02:41:58 +0200 Subject: [PATCH 03/11] Linux Vulkan: hooks for what DXVK calls, and hiding device extensions for add-ons - vkCmdSetViewportWithCount reaches bind_viewports, and viewports bound by add-ons use the same call as the application - vkCmdBindDescriptorSets2 and vkCmdPushDescriptorSet2 reach the descriptor events (loaded by hand before Vulkan 1.4, where they are VK_KHR_maintenance6) - ReShadeVulkanHideDeviceExtension: an add-on can hide a device extension from the application, e.g. VK_EXT_descriptor_heap, so that it binds through calls the add-on can follow --- include/reshade_vulkan_interop.h | 6 ++ source/vulkan/vulkan.cpp | 11 +++ source/vulkan/vulkan_hooks.hpp | 4 + source/vulkan/vulkan_hooks_command_list.cpp | 91 ++++++++++++++++++--- source/vulkan/vulkan_hooks_device.cpp | 56 +++++++++++++ source/vulkan/vulkan_impl_command_list.cpp | 5 +- source/vulkan/vulkan_impl_command_list.hpp | 4 + 7 files changed, 164 insertions(+), 13 deletions(-) diff --git a/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h index 7cba2cdc7..d04369177 100644 --- a/include/reshade_vulkan_interop.h +++ b/include/reshade_vulkan_interop.h @@ -21,6 +21,12 @@ extern "C" { /// and "VK_NV_optical_flow" the opticalFlow feature (the extension is only enabled when the feature is supported). void ReShadeVulkanRequestDeviceExtension(const char *name); +/// Hides a device extension from the application on every Vulkan device created afterwards, so that it +/// uses the paths without it. For add-ons that follow what the application draws through calls that a +/// newer extension replaces (e.g. "VK_EXT_descriptor_heap" replaces descriptor sets). Call from the +/// add-on's initialization. +void ReShadeVulkanHideDeviceExtension(const char *name); + /// Asks ReShade to give devices created from now on one more queue with at least 'queue_flags' (VkQueueFlags), /// for the add-on's own work. The family with the fewest other capabilities is preferred, so that the work can run /// beside the game's. The game never sees this queue. Call this from AddonInit. diff --git a/source/vulkan/vulkan.cpp b/source/vulkan/vulkan.cpp index 6cf21c118..ea496e4f0 100644 --- a/source/vulkan/vulkan.cpp +++ b/source/vulkan/vulkan.cpp @@ -92,6 +92,16 @@ PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char *p #if RESHADE_ADDON RESHADE_VULKAN_HOOK_PROC(CmdSetViewport); RESHADE_VULKAN_HOOK_PROC(CmdSetScissor); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetViewportWithCount, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetViewportWithCount, EXT); +#endif +#if RESHADE_ADDON >= 2 + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdPushDescriptorSet2, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdPushDescriptorSet2, KHR); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdBindDescriptorSets2, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdBindDescriptorSets2, KHR); +#endif +#if RESHADE_ADDON #endif #if RESHADE_ADDON >= 2 RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthTestEnable, ); @@ -344,6 +354,7 @@ PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const c RESHADE_VULKAN_HOOK_PROC(DestroyInstance); RESHADE_VULKAN_HOOK_PROC(CreateDevice); RESHADE_VULKAN_HOOK_PROC(DestroyDevice); + RESHADE_VULKAN_HOOK_PROC(EnumerateDeviceExtensionProperties); #endif #if VK_VERSION_1_3 diff --git a/source/vulkan/vulkan_hooks.hpp b/source/vulkan/vulkan_hooks.hpp index ee88350e0..cea5eb5ee 100644 --- a/source/vulkan/vulkan_hooks.hpp +++ b/source/vulkan/vulkan_hooks.hpp @@ -103,6 +103,7 @@ VkResult VKAPI_CALL vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalD VkResult VKAPI_CALL vkGetPhysicalDeviceToolPropertiesEXT(VkPhysicalDevice physicalDevice, uint32_t *pToolCount, VkPhysicalDeviceToolPropertiesEXT *pToolProperties); #endif +VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties); VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice); void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks *pAllocator); @@ -203,6 +204,9 @@ void VKAPI_CALL vkCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineB void VKAPI_CALL vkCmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport *pViewports); void VKAPI_CALL vkCmdSetScissor(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D *pScissors); +void VKAPI_CALL vkCmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo *pPushDescriptorSetInfo); +void VKAPI_CALL vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo *pBindDescriptorSetsInfo); +void VKAPI_CALL vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport *pViewports); void VKAPI_CALL vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable); void VKAPI_CALL vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable); diff --git a/source/vulkan/vulkan_hooks_command_list.cpp b/source/vulkan/vulkan_hooks_command_list.cpp index 8a879b8ef..cdb262f9a 100644 --- a/source/vulkan/vulkan_hooks_command_list.cpp +++ b/source/vulkan/vulkan_hooks_command_list.cpp @@ -302,6 +302,7 @@ void VKAPI_CALL vkCmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstVi return; reshade::vulkan::command_list_impl *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + cmd_impl->_viewports_with_count = false; temp_mem viewport_data(viewportCount); for (uint32_t i = 0; i < viewportCount; ++i) @@ -316,6 +317,33 @@ void VKAPI_CALL vkCmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstVi reshade::invoke_addon_event(cmd_impl, firstViewport, viewportCount, viewport_data.p); #endif } +void VKAPI_CALL vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport *pViewports) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetViewportWithCount, device_impl); + trampoline(commandBuffer, viewportCount, pViewports); + +#if RESHADE_ADDON + if (!reshade::has_addon_event()) + return; + + reshade::vulkan::command_list_impl *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + cmd_impl->_viewports_with_count = true; + + temp_mem viewport_data(viewportCount); + for (uint32_t i = 0; i < viewportCount; ++i) + { + std::memcpy(&viewport_data[i], &pViewports[i], sizeof(VkViewport)); + + // Flip viewport vertically (see 'command_list_impl::bind_viewports') + viewport_data[i].y += viewport_data[i].height; + viewport_data[i].height = -viewport_data[i].height; + } + + reshade::invoke_addon_event(cmd_impl, 0, viewportCount, viewport_data.p); +#endif +} void VKAPI_CALL vkCmdSetScissor(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D *pScissors) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); @@ -1893,17 +1921,10 @@ void VKAPI_CALL vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t #endif } -void VKAPI_CALL vkCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet *pDescriptorWrites) -{ - reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); - - RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdPushDescriptorSet, device_impl); - trampoline(commandBuffer, pipelineBindPoint, layout, set, descriptorWriteCount, pDescriptorWrites); - #if RESHADE_ADDON >= 2 - if (!reshade::has_addon_event()) - return; - +// Reports pushed descriptors to add-ons, one event per write (shared by both versions of the call) +static void report_push_descriptors(reshade::vulkan::device_impl *device_impl, VkCommandBuffer commandBuffer, reshade::api::shader_stage shader_stages, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet *pDescriptorWrites) +{ reshade::vulkan::command_list_impl *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); uint32_t max_descriptors = 0; @@ -1911,8 +1932,6 @@ void VKAPI_CALL vkCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipeline max_descriptors = std::max(max_descriptors, pDescriptorWrites[i].descriptorCount); temp_mem descriptors(max_descriptors * 2); - const auto shader_stages = reshade::vulkan::convert_shader_stages(pipelineBindPoint); - for (uint32_t i = 0, j = 0; i < descriptorWriteCount; ++i, j = 0) { const VkWriteDescriptorSet &write = pDescriptorWrites[i]; @@ -1978,6 +1997,54 @@ void VKAPI_CALL vkCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipeline set, update); } +} +#endif + +void VKAPI_CALL vkCmdPushDescriptorSet(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet *pDescriptorWrites) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdPushDescriptorSet, device_impl); + trampoline(commandBuffer, pipelineBindPoint, layout, set, descriptorWriteCount, pDescriptorWrites); + +#if RESHADE_ADDON >= 2 + if (reshade::has_addon_event()) + report_push_descriptors(device_impl, commandBuffer, reshade::vulkan::convert_shader_stages(pipelineBindPoint), layout, set, descriptorWriteCount, pDescriptorWrites); +#endif +} +void VKAPI_CALL vkCmdPushDescriptorSet2(VkCommandBuffer commandBuffer, const VkPushDescriptorSetInfo *pPushDescriptorSetInfo) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdPushDescriptorSet2, device_impl); + trampoline(commandBuffer, pPushDescriptorSetInfo); + +#if RESHADE_ADDON >= 2 + if (reshade::has_addon_event()) + report_push_descriptors(device_impl, commandBuffer, static_cast(pPushDescriptorSetInfo->stageFlags), pPushDescriptorSetInfo->layout, + pPushDescriptorSetInfo->set, pPushDescriptorSetInfo->descriptorWriteCount, pPushDescriptorSetInfo->pDescriptorWrites); +#endif +} +void VKAPI_CALL vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const VkBindDescriptorSetsInfo *pBindDescriptorSetsInfo) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBindDescriptorSets2, device_impl); + trampoline(commandBuffer, pBindDescriptorSetsInfo); + +#if RESHADE_ADDON >= 2 + if (!reshade::has_addon_event()) + return; + + reshade::vulkan::command_list_impl *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + + reshade::invoke_addon_event( + cmd_impl, + static_cast(pBindDescriptorSetsInfo->stageFlags), + reshade::api::pipeline_layout { (uint64_t)pBindDescriptorSetsInfo->layout }, + pBindDescriptorSetsInfo->firstSet, pBindDescriptorSetsInfo->descriptorSetCount, + reinterpret_cast(pBindDescriptorSetsInfo->pDescriptorSets), + pBindDescriptorSetsInfo->dynamicOffsetCount, pBindDescriptorSetsInfo->pDynamicOffsets); #endif } void VKAPI_CALL vkCmdPushDescriptorSetWithTemplate(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void *pData) diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index f52fac4e1..9667bd0aa 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -61,6 +61,7 @@ namespace std::mutex s_interop_mutex; std::vector s_requested_device_extensions; + std::vector s_hidden_device_extensions; std::vector s_requested_queues; // VkQueueFlags, in request order std::unordered_map s_device_interop; @@ -109,6 +110,58 @@ extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRequestDevic s_requested_device_extensions.emplace_back(name); } +extern "C" __attribute__((visibility("default"))) void ReShadeVulkanHideDeviceExtension(const char *name) +{ + if (name == nullptr) + return; + const std::lock_guard lock(s_interop_mutex); + if (std::find(s_hidden_device_extensions.begin(), s_hidden_device_extensions.end(), name) == s_hidden_device_extensions.end()) + s_hidden_device_extensions.emplace_back(name); +} + +// What the application sees of the device's extensions: all but the ones add-ons asked to hide +VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) +{ + // This layer has no device extensions of its own + if (pLayerName != nullptr && 0 == std::strcmp(pLayerName, "VK_LAYER_reshade")) + { + *pPropertyCount = 0; + return VK_SUCCESS; + } + + RESHADE_VULKAN_GET_INSTANCE_DISPATCH_PTR(EnumerateDeviceExtensionProperties, physicalDevice); + + std::vector hidden; + { + const std::lock_guard lock(s_interop_mutex); + hidden = s_hidden_device_extensions; + } + if (hidden.empty() || pLayerName != nullptr) + return trampoline(physicalDevice, pLayerName, pPropertyCount, pProperties); + + uint32_t count = 0; + VkResult result = trampoline(physicalDevice, nullptr, &count, nullptr); + if (result != VK_SUCCESS) + return result; + std::vector all(count); + result = trampoline(physicalDevice, nullptr, &count, all.data()); + if (result != VK_SUCCESS && result != VK_INCOMPLETE) + return result; + all.resize(count); + all.erase(std::remove_if(all.begin(), all.end(), [&](const VkExtensionProperties &extension) { + return std::find(hidden.begin(), hidden.end(), extension.extensionName) != hidden.end(); }), all.end()); + + if (pProperties == nullptr) + { + *pPropertyCount = static_cast(all.size()); + return VK_SUCCESS; + } + const uint32_t copied = std::min(*pPropertyCount, static_cast(all.size())); + std::copy_n(all.begin(), copied, pProperties); + *pPropertyCount = copied; + return copied < all.size() ? VK_INCOMPLETE : VK_SUCCESS; +} + extern "C" __attribute__((visibility("default"))) uint32_t ReShadeVulkanRequestQueue(uint32_t queue_flags) { const std::lock_guard lock(s_interop_mutex); @@ -928,6 +981,9 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi device.dispatch_table.CopyMemoryToImage = device.dispatch_table.CopyMemoryToImageEXT; device.dispatch_table.TransitionImageLayout = device.dispatch_table.TransitionImageLayoutEXT; #endif + // VK_KHR_maintenance6 (not in the glad loader): how DXVK binds and pushes descriptors on a pre-1.4 instance + device.dispatch_table.CmdBindDescriptorSets2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdBindDescriptorSets2KHR")); + device.dispatch_table.CmdPushDescriptorSet2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdPushDescriptorSet2KHR")); } #pragma endregion diff --git a/source/vulkan/vulkan_impl_command_list.cpp b/source/vulkan/vulkan_impl_command_list.cpp index 17b639e66..fe508d3ca 100644 --- a/source/vulkan/vulkan_impl_command_list.cpp +++ b/source/vulkan/vulkan_impl_command_list.cpp @@ -497,7 +497,10 @@ void reshade::vulkan::command_list_impl::bind_viewports(uint32_t first, uint32_t viewport_data[i].height = -viewport_data[i].height; } - vk.CmdSetViewport(_orig, first, count, viewport_data.p); + if (_viewports_with_count && first == 0) + vk.CmdSetViewportWithCount(_orig, count, viewport_data.p); + else + vk.CmdSetViewport(_orig, first, count, viewport_data.p); } void reshade::vulkan::command_list_impl::bind_scissor_rects(uint32_t first, uint32_t count, const api::rect *rects) { diff --git a/source/vulkan/vulkan_impl_command_list.hpp b/source/vulkan/vulkan_impl_command_list.hpp index 0b923112b..025c3e1b5 100644 --- a/source/vulkan/vulkan_impl_command_list.hpp +++ b/source/vulkan/vulkan_impl_command_list.hpp @@ -73,6 +73,10 @@ namespace reshade::vulkan void end_debug_event() final; void insert_debug_marker(const char *label, const float color[4]) final; + // The application sets viewports together with their count (extended dynamic state), so calls made + // on its behalf have to as well + bool _viewports_with_count = false; + protected: device_impl *const _device; From 2715bdff5c42dc57e0b0efc4ae6941031dcec288 Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Wed, 7 Oct 2026 02:41:58 +0200 Subject: [PATCH 04/11] Linux: load add-ons from an 'addon' folder too An add-on can live there together with the files it needs, apart from ReShade's configuration and logs. --- source/linux/addon_paths.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/linux/addon_paths.hpp b/source/linux/addon_paths.hpp index f6b873605..6d9cfe67b 100644 --- a/source/linux/addon_paths.hpp +++ b/source/linux/addon_paths.hpp @@ -8,11 +8,16 @@ namespace reshade::utils { inline std::vector find_addon_files(const std::filesystem::path &user_directory, const std::filesystem::path &installed_directory) { - std::vector files; + // Each directory, and before it its "addon" folder: where an add-on lives together with the files + // it needs (libraries it loads, its data), apart from ReShade's own configuration and logs + std::vector directories; for (const auto &directory : { user_directory, installed_directory }) + if (!directory.empty()) + directories.insert(directories.end(), { directory / "addon", directory }); + + std::vector files; + for (const auto &directory : directories) { - if (directory.empty()) - continue; std::error_code ec; for (std::filesystem::directory_iterator it(directory, std::filesystem::directory_options::skip_permission_denied, ec), end; !ec && it != end; it.increment(ec)) From df974cc95dd7ad7538ec55c267d5129eeedd8a43 Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Wed, 7 Oct 2026 17:50:50 +0200 Subject: [PATCH 05/11] Linux Vulkan: let an add-on interrupt the application's dynamic rendering ReShadeVulkanInterruptRendering ends the rendering a command buffer is in and returns what it was begun with; ReShadeVulkanResumeRendering begins it again with the attachments loaded. For add-ons that record copies or compute work at a point of the application's frame (for example between its 3D scene and its HUD). Rendering with extension structures is refused, except the attachment flags of VK_KHR_maintenance10, which DXVK chains and which are kept. --- include/reshade_vulkan_interop.h | 10 +++ source/vulkan/vulkan_hooks_command_list.cpp | 87 +++++++++++++++++++++ source/vulkan/vulkan_impl_command_list.hpp | 16 ++++ 3 files changed, 113 insertions(+) diff --git a/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h index d04369177..633966cb4 100644 --- a/include/reshade_vulkan_interop.h +++ b/include/reshade_vulkan_interop.h @@ -61,6 +61,16 @@ struct ReShadeVulkanDeviceInterop /// Describes a device ReShade hooked (the VkDevice of api::device::get_native()). Returns 0 for any other device. uint32_t ReShadeVulkanGetDeviceInterop(void *device, struct ReShadeVulkanDeviceInterop *out); +/// Ends the dynamic rendering the application's command buffer (VkCommandBuffer) is in, so that the add-on can +/// record commands that are not allowed inside one (copies, compute work) at this point of the application's +/// frame. Returns what it was begun with (a 'const VkRenderingInfo *', valid until rendering begins again), or +/// null when there is nothing that can be ended and begun again: no rendering, a render pass object, rendering +/// that is suspended or resumed, or extension structures. +/// Follow with ReShadeVulkanResumeRendering - or, inside an 'end_render_pass' event, return true from it instead. +const void *ReShadeVulkanInterruptRendering(void *command_buffer); +/// Begins the interrupted rendering again, with the attachments' contents loaded. +void ReShadeVulkanResumeRendering(void *command_buffer); + #ifdef __cplusplus } #endif diff --git a/source/vulkan/vulkan_hooks_command_list.cpp b/source/vulkan/vulkan_hooks_command_list.cpp index cdb262f9a..17dea976b 100644 --- a/source/vulkan/vulkan_hooks_command_list.cpp +++ b/source/vulkan/vulkan_hooks_command_list.cpp @@ -1842,6 +1842,45 @@ void VKAPI_CALL vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRende cmd_impl->_is_in_render_pass = 3; + // Keep what is needed to end this rendering and begin it again (see ReShadeVulkanInterruptRendering). + // Not when it is suspended or resumed, nor with extension structures other than the one DXVK chains + // to its attachments (their flags, VK_KHR_maintenance10), which would have to be copied too. + { + bool plain = pRenderingInfo->pNext == nullptr && pRenderingInfo->colorAttachmentCount <= 8 && + (pRenderingInfo->flags & (VK_RENDERING_SUSPENDING_BIT | VK_RENDERING_RESUMING_BIT)) == 0; + const auto keep = [&](uint32_t index, const VkRenderingAttachmentInfo &attachment) { + VkRenderingAttachmentInfo &kept = cmd_impl->current_rendering_attachments[index]; + kept = attachment; + kept.pNext = nullptr; + if (const auto flags = static_cast(attachment.pNext); flags != nullptr) + { + if (flags->sType != reshade::vulkan::rendering_attachment_flags_info::structure_type || flags->pNext != nullptr) + return false; + cmd_impl->current_rendering_attachment_flags[index] = *flags; + kept.pNext = &cmd_impl->current_rendering_attachment_flags[index]; + } + return true; + }; + VkRenderingInfo &kept = cmd_impl->current_rendering; + kept = *pRenderingInfo; + kept.pColorAttachments = cmd_impl->current_rendering_attachments; + for (uint32_t i = 0; i < pRenderingInfo->colorAttachmentCount && plain; ++i) + plain = keep(i, pRenderingInfo->pColorAttachments[i]); + if (plain && pRenderingInfo->pDepthAttachment != nullptr) + { + plain = keep(8, *pRenderingInfo->pDepthAttachment); + kept.pDepthAttachment = &cmd_impl->current_rendering_attachments[8]; + } + if (plain && pRenderingInfo->pStencilAttachment != nullptr) + { + plain = keep(9, *pRenderingInfo->pStencilAttachment); + kept.pStencilAttachment = &cmd_impl->current_rendering_attachments[9]; + } + if (!plain) + kept.sType = static_cast(0); + cmd_impl->rendering_interrupted = false; + } + temp_mem rts(pRenderingInfo->colorAttachmentCount); for (uint32_t i = 0; i < pRenderingInfo->colorAttachmentCount; ++i) rts[i] = reshade::vulkan::convert_render_pass_render_target_desc(pRenderingInfo->pColorAttachments + i); @@ -1861,6 +1900,54 @@ void VKAPI_CALL vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRende RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBeginRendering, device_impl); trampoline(commandBuffer, pRenderingInfo); } +// An add-on ends the application's dynamic rendering to record commands that cannot be inside one +extern "C" __attribute__((visibility("default"))) const void *ReShadeVulkanInterruptRendering(void *command_buffer) +{ +#if RESHADE_ADDON + const auto commandBuffer = static_cast(command_buffer); + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + if (device_impl == nullptr) + return nullptr; + const auto cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + if (cmd_impl == nullptr || cmd_impl->_is_in_render_pass != 3 || cmd_impl->current_rendering.sType != VK_STRUCTURE_TYPE_RENDERING_INFO) + return nullptr; + + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdEndRendering, device_impl); + if (trampoline == nullptr) + return nullptr; + trampoline(commandBuffer); + + cmd_impl->_is_in_render_pass = 0; + cmd_impl->rendering_interrupted = true; + return &cmd_impl->current_rendering; +#else + return nullptr; +#endif +} +extern "C" __attribute__((visibility("default"))) void ReShadeVulkanResumeRendering(void *command_buffer) +{ +#if RESHADE_ADDON + const auto commandBuffer = static_cast(command_buffer); + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + if (device_impl == nullptr) + return; + const auto cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + if (cmd_impl == nullptr || !cmd_impl->rendering_interrupted) + return; + + // The attachments keep what was drawn so far (and are not resolved a second time from a cleared state) + for (VkRenderingAttachmentInfo &attachment : cmd_impl->current_rendering_attachments) + if (attachment.loadOp != VK_ATTACHMENT_LOAD_OP_NONE) + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBeginRendering, device_impl); + trampoline(commandBuffer, &cmd_impl->current_rendering); + + cmd_impl->_is_in_render_pass = 3; + cmd_impl->rendering_interrupted = false; +#endif +} + void VKAPI_CALL vkCmdEndRendering(VkCommandBuffer commandBuffer) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); diff --git a/source/vulkan/vulkan_impl_command_list.hpp b/source/vulkan/vulkan_impl_command_list.hpp index 025c3e1b5..f482022a6 100644 --- a/source/vulkan/vulkan_impl_command_list.hpp +++ b/source/vulkan/vulkan_impl_command_list.hpp @@ -84,6 +84,15 @@ namespace reshade::vulkan uint8_t _is_in_render_pass = 0; }; + // VkRenderingAttachmentFlagsInfoKHR (VK_KHR_maintenance10), which the bundled headers may predate + struct rendering_attachment_flags_info + { + static constexpr VkStructureType structure_type = static_cast(1000630002); + VkStructureType sType; + const void *pNext; + VkFlags flags; + }; + template <> struct object_data : public command_list_impl { @@ -100,6 +109,13 @@ namespace reshade::vulkan VkFramebuffer current_framebuffer = VK_NULL_HANDLE; VkImageView current_color_attachments[8] = {}; VkImageView current_depth_stencil_attachment = VK_NULL_HANDLE; + + // What the application's dynamic rendering was begun with, for ReShadeVulkanInterruptRendering. + // 'pColorAttachments' etc. point at the copies below; 'sType' is zero when it cannot be begun again. + VkRenderingInfo current_rendering = {}; + VkRenderingAttachmentInfo current_rendering_attachments[8 + 2] = {}; + rendering_attachment_flags_info current_rendering_attachment_flags[8 + 2] = {}; + bool rendering_interrupted = false; #endif }; } From e601983f7581d2d422ab91ea8d6b9ad6af9dec3c Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Wed, 7 Oct 2026 18:11:43 +0200 Subject: [PATCH 06/11] Linux Vulkan: tell add-ons where the application has a buffer mapped ReShadeVulkanGetMappedBuffer returns the address of a buffer's first byte while the application has its memory mapped (vkMapMemory and vkMapMemory2 are followed), for add-ons that read what the application writes to its buffers, such as the constants of a draw. --- include/reshade_vulkan_interop.h | 5 ++ source/vulkan/vulkan.cpp | 7 +++ source/vulkan/vulkan_hooks.hpp | 5 ++ source/vulkan/vulkan_hooks_device.cpp | 86 +++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h index 633966cb4..d1fb66b35 100644 --- a/include/reshade_vulkan_interop.h +++ b/include/reshade_vulkan_interop.h @@ -61,6 +61,11 @@ struct ReShadeVulkanDeviceInterop /// Describes a device ReShade hooked (the VkDevice of api::device::get_native()). Returns 0 for any other device. uint32_t ReShadeVulkanGetDeviceInterop(void *device, struct ReShadeVulkanDeviceInterop *out); +/// Where the application has a buffer (VkBuffer) mapped: the address of the buffer's first byte, or null while +/// its memory is not mapped. For add-ons that read what the application writes to its buffers (for example +/// the constants of a draw). Valid until the application unmaps or frees the memory. +void *ReShadeVulkanGetMappedBuffer(void *device, uint64_t buffer); + /// Ends the dynamic rendering the application's command buffer (VkCommandBuffer) is in, so that the add-on can /// record commands that are not allowed inside one (copies, compute work) at this point of the application's /// frame. Returns what it was begun with (a 'const VkRenderingInfo *', valid until rendering begins again), or diff --git a/source/vulkan/vulkan.cpp b/source/vulkan/vulkan.cpp index ea496e4f0..0d6ed80e0 100644 --- a/source/vulkan/vulkan.cpp +++ b/source/vulkan/vulkan.cpp @@ -34,6 +34,13 @@ PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char *p #if VK_VERSION_1_0 #if RESHADE_ADDON RESHADE_VULKAN_HOOK_PROC(QueueSubmit); + RESHADE_VULKAN_HOOK_PROC(MapMemory); + RESHADE_VULKAN_HOOK_PROC(UnmapMemory); + RESHADE_VULKAN_HOOK_PROC(FreeMemory); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(MapMemory2, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(MapMemory2, KHR); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(UnmapMemory2, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(UnmapMemory2, KHR); RESHADE_VULKAN_HOOK_PROC(BindBufferMemory); RESHADE_VULKAN_HOOK_PROC(BindImageMemory); RESHADE_VULKAN_HOOK_PROC(CreateQueryPool); diff --git a/source/vulkan/vulkan_hooks.hpp b/source/vulkan/vulkan_hooks.hpp index cea5eb5ee..356fec695 100644 --- a/source/vulkan/vulkan_hooks.hpp +++ b/source/vulkan/vulkan_hooks.hpp @@ -120,6 +120,11 @@ VkResult VKAPI_CALL vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const Vk VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo); #endif +VkResult VKAPI_CALL vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void **ppData); +void VKAPI_CALL vkUnmapMemory(VkDevice device, VkDeviceMemory memory); +void VKAPI_CALL vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator); +VkResult VKAPI_CALL vkMapMemory2(VkDevice device, const VkMemoryMapInfo *pMemoryMapInfo, void **ppData); +VkResult VKAPI_CALL vkUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo *pMemoryUnmapInfo); VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset); VkResult VKAPI_CALL vkBindBufferMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo *pBindInfos); diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index 9667bd0aa..e6d4d1711 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -18,6 +18,7 @@ #include // std::strcmp, std::strncmp #include // std::find_if, std::min #include +#include #include #include @@ -984,6 +985,11 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi // VK_KHR_maintenance6 (not in the glad loader): how DXVK binds and pushes descriptors on a pre-1.4 instance device.dispatch_table.CmdBindDescriptorSets2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdBindDescriptorSets2KHR")); device.dispatch_table.CmdPushDescriptorSet2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdPushDescriptorSet2KHR")); + // VK_KHR_map_memory2 (likewise): how DXVK maps its memory + if (device.dispatch_table.MapMemory2 == nullptr) + device.dispatch_table.MapMemory2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkMapMemory2KHR")); + if (device.dispatch_table.UnmapMemory2 == nullptr) + device.dispatch_table.UnmapMemory2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkUnmapMemory2KHR")); } #pragma endregion @@ -1188,6 +1194,86 @@ VkResult VKAPI_CALL vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const Vk return trampoline(queue, submitCount, pSubmits, fence); } +// Where the application has its memory mapped (the address of the memory's first byte), for add-ons that +// read what it writes to its buffers (ReShadeVulkanGetMappedBuffer) +static std::shared_mutex s_mapped_memory_mutex; +static std::unordered_map s_mapped_memory; + +VkResult VKAPI_CALL vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void **ppData) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(MapMemory, device_impl); + + const VkResult result = trampoline(device, memory, offset, size, flags, ppData); + if (result == VK_SUCCESS && ppData != nullptr) + { + const std::unique_lock lock(s_mapped_memory_mutex); + s_mapped_memory[memory] = static_cast(*ppData) - offset; + } + return result; +} +void VKAPI_CALL vkUnmapMemory(VkDevice device, VkDeviceMemory memory) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); + { + const std::unique_lock lock(s_mapped_memory_mutex); + s_mapped_memory.erase(memory); + } + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(UnmapMemory, device_impl); + trampoline(device, memory); +} +VkResult VKAPI_CALL vkMapMemory2(VkDevice device, const VkMemoryMapInfo *pMemoryMapInfo, void **ppData) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(MapMemory2, device_impl); + + const VkResult result = trampoline(device, pMemoryMapInfo, ppData); + if (result == VK_SUCCESS && ppData != nullptr && pMemoryMapInfo != nullptr) + { + const std::unique_lock lock(s_mapped_memory_mutex); + s_mapped_memory[pMemoryMapInfo->memory] = static_cast(*ppData) - pMemoryMapInfo->offset; + } + return result; +} +VkResult VKAPI_CALL vkUnmapMemory2(VkDevice device, const VkMemoryUnmapInfo *pMemoryUnmapInfo) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); + if (pMemoryUnmapInfo != nullptr) + { + const std::unique_lock lock(s_mapped_memory_mutex); + s_mapped_memory.erase(pMemoryUnmapInfo->memory); + } + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(UnmapMemory2, device_impl); + return trampoline(device, pMemoryUnmapInfo); +} +void VKAPI_CALL vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); + { + const std::unique_lock lock(s_mapped_memory_mutex); + s_mapped_memory.erase(memory); + } + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(FreeMemory, device_impl); + trampoline(device, memory, pAllocator); +} + +extern "C" __attribute__((visibility("default"))) void *ReShadeVulkanGetMappedBuffer(void *device, uint64_t buffer) +{ +#if RESHADE_ADDON + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(static_cast(device))); + if (device_impl == nullptr || buffer == 0) + return nullptr; + const auto buffer_data = device_impl->get_private_data_for_object((VkBuffer)buffer); + if (buffer_data == nullptr || buffer_data->memory == VK_NULL_HANDLE) + return nullptr; + const std::shared_lock lock(s_mapped_memory_mutex); + const auto it = s_mapped_memory.find(buffer_data->memory); + return it != s_mapped_memory.end() ? static_cast(it->second) + buffer_data->memory_offset : nullptr; +#else + return nullptr; +#endif +} + VkResult VKAPI_CALL vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); From 644e99b192e7c541184b9f0598ba65231b369ec4 Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Wed, 7 Oct 2026 19:29:05 +0200 Subject: [PATCH 07/11] Vulkan: report vkCmdSetStencilTestEnable and vkCmdSetStencilOp to add-ons As bind_pipeline_states events (stencil_enable; the faces' operations and comparison), like the stencil masks and reference already are: an add-on that changes the stencil test for some draws can put the application's back. --- source/vulkan/vulkan.cpp | 4 ++ source/vulkan/vulkan_hooks.hpp | 2 + source/vulkan/vulkan_hooks_command_list.cpp | 41 +++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/source/vulkan/vulkan.cpp b/source/vulkan/vulkan.cpp index 0d6ed80e0..96644bddd 100644 --- a/source/vulkan/vulkan.cpp +++ b/source/vulkan/vulkan.cpp @@ -113,6 +113,10 @@ PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char *p #if RESHADE_ADDON >= 2 RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthTestEnable, ); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthTestEnable, EXT); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilTestEnable, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilTestEnable, EXT); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilOp, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilOp, EXT); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthWriteEnable, ); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthWriteEnable, EXT); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthCompareOp, ); diff --git a/source/vulkan/vulkan_hooks.hpp b/source/vulkan/vulkan_hooks.hpp index 356fec695..69d73bb42 100644 --- a/source/vulkan/vulkan_hooks.hpp +++ b/source/vulkan/vulkan_hooks.hpp @@ -214,6 +214,8 @@ void VKAPI_CALL vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, cons void VKAPI_CALL vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport *pViewports); void VKAPI_CALL vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable); +void VKAPI_CALL vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable); +void VKAPI_CALL vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp); void VKAPI_CALL vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable); void VKAPI_CALL vkCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp); void VKAPI_CALL vkCmdSetDepthBias(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp, float depthBiasSlopeFactor); diff --git a/source/vulkan/vulkan_hooks_command_list.cpp b/source/vulkan/vulkan_hooks_command_list.cpp index 17dea976b..bb34d2124 100644 --- a/source/vulkan/vulkan_hooks_command_list.cpp +++ b/source/vulkan/vulkan_hooks_command_list.cpp @@ -387,6 +387,47 @@ void VKAPI_CALL vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 #endif } +void VKAPI_CALL vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilTestEnable, device_impl); + trampoline(commandBuffer, stencilTestEnable); + +#if RESHADE_ADDON >= 2 + if (!reshade::has_addon_event()) + return; + + auto *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + const reshade::api::dynamic_state state = reshade::api::dynamic_state::stencil_enable; + const uint32_t value = stencilTestEnable; + reshade::invoke_addon_event(cmd_impl, 1, &state, &value); +#endif +} +void VKAPI_CALL vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilOp, device_impl); + trampoline(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp); + +#if RESHADE_ADDON >= 2 + if (!reshade::has_addon_event()) + return; + + auto *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + const reshade::api::dynamic_state states[8] = { + reshade::api::dynamic_state::front_stencil_fail_op, reshade::api::dynamic_state::front_stencil_pass_op, reshade::api::dynamic_state::front_stencil_depth_fail_op, reshade::api::dynamic_state::front_stencil_func, + reshade::api::dynamic_state::back_stencil_fail_op, reshade::api::dynamic_state::back_stencil_pass_op, reshade::api::dynamic_state::back_stencil_depth_fail_op, reshade::api::dynamic_state::back_stencil_func }; + const uint32_t face[4] = { + static_cast(reshade::vulkan::convert_stencil_op(failOp)), static_cast(reshade::vulkan::convert_stencil_op(passOp)), + static_cast(reshade::vulkan::convert_stencil_op(depthFailOp)), static_cast(reshade::vulkan::convert_compare_op(compareOp)) }; + const uint32_t values[8] = { face[0], face[1], face[2], face[3], face[0], face[1], face[2], face[3] }; + if ((faceMask & VK_STENCIL_FACE_FRONT_BIT) != 0) + reshade::invoke_addon_event(cmd_impl, 4, states, values); + if ((faceMask & VK_STENCIL_FACE_BACK_BIT) != 0) + reshade::invoke_addon_event(cmd_impl, 4, states + 4, values); +#endif +} + void VKAPI_CALL vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); From c98aba9e71950b81ddd7e1ef754364796dd116dc Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Wed, 7 Oct 2026 21:08:02 +0200 Subject: [PATCH 08/11] Linux Vulkan: let an add-on draw in the application's command buffer and put its state back - ReShadeVulkanRestoreState: the graphics pipeline, descriptor sets, vertex buffers and dynamic state the application last set on a command buffer are followed (viewports and scissors with count, depth bias, culling, front face, depth bounds, depth and stencil tests) and set again on request. - ReShadeVulkanObserveGraphicsPipelines: an add-on is shown each graphics pipeline with the create info it was made from, to build pipelines of its own like the application's. - ReShadeVulkanGetShaderModuleCode: the SPIR-V of a shader module. --- include/reshade_vulkan_interop.h | 13 ++ source/vulkan/vulkan.cpp | 11 ++ source/vulkan/vulkan_hooks.hpp | 6 + source/vulkan/vulkan_hooks_command_list.cpp | 138 ++++++++++++++++++++ source/vulkan/vulkan_hooks_device.cpp | 28 ++++ source/vulkan/vulkan_impl_command_list.hpp | 27 ++++ 6 files changed, 223 insertions(+) diff --git a/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h index d1fb66b35..2cac6e98b 100644 --- a/include/reshade_vulkan_interop.h +++ b/include/reshade_vulkan_interop.h @@ -76,6 +76,19 @@ const void *ReShadeVulkanInterruptRendering(void *command_buffer); /// Begins the interrupted rendering again, with the attachments' contents loaded. void ReShadeVulkanResumeRendering(void *command_buffer); +/// Puts back what the application last set on its command buffer (VkCommandBuffer), after an add-on recorded +/// draws of its own there: the graphics pipeline, its descriptor sets, its vertex buffers, and the dynamic state +/// (viewports, scissors, depth bias, culling, depth and stencil tests, depth bounds). +void ReShadeVulkanRestoreState(void *command_buffer); + +/// Shows the add-on every graphics pipeline the application creates from now on, with the +/// 'const VkGraphicsPipelineCreateInfo *' it was created from (valid during the call only): for add-ons that +/// build pipelines of their own like the application's. One observer; null to stop. +void ReShadeVulkanObserveGraphicsPipelines(void (*observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data), void *user_data); + +/// The SPIR-V of a shader module (VkShaderModule) the application created, or null. Valid while the module lives. +const uint32_t *ReShadeVulkanGetShaderModuleCode(void *device, uint64_t shader_module, uint32_t *word_count); + #ifdef __cplusplus } #endif diff --git a/source/vulkan/vulkan.cpp b/source/vulkan/vulkan.cpp index 96644bddd..5a4b411b3 100644 --- a/source/vulkan/vulkan.cpp +++ b/source/vulkan/vulkan.cpp @@ -113,6 +113,17 @@ PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice device, const char *p #if RESHADE_ADDON >= 2 RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthTestEnable, ); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthTestEnable, EXT); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetScissorWithCount, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetScissorWithCount, EXT); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetCullMode, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetCullMode, EXT); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetFrontFace, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetFrontFace, EXT); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthBiasEnable, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthBiasEnable, EXT); + RESHADE_VULKAN_HOOK_PROC(CmdSetDepthBounds); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthBoundsTestEnable, ); + RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetDepthBoundsTestEnable, EXT); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilTestEnable, ); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilTestEnable, EXT); RESHADE_VULKAN_HOOK_PROC_OPTIONAL(CmdSetStencilOp, ); diff --git a/source/vulkan/vulkan_hooks.hpp b/source/vulkan/vulkan_hooks.hpp index 69d73bb42..55d19a6b0 100644 --- a/source/vulkan/vulkan_hooks.hpp +++ b/source/vulkan/vulkan_hooks.hpp @@ -214,6 +214,12 @@ void VKAPI_CALL vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, cons void VKAPI_CALL vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport *pViewports); void VKAPI_CALL vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable); +void VKAPI_CALL vkCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D *pScissors); +void VKAPI_CALL vkCmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode); +void VKAPI_CALL vkCmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace); +void VKAPI_CALL vkCmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable); +void VKAPI_CALL vkCmdSetDepthBounds(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds); +void VKAPI_CALL vkCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable); void VKAPI_CALL vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable); void VKAPI_CALL vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp); void VKAPI_CALL vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable); diff --git a/source/vulkan/vulkan_hooks_command_list.cpp b/source/vulkan/vulkan_hooks_command_list.cpp index bb34d2124..e3833c6ec 100644 --- a/source/vulkan/vulkan_hooks_command_list.cpp +++ b/source/vulkan/vulkan_hooks_command_list.cpp @@ -15,6 +15,17 @@ extern lockfree_linear_map g_vulkan_devices; +#if RESHADE_ADDON +// The record of what the application set on a command buffer (see 'application_state') +static inline reshade::vulkan::application_state &app_state(reshade::vulkan::device_impl *device_impl, VkCommandBuffer commandBuffer) +{ + return device_impl->get_private_data_for_object(commandBuffer)->app_state; +} +#define RESHADE_VULKAN_APP_STATE(...) { reshade::vulkan::application_state &s = app_state(device_impl, commandBuffer); using S = reshade::vulkan::application_state; (void)sizeof(S); __VA_ARGS__ } +#else +#define RESHADE_VULKAN_APP_STATE(...) +#endif + #if RESHADE_ADDON static bool invoke_begin_render_pass_event(const reshade::vulkan::device_impl *device_impl, reshade::vulkan::object_data *cmd_impl, const VkRenderPassBeginInfo *begin_info, VkSubpassContents contents) { @@ -191,6 +202,7 @@ VkResult VKAPI_CALL vkBeginCommandBuffer(VkCommandBuffer commandBuffer, const Vk // Begin does perform an implicit reset if command pool was created with 'VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT' reshade::invoke_addon_event(cmd_impl); + cmd_impl->app_state = {}; assert(cmd_impl->current_render_pass == VK_NULL_HANDLE); @@ -276,6 +288,7 @@ void VKAPI_CALL vkCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindP RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBindPipeline, device_impl); trampoline(commandBuffer, pipelineBindPoint, pipeline); + RESHADE_VULKAN_APP_STATE(if (pipelineBindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) s.graphics_pipeline = pipeline;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -296,6 +309,7 @@ void VKAPI_CALL vkCmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstVi RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetViewport, device_impl); trampoline(commandBuffer, firstViewport, viewportCount, pViewports); + RESHADE_VULKAN_APP_STATE(if (firstViewport == 0 && viewportCount <= 8) { std::copy_n(pViewports, viewportCount, s.viewports); s.viewport_count = viewportCount; s.viewports_with_count = false; s.set_mask |= S::viewport; }) #if RESHADE_ADDON if (!reshade::has_addon_event()) @@ -323,6 +337,7 @@ void VKAPI_CALL vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_ RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetViewportWithCount, device_impl); trampoline(commandBuffer, viewportCount, pViewports); + RESHADE_VULKAN_APP_STATE(if (viewportCount <= 8) { std::copy_n(pViewports, viewportCount, s.viewports); s.viewport_count = viewportCount; s.viewports_with_count = true; s.set_mask |= S::viewport; }) #if RESHADE_ADDON if (!reshade::has_addon_event()) @@ -350,6 +365,7 @@ void VKAPI_CALL vkCmdSetScissor(VkCommandBuffer commandBuffer, uint32_t firstSci RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetScissor, device_impl); trampoline(commandBuffer, firstScissor, scissorCount, pScissors); + RESHADE_VULKAN_APP_STATE(if (firstScissor == 0 && scissorCount <= 8) { std::copy_n(pScissors, scissorCount, s.scissors); s.scissor_count = scissorCount; s.scissors_with_count = false; s.set_mask |= S::scissor; }) #if RESHADE_ADDON if (!reshade::has_addon_event()) @@ -375,6 +391,7 @@ void VKAPI_CALL vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthTestEnable, device_impl); trampoline(commandBuffer, depthTestEnable); + RESHADE_VULKAN_APP_STATE(s.depth_tested = depthTestEnable; s.set_mask |= S::depth_test;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -392,6 +409,7 @@ void VKAPI_CALL vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool3 reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilTestEnable, device_impl); trampoline(commandBuffer, stencilTestEnable); + RESHADE_VULKAN_APP_STATE(s.stencil_tested = stencilTestEnable; s.set_mask |= S::stencil_test;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -408,6 +426,7 @@ void VKAPI_CALL vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFl reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilOp, device_impl); trampoline(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp); + RESHADE_VULKAN_APP_STATE(for (int f = 0; f < 2; ++f) if (faceMask & (1u << f)) { s.stencil[f].fail = failOp; s.stencil[f].pass = passOp; s.stencil[f].depth_fail = depthFailOp; s.stencil[f].compare = compareOp; } s.set_mask |= S::stencil_op;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -428,11 +447,121 @@ void VKAPI_CALL vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFl #endif } +// Dynamic state that is only followed for 'ReShadeVulkanRestoreState' +void VKAPI_CALL vkCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D *pScissors) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetScissorWithCount, device_impl); + trampoline(commandBuffer, scissorCount, pScissors); + RESHADE_VULKAN_APP_STATE(if (scissorCount <= 8) { std::copy_n(pScissors, scissorCount, s.scissors); s.scissor_count = scissorCount; s.scissors_with_count = true; s.set_mask |= S::scissor; }) +} +void VKAPI_CALL vkCmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetCullMode, device_impl); + trampoline(commandBuffer, cullMode); + RESHADE_VULKAN_APP_STATE(s.cull = cullMode; s.set_mask |= S::cull_mode;) +} +void VKAPI_CALL vkCmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetFrontFace, device_impl); + trampoline(commandBuffer, frontFace); + RESHADE_VULKAN_APP_STATE(s.front = frontFace; s.set_mask |= S::front_face;) +} +void VKAPI_CALL vkCmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthBiasEnable, device_impl); + trampoline(commandBuffer, depthBiasEnable); + RESHADE_VULKAN_APP_STATE(s.depth_bias_enabled = depthBiasEnable; s.set_mask |= S::depth_bias_enable;) +} +void VKAPI_CALL vkCmdSetDepthBounds(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthBounds, device_impl); + trampoline(commandBuffer, minDepthBounds, maxDepthBounds); + RESHADE_VULKAN_APP_STATE(s.depth_bounds_values[0] = minDepthBounds; s.depth_bounds_values[1] = maxDepthBounds; s.set_mask |= S::depth_bounds;) +} +void VKAPI_CALL vkCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) +{ + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthBoundsTestEnable, device_impl); + trampoline(commandBuffer, depthBoundsTestEnable); + RESHADE_VULKAN_APP_STATE(s.depth_bounds_tested = depthBoundsTestEnable; s.set_mask |= S::depth_bounds_test;) +} + +// Puts back what the application last set on its command buffer: the graphics pipeline, its descriptor sets, +// its vertex buffers and its dynamic state, after an add-on recorded draws of its own there +extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRestoreState(void *command_buffer) +{ +#if RESHADE_ADDON + const auto commandBuffer = static_cast(command_buffer); + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); + if (device_impl == nullptr) + return; + const auto cmd_impl = device_impl->get_private_data_for_object(commandBuffer); + if (cmd_impl == nullptr) + return; + const reshade::vulkan::application_state &s = cmd_impl->app_state; + using S = reshade::vulkan::application_state; + const auto &vk = device_impl->_dispatch_table; + + if (s.graphics_pipeline != VK_NULL_HANDLE) + vk.CmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, s.graphics_pipeline); + for (uint32_t i = 0; i < 8; ++i) + if (s.sets[i].set != VK_NULL_HANDLE) + vk.CmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, s.sets[i].layout, i, 1, &s.sets[i].set, 0, nullptr); + for (uint32_t i = 0; i < 16; ++i) + if ((s.vertex_buffer_mask & (1u << i)) != 0) + { + const S::vertex_buffer &b = s.vertex_buffers[i]; + if (s.vertex_buffer_strides && vk.CmdBindVertexBuffers2 != nullptr) + vk.CmdBindVertexBuffers2(commandBuffer, i, 1, &b.buffer, &b.offset, b.buffer != VK_NULL_HANDLE ? &b.size : nullptr, &b.stride); + else + vk.CmdBindVertexBuffers(commandBuffer, i, 1, &b.buffer, &b.offset); + } + if ((s.set_mask & S::viewport) != 0) + { + if (s.viewports_with_count) + vk.CmdSetViewportWithCount(commandBuffer, s.viewport_count, s.viewports); + else + vk.CmdSetViewport(commandBuffer, 0, s.viewport_count, s.viewports); + } + if ((s.set_mask & S::scissor) != 0) + { + if (s.scissors_with_count) + vk.CmdSetScissorWithCount(commandBuffer, s.scissor_count, s.scissors); + else + vk.CmdSetScissor(commandBuffer, 0, s.scissor_count, s.scissors); + } + if ((s.set_mask & S::depth_bias) != 0) vk.CmdSetDepthBias(commandBuffer, s.depth_bias_values[0], s.depth_bias_values[1], s.depth_bias_values[2]); + if ((s.set_mask & S::depth_bias_enable) != 0) vk.CmdSetDepthBiasEnable(commandBuffer, s.depth_bias_enabled); + if ((s.set_mask & S::cull_mode) != 0) vk.CmdSetCullMode(commandBuffer, s.cull); + if ((s.set_mask & S::front_face) != 0) vk.CmdSetFrontFace(commandBuffer, s.front); + if ((s.set_mask & S::depth_bounds) != 0) vk.CmdSetDepthBounds(commandBuffer, s.depth_bounds_values[0], s.depth_bounds_values[1]); + if ((s.set_mask & S::depth_bounds_test) != 0) vk.CmdSetDepthBoundsTestEnable(commandBuffer, s.depth_bounds_tested); + if ((s.set_mask & S::depth_test) != 0) vk.CmdSetDepthTestEnable(commandBuffer, s.depth_tested); + if ((s.set_mask & S::depth_write) != 0) vk.CmdSetDepthWriteEnable(commandBuffer, s.depth_written); + if ((s.set_mask & S::depth_compare) != 0) vk.CmdSetDepthCompareOp(commandBuffer, s.depth_compare_op); + if ((s.set_mask & S::stencil_test) != 0) vk.CmdSetStencilTestEnable(commandBuffer, s.stencil_tested); + for (int f = 0; f < 2; ++f) + { + const VkStencilFaceFlags face = f == 0 ? VK_STENCIL_FACE_FRONT_BIT : VK_STENCIL_FACE_BACK_BIT; + if ((s.set_mask & S::stencil_op) != 0) vk.CmdSetStencilOp(commandBuffer, face, s.stencil[f].fail, s.stencil[f].pass, s.stencil[f].depth_fail, s.stencil[f].compare); + if ((s.set_mask & S::stencil_compare_mask) != 0) vk.CmdSetStencilCompareMask(commandBuffer, face, s.stencil[f].compare_mask); + if ((s.set_mask & S::stencil_write_mask) != 0) vk.CmdSetStencilWriteMask(commandBuffer, face, s.stencil[f].write_mask); + if ((s.set_mask & S::stencil_reference) != 0) vk.CmdSetStencilReference(commandBuffer, face, s.stencil[f].reference); + } +#endif +} + void VKAPI_CALL vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthWriteEnable, device_impl); trampoline(commandBuffer, depthWriteEnable); + RESHADE_VULKAN_APP_STATE(s.depth_written = depthWriteEnable; s.set_mask |= S::depth_write;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -450,6 +579,7 @@ void VKAPI_CALL vkCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareO reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthCompareOp, device_impl); trampoline(commandBuffer, depthCompareOp); + RESHADE_VULKAN_APP_STATE(s.depth_compare_op = depthCompareOp; s.set_mask |= S::depth_compare;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -468,6 +598,7 @@ void VKAPI_CALL vkCmdSetDepthBias(VkCommandBuffer commandBuffer, float depthBias RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetDepthBias, device_impl); trampoline(commandBuffer, depthBiasConstantFactor, depthBiasClamp, depthBiasSlopeFactor); + RESHADE_VULKAN_APP_STATE(s.depth_bias_values[0] = depthBiasConstantFactor; s.depth_bias_values[1] = depthBiasClamp; s.depth_bias_values[2] = depthBiasSlopeFactor; s.set_mask |= S::depth_bias;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -512,6 +643,7 @@ void VKAPI_CALL vkCmdSetStencilCompareMask(VkCommandBuffer commandBuffer, VkSten RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilCompareMask, device_impl); trampoline(commandBuffer, faceMask, compareMask); + RESHADE_VULKAN_APP_STATE(for (int f = 0; f < 2; ++f) if (faceMask & (1u << f)) s.stencil[f].compare_mask = compareMask; s.set_mask |= S::stencil_compare_mask;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -531,6 +663,7 @@ void VKAPI_CALL vkCmdSetStencilWriteMask(VkCommandBuffer commandBuffer, VkStenci RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilWriteMask, device_impl); trampoline(commandBuffer, faceMask, writeMask); + RESHADE_VULKAN_APP_STATE(for (int f = 0; f < 2; ++f) if (faceMask & (1u << f)) s.stencil[f].write_mask = writeMask; s.set_mask |= S::stencil_write_mask;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -550,6 +683,7 @@ void VKAPI_CALL vkCmdSetStencilReference(VkCommandBuffer commandBuffer, VkStenci RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdSetStencilReference, device_impl); trampoline(commandBuffer, faceMask, reference); + RESHADE_VULKAN_APP_STATE(for (int f = 0; f < 2; ++f) if (faceMask & (1u << f)) s.stencil[f].reference = reference; s.set_mask |= S::stencil_reference;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -570,6 +704,7 @@ void VKAPI_CALL vkCmdBindDescriptorSets(VkCommandBuffer commandBuffer, VkPipelin RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBindDescriptorSets, device_impl); trampoline(commandBuffer, pipelineBindPoint, layout, firstSet, descriptorSetCount, pDescriptorSets, dynamicOffsetCount, pDynamicOffsets); + RESHADE_VULKAN_APP_STATE(if (pipelineBindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) for (uint32_t i = 0; i < descriptorSetCount && firstSet + i < 8; ++i) s.sets[firstSet + i] = { layout, pDescriptorSets[i] };) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -610,6 +745,7 @@ void VKAPI_CALL vkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint32_t f RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBindVertexBuffers, device_impl); trampoline(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets); + RESHADE_VULKAN_APP_STATE(for (uint32_t i = 0; i < bindingCount && firstBinding + i < 16; ++i) { s.vertex_buffers[firstBinding + i] = { pBuffers[i], pOffsets[i], VK_WHOLE_SIZE, 0 }; s.vertex_buffer_mask |= 1u << (firstBinding + i); }) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -2030,6 +2166,7 @@ void VKAPI_CALL vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBindVertexBuffers2, device_impl); trampoline(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides); + RESHADE_VULKAN_APP_STATE(for (uint32_t i = 0; i < bindingCount && firstBinding + i < 16; ++i) { s.vertex_buffers[firstBinding + i] = { pBuffers[i], pOffsets[i], pSizes != nullptr ? pSizes[i] : VK_WHOLE_SIZE, pStrides != nullptr ? pStrides[i] : 0 }; s.vertex_buffer_mask |= 1u << (firstBinding + i); } s.vertex_buffer_strides |= pStrides != nullptr;) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) @@ -2159,6 +2296,7 @@ void VKAPI_CALL vkCmdBindDescriptorSets2(VkCommandBuffer commandBuffer, const Vk RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBindDescriptorSets2, device_impl); trampoline(commandBuffer, pBindDescriptorSetsInfo); + RESHADE_VULKAN_APP_STATE(if (pBindDescriptorSetsInfo->stageFlags & VK_SHADER_STAGE_ALL_GRAPHICS) for (uint32_t i = 0; i < pBindDescriptorSetsInfo->descriptorSetCount && pBindDescriptorSetsInfo->firstSet + i < 8; ++i) s.sets[pBindDescriptorSetsInfo->firstSet + i] = { pBindDescriptorSetsInfo->layout, pBindDescriptorSetsInfo->pDescriptorSets[i] };) #if RESHADE_ADDON >= 2 if (!reshade::has_addon_event()) diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index e6d4d1711..b42a7651f 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -1763,6 +1763,31 @@ void VKAPI_CALL vkDestroyShaderModule(VkDevice device, VkShaderModule shader trampoline(device, shaderModule, pAllocator); } +// An add-on that needs pipelines of its own built like the application's (the same vertex input, layout and +// dynamic state, another shader) is shown each graphics pipeline with its 'VkGraphicsPipelineCreateInfo' +static void (*s_graphics_pipeline_observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data) = nullptr; +static void *s_graphics_pipeline_observer_data = nullptr; +extern "C" __attribute__((visibility("default"))) void ReShadeVulkanObserveGraphicsPipelines(void (*observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data), void *user_data) +{ + s_graphics_pipeline_observer_data = user_data; + s_graphics_pipeline_observer = observer; +} +extern "C" __attribute__((visibility("default"))) const uint32_t *ReShadeVulkanGetShaderModuleCode(void *device, uint64_t shader_module, uint32_t *word_count) +{ +#if RESHADE_ADDON + reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(static_cast(device))); + if (device_impl == nullptr || shader_module == 0 || word_count == nullptr) + return nullptr; + const auto module_data = device_impl->get_private_data_for_object((VkShaderModule)shader_module); + if (module_data == nullptr || module_data->spirv.empty()) + return nullptr; + *word_count = static_cast(module_data->spirv.size() / 4); + return reinterpret_cast(module_data->spirv.data()); +#else + return nullptr; +#endif +} + VkResult VKAPI_CALL vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo *pCreateInfos, const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(device)); @@ -2012,6 +2037,9 @@ VkResult VKAPI_CALL vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache p { reshade::invoke_addon_event( device_impl, reshade::api::pipeline_layout { (uint64_t)create_info.layout }, static_cast(subobjects.size()), subobjects.data(), reshade::api::pipeline { (uint64_t)pPipelines[i] }); + // (with what it was created from, while that is at hand: see ReShadeVulkanObserveGraphicsPipelines) + if (s_graphics_pipeline_observer != nullptr) + s_graphics_pipeline_observer(device, &create_info, (uint64_t)pPipelines[i], s_graphics_pipeline_observer_data); } else { diff --git a/source/vulkan/vulkan_impl_command_list.hpp b/source/vulkan/vulkan_impl_command_list.hpp index f482022a6..d57455d48 100644 --- a/source/vulkan/vulkan_impl_command_list.hpp +++ b/source/vulkan/vulkan_impl_command_list.hpp @@ -84,6 +84,32 @@ namespace reshade::vulkan uint8_t _is_in_render_pass = 0; }; + // What the application last set on a command buffer, of the state an add-on may change when it records + // its own draws there: so that it can be put back (ReShadeVulkanRestoreState) + struct application_state + { + VkPipeline graphics_pipeline = VK_NULL_HANDLE; + struct descriptor_set { VkPipelineLayout layout; VkDescriptorSet set; } sets[8] = {}; + struct vertex_buffer { VkBuffer buffer; VkDeviceSize offset, size, stride; } vertex_buffers[16] = {}; + uint32_t vertex_buffer_mask = 0; + bool vertex_buffer_strides = false; + // One bit per dynamic state the application has set + enum : uint32_t { viewport = 1, scissor = 2, depth_bias = 4, depth_bias_enable = 8, cull_mode = 16, front_face = 32, depth_bounds = 64, + depth_bounds_test = 128, depth_test = 256, depth_write = 512, depth_compare = 1024, stencil_test = 2048, stencil_op = 4096, + stencil_compare_mask = 8192, stencil_write_mask = 16384, stencil_reference = 32768 }; + uint32_t set_mask = 0; + VkViewport viewports[8] = {}; + VkRect2D scissors[8] = {}; + uint32_t viewport_count = 0, scissor_count = 0; + bool viewports_with_count = false, scissors_with_count = false; + float depth_bias_values[3] = {}, depth_bounds_values[2] = {}; + VkBool32 depth_bias_enabled = VK_FALSE, depth_bounds_tested = VK_FALSE, depth_tested = VK_FALSE, depth_written = VK_FALSE, stencil_tested = VK_FALSE; + VkCullModeFlags cull = VK_CULL_MODE_NONE; + VkFrontFace front = VK_FRONT_FACE_COUNTER_CLOCKWISE; + VkCompareOp depth_compare_op = VK_COMPARE_OP_ALWAYS; + struct stencil_face { VkStencilOp fail, pass, depth_fail; VkCompareOp compare; uint32_t compare_mask, write_mask, reference; } stencil[2] = {}; + }; + // VkRenderingAttachmentFlagsInfoKHR (VK_KHR_maintenance10), which the bundled headers may predate struct rendering_attachment_flags_info { @@ -116,6 +142,7 @@ namespace reshade::vulkan VkRenderingAttachmentInfo current_rendering_attachments[8 + 2] = {}; rendering_attachment_flags_info current_rendering_attachment_flags[8 + 2] = {}; bool rendering_interrupted = false; + application_state app_state; #endif }; } From 9139440d480944ad7d9453782606ef7c9b8d7c8a Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Fri, 9 Oct 2026 20:19:16 +0200 Subject: [PATCH 09/11] Comments removed from the lines this branch adds over upstream --- examples/05-shader_dump/shader_dump_addon.cpp | 1 - examples/09-depth/generic_depth_addon.cpp | 59 +------------------ examples/09-depth/generic_depth_detection.hpp | 6 -- include/reshade.hpp | 1 - include/reshade_api_resource.hpp | 5 -- include/reshade_vulkan_interop.h | 59 ++++--------------- source/addon.hpp | 1 - source/addon_manager.cpp | 3 - source/addon_manager.hpp | 3 - source/dll_log.cpp | 1 - source/effect_preprocessor.cpp | 1 - source/ini_file.cpp | 1 - source/input.hpp | 9 --- source/linux/addon_paths.hpp | 3 - source/linux/clipboard.hpp | 2 - source/linux/game_identity.cpp | 7 --- source/linux/input_backend.cpp | 1 - source/linux/input_backend.hpp | 16 ----- source/linux/input_linux.cpp | 6 -- source/linux/key_translation.cpp | 1 - source/linux/key_translation.hpp | 8 --- source/linux/paths.hpp | 7 +-- source/linux/platform_utils.cpp | 6 -- source/linux/process_environment.cpp | 5 -- source/linux/runtime_platform.cpp | 16 +---- source/linux/wayland_clipboard.cpp | 9 --- source/linux/wayland_clipboard.hpp | 13 ---- source/linux/wayland_input.cpp | 17 ------ source/linux/wayland_input.hpp | 8 --- source/linux/wayland_overlay_surface.cpp | 4 -- source/linux/wayland_overlay_surface.hpp | 11 ---- source/linux/wayland_pointer.cpp | 6 -- source/linux/wayland_pointer.hpp | 19 ------ source/linux/window_registry.hpp | 2 - source/reshade_api_object_impl.hpp | 2 - source/runtime.cpp | 2 +- source/runtime_gui.cpp | 13 ---- source/vulkan/vulkan.cpp | 4 -- source/vulkan/vulkan_hooks_command_list.cpp | 11 ---- source/vulkan/vulkan_hooks_device.cpp | 35 ++--------- source/vulkan/vulkan_hooks_instance.cpp | 5 -- source/vulkan/vulkan_hooks_swapchain.cpp | 21 ------- source/vulkan/vulkan_impl_command_list.hpp | 8 --- source/vulkan/vulkan_impl_command_queue.cpp | 2 - source/vulkan/vulkan_impl_command_queue.hpp | 3 - source/vulkan/vulkan_impl_device.cpp | 3 +- source/vulkan/vulkan_impl_swapchain.hpp | 3 - tests/linux_x11_grab.cpp | 8 --- 48 files changed, 21 insertions(+), 416 deletions(-) diff --git a/examples/05-shader_dump/shader_dump_addon.cpp b/examples/05-shader_dump/shader_dump_addon.cpp index 031ad8262..54f3c7dfe 100644 --- a/examples/05-shader_dump/shader_dump_addon.cpp +++ b/examples/05-shader_dump/shader_dump_addon.cpp @@ -68,7 +68,6 @@ static void save_shader_code(device_api device_type, const shader_desc &desc) } catch (...) { - // An add-on must never terminate the host when its optional dump path is unavailable. } #endif } diff --git a/examples/09-depth/generic_depth_addon.cpp b/examples/09-depth/generic_depth_addon.cpp index 9dddb9c78..e172388b4 100644 --- a/examples/09-depth/generic_depth_addon.cpp +++ b/examples/09-depth/generic_depth_addon.cpp @@ -45,10 +45,6 @@ static bool s_disable_intz = false; // Enable or disable the creation of backup copies at clear operations on the selected depth-stencil static unsigned int s_preserve_depth_buffers = 0; #if defined(__linux__) -// Vulkan can use reversed-Z conventions that are not represented in the -// context itself. Detect these from rendered state when no explicit setting -// exists. This is deliberately Linux-only for now so existing Windows -// behavior is unchanged. static bool s_auto_detect_reversed_depth = true; #endif // Set to zero for automatic detection, otherwise will use the clear operation at the specific index within a frame (first = config value, second = actual corresponding index) @@ -106,10 +102,6 @@ struct depth_stencil_frame_stats uint8_t depth_compare_evidence = depth_detection::none; }; -// The depth state may come from a static pipeline description or from dynamic -// state commands recorded after binding the pipeline. Keep each field's -// knownness separate so a missing dynamic command remains unknown rather than -// silently inheriting an arbitrary API default. struct depth_pipeline_state { bool depth_enable = false; @@ -122,8 +114,6 @@ struct depth_pipeline_state static uint8_t get_depth_compare_evidence(const depth_pipeline_state &state) { - // A known-disabled test or write mask proves that this draw cannot provide - // depth-order evidence, even when the other dynamic fields were not set. if ((state.depth_enable_known && !state.depth_enable) || (state.depth_write_mask_known && !state.depth_write_mask)) return depth_detection::none; @@ -158,9 +148,6 @@ struct RESHADE_API_UUID("43319e83-387c-448e-881c-7e68fc2e52c4") state_tracking viewport current_viewport = {}; resource current_depth_stencil = { 0 }; depth_pipeline_state current_depth_state; - // A compute-only bind must not invalidate the graphics pipeline state. Keep - // track of whether this command list actually changed graphics state so a - // secondary command list with no such bind does not clobber its caller. bool graphics_pipeline_state_touched = false; std::unordered_map stats_per_used_depth_stencil; draw_stats best_copy_stats; @@ -245,15 +232,10 @@ struct RESHADE_API_UUID("7c6363c7-f94e-437a-9160-141782c44a98") generic_depth_da bool using_backup_texture = false; #if defined(__linux__) - // Detection belongs to an effect runtime, not to the device: different - // runtimes can select different depth resources and can use different - // presets. The observation itself also supplies the bounded frame budget. depth_detection::observation reversed_depth_detection; bool reversed_depth_detection_disabled = false; bool reversed_depth_detection_timeout_logged = false; bool reversed_depth_detection_applied = false; - // Temporary diagnostics for the unvalidated auto-detect feature; remove - // once live behavior has been confirmed. bool reversed_depth_detection_manual_disable_logged = false; uint32_t reversed_depth_detection_debug_tick = 0; uint32_t reversed_depth_detection_debug_skip_tick = 0; @@ -305,9 +287,6 @@ struct RESHADE_API_UUID("e006e162-33ac-4b9f-b10f-0e15335c7bdb") generic_depth_de // List of all encountered depth-stencils of the last frame std::unordered_map depth_stencil_resources; - // Static Vulkan pipeline depth state, keyed by the native pipeline handle. - // This is populated by 'init_pipeline' and copied into command-list state at - // bind time, so draw callbacks do not need to acquire another lock. std::unordered_map depth_pipeline_states; // List of depth-stencils that should be tracked throughout each frame and potentially be backed up during clear operations @@ -701,8 +680,6 @@ static void on_init_pipeline(device *device, pipeline_layout, uint32_t subobject } } - // Pipelines without a depth-stencil subobject (for example compute - // pipelines or incomplete pipeline-library pieces) remain fully unknown. if (!has_depth_stencil_state) { state.depth_enable_known = false; @@ -711,9 +688,6 @@ static void on_init_pipeline(device *device, pipeline_layout, uint32_t subobject } else { - // The dynamic-state sub-object is not required to appear before the - // depth-stencil sub-object. Apply its effects after the complete list has - // been scanned so the result is independent of sub-object ordering. if (dynamic_states_are_unknown || depth_enable_is_dynamic) state.depth_enable_known = false; if (dynamic_states_are_unknown || depth_write_mask_is_dynamic) @@ -760,7 +734,7 @@ static void on_bind_pipeline(command_list *cmd_list, pipeline_stage stages, pipe if (device->get_api() != device_api::vulkan) return; if ((pipeline_stage::all_graphics & stages) == 0) - return; // A compute-only bind does not change graphics depth state + return; auto &state = *cmd_list->get_private_data(); depth_pipeline_state pipeline_state; @@ -791,7 +765,6 @@ static void on_bind_pipeline_states(command_list *cmd_list, uint32_t count, cons auto &state = *cmd_list->get_private_data(); if (count != 0 && (states == nullptr || values == nullptr)) { - // Treat malformed or unavailable dynamic state as unknown evidence. state.current_depth_state = {}; state.graphics_pipeline_state_touched = true; return; @@ -1122,10 +1095,6 @@ static bool on_clear_depth_stencil(command_list *cmd_list, resource_view dsv, co lock.lock(); depth_stencil_frame_stats &stats = state.stats_per_used_depth_stencil[depth_stencil]; - // Keep this legacy flag unchanged for the UI. It intentionally marks - // every non-one clear as potentially reversed, while automatic - // detection below only accepts exact zero/one values in combination - // with matching depth comparisons. stats.reversed_clear_value = true; #if defined(__linux__) stats.clear_depth_evidence |= depth_detection::clear_evidence(*depth); @@ -1192,9 +1161,6 @@ static void on_execute_secondary(command_list *cmd_list, command_list *secondary { target_state.current_viewport = source_state.current_viewport; - // The source did not expose a graphics depth state. Record the - // inherited-depth draw as unknown instead of allowing a caller's stale - // pipeline state to create a false convention vote. const depth_pipeline_state inherited_depth_state = target_state.current_depth_state; target_state.current_depth_state = {}; on_draw_indirect(cmd_list, indirect_command::draw, { 0 }, 0, 1, 0); @@ -1279,14 +1245,9 @@ static constexpr char reversed_depth_definition[] = "RESHADE_DEPTH_INPUT_IS_REVE static bool has_manual_reversed_depth_definition(effect_runtime *runtime) { char value[32]; - // The public getter with no effect name intentionally resolves the global - // and active-preset scopes. Either one is an explicit user choice and must - // win over automatic detection. return runtime->get_preprocessor_definition(reversed_depth_definition, value); } -// Temporary diagnostics for the unvalidated auto-detect feature; remove once -// live behavior has been confirmed. static void log_reversed_depth_diagnostic(const char *reason, const generic_depth_data &data, device_api api, resource selected_depth_stencil, resource_view selected_shader_resource, const depth_stencil_resource *info, uint64_t frame) { @@ -1306,8 +1267,6 @@ static void log_reversed_depth_diagnostic(const char *reason, const generic_dept static void observe_reversed_depth(effect_runtime *runtime, generic_depth_data &data, device *device, resource selected_depth_stencil, const depth_stencil_resource *selected_depth_stencil_info, uint64_t frame) { - // Log roughly once per second (assuming ~60 present calls/s) so a single - // playtest shows progression without flooding the log. if ((data.reversed_depth_detection_debug_tick++ % 60) == 0) log_reversed_depth_diagnostic("tick", data, device->get_api(), selected_depth_stencil, data.selected_shader_resource, selected_depth_stencil_info, frame); @@ -1316,7 +1275,7 @@ static void observe_reversed_depth(effect_runtime *runtime, generic_depth_data & (data.reversed_depth_detection.finished && data.reversed_depth_detection_timeout_logged)) return; if (selected_depth_stencil_info->last_used_in_frame != frame || selected_depth_stencil_info->last_frame_stats.total.drawcalls == 0) - return; // Do not spend the budget on a stale/manual override selection + return; if (has_manual_reversed_depth_definition(runtime)) { @@ -1339,9 +1298,6 @@ static void observe_reversed_depth(effect_runtime *runtime, generic_depth_data & if (result == depth_detection::normal || result == depth_detection::reversed) { - // Check again immediately before the public setter. This keeps a manual - // definition authoritative even if another runtime changed the preset - // earlier in this frame. if (has_manual_reversed_depth_definition(runtime)) { data.reversed_depth_detection_disabled = true; @@ -1349,10 +1305,6 @@ static void observe_reversed_depth(effect_runtime *runtime, generic_depth_data & } const char *const value = result == depth_detection::reversed ? "1" : "0"; - // The public setter writes a new definition to the active preset when no - // explicit global/preset definition exists. It updates the runtime cache - // and schedules the reload for the next frame; it does not hold the depth - // add-on mutex while doing so. runtime->set_preprocessor_definition(reversed_depth_definition, value); data.reversed_depth_detection_applied = true; reshade::log::message(reshade::log::level::info, @@ -1477,7 +1429,6 @@ static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_l } 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; } @@ -1568,16 +1519,10 @@ static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_l } while (0); #if defined(__linux__) - // Observe only the resource that was successfully selected and exposed to - // this runtime. In particular, a failed view/backup creation must not - // consume the bounded observation budget. if (data.selected_depth_stencil != 0 && data.selected_shader_resource != 0 && data.selected_depth_stencil == selected_depth_stencil) { observe_reversed_depth(runtime, data, device, data.selected_depth_stencil, selected_depth_stencil_info, frame_index); } - // Temporary diagnostic for the unvalidated auto-detect feature: show why - // the call-site gate above is not letting observation run at all. Remove - // once live behavior has been confirmed. else if (s_auto_detect_reversed_depth && !data.reversed_depth_detection_applied && !data.reversed_depth_detection_disabled && (data.reversed_depth_detection_debug_skip_tick++ % 60) == 0) { diff --git a/examples/09-depth/generic_depth_detection.hpp b/examples/09-depth/generic_depth_detection.hpp index b0fd4f388..55d49b8dc 100644 --- a/examples/09-depth/generic_depth_detection.hpp +++ b/examples/09-depth/generic_depth_detection.hpp @@ -4,8 +4,6 @@ namespace depth_detection { - // Bit masks permit lossless merging across command lists. Unknown is an - // 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) { @@ -23,8 +21,6 @@ namespace depth_detection uint8_t candidate = none; bool finished = false; - // Call once for each actual frame with a selected, rendered depth buffer. - // A buffer change resets confidence, but does not extend the total budget. uint8_t observe(uint64_t selected, uint64_t frame, uint8_t clears, uint8_t comparisons) { if (finished || selected == 0 || frame == last_frame) @@ -37,8 +33,6 @@ namespace depth_detection candidate = none; consistent_frames = 0; } - // 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) diff --git a/include/reshade.hpp b/include/reshade.hpp index 518572ff0..c517ab8f0 100644 --- a/include/reshade.hpp +++ b/include/reshade.hpp @@ -270,7 +270,6 @@ namespace reshade return false; #if defined(__linux__) && defined(IMGUI_VERSION_NUM) - // The function table keeps add-ons independent of ReShade's ImGui ABI. if (!(imgui_function_table_instance() = static_cast(ReShadeGetImGuiFunctionTable(IMGUI_VERSION_NUM)))) { ReShadeUnregisterAddon(addon_module); diff --git a/include/reshade_api_resource.hpp b/include/reshade_api_resource.hpp index 094d09329..5782addf2 100644 --- a/include/reshade_api_resource.hpp +++ b/include/reshade_api_resource.hpp @@ -244,11 +244,6 @@ 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/include/reshade_vulkan_interop.h b/include/reshade_vulkan_interop.h index 2cac6e98b..bd921f5b6 100644 --- a/include/reshade_vulkan_interop.h +++ b/include/reshade_vulkan_interop.h @@ -1,13 +1,6 @@ -/* - * Copyright (C) 2014 Patrick Mours - * SPDX-License-Identifier: BSD-3-Clause OR MIT - */ #pragma once -// Linux Vulkan layer only: lets an add-on drive the game's VkDevice with native Vulkan calls (for -// example a library that records its own commands), beyond what the ReShade API covers. -// Vulkan types are passed as void pointers so that this header does not need the Vulkan headers. #include @@ -15,78 +8,46 @@ extern "C" { #endif -/// Asks ReShade to enable a device extension on devices created from now on, when the driver supports it. -/// Call this from AddonInit: add-ons are loaded when the instance is created, before its devices. -/// Requesting "VK_KHR_buffer_device_address" also enables the bufferDeviceAddress feature (core since Vulkan 1.2), -/// and "VK_NV_optical_flow" the opticalFlow feature (the extension is only enabled when the feature is supported). void ReShadeVulkanRequestDeviceExtension(const char *name); -/// Hides a device extension from the application on every Vulkan device created afterwards, so that it -/// uses the paths without it. For add-ons that follow what the application draws through calls that a -/// newer extension replaces (e.g. "VK_EXT_descriptor_heap" replaces descriptor sets). Call from the -/// add-on's initialization. void ReShadeVulkanHideDeviceExtension(const char *name); -/// Asks ReShade to give devices created from now on one more queue with at least 'queue_flags' (VkQueueFlags), -/// for the add-on's own work. The family with the fewest other capabilities is preferred, so that the work can run -/// beside the game's. The game never sees this queue. Call this from AddonInit. -/// Returns the queue's index in ReShadeVulkanDeviceInterop::queues; asking again for the same flags returns the same index. uint32_t ReShadeVulkanRequestQueue(uint32_t queue_flags); struct ReShadeVulkanQueue { - void *queue; ///< VkQueue, or null when no family had room + void *queue; uint32_t family_index; }; struct ReShadeVulkanDeviceInterop { - uint32_t size; ///< Set by the caller to sizeof(ReShadeVulkanDeviceInterop) - void *instance; ///< VkInstance - void *physical_device; ///< VkPhysicalDevice - /// The next layer's vkGetInstanceProcAddr and vkGetDeviceProcAddr: calls through these do not come back through ReShade. + uint32_t size; + void *instance; + void *physical_device; void *(*get_instance_proc_addr)(void *instance, const char *name); void *(*get_device_proc_addr)(void *device, const char *name); - uint32_t api_version; ///< The instance's Vulkan API version + uint32_t api_version; uint32_t enabled_extension_count; - const char *const *enabled_extensions; ///< Valid until the device is destroyed - uint32_t buffer_device_address; ///< Whether the bufferDeviceAddress feature is enabled - // Filled only when 'size' covers them: + const char *const *enabled_extensions; + uint32_t buffer_device_address; uint32_t queue_count; - const struct ReShadeVulkanQueue *queues; ///< Added for ReShadeVulkanRequestQueue, in request order; valid until the device is destroyed - uint32_t queue_family_count; ///< The queue families the device was created with (for concurrent sharing) - const uint32_t *queue_family_indices; ///< Valid until the device is destroyed + const struct ReShadeVulkanQueue *queues; + uint32_t queue_family_count; + const uint32_t *queue_family_indices; }; -/// Describes a device ReShade hooked (the VkDevice of api::device::get_native()). Returns 0 for any other device. uint32_t ReShadeVulkanGetDeviceInterop(void *device, struct ReShadeVulkanDeviceInterop *out); -/// Where the application has a buffer (VkBuffer) mapped: the address of the buffer's first byte, or null while -/// its memory is not mapped. For add-ons that read what the application writes to its buffers (for example -/// the constants of a draw). Valid until the application unmaps or frees the memory. void *ReShadeVulkanGetMappedBuffer(void *device, uint64_t buffer); -/// Ends the dynamic rendering the application's command buffer (VkCommandBuffer) is in, so that the add-on can -/// record commands that are not allowed inside one (copies, compute work) at this point of the application's -/// frame. Returns what it was begun with (a 'const VkRenderingInfo *', valid until rendering begins again), or -/// null when there is nothing that can be ended and begun again: no rendering, a render pass object, rendering -/// that is suspended or resumed, or extension structures. -/// Follow with ReShadeVulkanResumeRendering - or, inside an 'end_render_pass' event, return true from it instead. const void *ReShadeVulkanInterruptRendering(void *command_buffer); -/// Begins the interrupted rendering again, with the attachments' contents loaded. void ReShadeVulkanResumeRendering(void *command_buffer); -/// Puts back what the application last set on its command buffer (VkCommandBuffer), after an add-on recorded -/// draws of its own there: the graphics pipeline, its descriptor sets, its vertex buffers, and the dynamic state -/// (viewports, scissors, depth bias, culling, depth and stencil tests, depth bounds). void ReShadeVulkanRestoreState(void *command_buffer); -/// Shows the add-on every graphics pipeline the application creates from now on, with the -/// 'const VkGraphicsPipelineCreateInfo *' it was created from (valid during the call only): for add-ons that -/// build pipelines of their own like the application's. One observer; null to stop. void ReShadeVulkanObserveGraphicsPipelines(void (*observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data), void *user_data); -/// The SPIR-V of a shader module (VkShaderModule) the application created, or null. Valid while the module lives. const uint32_t *ReShadeVulkanGetShaderModuleCode(void *device, uint64_t shader_module, uint32_t *word_count); #ifdef __cplusplus diff --git a/source/addon.hpp b/source/addon.hpp index 38812ebf8..33ad2c763 100644 --- a/source/addon.hpp +++ b/source/addon.hpp @@ -28,7 +28,6 @@ namespace reshade std::string author; std::string website_url; std::string issues_url; - /// Human-readable reason why this add-on could not be loaded. std::string error; union version { diff --git a/source/addon_manager.cpp b/source/addon_manager.cpp index 3dafcdab1..b9f30538a 100644 --- a/source/addon_manager.cpp +++ b/source/addon_manager.cpp @@ -47,7 +47,6 @@ namespace { void *get_module_handle(const void *address) { - // 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; } @@ -213,7 +212,6 @@ static std::atomic_ulong s_reference_count_linux = 0; std::filesystem::path reshade::get_default_addon_search_path() { #if defined(__linux__) - // Keep add-ons next to the shared shader installation, rather than per-application configuration. if (const std::filesystem::path root = utils::xdg_path("XDG_DATA_HOME", ".local/share"); !root.empty()) return root / "reshade"; #endif @@ -552,7 +550,6 @@ reshade::addon_info *reshade::find_addon(const void *address) if (address == nullptr) return nullptr; - // Loader handles are opaque on Linux (and are not necessarily code addresses). for (auto it = addon_loaded_info.rbegin(); it != addon_loaded_info.rend(); ++it) if (it->handle == address) return &(*it); diff --git a/source/addon_manager.hpp b/source/addon_manager.hpp index 8fcaa6513..c19f11b7e 100644 --- a/source/addon_manager.hpp +++ b/source/addon_manager.hpp @@ -36,9 +36,6 @@ namespace reshade /// extern thread_local const addon_info *addon_current; - /// - /// Gets the default directory in which external add-ons are discovered. - /// std::filesystem::path get_default_addon_search_path(); /// diff --git a/source/dll_log.cpp b/source/dll_log.cpp index a6d6aaf2b..12abda594 100644 --- a/source/dll_log.cpp +++ b/source/dll_log.cpp @@ -153,7 +153,6 @@ void reshade::log::message(level level, const char *format, ...) line_string += '\n'; // Terminate line with line feed - // Replace all LF with CRLF on Windows #ifdef _WIN32 for (size_t offset = 0; (offset = line_string.find('\n', offset)) != std::string::npos; offset += 2) line_string.replace(offset, 1, "\r\n", 2); diff --git a/source/effect_preprocessor.cpp b/source/effect_preprocessor.cpp index 822bb2ae0..659e323c6 100644 --- a/source/effect_preprocessor.cpp +++ b/source/effect_preprocessor.cpp @@ -22,7 +22,6 @@ namespace std::filesystem::path path_from_effect_literal(std::string literal) { #if defined(__linux__) - // Effect files commonly use Windows-style separators in include and exists directives. std::replace(literal.begin(), literal.end(), '\\', '/'); #endif return std::filesystem::u8path(literal); diff --git a/source/ini_file.cpp b/source/ini_file.cpp index b7e30fa14..b89a31034 100644 --- a/source/ini_file.cpp +++ b/source/ini_file.cpp @@ -29,7 +29,6 @@ bool reshade::ini_file::load() { std::error_code ec; const std::filesystem::file_time_type modified_at = std::filesystem::last_write_time(_path, ec); - // A default timestamp means the file has not been loaded yet if (!ec && _modified_at != std::filesystem::file_time_type {} && _modified_at >= modified_at) return true; // Skip loading if there was no modification to the file since it was last loaded diff --git a/source/input.hpp b/source/input.hpp index 1256fa333..8ba29016a 100644 --- a/source/input.hpp +++ b/source/input.hpp @@ -140,12 +140,9 @@ namespace reshade void release_primary_handler(); #if defined(__linux__) enum class wsi_kind { wayland, xcb, xlib }; - // Called by the Vulkan WSI hooks, so 'register_window' knows which backend a window needs. - // A window stays registered while any of its Vulkan surfaces exists. static void register_surface(window_handle window, wsi_kind kind, void *display, uintptr_t vulkan_surface, unsigned int width, unsigned int height); static void unregister_surface(window_handle window, uintptr_t vulkan_surface); - // ImGui clipboard callbacks, 'user_data' is the 'reshade::input' instance. static const char *get_clipboard_text(void *user_data); static void set_clipboard_text(void *user_data, const char *text); #endif @@ -200,13 +197,9 @@ namespace reshade bool is_blocking_mouse_cursor_warping() const { return _block_cursor_warping; } #if defined(__linux__) bool is_mouse_position_valid() const; - // Whether the overlay has to draw a cursor, because the application's one is hidden where the pointer is. bool needs_overlay_cursor() const; - // Cursor the compositor shows over the pointer capture areas, as 'ImGuiMouseCursor' (-1 for none). void set_overlay_cursor(int cursor); - // Linux cannot filter the application's events like 'block_mouse_input' does on Windows. Instead the - // backend covers these areas (relative to the window size) with a layer that takes pointer input. struct capture_rect { float x, y, width, height; }; void set_pointer_capture(std::vector regions) { _pointer_capture = std::move(regions); } struct key_transition { unsigned int key; bool down; }; @@ -262,8 +255,6 @@ namespace reshade uint64_t _frame_count = 0; // Keep track of frame count to identify windows with a lot of rendering std::wstring _text_input; #if defined(__linux__) - // Backends deliver events asynchronously to rendering, so a key can go down and up within - // one frame. These '_keys' bits latch such edges until 'next_frame'. static constexpr uint8_t key_pressed_in_frame = 0x10; static constexpr uint8_t key_released_in_frame = 0x20; diff --git a/source/linux/addon_paths.hpp b/source/linux/addon_paths.hpp index 6d9cfe67b..f056515dd 100644 --- a/source/linux/addon_paths.hpp +++ b/source/linux/addon_paths.hpp @@ -8,8 +8,6 @@ namespace reshade::utils { inline std::vector find_addon_files(const std::filesystem::path &user_directory, const std::filesystem::path &installed_directory) { - // Each directory, and before it its "addon" folder: where an add-on lives together with the files - // it needs (libraries it loads, its data), apart from ReShade's own configuration and logs std::vector directories; for (const auto &directory : { user_directory, installed_directory }) if (!directory.empty()) @@ -25,7 +23,6 @@ namespace reshade::utils const auto &path = it->path(); if (path.extension() != ".addon" && path.extension() != ".addon64") continue; - // A user copy takes precedence over an installed copy of the same add-on. if (std::none_of(files.begin(), files.end(), [&path](const auto &file) { return file.filename() == path.filename(); })) files.push_back(path); } diff --git a/source/linux/clipboard.hpp b/source/linux/clipboard.hpp index ebd9f8165..3f85852ff 100644 --- a/source/linux/clipboard.hpp +++ b/source/linux/clipboard.hpp @@ -8,8 +8,6 @@ namespace reshade::utils { - // Runs only on the clipboard worker thread. Keep SIGPIPE blocked until that - // thread exits, so a recipient closing its pipe cannot terminate the host. inline void write_clipboard_text(int fd, const std::string &text) { sigset_t signals; diff --git a/source/linux/game_identity.cpp b/source/linux/game_identity.cpp index 87fe124ac..bb16fd758 100644 --- a/source/linux/game_identity.cpp +++ b/source/linux/game_identity.cpp @@ -8,7 +8,6 @@ namespace { constexpr size_t max_name_length = 96; - // Wine guest paths use backslashes, which are not separators on Linux std::string guest_filename(std::string path) { const size_t separator = path.find_last_of("/\\"); @@ -50,7 +49,6 @@ namespace return path.filename().u8string(); } - // Keep UTF-8 characters, but replace ASCII punctuation std::string sanitize(std::string name) { for (char &value : name) @@ -78,7 +76,6 @@ namespace return base + '-' + safe_suffix; } - // Steam only passes the install directory to games run through a compatibility tool, so extract it from the executable path otherwise std::filesystem::path steam_install_directory(const std::filesystem::path &executable_path) { const auto end = executable_path.end(); @@ -91,7 +88,6 @@ namespace const auto common = std::next(it); if (common == end || *common != "common") continue; - // The game directory must contain the executable rather than be it if (const auto game = std::next(common); game != end && std::next(game) != end) return result / *common / *game; } @@ -104,7 +100,6 @@ namespace return name == "wine" || name == "wine64" || name == "wine-preloader" || name == "wine64-preloader"; } - // Only match at the start of a word, so that e.g. "sweden" is not detected as Eden bool contains_word_prefix(const std::string &value, std::string_view marker) { const std::string lower = to_lower(value); @@ -144,7 +139,6 @@ namespace if (!is_game_content_path(*it)) continue; std::string name = without_extension(guest_filename(*it)); - // PS3 games boot "PS3_GAME/USRDIR/EBOOT.BIN", so name them after the folder that contains the game if (to_lower(name) == "eboot") { std::filesystem::path parent = std::filesystem::u8path(*it).parent_path(); @@ -189,7 +183,6 @@ reshade::process::game_identity reshade::process::resolve_game_identity(const ga return result; } - // The Wine loader lives in the Proton installation, which does not name the game const std::filesystem::path steam_install_path = inputs.steam_install_path.empty() && !result.wine_host ? steam_install_directory(inputs.executable_path) : inputs.steam_install_path; if (!inputs.steam_app_id.empty() && !steam_install_path.empty()) diff --git a/source/linux/input_backend.cpp b/source/linux/input_backend.cpp index 9d120be65..a03be4067 100644 --- a/source/linux/input_backend.cpp +++ b/source/linux/input_backend.cpp @@ -5,7 +5,6 @@ auto reshade::input_backend::to_pixel_rects(const std::vector ®ions, unsigned int width, unsigned int height) -> std::vector { - // Round the edges rather than the size, so adjacent regions stay adjacent. const auto edge = [](float position, unsigned int extent) { return static_cast(std::lround(std::clamp(static_cast(position), 0.0, 1.0) * extent)); }; diff --git a/source/linux/input_backend.hpp b/source/linux/input_backend.hpp index 4fc645b45..95c08781e 100644 --- a/source/linux/input_backend.hpp +++ b/source/linux/input_backend.hpp @@ -7,11 +7,8 @@ namespace reshade { - // Grants the unit tests access to backend internals. struct input_test_access; - // Base of the Wayland and X11 input backends. Holds the state they share and is the only - // writer of the 'reshade::input' state they produce. class input_backend { public: @@ -21,18 +18,13 @@ namespace reshade input_backend &operator=(const input_backend &) = delete; virtual bool initialize() = 0; - // Consumes the events received since the previous frame. virtual void next_frame() = 0; virtual const char *name() const = 0; - // Over a capture layer the host's cursor is hidden, so the overlay draws one unless the compositor can draw it. - // Elsewhere the overlay draws a cursor only while the host shows none. bool needs_overlay_cursor() const { return (is_pointer_in_capture() && !has_cursor_shapes()) || is_host_cursor_hidden(); } - // Cursor the compositor shows over a capture layer, as 'ImGuiMouseCursor' (-1 for none). virtual void set_overlay_cursor(int) {} virtual std::string clipboard_text() { return {}; } virtual void set_clipboard_text(const char *) {} - // Whether the overlay currently wants the pointer (see 'input::block_mouse_cursor_warping'). void set_overlay_active(bool active); void set_extent(unsigned int width, unsigned int height); unsigned int width() const { return _width; } @@ -40,14 +32,12 @@ namespace reshade bool keyboard_focused() const { return _keyboard_focused; } bool pointer_focused() const { return _pointer_focused; } - // A capture region in pixels of the layer that implements it, clipped to that layer. struct pixel_rect { int32_t x, y, width, height; bool operator==(const pixel_rect &other) const { return x == other.x && y == other.y && width == other.width && height == other.height; } }; - // Converts the normalized capture regions, dropping those that are empty after clipping. static std::vector to_pixel_rects(const std::vector ®ions, unsigned int width, unsigned int height); protected: @@ -55,25 +45,19 @@ namespace reshade virtual void on_overlay_active_changed() {} virtual bool is_host_cursor_hidden() const = 0; - // Whether the compositor can draw the cursor over a capture layer (see 'set_overlay_cursor'). virtual bool has_cursor_shapes() const { return false; } - // Whether the layer implementing 'pointer_capture' is shown, which it cannot always be. virtual bool is_capture_layer_shown() const = 0; bool overlay_active() const { return _overlay_active; } const std::vector &pointer_capture() const { return _owner._pointer_capture; } - // Whether the pointer is over a shown capture layer, which hides the host's cursor. bool is_pointer_in_capture() const; bool is_key_down(unsigned int key) const { return _owner.is_key_down(key); } - // Updates 'key' and keeps the side-agnostic Ctrl/Shift/Alt keys in sync with their sides. void set_key(unsigned int key, bool down); void set_modifiers(bool ctrl, bool shift, bool alt); void add_text(uint32_t utf32); - // Adds the text of a key press, applying dead keys and compose sequences. void add_key_text(xkb_keysym_t keysym, uint32_t utf32) { for (const char32_t c : _composer.feed(keysym, utf32)) add_text(c); } void add_wheel_delta(int delta); void set_mouse_position(unsigned int x, unsigned int y); - // Releases every held key (or mouse button) and drops its pending transitions, e.g. on focus loss. void release_keyboard(); void release_pointer(); diff --git a/source/linux/input_linux.cpp b/source/linux/input_linux.cpp index 65d7f6381..4336b914e 100644 --- a/source/linux/input_linux.cpp +++ b/source/linux/input_linux.cpp @@ -1,5 +1,3 @@ -// Linux implementation of 'reshade::input': picks the Wayland or X11 backend from the WSI kind the -// window was presented with, and forwards to it. #include "input.hpp" #include "dll_log.hpp" #include "key_translation.hpp" @@ -25,7 +23,6 @@ bool reshade::input::is_keyboard_layout_german() std::shared_ptr reshade::input::register_window(window_handle window) { - // Must be called with 's_windows_mutex' held. const auto existing_input = [](const registered_window ®istered) { if (registered.input_instance != nullptr) registered.input_instance->_backend->set_extent(registered.width, registered.height); @@ -43,7 +40,6 @@ std::shared_ptr reshade::input::register_window(window_handle wi info = it->second; } - // Initialization may block on round trips, so it runs without holding the registry lock. auto result = std::make_shared(window); std::unique_ptr backend = create_backend(*result, window, info); backend->set_extent(info.width, info.height); @@ -58,7 +54,6 @@ std::shared_ptr reshade::input::register_window(window_handle wi const auto it = s_windows.find(window); if (it == s_windows.end() || it->second.kind != info.kind || it->second.display != info.display) return nullptr; - // Another runtime may have registered the same window meanwhile. if (std::shared_ptr existing = existing_input(it->second)) return existing; @@ -93,7 +88,6 @@ void reshade::input::unregister_surface(window_handle window, uintptr_t vulkan_s const char *reshade::input::get_clipboard_text(void *user_data) { - // ImGui reads the returned string before calling again, from the single GUI thread. static std::string buffer; const auto *const self = static_cast(user_data); buffer = self != nullptr && self->_backend != nullptr ? self->_backend->clipboard_text() : std::string(); diff --git a/source/linux/key_translation.cpp b/source/linux/key_translation.cpp index 58a51b95e..59d5d5ba0 100644 --- a/source/linux/key_translation.cpp +++ b/source/linux/key_translation.cpp @@ -210,7 +210,6 @@ std::u32string reshade::text_composer::feed(xkb_keysym_t keysym, uint32_t utf32) break; case XKB_COMPOSE_COMPOSED: { - // Not every rule names a keysym for its result, but all of them have a string char utf8[64]; const int length = xkb_compose_state_get_utf8(_state, utf8, sizeof(utf8)); if (length > 0 && static_cast(length) < sizeof(utf8)) diff --git a/source/linux/key_translation.hpp b/source/linux/key_translation.hpp index cdf54fd54..98e0e16e6 100644 --- a/source/linux/key_translation.hpp +++ b/source/linux/key_translation.hpp @@ -8,18 +8,14 @@ namespace reshade { - // Linux has no 'GetKeyboardLayout', so backends cache whether the active XKB layout is German - // (only used to label the Home key "Pos1"). extern std::atomic s_keyboard_layout_german; - // Translations to ReShade (Windows) virtual-key codes, 0 for keys ReShade does not track. unsigned int virtual_key_from_keysym(xkb_keysym_t keysym); unsigned int virtual_key_from_evdev_button(uint32_t button); unsigned int virtual_key_from_x11_button(uint32_t button); void update_keyboard_layout_german(xkb_keymap *keymap, xkb_layout_index_t group); - // Applies dead keys and compose sequences to typed text. Without a compose table, text passes through unchanged. class text_composer { public: @@ -28,13 +24,9 @@ namespace reshade text_composer(const text_composer &) = delete; text_composer &operator=(const text_composer &) = delete; - // Loads the compose table of the user's locale, like GTK and Qt do. void load_locale_table(); - // Takes ownership of 'table'. void set_table(xkb_compose_table *table); - // Returns the text typed by a key press with 'keysym', which types 'utf32' on its own: nothing while a - // sequence is pending or when a key cancels it, and the composed text when it completes. std::u32string feed(xkb_keysym_t keysym, uint32_t utf32); void reset(); diff --git a/source/linux/paths.hpp b/source/linux/paths.hpp index 2db51abe5..97db98193 100644 --- a/source/linux/paths.hpp +++ b/source/linux/paths.hpp @@ -6,7 +6,6 @@ namespace reshade::utils { - // XDG directory overrides and HOME must be absolute paths. inline std::filesystem::path xdg_path(const char *variable, const char *fallback) { if (const char *value = std::getenv(variable); value != nullptr && value[0] != '\0') @@ -20,8 +19,6 @@ namespace reshade::utils return {}; } - // Windows file systems are case-insensitive, so rename differently cased variants (e.g. "reshade.ini") to the expected name. - // Returns whether the file exists afterwards. inline bool normalize_file_name_case(const std::filesystem::path &path) { const auto lower = [](std::string value) { @@ -47,14 +44,12 @@ namespace reshade::utils return std::filesystem::is_regular_file(path, ec); } - // Moves the staged file into place unless the destination already exists, so concurrent writers never replace each other's file. - // Returns whether the destination exists afterwards. inline bool publish_file(const std::filesystem::path &staged, const std::filesystem::path &destination, std::error_code &ec) { std::filesystem::create_hard_link(staged, destination, ec); if (ec == std::errc::file_exists) ec.clear(); - else if (ec) // File system may not support hard links + else if (ec) std::filesystem::rename(staged, destination, ec); std::error_code remove_ec; diff --git a/source/linux/platform_utils.cpp b/source/linux/platform_utils.cpp index e585a0b39..01e963db5 100644 --- a/source/linux/platform_utils.cpp +++ b/source/linux/platform_utils.cpp @@ -13,8 +13,6 @@ extern char **environ; namespace { - // Starts 'argv' (nullptr-terminated, resolved through $PATH) without waiting for it. 'hide_window' - // redirects its stdio to /dev/null. A detached thread reaps the child so it does not stay a zombie. bool spawn_detached(const std::vector &argv, const std::filesystem::path &working_directory, bool hide_window) { posix_spawn_file_actions_t file_actions; @@ -49,7 +47,6 @@ namespace } catch (...) { - // The child was started, it just stays a zombie until this process exits. } return true; } @@ -88,7 +85,6 @@ std::filesystem::path reshade::utils::find_system_font(const char *family) return result; } -// Opens the directory containing 'path'. Selecting the file itself would need the FileManager1 D-Bus interface. bool reshade::utils::open_explorer(const std::filesystem::path &path) { std::error_code ec; @@ -99,7 +95,6 @@ bool reshade::utils::open_explorer(const std::filesystem::path &path) return false; } -// 'command_line' is the user's own post-save command setting, so it runs through the shell like on Windows. bool reshade::utils::execute_command(const std::string &command_line, const std::filesystem::path &working_directory, bool hide_window) { if (command_line.empty()) @@ -110,7 +105,6 @@ bool reshade::utils::execute_command(const std::string &command_line, const std: return false; } -// Linux has no system API like 'PlaySound', so use the player of whichever sound server is available. void reshade::utils::play_sound_async(const std::filesystem::path &path) { for (const char *const player : { "pw-play", "paplay", "aplay" }) diff --git a/source/linux/process_environment.cpp b/source/linux/process_environment.cpp index ef2485ac2..a205aa99a 100644 --- a/source/linux/process_environment.cpp +++ b/source/linux/process_environment.cpp @@ -24,14 +24,12 @@ namespace std::filesystem::path s_config_path; std::filesystem::path s_log_path; - // Leave preset path unset, so that it defaults to a preset next to the configuration like on Windows bool write_default_config(const std::filesystem::path &config_path, const std::filesystem::path &data_root) { const std::filesystem::path cache_root = reshade::utils::xdg_path("XDG_CACHE_HOME", ".cache"); if (cache_root.empty()) return false; - // Runtime falls back to a temporary directory if the cache path does not exist std::error_code ec; std::filesystem::create_directories(cache_root / "reshade", ec); @@ -43,7 +41,6 @@ namespace return config.save(); } - // Proton helper processes start alongside the game and share its configuration, so write to a temporary file first and publish that atomically bool create_config(const std::filesystem::path &config_path, const std::filesystem::path &data_root) { std::error_code ec; @@ -67,7 +64,6 @@ namespace std::filesystem::path executable_path(std::error_code &ec) { - // AppImage mounts are ephemeral, so use the original image path as the stable identity. if (const char *const appimage = std::getenv("APPIMAGE"); appimage != nullptr && appimage[0] != '\0') { std::filesystem::path result = canonical_path(std::filesystem::u8path(appimage), ec); @@ -133,7 +129,6 @@ bool reshade::process::initialize(const char *vulkan_application_name) environment("RESHADE_PROFILE") }); s_application_id = identity.directory_name; - // Prefer a configuration next to the executable like on Windows (except for Wine, where the executable is the Wine loader) const std::filesystem::path adjacent_config = g_target_executable_path.parent_path() / "ReShade.ini"; if (!identity.wine_host && reshade::utils::normalize_file_name_case(adjacent_config)) { diff --git a/source/linux/runtime_platform.cpp b/source/linux/runtime_platform.cpp index 836fc9e5c..55ec28f57 100644 --- a/source/linux/runtime_platform.cpp +++ b/source/linux/runtime_platform.cpp @@ -47,17 +47,8 @@ reshade::resources::data_resource reshade::resources::load_data_resource(unsigne namespace { - // Empty means "use the system default", matching how set_current_language("") is used on - // Windows to mean "MUI thread default"; resolved lazily so a later locale environment - // variable change (there is no equivalent of a live system language change notification - // on Linux) does not require re-selecting it explicitly. thread_local std::string s_current_language; - // There is no per-thread MUI preferred-language concept on Linux; approximate the same - // "what language is the user running their desktop in" question from the same environment - // variables every other localized application on the system already reads, in POSIX locale - // precedence order. Normalizes e.g. "de_DE.UTF-8" to "de-DE" to match the res/lang_*.rc2 - // naming convention (see source/linux/generate_localization.py). std::string detect_system_language() { for (const char *const name : { "LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG" }) @@ -68,9 +59,9 @@ namespace std::string language = value; if (const size_t colon = language.find(':'); colon != std::string::npos) - language.resize(colon); // "LANGUAGE" may list multiple ':'-separated fallbacks; only the first is used + language.resize(colon); if (const size_t suffix = language.find_first_of(".@"); suffix != std::string::npos) - language.resize(suffix); // Strip encoding/modifier, e.g. ".UTF-8" or "@euro" + language.resize(suffix); std::replace(language.begin(), language.end(), '_', '-'); return language; } @@ -84,9 +75,6 @@ std::string reshade::resources::load_string(unsigned short id) const std::string language = get_current_language(); if (const char *const text = find_localized_string_linux(language, id)) return text; - // Fall back to English when the current language exists but is missing this particular - // string (translations lag behind as new strings are added), mirroring the secondary - // MUI language Windows' set_current_language() implicitly appends. if (language != "en-US") if (const char *const text = find_localized_string_linux("en-US", id)) return text; diff --git a/source/linux/wayland_clipboard.cpp b/source/linux/wayland_clipboard.cpp index 2e4e7dea8..e0f5c2e92 100644 --- a/source/linux/wayland_clipboard.cpp +++ b/source/linux/wayland_clipboard.cpp @@ -11,7 +11,6 @@ namespace { constexpr size_t max_text_size = 16 * 1024 * 1024; - // A peer that never reads its pipe would otherwise accumulate send threads indefinitely. constexpr int max_sends_in_flight = 4; constexpr auto receive_timeout = std::chrono::milliseconds(100); constexpr const char *text_mime_types[] = { "text/plain;charset=utf-8", "text/plain", "UTF8_STRING" }; @@ -57,15 +56,12 @@ void reshade::wayland_clipboard::offer_info::add_mime_type(const char *type) return; has_text = true; - // Prefer explicit UTF-8 over the legacy text types. if (mime_type.empty() || std::strcmp(type, text_mime_types[0]) == 0) mime_type = type; } reshade::wayland_clipboard *reshade::wayland_clipboard::get(wl_display *display) { - // Never destroyed, see the class description. Freeing it at exit could also touch a connection - // the host already closed. static std::mutex s_mutex; static std::unordered_map s_clipboards; @@ -84,7 +80,6 @@ reshade::wayland_clipboard *reshade::wayland_clipboard::get(wl_display *display) reshade::wayland_clipboard::~wayland_clipboard() { - // Only reached when initialization failed, which is before the data device exists. if (_seat != nullptr) wl_seat_destroy(_seat); if (_manager != nullptr) @@ -155,7 +150,6 @@ std::string reshade::wayland_clipboard::text() { const std::lock_guard lock(_mutex); - // Our own source is served on the same queue, which cannot dispatch while waiting here. if (_source != nullptr) return _source_text; @@ -197,7 +191,6 @@ void reshade::wayland_clipboard::on_data_offer(wl_data_offer *offer) void reshade::wayland_clipboard::on_drag_enter(wl_data_offer *offer) { - // Drag and drop is not supported. if (offer != nullptr && offer != _selection) forget_offer(offer); } @@ -217,8 +210,6 @@ void reshade::wayland_clipboard::on_offer_mime_type(wl_data_offer *offer, const void reshade::wayland_clipboard::on_send(int32_t fd) { - // The receiving client may read slowly or never, so write on a detached thread that only owns - // copies of what it needs and therefore may outlive this object. const std::shared_ptr> in_flight = _sends_in_flight; if (in_flight->fetch_add(1, std::memory_order_relaxed) >= max_sends_in_flight) { diff --git a/source/linux/wayland_clipboard.hpp b/source/linux/wayland_clipboard.hpp index bb0bc8b96..50fbaab70 100644 --- a/source/linux/wayland_clipboard.hpp +++ b/source/linux/wayland_clipboard.hpp @@ -9,12 +9,6 @@ namespace reshade { - // Clipboard integration through the core 'wl_data_device' protocol, one per Wayland connection. - // - // The compositor announces the selection to every data device of a client whenever keyboard - // focus enters it, creating a new 'wl_data_offer' each time. libwayland drops such objects when - // they arrive for a destroyed device, after which the client rejects every later server-created - // object and the whole connection fails. So the data device lives as long as the process does. class wayland_clipboard { public: @@ -26,15 +20,10 @@ namespace reshade void add_mime_type(const char *type); }; - // Returns the clipboard of 'display', creating it on first use, or nullptr if unsupported. static wayland_clipboard *get(wl_display *display); - // Handles the events received since the last call. void dispatch(); - // 'serial' is the latest input serial, which Wayland requires to change the selection. void set_text(const char *text, uint32_t serial); - // ImGui asks for the text synchronously on the render thread, so the wait for the source - // client is bounded and a slow one is treated as having no text. std::string text(); void on_global(wl_registry *registry, uint32_t name, const char *interface, uint32_t version); @@ -59,13 +48,11 @@ namespace reshade wl_data_device_manager *_manager = nullptr; wl_data_device *_device = nullptr; - // Every offer the compositor announced, until it is identified as the selection or discarded. std::unordered_map _offers; wl_data_offer *_selection = nullptr; wl_data_source *_source = nullptr; std::string _source_text; - // Shared with detached send threads, which may outlive a transfer. std::shared_ptr> _sends_in_flight = std::make_shared>(0); }; } diff --git a/source/linux/wayland_input.cpp b/source/linux/wayland_input.cpp index 9d4f8157a..f710c8bf2 100644 --- a/source/linux/wayland_input.cpp +++ b/source/linux/wayland_input.cpp @@ -28,7 +28,6 @@ namespace device = nullptr; } - // Only a release request makes the compositor stop sending events to these objects. void release_pointer_proxy(wl_pointer *pointer) { if (pointer == nullptr) @@ -71,7 +70,6 @@ namespace [](void *data, wl_pointer *, uint32_t serial, uint32_t, uint32_t button, uint32_t state) { self(data)->on_pointer_button(serial, button, state); }, [](void *data, wl_pointer *pointer, uint32_t, uint32_t axis, wl_fixed_t value) { self(data)->on_pointer_axis(axis, wl_fixed_to_double(value)); - // Before version 5 there are no frame events, so every axis event is a frame of its own. if (wl_pointer_get_version(pointer) < WL_POINTER_FRAME_SINCE_VERSION) self(data)->on_pointer_frame(); }, @@ -135,8 +133,6 @@ bool reshade::wayland_input::initialize() if (_queue == nullptr || _xkb_context == nullptr) return false; - // Create the registry directly on the private queue. Moving it there after creation races - // the host's own dispatching of the default queue. auto *const display_wrapper = static_cast(wl_proxy_create_wrapper(_display)); if (display_wrapper == nullptr) return false; @@ -147,8 +143,6 @@ bool reshade::wayland_input::initialize() return false; wl_registry_add_listener(_registry, ®istry_listener, this); - // The first round trip binds the globals, the second receives the seat capabilities (creating - // the devices), the third their initial state such as the keymap and current focus. for (int i = 0; i < 3; ++i) { if (wl_display_roundtrip_queue(_display, _queue) < 0 || _seat == nullptr) @@ -171,8 +165,6 @@ bool reshade::wayland_input::initialize() void reshade::wayland_input::next_frame() { - // The host is the only reader of its connection. Reading the socket here could block behind - // a host thread that prepared a read, so only events already routed to the queue are handled. _pointer.set_extent(width(), height()); const int result = wl_display_dispatch_queue_pending(_display, _queue); if (_clipboard != nullptr) @@ -205,7 +197,6 @@ void reshade::wayland_input::on_global(wl_registry *registry, uint32_t name, con } else if (std::strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0 && _cursor_shape_manager == nullptr) { - // Version 1 is enough for all shapes 'wayland_cursor_shape' maps to _cursor_shape_manager = static_cast(wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1)); _cursor_shape_manager_name = name; set_queue(_cursor_shape_manager, _queue); @@ -307,7 +298,6 @@ void reshade::wayland_input::on_key(uint32_t serial, uint32_t key, uint32_t stat return; _last_serial = serial; - // Wayland sends evdev codes, XKB keycodes are offset by 8. const xkb_keycode_t keycode = key + 8; const bool pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED; const xkb_keysym_t keysym = xkb_state_key_get_one_sym(_xkb_state, keycode); @@ -331,7 +321,6 @@ void reshade::wayland_input::on_modifiers(uint32_t depressed, uint32_t latched, void reshade::wayland_input::on_pointer_enter(uint32_t serial, wl_surface *surface, double x, double y) { release_pointer(); - // The capture layer covers the host surface at the same origin, and replaces the host's cursor. _pointer_on_overlay = surface != nullptr && surface == _overlay.surface(); if (_pointer_on_overlay) { @@ -413,7 +402,6 @@ void reshade::wayland_input::apply_overlay_cursor() if (!_pointer_on_overlay || _pointer_device == nullptr) return; - // Without cursor shapes the overlay draws the cursor itself (see 'needs_overlay_cursor') if (const uint32_t shape = wayland_cursor_shape(_overlay_cursor); shape != 0 && _cursor_shape_device != nullptr) wp_cursor_shape_device_v1_set_shape(_cursor_shape_device, _overlay_enter_serial, shape); else @@ -428,14 +416,12 @@ void reshade::wayland_input::on_overlay_active_changed() void reshade::wayland_input::update_pointer_scale() { - // Wine maps its own coordinates, so they already match the swapchain. if (!_wine_host) _pointer.set_preferred_scale(_overlay.preferred()); } void reshade::wayland_input::update_capture() { - // Size the layer by the lowest plausible scale, so it can never extend beyond the host surface. double scale = _wine_host ? _overlay.preferred() : _pointer.scale(); if (scale < 1.0) scale = 1.0; @@ -480,8 +466,6 @@ void reshade::wayland_input::log_pointer_changes() bool reshade::wayland_input::accepts_focus(wl_surface *focused_surface, const char *device) const { const bool exact_match = focused_surface == _surface; - // Wine presents through a surface of its own, so focus is on a different surface of the same - // connection. Accept it when that connection has no other Vulkan surface it could belong to. const bool wine_match = !exact_match && _wine_host && count_windows_on_display(_display) == 1; log::message(log::level::info, "Wayland %s enter: focused_surface=%p vulkan_surface=%p exact_match=%d%s.", device, focused_surface, _surface, exact_match, wine_match ? " fallback=wine-single-surface" : ""); @@ -550,7 +534,6 @@ void reshade::wayland_input::release_pointer_device() uint32_t reshade::wayland_cursor_shape(int cursor) { - // Indexed by 'ImGuiMouseCursor' constexpr uint32_t shapes[] = { WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, diff --git a/source/linux/wayland_input.hpp b/source/linux/wayland_input.hpp index 9fe37a1b4..90b129a02 100644 --- a/source/linux/wayland_input.hpp +++ b/source/linux/wayland_input.hpp @@ -15,11 +15,8 @@ struct wp_cursor_shape_device_v1; namespace reshade { - // Maps an 'ImGuiMouseCursor' to a shape of version 1 of the cursor shape protocol, or 0 if there is none. uint32_t wayland_cursor_shape(int cursor); - // Input for one Vulkan surface on a Wayland connection owned by the host application. ReShade - // binds its own seat devices on a private event queue of that connection. class wayland_input final : public input_backend { public: @@ -33,7 +30,6 @@ namespace reshade void set_clipboard_text(const char *text) override { if (_clipboard != nullptr) _clipboard->set_text(text, _last_serial); } void set_overlay_cursor(int cursor) override; - // Protocol event handlers, called by the listeners and by unit tests. void on_global(wl_registry *registry, uint32_t name, const char *interface, uint32_t version); void on_global_remove(uint32_t name); void on_seat_capabilities(uint32_t capabilities); @@ -62,8 +58,6 @@ namespace reshade void publish_pointer(); void log_pointer_changes(); bool accepts_focus(wl_surface *focused_surface, const char *device) const; - // Qt can give keyboard focus to its parent surface while the Vulkan subsurface has pointer - // focus, so a foreign keyboard focus counts while the pointer is over this surface. void refresh_keyboard_focus(); void sync_modifiers(); void bind_pointer_objects(); @@ -85,11 +79,9 @@ namespace reshade wp_cursor_shape_manager_v1 *_cursor_shape_manager = nullptr; uint32_t _cursor_shape_manager_name = 0; wp_cursor_shape_device_v1 *_cursor_shape_device = nullptr; - // Serial of the pointer entering the capture layer, which Wayland requires to change the cursor. uint32_t _overlay_enter_serial = 0; bool _pointer_on_overlay = false; int _overlay_cursor = 0; - // Latest key or button serial, which Wayland requires to set the clipboard selection. uint32_t _last_serial = 0; xkb_context *_xkb_context = nullptr; diff --git a/source/linux/wayland_overlay_surface.cpp b/source/linux/wayland_overlay_surface.cpp index dc2289df2..35e6132e4 100644 --- a/source/linux/wayland_overlay_surface.cpp +++ b/source/linux/wayland_overlay_surface.cpp @@ -26,14 +26,12 @@ bool reshade::wayland_overlay_surface::attach(wl_surface *parent) if (_surface != nullptr || parent == nullptr || _compositor == nullptr || _subcompositor == nullptr) return false; - // Never commit the host surface. The child stays unmapped until it gets a buffer. _surface = wl_compositor_create_surface(_compositor); if (_surface == nullptr) return false; _subsurface = wl_subcompositor_get_subsurface(_subcompositor, _surface, parent); if (_subsurface == nullptr) return false; - // Apply own commits immediately, rather than with the next frame the host presents. wl_subsurface_set_desync(_subsurface); if (_fractional_manager != nullptr) @@ -104,7 +102,6 @@ bool reshade::wayland_overlay_surface::set_capture(const std::vector(logical_width), static_cast(logical_height)); wl_region *const input_region = wl_compositor_create_region(_compositor); @@ -140,7 +137,6 @@ bool reshade::wayland_overlay_surface::create_transparent_buffer() const int fd = memfd_create("reshade-overlay", MFD_CLOEXEC); if (fd < 0) return false; - // Zero-filled, so the pixel is fully transparent. if (ftruncate(fd, 4) != 0) { close(fd); diff --git a/source/linux/wayland_overlay_surface.hpp b/source/linux/wayland_overlay_surface.hpp index c458f5506..83e0b421a 100644 --- a/source/linux/wayland_overlay_surface.hpp +++ b/source/linux/wayland_overlay_surface.hpp @@ -12,11 +12,6 @@ struct wp_viewport; namespace reshade { - // An owned child subsurface of the host's Vulkan surface. While unmapped it only learns the - // compositor's preferred fractional scale for the host surface (KWin and Mutter propagate it to - // children). That is the scale the compositor recommends, not necessarily the one the host - // applies to its buffer. While mapped with a transparent buffer it covers the host surface and - // takes pointer input away from it in the capture regions. class wayland_overlay_surface { public: @@ -25,19 +20,13 @@ namespace reshade wayland_overlay_surface(const wayland_overlay_surface &) = delete; wayland_overlay_surface &operator=(const wayland_overlay_surface &) = delete; - // Offer every registry global, proxies inherit the registry's event queue. void bind(wl_registry *registry, uint32_t name, const char *interface); bool attach(wl_surface *parent); - // Destroys every proxy. Must run before the event queue is destroyed. void reset(); wl_surface *surface() const { return _surface; } - // Zero while unknown. double preferred() const { return _preferred / 120.0; } - // Maps the surface with the size of its parent in logical coordinates, taking pointer input - // in 'regions' (in those coordinates), or unmaps it when 'regions' is empty. Returns whether - // anything changed and needs to be flushed. bool set_capture(const std::vector ®ions, unsigned int logical_width, unsigned int logical_height); private: diff --git a/source/linux/wayland_pointer.cpp b/source/linux/wayland_pointer.cpp index 691e2765f..c95bbc92e 100644 --- a/source/linux/wayland_pointer.cpp +++ b/source/linux/wayland_pointer.cpp @@ -56,11 +56,9 @@ void reshade::wayland_pointer::leave() void reshade::wayland_pointer::absolute_motion(double x, double y) { - // Some compositors repeat the lock position, which carries no movement. if (_mode == mode::software_relative && std::abs(x - _lock_anchor[0]) < 0.5 && std::abs(y - _lock_anchor[1]) < 0.5) return; - // A host that follows the preferred scale never reports a point beyond its swapchain. if (!_scale_refuted && _scale > 1.0 && _stable_batches >= stable_batches_required && (x * _scale > _extent[0] + 2.0 || y * _scale > _extent[1] + 2.0)) { @@ -87,7 +85,6 @@ void reshade::wayland_pointer::relative_motion(double dx, double dy, uint64_t ti void reshade::wayland_pointer::end_batch() { - // A pointer pushed against a screen edge also moves relatively without moving absolutely. if (_absolute_in_batch) _lock_evidence = 0; else if (_relative_in_batch && !is_on_edge() && _lock_evidence < lock_batches && _lock_evidence++ == 0) @@ -133,7 +130,6 @@ void reshade::wayland_pointer::axis_discrete(int steps) int reshade::wayland_pointer::end_axis_frame() { - // Discrete and continuous values describe the same wheel event, prefer the discrete one. int delta = 0; if (_has_scroll_steps) delta = -_scroll_steps; @@ -169,14 +165,12 @@ void reshade::wayland_pointer::set_mode(mode new_mode) if (new_mode == mode::software_relative) { - // Start from where the cursor was last seen, locks keep the pointer in place. _virtual[0] = _position[0]; _virtual[1] = _position[1]; std::copy_n(_logical, 2, _lock_anchor); } else if (_mode == mode::software_relative) { - // Back to the host cursor, wherever the host left it. std::copy_n(_absolute, 2, _virtual); publish(_absolute); } diff --git a/source/linux/wayland_pointer.hpp b/source/linux/wayland_pointer.hpp index 0b78cbcf8..10b148156 100644 --- a/source/linux/wayland_pointer.hpp +++ b/source/linux/wayland_pointer.hpp @@ -4,23 +4,12 @@ namespace reshade { - // Protocol-free pointer model for one Wayland surface. Motion is collected per dispatch batch and - // resolved once in 'end_batch'. - // - // ReShade cannot hide or move the host's cursor, so while the overlay is open it either follows - // the host cursor ('host_absolute') or, when the host has locked the pointer (and so hides it), - // draws its own cursor driven by relative motion ('software_relative'). The lock belongs to the - // host's objects and cannot be observed directly. It is inferred from relative motion without - // absolute motion away from a surface edge, sustained over several batches and a minimum time, - // because the protocol does not order the two kinds of motion against each other. class wayland_pointer { public: enum class mode { passive, host_absolute, software_relative }; void set_extent(unsigned int width, unsigned int height); - // The compositor's preferred scale for the surface, 0 while unknown. It maps logical to swapchain - // coordinates unless the host is observed to render at 1:1 instead (see 'absolute_motion'). void set_preferred_scale(double scale); double scale() const { return _scale; } bool scale_refuted() const { return _scale_refuted; } @@ -30,27 +19,19 @@ namespace reshade void enter(double x, double y); void leave(); void absolute_motion(double x, double y); - // 'time' is the relative motion's timestamp in microseconds. void relative_motion(double dx, double dy, uint64_t time); void end_batch(); void axis(double distance); void axis_discrete(int steps); - // Ends a 'wl_pointer.frame' and returns the accumulated wheel delta in notches. int end_axis_frame(); unsigned int x() const { return _position[0]; } unsigned int y() const { return _position[1]; } private: - // Batches with relative but without absolute motion, and the time they have to span in - // microseconds, before the pointer counts as locked. Batches are frames, so their count - // alone would make the threshold depend on the frame rate. static constexpr unsigned int lock_batches = 2; static constexpr uint64_t lock_duration = 25000; - // Batches the extent and scale must stay unchanged for, before pointer coordinates beyond the - // extent refute the preferred scale. A window that grows or moves to a monitor with another - // scale reports coordinates before its new swapchain exists. static constexpr unsigned int stable_batches_required = 30; void apply_scale(double scale); diff --git a/source/linux/window_registry.hpp b/source/linux/window_registry.hpp index baa1977a5..1c29e8ddb 100644 --- a/source/linux/window_registry.hpp +++ b/source/linux/window_registry.hpp @@ -8,7 +8,6 @@ namespace reshade { - // Native windows seen by the Vulkan WSI hooks, keyed by their 'input::window_handle'. struct registered_window { input::wsi_kind kind = input::wsi_kind::wayland; @@ -22,6 +21,5 @@ namespace reshade extern std::mutex s_windows_mutex; extern std::unordered_map s_windows; - // Number of registered windows presented through 'display'. Acquires 's_windows_mutex'. size_t count_windows_on_display(const void *display); } diff --git a/source/reshade_api_object_impl.hpp b/source/reshade_api_object_impl.hpp index 7a626d783..6a9aa4427 100644 --- a/source/reshade_api_object_impl.hpp +++ b/source/reshade_api_object_impl.hpp @@ -51,8 +51,6 @@ namespace reshade::api static guid_t make_guid(const uint8_t guid[16]) { - // Public API GUIDs are byte arrays and do not promise 'guid_t' alignment. - // Copying avoids an unaligned load on Linux while preserving the existing key layout. guid_t result; std::memcpy(&result, guid, sizeof(result)); return result; diff --git a/source/runtime.cpp b/source/runtime.cpp index d3e6ff317..08fb3ce51 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -1788,7 +1788,7 @@ bool reshade::runtime::load_effect(const std::filesystem::path &source_file, con #if defined(_WIN32) codegen.reset(reshadefx::create_codegen_dxbc(shader_model, !_no_debug_info, _performance_mode, _performance_mode ? 3 : 1)); #elif defined(__linux__) - assert(false); // Direct3D effect compilation is not available in the Vulkan-only Linux build. + assert(false); #endif else if (_renderer_id < 0x20000) codegen.reset(reshadefx::create_codegen_glsl(false, !_no_debug_info, _performance_mode, false, true)); diff --git a/source/runtime_gui.cpp b/source/runtime_gui.cpp index 0beb5c751..5617513b4 100644 --- a/source/runtime_gui.cpp +++ b/source/runtime_gui.cpp @@ -29,9 +29,6 @@ extern bool resolve_path(std::filesystem::path &path, std::error_code &ec, const namespace { #if defined(__linux__) - // Dear ImGui's clipboard callbacks moved from 'ImGuiIO' (a plain 'void *user_data') to - // 'ImGuiPlatformIO' (an 'ImGuiContext *') in 1.91.1; adapt to the newer shape here so - // 'reshade::input's clipboard functions can stay framework-agnostic like the rest of it. const char *imgui_get_clipboard_text(ImGuiContext *ctx) { return reshade::input::get_clipboard_text(ctx->PlatformIO.Platform_ClipboardUserData); } void imgui_set_clipboard_text(ImGuiContext *ctx, const char *text) { reshade::input::set_clipboard_text(ctx->PlatformIO.Platform_ClipboardUserData, text); } #endif @@ -134,9 +131,6 @@ void reshade::runtime::init_gui() imgui_io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; #endif #if defined(__linux__) - // Dear ImGui's built-in clipboard default implementation only has native support for - // Windows and macOS; wire up the Wayland data-device based implementation here instead - // (Platform_ClipboardUserData is kept current in draw_gui(), since '_input' is not yet valid here) _imgui_context->PlatformIO.Platform_GetClipboardTextFn = imgui_get_clipboard_text; _imgui_context->PlatformIO.Platform_SetClipboardTextFn = imgui_set_clipboard_text; #endif @@ -251,7 +245,6 @@ void reshade::runtime::build_font_atlas() else if (language.compare(0, 2, "zh") == 0) { - // Simplified Chinese (zh-CN, zh-SG, ...) vs. Traditional Chinese (zh-HK, zh-TW, zh-Hant, ...) const bool traditional = language.find("HK") != std::string::npos || language.find("TW") != std::string::npos || language.find("Hant") != std::string::npos; _default_font_path = reshade::utils::find_system_font(traditional ? "Noto Sans CJK TC" : "Noto Sans CJK SC"); } @@ -927,8 +920,6 @@ void reshade::runtime::draw_gui() unsigned int max_position[2]; _input->max_mouse_position(max_position); #if defined(__linux__) - // The compositor owns the pointer outside the render surface. Do not leave - // a second cursor or an active hover target at its last in-surface position. if (!_input->is_mouse_position_valid()) imgui_io.AddMousePosEvent(-std::numeric_limits::max(), -std::numeric_limits::max()); else @@ -1051,8 +1042,6 @@ void reshade::runtime::draw_gui() }; #if defined(__linux__) - // Submit complete taps in event order. A pressed-or-down snapshot merges - // consecutive short taps into one held key (or mouse button). constexpr unsigned int mouse_keys[] = { input::key_button_left, input::key_button_right, input::key_button_middle, input::key_button_xbutton1, input::key_button_xbutton2 }; for (const auto &event : _input->key_transitions()) { @@ -1064,7 +1053,6 @@ void reshade::runtime::draw_gui() imgui_io.AddMouseButtonEvent(i, event.down); } #endif - // Reconcile polled inputs and focus-loss releases as well. for (const std::pair &mapping : key_mappings) imgui_io.AddKeyEvent(mapping.first, _input->is_key_down(mapping.second)); imgui_io.AddKeyEvent(ImGuiMod_Super, _input->is_key_down(input::key_left_windows) || _input->is_key_down(input::key_right_windows)); @@ -1596,7 +1584,6 @@ void reshade::runtime::draw_gui() _input->block_mouse_cursor_warping(_show_overlay || _block_input_next_frame || block_mouse_input); #if defined(__linux__) - // Block everything, or only what is on top of overlay windows (the Linux equivalent of 'WantCaptureMouse'). std::vector capture; if (block_input && _input_processing_mode == 2) capture.push_back({ 0.0f, 0.0f, 1.0f, 1.0f }); diff --git a/source/vulkan/vulkan.cpp b/source/vulkan/vulkan.cpp index 5a4b411b3..47a21a2be 100644 --- a/source/vulkan/vulkan.cpp +++ b/source/vulkan/vulkan.cpp @@ -427,8 +427,6 @@ enum VkNegotiateLayerStructType constexpr uint32_t loader_layer_interface_version = 2; -// ABI mirror of the Vulkan loader's 'vk_layer.h'. Keep this local to avoid making the -// loader implementation headers a build dependency for the public Vulkan headers. struct VkNegotiateLayerInterface { VkNegotiateLayerStructType sType; @@ -446,8 +444,6 @@ extern "C" __attribute__((visibility("default"))) VkResult VKAPI_CALL vkNegotiat pVersionStruct->loaderLayerInterfaceVersion < loader_layer_interface_version) return VK_ERROR_INITIALIZATION_FAILED; - // Version 2 added 'vkNegotiateLoaderLayerInterfaceVersion'. Do not perform process - // initialization until the loader ABI handshake has completed successfully. pVersionStruct->loaderLayerInterfaceVersion = loader_layer_interface_version; pVersionStruct->pfnGetInstanceProcAddr = vkGetInstanceProcAddr; pVersionStruct->pfnGetDeviceProcAddr = vkGetDeviceProcAddr; diff --git a/source/vulkan/vulkan_hooks_command_list.cpp b/source/vulkan/vulkan_hooks_command_list.cpp index e3833c6ec..9a8b2ecad 100644 --- a/source/vulkan/vulkan_hooks_command_list.cpp +++ b/source/vulkan/vulkan_hooks_command_list.cpp @@ -16,7 +16,6 @@ extern lockfree_linear_map g_vulkan_devices; #if RESHADE_ADDON -// The record of what the application set on a command buffer (see 'application_state') static inline reshade::vulkan::application_state &app_state(reshade::vulkan::device_impl *device_impl, VkCommandBuffer commandBuffer) { return device_impl->get_private_data_for_object(commandBuffer)->app_state; @@ -351,7 +350,6 @@ void VKAPI_CALL vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_ { std::memcpy(&viewport_data[i], &pViewports[i], sizeof(VkViewport)); - // Flip viewport vertically (see 'command_list_impl::bind_viewports') viewport_data[i].y += viewport_data[i].height; viewport_data[i].height = -viewport_data[i].height; } @@ -447,7 +445,6 @@ void VKAPI_CALL vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFl #endif } -// Dynamic state that is only followed for 'ReShadeVulkanRestoreState' void VKAPI_CALL vkCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D *pScissors) { reshade::vulkan::device_impl *const device_impl = g_vulkan_devices.at(dispatch_key_from_handle(commandBuffer)); @@ -491,8 +488,6 @@ void VKAPI_CALL vkCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkB RESHADE_VULKAN_APP_STATE(s.depth_bounds_tested = depthBoundsTestEnable; s.set_mask |= S::depth_bounds_test;) } -// Puts back what the application last set on its command buffer: the graphics pipeline, its descriptor sets, -// its vertex buffers and its dynamic state, after an add-on recorded draws of its own there extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRestoreState(void *command_buffer) { #if RESHADE_ADDON @@ -2019,9 +2014,6 @@ void VKAPI_CALL vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRende cmd_impl->_is_in_render_pass = 3; - // Keep what is needed to end this rendering and begin it again (see ReShadeVulkanInterruptRendering). - // Not when it is suspended or resumed, nor with extension structures other than the one DXVK chains - // to its attachments (their flags, VK_KHR_maintenance10), which would have to be copied too. { bool plain = pRenderingInfo->pNext == nullptr && pRenderingInfo->colorAttachmentCount <= 8 && (pRenderingInfo->flags & (VK_RENDERING_SUSPENDING_BIT | VK_RENDERING_RESUMING_BIT)) == 0; @@ -2077,7 +2069,6 @@ void VKAPI_CALL vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRende RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBeginRendering, device_impl); trampoline(commandBuffer, pRenderingInfo); } -// An add-on ends the application's dynamic rendering to record commands that cannot be inside one extern "C" __attribute__((visibility("default"))) const void *ReShadeVulkanInterruptRendering(void *command_buffer) { #if RESHADE_ADDON @@ -2112,7 +2103,6 @@ extern "C" __attribute__((visibility("default"))) void ReShadeVulkanResumeRender if (cmd_impl == nullptr || !cmd_impl->rendering_interrupted) return; - // The attachments keep what was drawn so far (and are not resolved a second time from a cleared state) for (VkRenderingAttachmentInfo &attachment : cmd_impl->current_rendering_attachments) if (attachment.loadOp != VK_ATTACHMENT_LOAD_OP_NONE) attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; @@ -2187,7 +2177,6 @@ void VKAPI_CALL vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t } #if RESHADE_ADDON >= 2 -// Reports pushed descriptors to add-ons, one event per write (shared by both versions of the call) static void report_push_descriptors(reshade::vulkan::device_impl *device_impl, VkCommandBuffer commandBuffer, reshade::api::shader_stage shader_stages, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet *pDescriptorWrites) { reshade::vulkan::command_list_impl *const cmd_impl = device_impl->get_private_data_for_object(commandBuffer); diff --git a/source/vulkan/vulkan_hooks_device.cpp b/source/vulkan/vulkan_hooks_device.cpp index b42a7651f..622a04b4a 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -31,15 +31,12 @@ lockfree_linear_map g_vulkan_devices; #if defined(__linux__) #include "reshade_vulkan_interop.h" -// Device extensions and queues add-ons asked for (see reshade_vulkan_interop.h), and what each device -// got, for add-ons that drive the device with native Vulkan calls namespace { - // From the Vulkan registry: newer than the bundled headers (generated without VK_NV_optical_flow) constexpr VkQueueFlags queue_video_decode_bit = 0x20, queue_video_encode_bit = 0x40, queue_optical_flow_bit = 0x100; constexpr const char optical_flow_extension_name[] = "VK_NV_optical_flow"; constexpr VkStructureType structure_type_optical_flow_features = static_cast(1000464000); - struct optical_flow_features // VkPhysicalDeviceOpticalFlowFeaturesNV + struct optical_flow_features { VkStructureType sType; void *pNext; @@ -56,14 +53,14 @@ namespace std::vector extension_names; std::vector extensions; bool buffer_device_address; - std::vector queues; // in request order + std::vector queues; std::vector queue_families; }; std::mutex s_interop_mutex; std::vector s_requested_device_extensions; std::vector s_hidden_device_extensions; - std::vector s_requested_queues; // VkQueueFlags, in request order + std::vector s_requested_queues; std::unordered_map s_device_interop; std::vector requested_device_extensions() @@ -77,9 +74,6 @@ namespace return s_requested_queues; } - // The family for a queue with `required` capabilities: the one with room that has the fewest other - // capabilities (graphics counting most), so that the work runs beside the game's. `used` counts the - // queues already asked for per family, and is updated. bool choose_queue_family(const std::vector &families, VkQueueFlags required, std::vector &used, const std::vector &special, uint32_t &out_family, uint32_t &out_index) { uint32_t best = std::numeric_limits::max(), best_score = std::numeric_limits::max(); @@ -106,7 +100,6 @@ extern "C" __attribute__((visibility("default"))) void ReShadeVulkanRequestDevic if (name == nullptr) return; const std::lock_guard lock(s_interop_mutex); - // Add-ons are loaded again for every instance, and ask again: keep each name once if (std::find(s_requested_device_extensions.begin(), s_requested_device_extensions.end(), name) == s_requested_device_extensions.end()) s_requested_device_extensions.emplace_back(name); } @@ -120,10 +113,8 @@ extern "C" __attribute__((visibility("default"))) void ReShadeVulkanHideDeviceEx s_hidden_device_extensions.emplace_back(name); } -// What the application sees of the device's extensions: all but the ones add-ons asked to hide VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) { - // This layer has no device extensions of its own if (pLayerName != nullptr && 0 == std::strcmp(pLayerName, "VK_LAYER_reshade")) { *pPropertyCount = 0; @@ -166,7 +157,6 @@ VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physic extern "C" __attribute__((visibility("default"))) uint32_t ReShadeVulkanRequestQueue(uint32_t queue_flags) { const std::lock_guard lock(s_interop_mutex); - // Asked again on every load of the add-on: the same request keeps its index const auto it = std::find(s_requested_queues.begin(), s_requested_queues.end(), queue_flags); if (it != s_requested_queues.end()) return static_cast(it - s_requested_queues.begin()); @@ -322,7 +312,6 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi bool force_buffer_device_address = false; bool force_optical_flow = false; #if defined(__linux__) - // Outlives the call below: enabled_extensions points into it const std::vector requested_extensions = requested_device_extensions(); #endif for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) @@ -467,8 +456,6 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi #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 @@ -504,12 +491,10 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi #endif #if defined(__linux__) - // Extensions add-ons requested, when supported and not already enabled for (const std::string &name : requested_extensions) { if (name == VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) { - // The feature: core in Vulkan 1.2, the extension before VkPhysicalDeviceBufferDeviceAddressFeatures supported_bda { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES }; VkPhysicalDeviceFeatures2 supported { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &supported_bda }; const auto get_features2 = instance.dispatch_table.GetPhysicalDeviceFeatures2 != nullptr ? instance.dispatch_table.GetPhysicalDeviceFeatures2 : instance.dispatch_table.GetPhysicalDeviceFeatures2KHR; @@ -530,7 +515,6 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi continue; if (name == optical_flow_extension_name) { - // The extension is useless without its feature optical_flow_features supported_flow { structure_type_optical_flow_features }; VkPhysicalDeviceFeatures2 supported { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &supported_flow }; const auto get_features2 = instance.dispatch_table.GetPhysicalDeviceFeatures2 != nullptr ? instance.dispatch_table.GetPhysicalDeviceFeatures2 : instance.dispatch_table.GetPhysicalDeviceFeatures2KHR; @@ -555,11 +539,9 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi create_info.ppEnabledExtensionNames = enabled_extensions.data(); #if defined(__linux__) - // Queues for add-ons' own work, when they asked and ReShade runs on this device. The game's queues - // keep their indices: added queues come after them in their family. std::vector queue_create_infos(pCreateInfo->pQueueCreateInfos, pCreateInfo->pQueueCreateInfos + pCreateInfo->queueCreateInfoCount); std::vector> queue_priorities; - std::vector added_queues; // family and index for now, the VkQueue after creation + std::vector added_queues; std::vector added_queue_indices; if (graphics_queue_family_index != std::numeric_limits::max()) { @@ -655,7 +637,6 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi } else if (ext.timeline_semaphore || instance.api_version >= VK_API_VERSION_1_2) { - // Core in Vulkan 1.2 but still off unless enabled: force it here too, as for the Vulkan 1.2 features structure above append_to_structure_chain(&create_info, &ext.timeline_semaphore_features); ext.timeline_semaphore_features.timelineSemaphore = VK_TRUE; ext.timeline_semaphore = 1; @@ -982,10 +963,8 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi device.dispatch_table.CopyMemoryToImage = device.dispatch_table.CopyMemoryToImageEXT; device.dispatch_table.TransitionImageLayout = device.dispatch_table.TransitionImageLayoutEXT; #endif - // VK_KHR_maintenance6 (not in the glad loader): how DXVK binds and pushes descriptors on a pre-1.4 instance device.dispatch_table.CmdBindDescriptorSets2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdBindDescriptorSets2KHR")); device.dispatch_table.CmdPushDescriptorSet2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdPushDescriptorSet2KHR")); - // VK_KHR_map_memory2 (likewise): how DXVK maps its memory if (device.dispatch_table.MapMemory2 == nullptr) device.dispatch_table.MapMemory2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkMapMemory2KHR")); if (device.dispatch_table.UnmapMemory2 == nullptr) @@ -1001,7 +980,6 @@ VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDevi continue; VkQueue queue = VK_NULL_HANDLE; device.dispatch_table.GetDeviceQueue(device.handle, added_queues[i].family_index, added_queue_indices[i], &queue); - // As for the game's queues below: the loader did not set the dispatch pointer on these if (queue != VK_NULL_HANDLE) *reinterpret_cast(queue) = *reinterpret_cast(device.handle); added_queues[i].queue = queue; @@ -1194,8 +1172,6 @@ VkResult VKAPI_CALL vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const Vk return trampoline(queue, submitCount, pSubmits, fence); } -// Where the application has its memory mapped (the address of the memory's first byte), for add-ons that -// read what it writes to its buffers (ReShadeVulkanGetMappedBuffer) static std::shared_mutex s_mapped_memory_mutex; static std::unordered_map s_mapped_memory; @@ -1763,8 +1739,6 @@ void VKAPI_CALL vkDestroyShaderModule(VkDevice device, VkShaderModule shader trampoline(device, shaderModule, pAllocator); } -// An add-on that needs pipelines of its own built like the application's (the same vertex input, layout and -// dynamic state, another shader) is shown each graphics pipeline with its 'VkGraphicsPipelineCreateInfo' static void (*s_graphics_pipeline_observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data) = nullptr; static void *s_graphics_pipeline_observer_data = nullptr; extern "C" __attribute__((visibility("default"))) void ReShadeVulkanObserveGraphicsPipelines(void (*observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data), void *user_data) @@ -2037,7 +2011,6 @@ VkResult VKAPI_CALL vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache p { reshade::invoke_addon_event( device_impl, reshade::api::pipeline_layout { (uint64_t)create_info.layout }, static_cast(subobjects.size()), subobjects.data(), reshade::api::pipeline { (uint64_t)pPipelines[i] }); - // (with what it was created from, while that is at hand: see ReShadeVulkanObserveGraphicsPipelines) if (s_graphics_pipeline_observer != nullptr) s_graphics_pipeline_observer(device, &create_info, (uint64_t)pPipelines[i], s_graphics_pipeline_observer_data); } diff --git a/source/vulkan/vulkan_hooks_instance.cpp b/source/vulkan/vulkan_hooks_instance.cpp index 17c568c02..6a012565c 100644 --- a/source/vulkan/vulkan_hooks_instance.cpp +++ b/source/vulkan/vulkan_hooks_instance.cpp @@ -23,9 +23,6 @@ lockfree_linear_map g_vulkan_surfaces; lockfree_linear_map g_vulkan_instances; -// Some applications destroy a surface twice on error paths, possibly from two threads at once (e.g. -// PCSX2 when creating a swap chain fails), which drivers do not tolerate. Remember destroyed handles -// until they are handed out again. The mutex also orders updates of 'g_vulkan_surfaces' against it. static std::mutex s_surfaces_mutex; static std::unordered_set s_destroyed_surfaces; @@ -152,7 +149,6 @@ VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, co // 'vkEnumerateInstanceExtensionProperties' is not included in the next 'vkGetInstanceProcAddr' from the call chain, so use global one instead const auto enum_instance_extensions = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"vulkan-1.dll"), "vkEnumerateInstanceExtensionProperties")); #else - // Global commands are not guaranteed to be exposed by the next layer's GIPA. Resolve this one from the loader itself. void *const vulkan_loader = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD); const auto enum_instance_extensions = vulkan_loader != nullptr ? reinterpret_cast(dlsym(vulkan_loader, "vkEnumerateInstanceExtensionProperties")) : nullptr; #endif @@ -343,7 +339,6 @@ void VKAPI_CALL vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surfac vulkan_surface surface_info; { - // Decide under one lock, the repeated call may come from another thread at the same time. const std::lock_guard lock(s_surfaces_mutex); if (g_vulkan_surfaces.erase(surface, surface_info)) { diff --git a/source/vulkan/vulkan_hooks_swapchain.cpp b/source/vulkan/vulkan_hooks_swapchain.cpp index 8afccf4e6..c95807b1e 100644 --- a/source/vulkan/vulkan_hooks_swapchain.cpp +++ b/source/vulkan/vulkan_hooks_swapchain.cpp @@ -44,9 +44,6 @@ static void retire_swapchain(reshade::vulkan::object_data_retired) return; - // Vulkan permits presenting an image acquired from the old swapchain after a replacement - // is created. Keep the object and image metadata alive until vkDestroySwapchainKHR, but - // release the effect runtime so ReShade never renders into a retired swapchain. reshade::reset_effect_runtime(swapchain_impl); #if RESHADE_ADDON reshade::invoke_addon_event(swapchain_impl, false); @@ -76,7 +73,6 @@ VkResult VKAPI_CALL vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreat if (device_impl->_primary_graphics_queue != nullptr) { // Add required usage flags to create info - // 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 @@ -255,9 +251,6 @@ VkResult VKAPI_CALL vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreat if (reshade::invoke_addon_event(reshade::api::device_api::vulkan, desc, hwnd)) { - // A Vulkan application bakes the swap chain format into render passes and - // pipelines. Keep its original images when an add-on changes only the WSI - // output format, rather than exposing the new format to the application. use_proxy_images = desc.back_buffer.texture.format != reshade::vulkan::convert_format(application_create_info.imageFormat) || desc.color_space != reshade::vulkan::convert_color_space(application_create_info.imageColorSpace); @@ -312,14 +305,10 @@ VkResult VKAPI_CALL vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreat if (use_proxy_images) { - // ReShade copies the completed application image into the actual WSI image - // immediately before it renders effects and presents it. create_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; reshade::log::message(reshade::log::level::info, "Using Vulkan proxy swapchain images to preserve the application's render format."); } - // The old handle remains valid for presentation of previously acquired images. Do not - // unregister or reuse its object: both old and replacement handles can coexist. reshade::vulkan::object_data *old_swapchain_impl = nullptr; if (create_info.oldSwapchain != VK_NULL_HANDLE) old_swapchain_impl = device_impl->get_private_data_for_object(create_info.oldSwapchain); @@ -329,8 +318,6 @@ VkResult VKAPI_CALL vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreat const VkResult result = trampoline(device, &create_info, pAllocator, pSwapchain); g_in_dxgi_runtime = false; - // The Vulkan specification retires 'oldSwapchain' whether creation succeeds or fails. - // Retire our matching runtime only after the driver has observed the replacement request. retire_swapchain(old_swapchain_impl); if (result < VK_SUCCESS) { @@ -477,8 +464,6 @@ void VKAPI_CALL vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapch swapchain_impl->_runtime_destroyed = true; } - // Image/private-data cleanup is deliberately delayed until the application's destroy - // call, because an old swapchain remains legal to present after recreation. uint32_t num_images = 0; vk.GetSwapchainImagesKHR(device, swapchain, &num_images, nullptr); temp_mem swapchain_images(num_images); @@ -531,8 +516,6 @@ VkResult VKAPI_CALL vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swap } #if defined(__linux__) -// Lets add-ons check that flushing the immediate command list during the present event waits for the -// back buffer to be complete (see 'command_queue_impl::_present_wait_info'). Returns the behavior's version. extern "C" __attribute__((visibility("default"))) uint32_t ReShadePresentEventFlushWaits() { return 1; @@ -554,7 +537,6 @@ VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPr else queue_impl->_mutex.lock(); - // The present's waits, taken over by the first flush of the immediate command list (see '_present_wait_info') temp_mem wait_stages(present_info.waitSemaphoreCount); std::fill_n(wait_stages.p, present_info.waitSemaphoreCount, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); @@ -598,9 +580,6 @@ VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPr command_list->copy_texture_region(source, 0, nullptr, srgb_source, 0, nullptr, reshade::api::filter_mode::min_mag_mip_point); command_list->barrier(destination, reshade::api::resource_usage::present, reshade::api::resource_usage::copy_dest); - // A non-null destination box forces a blit even when the formats have the - // same byte size (e.g. BGRA8 to RGB10A2). This applies the sRGB decode - // before the final HDR format conversion rather than copying raw bits. const reshade::api::subresource_box destination_box = { 0, 0, 0, swapchain_impl->_create_info.imageExtent.width, swapchain_impl->_create_info.imageExtent.height, 1 }; command_list->barrier(srgb_source, reshade::api::resource_usage::copy_dest, reshade::api::resource_usage::copy_source); command_list->copy_texture_region(srgb_source, 0, nullptr, destination, 0, &destination_box, reshade::api::filter_mode::min_mag_mip_linear); diff --git a/source/vulkan/vulkan_impl_command_list.hpp b/source/vulkan/vulkan_impl_command_list.hpp index d57455d48..7bb212336 100644 --- a/source/vulkan/vulkan_impl_command_list.hpp +++ b/source/vulkan/vulkan_impl_command_list.hpp @@ -73,8 +73,6 @@ namespace reshade::vulkan void end_debug_event() final; void insert_debug_marker(const char *label, const float color[4]) final; - // The application sets viewports together with their count (extended dynamic state), so calls made - // on its behalf have to as well bool _viewports_with_count = false; protected: @@ -84,8 +82,6 @@ namespace reshade::vulkan uint8_t _is_in_render_pass = 0; }; - // What the application last set on a command buffer, of the state an add-on may change when it records - // its own draws there: so that it can be put back (ReShadeVulkanRestoreState) struct application_state { VkPipeline graphics_pipeline = VK_NULL_HANDLE; @@ -93,7 +89,6 @@ namespace reshade::vulkan struct vertex_buffer { VkBuffer buffer; VkDeviceSize offset, size, stride; } vertex_buffers[16] = {}; uint32_t vertex_buffer_mask = 0; bool vertex_buffer_strides = false; - // One bit per dynamic state the application has set enum : uint32_t { viewport = 1, scissor = 2, depth_bias = 4, depth_bias_enable = 8, cull_mode = 16, front_face = 32, depth_bounds = 64, depth_bounds_test = 128, depth_test = 256, depth_write = 512, depth_compare = 1024, stencil_test = 2048, stencil_op = 4096, stencil_compare_mask = 8192, stencil_write_mask = 16384, stencil_reference = 32768 }; @@ -110,7 +105,6 @@ namespace reshade::vulkan struct stencil_face { VkStencilOp fail, pass, depth_fail; VkCompareOp compare; uint32_t compare_mask, write_mask, reference; } stencil[2] = {}; }; - // VkRenderingAttachmentFlagsInfoKHR (VK_KHR_maintenance10), which the bundled headers may predate struct rendering_attachment_flags_info { static constexpr VkStructureType structure_type = static_cast(1000630002); @@ -136,8 +130,6 @@ namespace reshade::vulkan VkImageView current_color_attachments[8] = {}; VkImageView current_depth_stencil_attachment = VK_NULL_HANDLE; - // What the application's dynamic rendering was begun with, for ReShadeVulkanInterruptRendering. - // 'pColorAttachments' etc. point at the copies below; 'sType' is zero when it cannot be begun again. VkRenderingInfo current_rendering = {}; VkRenderingAttachmentInfo current_rendering_attachments[8 + 2] = {}; rendering_attachment_flags_info current_rendering_attachment_flags[8 + 2] = {}; diff --git a/source/vulkan/vulkan_impl_command_queue.cpp b/source/vulkan/vulkan_impl_command_queue.cpp index 2191010fb..82b793269 100644 --- a/source/vulkan/vulkan_impl_command_queue.cpp +++ b/source/vulkan/vulkan_impl_command_queue.cpp @@ -84,8 +84,6 @@ void reshade::vulkan::command_queue_impl::flush_immediate_command_list(VkSubmitI { if (_immediate_cmd_list == nullptr) return; - // During the present event the first flush takes over the present's waits: the back buffer is only - // complete once they are signaled. The present then waits on that flush's semaphore instead. if (_present_wait_info != nullptr && wait_semaphore_info != _present_wait_info && _present_wait_info->waitSemaphoreCount != 0) _immediate_cmd_list->flush(_present_wait_info); _immediate_cmd_list->flush(wait_semaphore_info); diff --git a/source/vulkan/vulkan_impl_command_queue.hpp b/source/vulkan/vulkan_impl_command_queue.hpp index 694b55f15..0b3a3fbf2 100644 --- a/source/vulkan/vulkan_impl_command_queue.hpp +++ b/source/vulkan/vulkan_impl_command_queue.hpp @@ -40,9 +40,6 @@ namespace reshade::vulkan mutable std::recursive_mutex _mutex; - // Set by 'vkQueuePresentKHR' while the present event runs: the semaphores the present waits on. - // Any flush of the immediate command list during the event waits on them first, so add-ons can - // submit work that reads the back buffer and wait for it before present. VkSubmitInfo *_present_wait_info = nullptr; protected: diff --git a/source/vulkan/vulkan_impl_device.cpp b/source/vulkan/vulkan_impl_device.cpp index 0945c30b9..a95f496e6 100644 --- a/source/vulkan/vulkan_impl_device.cpp +++ b/source/vulkan/vulkan_impl_device.cpp @@ -759,7 +759,6 @@ bool reshade::vulkan::device_impl::import_host_buffer(const api::resource_desc & 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; @@ -785,7 +784,7 @@ bool reshade::vulkan::device_impl::import_host_buffer(const api::resource_desc & 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 + data.create_info.pNext = nullptr; register_object(object, std::move(data)); diff --git a/source/vulkan/vulkan_impl_swapchain.hpp b/source/vulkan/vulkan_impl_swapchain.hpp index 42caef772..8c868aecb 100644 --- a/source/vulkan/vulkan_impl_swapchain.hpp +++ b/source/vulkan/vulkan_impl_swapchain.hpp @@ -38,9 +38,6 @@ namespace reshade::vulkan bool _retired = false; bool _runtime_destroyed = false; - // When an add-on requests a presentation format different from the format - // exposed to the application, the application renders into these ordinary - // images while '_orig' remains the WSI swap chain used by ReShade. std::vector _proxy_images; std::vector _proxy_srgb_images; std::vector _proxy_srgb_images_initialized; diff --git a/tests/linux_x11_grab.cpp b/tests/linux_x11_grab.cpp index 87ff0a570..66c8388db 100644 --- a/tests/linux_x11_grab.cpp +++ b/tests/linux_x11_grab.cpp @@ -1,5 +1,3 @@ -// Run only against an isolated X server without a window manager: this test grabs its pointer and -// injects input. For example a rootful 'Xwayland :77' started inside a headless 'kwin_wayland --virtual'. #include "dll_log.hpp" #include "linux/x11_input.hpp" #include @@ -22,7 +20,6 @@ struct reshade::input_test_access }; using test_access = reshade::input_test_access; -// Waits until the backend observed the cursor change the host just made. static void settle(reshade::x11_input &backend, xcb_connection_t *host) { xcb_flush(host); @@ -62,7 +59,6 @@ int main() backend.set_overlay_active(true); assert(!test_access::uses_relative_motion(backend) && !backend.needs_overlay_cursor()); - // A host hiding its cursor gets the overlay cursor, driven by raw motion. const xcb_pixmap_t blank = xcb_generate_id(host); xcb_create_pixmap(host, 1, blank, window, 1, 1); const xcb_cursor_t invisible = xcb_generate_id(host); @@ -99,8 +95,6 @@ int main() assert(owner.is_key_pressed(reshade::input::key_home) && owner.is_key_released(reshade::input::key_home)); assert(owner.mouse_position_x() > start_x); std::cout << "Home tap and cursor motion during host grabs: PASS" << std::endl; - // A windowed overlay must follow the server pointer, not retain a separate - // raw-delta position when the host pointer moves towards the menu bar. const uint32_t default_cursor = XCB_CURSOR_NONE; xcb_change_window_attributes(host, window, XCB_CW_CURSOR, &default_cursor); settle(backend, host); @@ -120,7 +114,6 @@ int main() } std::cout << "Windowed pointer alignment at all four corners: PASS" << std::endl; - // While the overlay blocks keyboard input the keyboard is grabbed, so the host cannot grab it. const auto host_grab_status = [&]() { auto *const reply = xcb_grab_keyboard_reply(host, xcb_grab_keyboard(host, false, window, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC), nullptr); assert(reply); @@ -137,7 +130,6 @@ int main() assert(host_grab_status() == XCB_GRAB_STATUS_SUCCESS); std::cout << "Keyboard grab while blocking keyboard input: PASS" << std::endl; } - // Exercise the public validity signal consumed by the ImGui cursor/hover path. const uint32_t size[] = {400, 300}; xcb_configure_window(host, window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, size); xcb_warp_pointer(host, XCB_WINDOW_NONE, window, 0, 0, 0, 0, 100, 100); From 4b7e5eee388ae1cacd914c723c20736533777a82 Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Fri, 9 Oct 2026 20:19:16 +0200 Subject: [PATCH 10/11] Vulkan: a swapchain's images are asked for once, at its creation; an object's private data is a short list, not a hash map Below the Steam overlay's layer, every vkGetSwapchainImagesKHR opens a handle to the GPU device that is never closed, and the driver's answer slows with each one; add-ons asking for the back buffer every present paid for it. The private data of a command buffer is looked up by add-ons on every draw. --- source/reshade_api_object_impl.hpp | 36 ++++++++++++++++--------- source/vulkan/vulkan_impl_swapchain.cpp | 15 +++++++---- source/vulkan/vulkan_impl_swapchain.hpp | 1 + 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/source/reshade_api_object_impl.hpp b/source/reshade_api_object_impl.hpp index 6a9aa4427..59e7b2620 100644 --- a/source/reshade_api_object_impl.hpp +++ b/source/reshade_api_object_impl.hpp @@ -8,7 +8,9 @@ #include "reshade_api_device.hpp" #include #include +#include #include +#include namespace reshade::api { @@ -64,24 +66,32 @@ namespace reshade::api { assert(data != nullptr); - if (_private_data.empty()) // Early-out to avoid crash when this is called after the object was destroyed + const guid_t key = make_guid(guid); + for (const auto &entry : _private_data) { - *data = 0; - return; + if (typename guid_t::equal()(entry.first, key)) + { + *data = entry.second; + return; + } } - - if (const auto it = _private_data.find(make_guid(guid)); - it != _private_data.end()) - *data = it->second; - else - *data = 0; + *data = 0; } void set_private_data(const uint8_t guid[16], const uint64_t data) final { + const guid_t key = make_guid(guid); + const auto it = std::find_if(_private_data.begin(), _private_data.end(), [&key](const std::pair &entry) { return typename guid_t::equal()(entry.first, key); }); if (data != 0) - _private_data[make_guid(guid)] = data; - else - _private_data.erase(make_guid(guid)); + { + if (it != _private_data.end()) + it->second = data; + else + _private_data.emplace_back(key, data); + } + else if (it != _private_data.end()) + { + _private_data.erase(it); + } } uint64_t get_native() const final { return (uint64_t)_orig; } @@ -98,7 +108,7 @@ namespace reshade::api } private: - std::unordered_map _private_data; + std::vector> _private_data; }; } diff --git a/source/vulkan/vulkan_impl_swapchain.cpp b/source/vulkan/vulkan_impl_swapchain.cpp index 610be90c8..d94073f59 100644 --- a/source/vulkan/vulkan_impl_swapchain.cpp +++ b/source/vulkan/vulkan_impl_swapchain.cpp @@ -18,6 +18,14 @@ reshade::vulkan::swapchain_impl::swapchain_impl(device_impl *device, VkSwapchain _create_info.pNext = nullptr; assert(create_info.imageUsage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + + uint32_t num_images = 0; + if (vk.GetSwapchainImagesKHR(_device->_orig, _orig, &num_images, nullptr) == VK_SUCCESS && num_images != 0) + { + _images.resize(num_images); + if (vk.GetSwapchainImagesKHR(_device->_orig, _orig, &num_images, _images.data()) < VK_SUCCESS) + _images.clear(); + } } reshade::api::device *reshade::vulkan::swapchain_impl::get_device() @@ -32,15 +40,12 @@ void *reshade::vulkan::swapchain_impl::get_hwnd() const reshade::api::resource reshade::vulkan::swapchain_impl::get_back_buffer(uint32_t index) { - uint32_t num_images = index + 1; - temp_mem swapchain_images(num_images); - return vk.GetSwapchainImagesKHR(_device->_orig, _orig, &num_images, swapchain_images.p) >= VK_SUCCESS ? api::resource { (uint64_t)swapchain_images[index] } : api::resource {}; + return index < _images.size() ? api::resource { (uint64_t)_images[index] } : api::resource {}; } uint32_t reshade::vulkan::swapchain_impl::get_back_buffer_count() const { - uint32_t num_images = 0; - return vk.GetSwapchainImagesKHR(_device->_orig, _orig, &num_images, nullptr) == VK_SUCCESS ? num_images : 0; + return static_cast(_images.size()); } uint32_t reshade::vulkan::swapchain_impl::get_current_back_buffer_index() const diff --git a/source/vulkan/vulkan_impl_swapchain.hpp b/source/vulkan/vulkan_impl_swapchain.hpp index 8c868aecb..74694ba35 100644 --- a/source/vulkan/vulkan_impl_swapchain.hpp +++ b/source/vulkan/vulkan_impl_swapchain.hpp @@ -38,6 +38,7 @@ namespace reshade::vulkan bool _retired = false; bool _runtime_destroyed = false; + std::vector _images; std::vector _proxy_images; std::vector _proxy_srgb_images; std::vector _proxy_srgb_images_initialized; From 25350eac1471b18338f3725c4777401b18f5ccfe Mon Sep 17 00:00:00 2001 From: Gabriel El Faouzi Date: Fri, 9 Oct 2026 20:52:36 +0200 Subject: [PATCH 11/11] Generic Depth: the depth buffer is copied only while an enabled technique samples it; the per-second reversed-depth log lines are gone With 44 effects loaded and one sharpening technique enabled, a 4K frame went from 1.29 to 0.78 ms in the test game: a disabled effect declaring a depth texture no longer costs a full-size copy every frame. The runtime answers whether any enabled technique binds a texture of a given semantic. --- examples/09-depth/generic_depth_addon.cpp | 34 ++++++----------------- source/runtime.hpp | 1 + source/runtime_api.cpp | 29 +++++++++++++++++++ 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/examples/09-depth/generic_depth_addon.cpp b/examples/09-depth/generic_depth_addon.cpp index e172388b4..9b6cd1348 100644 --- a/examples/09-depth/generic_depth_addon.cpp +++ b/examples/09-depth/generic_depth_addon.cpp @@ -5,6 +5,9 @@ #include #include +#if defined(BUILTIN_ADDON) +#include "runtime.hpp" +#endif #include "generic_depth_detection.hpp" #include #include @@ -237,8 +240,6 @@ struct RESHADE_API_UUID("7c6363c7-f94e-437a-9160-141782c44a98") generic_depth_da bool reversed_depth_detection_timeout_logged = false; bool reversed_depth_detection_applied = false; bool reversed_depth_detection_manual_disable_logged = false; - uint32_t reversed_depth_detection_debug_tick = 0; - uint32_t reversed_depth_detection_debug_skip_tick = 0; #endif }; RESHADE_DEFINE_PRIVATE_DATA_TYPE(generic_depth_data, @@ -1248,27 +1249,9 @@ static bool has_manual_reversed_depth_definition(effect_runtime *runtime) return runtime->get_preprocessor_definition(reversed_depth_definition, value); } -static void log_reversed_depth_diagnostic(const char *reason, const generic_depth_data &data, device_api api, - resource selected_depth_stencil, resource_view selected_shader_resource, const depth_stencil_resource *info, uint64_t frame) -{ - char message[320]; - std::snprintf(message, sizeof(message), - "[DEBUG-depth-auto] %s: enabled=%d api=%d depth_stencil=%p shader_resource=%p frame=%llu disabled=%d applied=%d finished=%d frames=%u consistent=%u candidate=%u clears=0x%x compares=0x%x.", - reason, s_auto_detect_reversed_depth, static_cast(api), - reinterpret_cast(selected_depth_stencil.handle), reinterpret_cast(selected_shader_resource.handle), - static_cast(frame), - data.reversed_depth_detection_disabled, data.reversed_depth_detection_applied, data.reversed_depth_detection.finished, - data.reversed_depth_detection.frames, data.reversed_depth_detection.consistent_frames, data.reversed_depth_detection.candidate, - info != nullptr ? info->last_frame_stats.clear_depth_evidence : 0u, - info != nullptr ? info->last_frame_stats.depth_compare_evidence : 0u); - reshade::log::message(reshade::log::level::info, message); -} - static void observe_reversed_depth(effect_runtime *runtime, generic_depth_data &data, device *device, resource selected_depth_stencil, const depth_stencil_resource *selected_depth_stencil_info, uint64_t frame) { - if ((data.reversed_depth_detection_debug_tick++ % 60) == 0) - log_reversed_depth_diagnostic("tick", data, device->get_api(), selected_depth_stencil, data.selected_shader_resource, selected_depth_stencil_info, frame); if (!s_auto_detect_reversed_depth || device->get_api() != device_api::vulkan || selected_depth_stencil == 0 || selected_depth_stencil_info == nullptr || data.reversed_depth_detection_disabled || data.reversed_depth_detection_applied || @@ -1380,6 +1363,11 @@ static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_l } const resource_view prev_shader_resource = data.selected_shader_resource; +#if defined(BUILTIN_ADDON) + const bool depth_in_use = static_cast(runtime)->is_texture_semantic_in_use("DEPTH"); +#else + const bool depth_in_use = true; +#endif if (selected_depth_stencil != 0) do { @@ -1456,6 +1444,7 @@ static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_l // Copy to backup texture unless already copied during the current frame if (!selected_depth_stencil_info->last_frame_stats.copied_during_frame && + depth_in_use && (selected_depth_stencil_info->desc.usage & (resource_usage::copy_source | resource_usage::resolve_source)) != 0 && (s_preserve_depth_buffers != 2 || !(api == device_api::d3d12 || api == device_api::vulkan))) { @@ -1523,11 +1512,6 @@ static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_l { observe_reversed_depth(runtime, data, device, data.selected_depth_stencil, selected_depth_stencil_info, frame_index); } - else if (s_auto_detect_reversed_depth && !data.reversed_depth_detection_applied && !data.reversed_depth_detection_disabled && - (data.reversed_depth_detection_debug_skip_tick++ % 60) == 0) - { - log_reversed_depth_diagnostic("skipped", data, device->get_api(), selected_depth_stencil, data.selected_shader_resource, selected_depth_stencil_info, frame_index); - } #endif if (prev_shader_resource != data.selected_shader_resource) diff --git a/source/runtime.hpp b/source/runtime.hpp index e6b666227..f4c64d0c8 100644 --- a/source/runtime.hpp +++ b/source/runtime.hpp @@ -62,6 +62,7 @@ namespace reshade bool is_loading() const { return _reload_remaining_effects != std::numeric_limits::max() || !_reload_create_queue.empty(); } void render_effects(api::command_list *cmd_list, api::resource_view rtv, api::resource_view rtv_srgb) final; + bool is_texture_semantic_in_use(const char *semantic) const; void render_technique(api::effect_technique handle, api::command_list *cmd_list, api::resource_view rtv, api::resource_view rtv_srgb) final; /// diff --git a/source/runtime_api.cpp b/source/runtime_api.cpp index 68ee19e69..27bb074bb 100644 --- a/source/runtime_api.cpp +++ b/source/runtime_api.cpp @@ -667,6 +667,35 @@ void reshade::runtime::get_texture_binding(api::effect_texture_variable handle, *out_srv_srgb = { 0 }; } +bool reshade::runtime::is_texture_semantic_in_use(const char *semantic) const +{ + for (const technique &tech : _techniques) + { + if (!tech.enabled || tech.effect_index >= _effects.size()) + continue; + const effect &effect_data = _effects[tech.effect_index]; + if (!effect_data.compiled) + continue; + for (size_t permutation_index = 0; permutation_index < tech.permutations.size() && permutation_index < effect_data.permutations.size(); ++permutation_index) + { + const reshadefx::effect_module &module = effect_data.permutations[permutation_index].module; + for (const technique::pass &pass : tech.permutations[permutation_index].passes) + { + for (const reshadefx::texture_binding &binding : pass.texture_bindings) + { + if (binding.index >= module.samplers.size()) + continue; + const std::string &texture_name = module.samplers[binding.index].texture_name; + for (const reshadefx::texture &texture : module.textures) + if ((texture.unique_name == texture_name || texture.name == texture_name) && texture.semantic == semantic) + return true; + } + } + } + } + return false; +} + void reshade::runtime::update_texture_bindings(const char *semantic, api::resource_view srv, api::resource_view srv_srgb) { if (srv_srgb == 0)