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..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 @@ -45,10 +48,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 +105,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 +117,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 +151,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,18 +235,11 @@ 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; #endif }; RESHADE_DEFINE_PRIVATE_DATA_TYPE(generic_depth_data, @@ -305,9 +288,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 +681,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 +689,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 +735,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 +766,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 +1096,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 +1162,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,44 +1246,19 @@ 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) -{ - 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) { - // 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); 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 || (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 +1281,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 +1288,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, @@ -1428,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 { @@ -1477,7 +1417,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; } @@ -1505,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))) { @@ -1568,21 +1508,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) - { - 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/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 new file mode 100644 index 000000000..bd921f5b6 --- /dev/null +++ b/include/reshade_vulkan_interop.h @@ -0,0 +1,55 @@ + +#pragma once + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void ReShadeVulkanRequestDeviceExtension(const char *name); + +void ReShadeVulkanHideDeviceExtension(const char *name); + +uint32_t ReShadeVulkanRequestQueue(uint32_t queue_flags); + +struct ReShadeVulkanQueue +{ + void *queue; + uint32_t family_index; +}; + +struct ReShadeVulkanDeviceInterop +{ + 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; + uint32_t enabled_extension_count; + const char *const *enabled_extensions; + uint32_t buffer_device_address; + uint32_t queue_count; + const struct ReShadeVulkanQueue *queues; + uint32_t queue_family_count; + const uint32_t *queue_family_indices; +}; + +uint32_t ReShadeVulkanGetDeviceInterop(void *device, struct ReShadeVulkanDeviceInterop *out); + +void *ReShadeVulkanGetMappedBuffer(void *device, uint64_t buffer); + +const void *ReShadeVulkanInterruptRendering(void *command_buffer); +void ReShadeVulkanResumeRendering(void *command_buffer); + +void ReShadeVulkanRestoreState(void *command_buffer); + +void ReShadeVulkanObserveGraphicsPipelines(void (*observer)(void *device, const void *create_info, uint64_t pipeline, void *user_data), void *user_data); + +const uint32_t *ReShadeVulkanGetShaderModuleCode(void *device, uint64_t shader_module, uint32_t *word_count); + +#ifdef __cplusplus +} +#endif 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 f6b873605..f056515dd 100644 --- a/source/linux/addon_paths.hpp +++ b/source/linux/addon_paths.hpp @@ -8,11 +8,14 @@ namespace reshade::utils { inline std::vector find_addon_files(const std::filesystem::path &user_directory, const std::filesystem::path &installed_directory) { - std::vector files; + 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)) @@ -20,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..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 { @@ -51,8 +53,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; @@ -66,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; } @@ -100,7 +108,7 @@ namespace reshade::api } private: - std::unordered_map _private_data; + std::vector> _private_data; }; } 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.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) 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 6cf21c118..47a21a2be 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); @@ -92,10 +99,35 @@ 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, ); 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, ); + 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, ); @@ -344,6 +376,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 @@ -394,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; @@ -413,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.hpp b/source/vulkan/vulkan_hooks.hpp index ee88350e0..55d19a6b0 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); @@ -119,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); @@ -203,8 +209,19 @@ 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 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); 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 8a879b8ef..9a8b2ecad 100644 --- a/source/vulkan/vulkan_hooks_command_list.cpp +++ b/source/vulkan/vulkan_hooks_command_list.cpp @@ -15,6 +15,16 @@ extern lockfree_linear_map g_vulkan_devices; +#if RESHADE_ADDON +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 +201,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 +287,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,12 +308,14 @@ 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()) 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,12 +330,40 @@ 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); + 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()) + 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)); + + 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)); 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()) @@ -347,6 +389,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()) @@ -359,11 +402,161 @@ 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); + RESHADE_VULKAN_APP_STATE(s.stencil_tested = stencilTestEnable; s.set_mask |= S::stencil_test;) + +#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); + 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()) + 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 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;) +} + +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()) @@ -381,6 +574,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()) @@ -399,6 +593,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()) @@ -443,6 +638,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()) @@ -462,6 +658,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()) @@ -481,6 +678,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()) @@ -501,6 +699,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()) @@ -541,6 +740,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()) @@ -1814,6 +2014,42 @@ void VKAPI_CALL vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRende cmd_impl->_is_in_render_pass = 3; + { + 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); @@ -1833,6 +2069,52 @@ void VKAPI_CALL vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRende RESHADE_VULKAN_GET_DEVICE_DISPATCH_PTR(CmdBeginRendering, device_impl); trampoline(commandBuffer, pRenderingInfo); } +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; + + 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)); @@ -1874,6 +2156,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()) @@ -1893,17 +2176,9 @@ 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; - +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 +2186,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 +2251,55 @@ 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); + 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()) + 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 01bfe2111..622a04b4a 100644 --- a/source/vulkan/vulkan_hooks_device.cpp +++ b/source/vulkan/vulkan_hooks_device.cpp @@ -17,6 +17,10 @@ #include "process_environment.hpp" #include // std::strcmp, std::strncmp #include // std::find_if, std::min +#include +#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 +28,170 @@ 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" + +namespace +{ + 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 + { + VkStructureType sType; + void *pNext; + VkBool32 opticalFlow; + }; + + 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::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; + std::unordered_map s_device_interop; + + std::vector requested_device_extensions() + { + 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; + } + + 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) +{ + if (name == nullptr) + return; + const std::lock_guard lock(s_interop_mutex); + 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"))) 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); +} + +VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties) +{ + 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); + 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 < offsetof(ReShadeVulkanDeviceInterop, queue_count)) + 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; + 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 + #if RESHADE_ADDON void create_default_view(reshade::vulkan::device_impl *device_impl, VkImage image) { @@ -141,6 +309,11 @@ 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__) + 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]); @@ -283,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 @@ -318,6 +489,48 @@ 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__) + for (const std::string &name : requested_extensions) + { + if (name == VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) + { + 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 (name == optical_flow_extension_name) + { + 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()); + } +#endif } } @@ -325,6 +538,61 @@ 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__) + std::vector queue_create_infos(pCreateInfo->pQueueCreateInfos, pCreateInfo->pQueueCreateInfos + pCreateInfo->queueCreateInfoCount); + std::vector> queue_priorities; + std::vector added_queues; + 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); @@ -345,6 +613,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 @@ -365,19 +635,23 @@ 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) { 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( 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; } @@ -459,6 +733,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)) @@ -673,9 +963,39 @@ 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 + device.dispatch_table.CmdBindDescriptorSets2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdBindDescriptorSets2KHR")); + device.dispatch_table.CmdPushDescriptorSet2 = reinterpret_cast(get_device_proc_addr(*pDevice, "vkCmdPushDescriptorSet2KHR")); + 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 +#if defined(__linux__) + { + 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); + 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()); + 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 +1099,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); } @@ -845,6 +1172,84 @@ VkResult VKAPI_CALL vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const Vk return trampoline(queue, submitCount, pSubmits, fence); } +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)); @@ -1334,6 +1739,29 @@ void VKAPI_CALL vkDestroyShaderModule(VkDevice device, VkShaderModule shader trampoline(device, shaderModule, pAllocator); } +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)); @@ -1583,6 +2011,8 @@ 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] }); + 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_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 a71f0290f..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); @@ -530,6 +515,13 @@ VkResult VKAPI_CALL vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swap return trampoline(device, swapchain, pSwapchainImageCount, pSwapchainImages); } +#if defined(__linux__) +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 +537,16 @@ VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPr else queue_impl->_mutex.lock(); + 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]); @@ -578,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); @@ -662,16 +661,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_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..7bb212336 100644 --- a/source/vulkan/vulkan_impl_command_list.hpp +++ b/source/vulkan/vulkan_impl_command_list.hpp @@ -73,6 +73,8 @@ namespace reshade::vulkan void end_debug_event() final; void insert_debug_marker(const char *label, const float color[4]) final; + bool _viewports_with_count = false; + protected: device_impl *const _device; @@ -80,6 +82,37 @@ namespace reshade::vulkan uint8_t _is_in_render_pass = 0; }; + 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; + 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] = {}; + }; + + 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 { @@ -96,6 +129,12 @@ namespace reshade::vulkan VkFramebuffer current_framebuffer = VK_NULL_HANDLE; VkImageView current_color_attachments[8] = {}; VkImageView current_depth_stencil_attachment = VK_NULL_HANDLE; + + VkRenderingInfo current_rendering = {}; + 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 }; } diff --git a/source/vulkan/vulkan_impl_command_queue.cpp b/source/vulkan/vulkan_impl_command_queue.cpp index a83855364..82b793269 100644 --- a/source/vulkan/vulkan_impl_command_queue.cpp +++ b/source/vulkan/vulkan_impl_command_queue.cpp @@ -82,8 +82,11 @@ 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; + 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..0b3a3fbf2 100644 --- a/source/vulkan/vulkan_impl_command_queue.hpp +++ b/source/vulkan/vulkan_impl_command_queue.hpp @@ -40,6 +40,8 @@ namespace reshade::vulkan mutable std::recursive_mutex _mutex; + VkSubmitInfo *_present_wait_info = nullptr; + protected: device_impl *const _device; 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.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 42caef772..74694ba35 100644 --- a/source/vulkan/vulkan_impl_swapchain.hpp +++ b/source/vulkan/vulkan_impl_swapchain.hpp @@ -38,9 +38,7 @@ 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 _images; 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);