diff --git a/src/devkit/addon.cpp b/src/devkit/addon.cpp index 8f28d08a..992170c1 100644 --- a/src/devkit/addon.cpp +++ b/src/devkit/addon.cpp @@ -3,7 +3,12 @@ * SPDX-License-Identifier: MIT */ +#include #include +#include +#include +#include +#include #define ImTextureID ImU64 #pragma comment(lib, "dxguid.lib") @@ -14,1314 +19,222 @@ #include #include -#include -#include #include #include #include #include -#include #include -#include #include #include #include #include "../utils/descriptor.hpp" -#include "../utils/format.hpp" -#include "../utils/pipeline.hpp" +#include "../utils/shader.hpp" #include "../utils/shader_compiler.hpp" +#include "../utils/shader_compiler_watcher.hpp" +#include "../utils/shader_dump.hpp" +#include "../utils/swapchain.hpp" +#include "../utils/trace.hpp" #define ICON_FK_REFRESH u8"\uf021" #define ICON_FK_FLOPPY u8"\uf0c7" namespace { -struct CachedPipeline { - reshade::api::pipeline pipeline; - reshade::api::device* device; - reshade::api::pipeline_layout layout; - // Cloned subojects from the oriignal pipeline - reshade::api::pipeline_subobject* subobjects_cache; - uint32_t subobject_count; - bool cloned = false; - bool ready_for_binding = true; - reshade::api::pipeline pipeline_clone; - // Original shaders hash (there should only be one) - std::vector shader_hashes; - // If true, this pipeline is currently being "tested" - bool test = false; - - bool HasPixelShader() const { - for (uint32_t i = 0; i < subobject_count; i++) { - if (subobjects_cache[i].type == reshade::api::pipeline_subobject_type::pixel_shader) return true; - } - return false; - } - bool HasComputeShader() const { - for (uint32_t i = 0; i < subobject_count; i++) { - if (subobjects_cache[i].type == reshade::api::pipeline_subobject_type::compute_shader) return true; - } - return false; - } - bool HasVertexShader() const { - for (uint32_t i = 0; i < subobject_count; i++) { - if (subobjects_cache[i].type == reshade::api::pipeline_subobject_type::vertex_shader) return true; - } - return false; - } -}; - -struct InstructionState { - reshade::addon_event action; - std::vector textures; - std::vector uavs; - std::vector render_targets; - uint32_t shader; -}; -struct CachedShader { - void* data = nullptr; - size_t size = 0; - reshade::api::pipeline_subobject_type type; - int32_t index = -1; - std::string disasm; -}; +std::atomic_bool is_snapshotting = false; -struct CachedCustomShader { - std::vector code; - bool is_hlsl = false; - std::filesystem::path file_path; - std::string compilation_error; +struct ShaderDetails { + uint32_t shader_hash; + std::variant disassembly = std::nullopt; + std::optional program_version = std::nullopt; + std::optional custom_shader = std::nullopt; }; -// For "pipeline_cache_by_pipeline_handle", "pipeline_caches_by_shader_hash", "shader_cache", "pipelines_to_destroy" -std::recursive_mutex s_mutex_generic; -// For "shaders_to_dump", "dumped_shaders", "shader_cache" -std::recursive_mutex s_mutex_dumping; -// For "custom_shaders_cache", "pipelines_to_reload" -std::recursive_mutex s_mutex_loading; - -std::thread thread_auto_dumping; -std::atomic thread_auto_dumping_running = false; -std::thread thread_auto_loading; -std::atomic thread_auto_loading_running = false; - -struct __declspec(uuid("3b70b2b2-52dc-4637-bd45-c1171c4c322e")) DeviceData { - // - std::unordered_map resource_views; - // > - std::unordered_map> resource_views_by_resource; - std::unordered_map resource_names; - std::unordered_set resources; - std::shared_mutex mutex; - reshade::api::device_api device_api; +struct ResourceViewDetails { + reshade::api::resource_view resource_view; + reshade::api::resource_view_desc resource_view_desc; + reshade::api::resource resource; + reshade::api::resource_desc resource_desc; + std::string resource_tag; + std::string resource_view_tag; + bool is_swapchain; }; -std::unordered_set compute_shader_layouts; - -// Pipelines by handle. Multiple pipelines can target the same shader, and even have multiple shaders within themselved -std::unordered_map pipeline_cache_by_pipeline_handle; -// All the pipelines linked to a shader -std::unordered_map> pipeline_caches_by_shader_hash; -// All the shaders the game ever loaded (including the ones that have been unloaded) -std::unordered_map shader_cache; -// All the shaders the user has (and has had) as custom in the live folder -std::unordered_map custom_shaders_cache; - -std::unordered_set pipelines_to_reload; -static_assert(sizeof(reshade::api::pipeline::handle) == sizeof(uint64_t)); -// Map of "reshade::api::pipeline::handle" -std::unordered_map pipelines_to_destroy; -// Newly loaded shaders that still need to be (auto) dumped -std::unordered_set shaders_to_dump; -// All the shaders we have already dumped -std::unordered_set dumped_shaders; - -std::vector trace_shader_hashes; -std::vector trace_pipeline_handles; -std::vector instructions; - -constexpr uint32_t MAX_SHADER_DEFINES = 10; - -// Settings -bool auto_dump = true; -bool auto_load = true; -bool live_reload = false; -bool trace_list_unique_shaders_only = false; -bool trace_ignore_vertex_shaders = true; -const bool PRECOMPILE_CUSTOM_SHADERS = true; -std::vector shader_defines; - -bool trace_scheduled = false; -bool trace_running = false; -bool needs_unload_shaders = false; -bool needs_load_shaders = false; -bool needs_live_reload_update = live_reload; -std::atomic cloned_pipelines_changed = false; -uint32_t cloned_pipeline_count = 0; -uint32_t shader_cache_count = 0; -uint32_t shader_cache_size = 0; -uint32_t resource_count = 0; -uint32_t resource_view_count = 0; -uint32_t trace_count = 0; -uint32_t present_count = 0; - -const uint32_t MAX_PRESENT_COUNT = 60; -bool force_all = false; -bool trace_names = false; - -// Forward declares: -void ToggleLiveWatching(); -void DumpShader(uint32_t shader_hash, bool auto_detect_type); -void AutoDumpShaders(); -void AutoLoadShaders(); - -inline void GetD3DName(ID3D11DeviceChild* obj, std::string& name) { - if (obj == nullptr) { - return; - } - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - char c_name[128] = {}; - UINT size = sizeof(name); - if (obj->GetPrivateData(WKPDID_D3DDebugObjectName, &size, c_name) == S_OK) { - name = c_name; - } -} - -inline void GetD3DName(ID3D12Resource* obj, std::string& name) { - if (obj == nullptr) { - return; - } - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - char c_name[128] = {}; - UINT size = sizeof(name); - if (obj->GetPrivateData(WKPDID_D3DDebugObjectName, &size, c_name) == S_OK) { - name = c_name; - } -} - -uint64_t GetResourceByViewHandle(DeviceData& data, uint64_t handle) { - if ( - auto pair = data.resource_views.find(handle); - pair != data.resource_views.end()) return pair->second; - - return 0; -} - -std::string GetResourceNameByViewHandle(DeviceData& data, uint64_t handle) { - if (!trace_names) return "?"; - auto resource_handle = GetResourceByViewHandle(data, handle); - if (resource_handle == 0) return "?"; - if (!data.resources.contains(resource_handle)) return "?"; - - if ( - auto pair = data.resource_names.find(resource_handle); - pair != data.resource_names.end()) return pair->second; - - std::string name; - if (data.device_api == reshade::api::device_api::d3d11) { - auto* native_resource = reinterpret_cast(resource_handle); - GetD3DName(native_resource, name); - } else if (data.device_api == reshade::api::device_api::d3d12) { - auto* native_resource = reinterpret_cast(resource_handle); - GetD3DName(native_resource, name); - } else { - name = "?"; - } - if (!name.empty()) { - data.resource_names[resource_handle] = name; - } - return name; -} - -std::filesystem::path GetShaderPath() { - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - wchar_t file_prefix[MAX_PATH] = L""; - GetModuleFileNameW(nullptr, file_prefix, ARRAYSIZE(file_prefix)); - - std::filesystem::path dump_path = file_prefix; - dump_path = dump_path.parent_path(); - dump_path /= ".\\renodx-dev"; - return dump_path; -} - -void DestroyPipelineSubojects(reshade::api::pipeline_subobject* subojects, uint32_t subobject_count) { - for (uint32_t i = 0; i < subobject_count; ++i) { - auto& suboject = subojects[i]; - - switch (suboject.type) { - case reshade::api::pipeline_subobject_type::vertex_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::compute_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::pixel_shader: { - auto* desc = static_cast(suboject.data); - free(const_cast(desc->code)); - desc->code = nullptr; - break; - } - default: - break; +struct DrawDetails { + std::vector pipeline_handles_seen; + enum class DrawMethods { + PRESENT, + DRAW, + DRAW_INDEXED, + DRAW_INDEXED_OR_INDIRECT, + DISPATCH + } draw_method; + std::vector render_targets; + + [[nodiscard]] std::string DrawMethodString() const { + switch (draw_method) { + case DrawMethods::PRESENT: return "Present"; + case DrawMethods::DRAW: return "Draw"; + case DrawMethods::DRAW_INDEXED: return "DrawIndexed"; + case DrawMethods::DRAW_INDEXED_OR_INDIRECT: return "DrawIndirect"; + case DrawMethods::DISPATCH: return "Dispatch"; + default: return "Unknown"; } - - free(suboject.data); - suboject.data = nullptr; } - delete[] subojects; -} +}; -void ClearCustomShader(uint32_t shader_hash) { - const std::lock_guard lock(s_mutex_loading); - auto custom_shader = custom_shaders_cache.find(shader_hash); - if (custom_shader != custom_shaders_cache.end() && custom_shader->second != nullptr) { - custom_shader->second->code.clear(); - custom_shader->second->is_hlsl = false; - custom_shader->second->file_path.clear(); - custom_shader->second->compilation_error.clear(); - } -} +struct __declspec(uuid("3224946b-5c5f-478a-8691-83fbb9f88f1b")) CommandListData { + std::vector draw_details; -void UnloadCustomShaders(const std::unordered_set& pipelines_filter = std::unordered_set(), bool immediate = false, bool clean_custom_shader = true) { - const std::lock_guard lock(s_mutex_generic); - for (auto& pair : pipeline_cache_by_pipeline_handle) { - auto& cached_pipeline = pair.second; - if (cached_pipeline == nullptr || (!pipelines_filter.empty() && !pipelines_filter.contains(cached_pipeline->pipeline.handle))) continue; - - // In case this is a full "unload" of all shaders - if (pipelines_filter.empty()) { - // Disable testing here, otherwise we might not always have a way to do it - cached_pipeline->test = false; - - // Clear their compilation state, we might not have any other way of doing it - if (clean_custom_shader) { - for (auto shader_hash : cached_pipeline->shader_hashes) { - ClearCustomShader(shader_hash); - } - } + DrawDetails& GetCurrentDrawDetails() { + if (draw_details.empty()) { + draw_details.push_back({}); } + auto& item = draw_details[draw_details.size() - 1]; + return item; + } +}; - if (!cached_pipeline->cloned) continue; - cached_pipeline->cloned = false; // This stops the cloned pipeline from being used in the next frame, allowing us to destroy it - cloned_pipeline_count--; - cloned_pipelines_changed = true; +struct __declspec(uuid("0190ec1a-2e19-74a6-ad41-4df0d4d8caed")) DeviceData { + std::unordered_map shader_details; + std::unordered_map resource_view_details; + std::vector command_list_data; - if (immediate) { - cached_pipeline->device->destroy_pipeline(reshade::api::pipeline{cached_pipeline->pipeline_clone.handle}); - } else { - pipelines_to_destroy[cached_pipeline->pipeline_clone.handle] = cached_pipeline->device; - } - cached_pipeline->pipeline_clone = {0}; + void StartSnapshot() { + this->command_list_data.clear(); + is_snapshotting = true; } -} -void CompileCustomShaders(const std::unordered_set& pipelines_filter = std::unordered_set()) { - auto directory = GetShaderPath(); - if (!std::filesystem::exists(directory)) { - std::filesystem::create_directory(directory); + static void StopSnapshot() { + is_snapshotting = false; } - directory /= ".\\live"; + ShaderDetails& GetShaderDetails(uint32_t shader_hash) { + if (auto pair = shader_details.find(shader_hash); + pair != shader_details.end()) { + return pair->second; + } - if (!std::filesystem::exists(directory)) { - std::filesystem::create_directory(directory); - return; + auto [iterator, is_new] = shader_details.emplace(shader_hash, shader_hash); + return iterator->second; } - for (const auto& entry : std::filesystem::directory_iterator(directory)) { - if (!entry.is_regular_file()) { - reshade::log_message(reshade::log_level::warning, "loadCustomShaders(not a regular file)"); - continue; - } - const auto& entry_path = entry.path(); - const bool is_hlsl = entry_path.extension().compare(".hlsl") == 0; - const bool is_cso = entry_path.extension().compare(".cso") == 0; - if (!entry_path.has_extension() || !entry_path.has_stem() || (!is_hlsl && !is_cso)) { - std::stringstream s; - s << "loadCustomShaders(Missing extension or stem or unknown extension: "; - s << entry_path.string(); - s << ")"; - reshade::log_message(reshade::log_level::warning, s.str().c_str()); - continue; + ResourceViewDetails& GetResourceViewDetails(reshade::api::resource_view resource_view, reshade::api::device* device) { + if (auto pair = resource_view_details.find(resource_view.handle); + pair != resource_view_details.end()) { + return pair->second; } - auto filename_no_extension = entry_path.stem(); - auto filename_no_extension_string = filename_no_extension.string(); - std::string hash_string; - std::string shader_target; - - if (is_hlsl) { - auto length = filename_no_extension_string.length(); - if (length < strlen("0x12345678.xx_x_x")) continue; - shader_target = filename_no_extension_string.substr(length - strlen("xx_x_x"), strlen("xx_x_x")); - if (shader_target[2] != '_') continue; - if (shader_target[4] != '_') continue; - // uint32_t versionMajor = shader_target[3] - '0'; - hash_string = filename_no_extension_string.substr(length - strlen("12345678.xx_x_x"), 8); - } else if (is_cso) { - // As long as cso starts from "0x12345678", it's good, they don't need the shader type specified - if (filename_no_extension_string.size() < 10) { - std::stringstream s; - s << "loadCustomShaders(Invalid cso file format: "; - s << filename_no_extension_string; - s << ")"; - reshade::log_message(reshade::log_level::warning, s.str().c_str()); - continue; + ResourceViewDetails details = { + .resource_view = resource_view, + .resource_view_desc = device->get_resource_view_desc(resource_view), + .resource = device->get_resource_from_view(resource_view), + }; + auto device_api = device->get_api(); + if (device_api == reshade::api::device_api::d3d11) { + auto resource_view_tag = renodx::utils::trace::GetDebugName(device->get_api(), resource_view); + if (resource_view_tag.has_value()) { + details.resource_view_tag = resource_view_tag.value(); } - hash_string = filename_no_extension_string.substr(2, 8); - } - // Any other case (non hlsl non cso) is already earlied out above - - uint32_t shader_hash; - try { - shader_hash = std::stoul(hash_string, nullptr, 16); - } catch (const std::exception& e) { - continue; } - // Early out before compiling - if (!pipelines_filter.empty()) { - const std::lock_guard lock(s_mutex_generic); - bool pipeline_found = false; - for (const auto& pipeline_pair : pipeline_cache_by_pipeline_handle) { - if (std::find(pipeline_pair.second->shader_hashes.begin(), pipeline_pair.second->shader_hashes.end(), shader_hash) == pipeline_pair.second->shader_hashes.end()) continue; - if (pipelines_filter.contains(pipeline_pair.first)) { - pipeline_found = true; - } - break; + if (details.resource.handle != 0u) { + details.resource_desc = device->get_resource_desc(details.resource); + details.is_swapchain = renodx::utils::swapchain::IsBackBuffer(device, details.resource); + auto resource_tag = renodx::utils::trace::GetDebugName(device->get_api(), details.resource); + if (resource_tag.has_value()) { + details.resource_tag = resource_tag.value(); } - if (!pipeline_found) { - continue; - } - } - - const std::lock_guard lock(s_mutex_loading); - const bool has_custom_shader = custom_shaders_cache.find(shader_hash) != custom_shaders_cache.end(); - auto& custom_shader = custom_shaders_cache[shader_hash]; - if (!has_custom_shader || custom_shader == nullptr) { - custom_shader = new CachedCustomShader(); - } else { - ClearCustomShader(shader_hash); } - custom_shader->file_path = entry_path; - custom_shader->is_hlsl = is_hlsl; - - // First try to load hlsl shaders - if (is_hlsl) { - { - std::stringstream s; - s << "loadCustomShaders(Compiling file: "; - s << entry_path.string(); - s << ", hash: " << PRINT_CRC32(shader_hash); - s << ", target: " << shader_target; - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - custom_shader->code = renodx::utils::shader::compiler::CompileShaderFromFile( - entry_path.c_str(), - shader_target.c_str(), - shader_defines, - &custom_shader->compilation_error); - if (custom_shader->code.empty()) { - std::stringstream s; - s << "loadCustomShaders(Compilation failed: "; - s << entry_path.string(); - s << ")"; - reshade::log_message(reshade::log_level::warning, s.str().c_str()); - - continue; - } - - { - std::stringstream s; - s << "loadCustomShaders(Shader built with size: " << custom_shader->code.size() << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - } - // Second try to load cso shaders - else if (is_cso) { - std::ifstream file(entry_path, std::ios::binary); - file.seekg(0, std::ios::end); - custom_shader->code.resize(file.tellg()); - { - std::stringstream s; - s << "loadCustomShaders(Reading " << custom_shader->code.size() << " from " << filename_no_extension_string << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - if (!custom_shader->code.empty()) { - file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(custom_shader->code.data()), custom_shader->code.size()); - } - } - } -} - -void LoadCustomShaders(const std::unordered_set& pipelines_filter = std::unordered_set(), bool recompile_shaders = true, bool immediate_load = true, bool immediate_unload = false) { - reshade::log_message(reshade::log_level::debug, "loadCustomShaders()"); - - if (recompile_shaders) { - CompileCustomShaders(pipelines_filter); - } - - // We can, and should, only lock this after compiling new shaders - const std::lock_guard lock(s_mutex_generic); - - // Clear all previously loaded custom shaders - UnloadCustomShaders(pipelines_filter, immediate_unload, false); - - std::unordered_set cloned_pipelines; - - const std::lock_guard lock_loading(s_mutex_loading); - for (const auto& custom_shader_pair : custom_shaders_cache) { - uint32_t shader_hash = custom_shader_pair.first; - auto* custom_shader = custom_shaders_cache[shader_hash]; - - // Skip shaders that don't have code binaries at the moment - if (custom_shader == nullptr || custom_shader->code.empty()) continue; - - auto pipelines_pair = pipeline_caches_by_shader_hash.find(shader_hash); - if (pipelines_pair == pipeline_caches_by_shader_hash.end()) { - std::stringstream s; - s << "loadCustomShaders(Unknown hash: "; - s << PRINT_CRC32(shader_hash); - s << ")"; - reshade::log_message(reshade::log_level::warning, s.str().c_str()); - continue; - } - - // Re-clone all the pipelines that used this shader hash (except the ones that are filtered out) - for (CachedPipeline* cached_pipeline : pipelines_pair->second) { - if (cached_pipeline == nullptr) continue; - if (!pipelines_filter.empty() && !pipelines_filter.contains(cached_pipeline->pipeline.handle)) continue; - if (cloned_pipelines.contains(cached_pipeline->pipeline.handle)) { assert(false); continue; } - cloned_pipelines.emplace(cached_pipeline->pipeline.handle); - // Force destroy this pipeline in case it was already cloned - UnloadCustomShaders({cached_pipeline->pipeline.handle}, immediate_unload, false); - - { - std::stringstream s; - s << "loadCustomShaders(Read "; - s << custom_shader->code.size() << " bytes "; - s << " from " << custom_shader->file_path.string(); - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - - // DX12 can use PSO objects that need to be cloned - - const uint32_t subobject_count = cached_pipeline->subobject_count; - reshade::api::pipeline_subobject* subobjects = cached_pipeline->subobjects_cache; - reshade::api::pipeline_subobject* new_subobjects = renodx::utils::pipeline::ClonePipelineSubObjects(subobject_count, subobjects); - - { - std::stringstream s; - s << "loadCustomShaders(Cloning pipeline "; - s << reinterpret_cast(cached_pipeline->pipeline.handle); - s << " with " << subobject_count << " object(s)"; - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - reshade::log_message(reshade::log_level::debug, "Iterating pipeline..."); - - for (uint32_t i = 0; i < subobject_count; ++i) { -#ifdef DEBUG_LEVEL_2 - reshade::log_message(reshade::log_level::debug, "Checking subobject..."); -#endif - const auto& subobject = subobjects[i]; - switch (subobject.type) { - case reshade::api::pipeline_subobject_type::vertex_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::compute_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::pixel_shader: - break; - default: - continue; - } -#if 0 - const reshade::api::shader_desc& desc = *static_cast(subobject.data); - - if (desc.code_size == 0) { - reshade::log_message(reshade::log_level::warning, "Code size 0"); - continue; - } - - reshade::log_message(reshade::log_level::debug, "Computing hash..."); - // Pipeline has a pixel shader with code. Hash code and check - auto shader_hash = compute_crc32(static_cast(desc.code), desc.code_size); - if (hash != shader_hash) { - reshade::log_message(reshade::log_level::warning, ""); - continue; - } -#endif - - auto& clone_subject = new_subobjects[i]; - - auto* new_desc = static_cast(clone_subject.data); - - new_desc->code_size = custom_shader->code.size(); - new_desc->code = malloc(custom_shader->code.size()); - // TODO(clshortfuse): Workaround leak - memcpy(const_cast(new_desc->code), custom_shader->code.data(), custom_shader->code.size()); - - auto new_hash = compute_crc32(static_cast(new_desc->code), new_desc->code_size); - - std::stringstream s; - s << "loadCustomShaders(Injected pipeline data"; - s << " with " << PRINT_CRC32(new_hash); - s << " (" << custom_shader->code.size() << " bytes)"; - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - - { - std::stringstream s; - s << "Creating pipeline clone ("; - s << "hash: " << PRINT_CRC32(shader_hash); - s << ", layout: " << reinterpret_cast(cached_pipeline->layout.handle); - s << ", subobject_count: " << subobject_count; - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } - - reshade::api::pipeline pipeline_clone; - const bool built_pipeline_ok = cached_pipeline->device->create_pipeline( - cached_pipeline->layout, - subobject_count, - new_subobjects, - &pipeline_clone); - std::stringstream s; - s << "loadCustomShaders(cloned "; - s << reinterpret_cast(cached_pipeline->pipeline.handle); - s << " => " << reinterpret_cast(pipeline_clone.handle); - s << ", layout: " << reinterpret_cast(cached_pipeline->layout.handle); - s << ", size: " << subobject_count; - s << ", " << (built_pipeline_ok ? "OK" : "FAILED!"); - s << ")"; - reshade::log_message(built_pipeline_ok ? reshade::log_level::info : reshade::log_level::error, s.str().c_str()); - - if (built_pipeline_ok) { - assert(!cached_pipeline->cloned && cached_pipeline->pipeline_clone.handle == 0); - cached_pipeline->cloned = true; - cached_pipeline->ready_for_binding = immediate_load; - cached_pipeline->pipeline_clone = pipeline_clone; - cloned_pipeline_count++; - cloned_pipelines_changed = true; - } - // Clean up unused cloned subobjects - else { - DestroyPipelineSubojects(new_subobjects, subobject_count); - new_subobjects = nullptr; - } - } + auto [iterator, is_new] = resource_view_details.emplace(resource_view.handle, details); + return iterator->second; } -} -std::optional ReadTextFile(const std::filesystem::path& path) { - std::vector data; - std::optional result; - std::ifstream file(path, std::ios::binary); - file.seekg(0, std::ios::end); - const size_t file_size = file.tellg(); - if (file_size == 0) return result; - - data.resize(file_size); - file.seekg(0, std::ios::beg).read(reinterpret_cast(data.data()), file_size); - result = std::string(reinterpret_cast(data.data()), file_size); - return result; -} - -OVERLAPPED overlapped; -HANDLE m_target_dir_handle = INVALID_HANDLE_VALUE; + std::shared_mutex mutex; +}; -bool needs_watcher_init = true; +// Settings +std::atomic_bool is_tracing_pipelines = false; -std::aligned_storage_t<1U << 18, std::max(alignof(FILE_NOTIFY_EXTENDED_INFORMATION), alignof(FILE_NOTIFY_INFORMATION))> watch_buffer; +bool setting_auto_dump = false; +bool setting_auto_compile = false; +bool setting_live_reload = false; +bool setting_unique_shaders_only = false; +bool setting_show_vertex_shaders = false; -void CALLBACK HandleEventCallback(DWORD error_code, DWORD bytes_transferred, LPOVERLAPPED overlapped) { - reshade::log_message(reshade::log_level::info, "Live callback."); - // TODO: only re-load the shaders that were changed to improve performance, and also verify this is safe. Replacing shaders from another thread at a random time could break as we need to wait one frame or the pipeline binding could hang. - LoadCustomShaders(); - // Trigger the watch again as the event is only triggered once - ToggleLiveWatching(); -} +struct { + uint32_t row_id = 0; + uint64_t pipeline_handle = 0; + uint32_t shader_hash = 0; + uint64_t resource_handle = 0; -void CheckForLiveUpdate() { - if (live_reload) { - WaitForSingleObjectEx(overlapped.hEvent, 0, TRUE); + [[nodiscard]] auto GetTreeNodeFlag(uint32_t row_index) const { + return row_index == row_id ? ImGuiTreeNodeFlags_Selected : 0; } -} - -void ToggleLiveWatching() { - if (live_reload) { - auto directory = GetShaderPath(); - if (!std::filesystem::exists(directory)) { - std::filesystem::create_directory(directory); - } - - directory /= ".\\live"; - - if (!std::filesystem::exists(directory)) { - std::filesystem::create_directory(directory); - } - - reshade::log_message(reshade::log_level::info, "Watching live."); - - // Clean up any previous handle for safety - if (m_target_dir_handle != INVALID_HANDLE_VALUE) { - CancelIoEx(m_target_dir_handle, &overlapped); - } - m_target_dir_handle = CreateFileW( - directory.c_str(), - FILE_LIST_DIRECTORY, - (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), - NULL, // NOLINT - OPEN_EXISTING, - (FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED), - NULL // NOLINT - ); - if (m_target_dir_handle == INVALID_HANDLE_VALUE) { - reshade::log_message(reshade::log_level::error, "ToggleLiveWatching(targetHandle: invalid)"); - return; - } - { - std::stringstream s; - s << "ToggleLiveWatching(targetHandle: "; - s << reinterpret_cast(m_target_dir_handle); - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - - memset(&watch_buffer, 0, sizeof(watch_buffer)); - overlapped = {0}; - overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // NOLINT - - const BOOL success = ReadDirectoryChangesExW( - m_target_dir_handle, - &watch_buffer, - sizeof(watch_buffer), - TRUE, - FILE_NOTIFY_CHANGE_FILE_NAME - | FILE_NOTIFY_CHANGE_DIR_NAME - | FILE_NOTIFY_CHANGE_ATTRIBUTES - | FILE_NOTIFY_CHANGE_SIZE - | FILE_NOTIFY_CHANGE_CREATION - | FILE_NOTIFY_CHANGE_LAST_WRITE, - NULL, // NOLINT - &overlapped, - &HandleEventCallback, - ReadDirectoryNotifyExtendedInformation); - - if (success == S_OK) { - reshade::log_message(reshade::log_level::info, "ToggleLiveWatching(ReadDirectoryChangesExW: Listening.)"); - } else { - std::stringstream s; - s << "ToggleLiveWatching(ReadDirectoryChangesExW: Failed: "; - s << GetLastError(); - s << ")"; - reshade::log_message(reshade::log_level::error, s.str().c_str()); - } +} setting_row_selection; - LoadCustomShaders(); - } else { - reshade::log_message(reshade::log_level::info, "Cancelling live."); - CancelIoEx(m_target_dir_handle, &overlapped); - } -} +std::vector> setting_shader_defines; -void LogLayout( - const uint32_t param_count, - const reshade::api::pipeline_layout_param* params, - const reshade::api::pipeline_layout layout) { - for (uint32_t param_index = 0; param_index < param_count; ++param_index) { - auto param = params[param_index]; - switch (param.type) { - case reshade::api::pipeline_layout_param_type::descriptor_table: - for (uint32_t range_index = 0; range_index < param.descriptor_table.count; ++range_index) { - auto range = param.descriptor_table.ranges[range_index]; - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | TBL"; - s << " | " << reinterpret_cast(¶m.descriptor_table.ranges); - s << " | "; - switch (range.type) { - case reshade::api::descriptor_type::sampler: - s << "SMP"; - break; - case reshade::api::descriptor_type::sampler_with_resource_view: - s << "SMPRV"; - break; - case reshade::api::descriptor_type::texture_shader_resource_view: - s << "TSRV"; - break; - case reshade::api::descriptor_type::texture_unordered_access_view: - s << "TUAV"; - break; - case reshade::api::descriptor_type::constant_buffer: - s << "CBV"; - break; - case reshade::api::descriptor_type::shader_storage_buffer: - s << "SSB"; - break; - case reshade::api::descriptor_type::acceleration_structure: - s << "ACC"; - break; - default: - s << "??? (0x" << std::hex << static_cast(range.type) << std::dec << ")"; - } - - s << ", array_size: " << range.array_size; - s << ", binding: " << range.binding; - s << ", count: " << range.count; - s << ", register: " << range.dx_register_index; - s << ", space: " << range.dx_register_space; - s << ", visibility: " << range.visibility; - s << ")"; - s << " [" << range_index << "/" << param.descriptor_table.count << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - break; - case reshade::api::pipeline_layout_param_type::push_constants: { - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | PC"; - s << ", binding: " << param.push_constants.binding; - s << ", count " << param.push_constants.count; - s << ", register: " << param.push_constants.dx_register_index; - s << ", space: " << param.push_constants.dx_register_space; - s << ", visibility " << param.push_constants.visibility; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - break; - } - case reshade::api::pipeline_layout_param_type::push_descriptors: { - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | PD |"; - s << " array_size: " << param.push_descriptors.array_size; - s << ", binding: " << param.push_descriptors.binding; - s << ", count " << param.push_descriptors.count; - s << ", register: " << param.push_descriptors.dx_register_index; - s << ", space: " << param.push_descriptors.dx_register_space; - s << ", type: " << param.push_descriptors.type; - s << ", visibility " << param.push_descriptors.visibility; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - break; - } - case reshade::api::pipeline_layout_param_type::push_descriptors_with_ranges: { - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | PDR?? | "; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - break; - } -#if RESHADE_API_VERSION >= 13 - case reshade::api::pipeline_layout_param_type::descriptor_table_with_static_samplers: - for (uint32_t range_index = 0; range_index < param.descriptor_table_with_static_samplers.count; ++range_index) { - auto range = param.descriptor_table_with_static_samplers.ranges[range_index]; - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | TBLSS"; - s << " | " << reinterpret_cast(¶m.descriptor_table.ranges); - s << " | "; - if (range.static_samplers == nullptr) { - s << " null "; - } else { - s << ", filter: " << static_cast(range.static_samplers->filter); - s << ", address_u: " << static_cast(range.static_samplers->address_u); - s << ", address_v: " << static_cast(range.static_samplers->address_v); - s << ", address_w: " << static_cast(range.static_samplers->address_w); - s << ", mip_lod_bias: " << static_cast(range.static_samplers->mip_lod_bias); - s << ", max_anisotropy: " << static_cast(range.static_samplers->max_anisotropy); - s << ", compare_op: " << static_cast(range.static_samplers->compare_op); - s << ", border_color: [" << range.static_samplers->border_color[0] << ", " << range.static_samplers->border_color[1] << ", " << range.static_samplers->border_color[2] << ", " << range.static_samplers->border_color[3] << "]"; - s << ", min_lod: " << range.static_samplers->min_lod; - s << ", max_lod: " << range.static_samplers->max_lod; - } - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - break; - case reshade::api::pipeline_layout_param_type::push_descriptors_with_static_samplers: - for (uint32_t range_index = 0; range_index < param.descriptor_table.count; ++range_index) { - auto range = param.descriptor_table_with_static_samplers.ranges[range_index]; - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | PDSS"; - s << " | " << reinterpret_cast(&range); - s << " | "; - if (range.static_samplers == nullptr) { - s << "not"; - } else { - s << "filter: " << static_cast(range.static_samplers->filter); - s << ", address_u: " << static_cast(range.static_samplers->address_u); - s << ", address_v: " << static_cast(range.static_samplers->address_v); - s << ", address_w: " << static_cast(range.static_samplers->address_w); - s << ", mip_lod_bias: " << static_cast(range.static_samplers->mip_lod_bias); - s << ", max_anisotropy: " << static_cast(range.static_samplers->max_anisotropy); - s << ", compare_op: " << static_cast(range.static_samplers->compare_op); - s << ", border_color: [" << range.static_samplers->border_color[0] << ", " << range.static_samplers->border_color[1] << ", " << range.static_samplers->border_color[2] << ", " << range.static_samplers->border_color[3] << "]"; - s << ", min_lod: " << range.static_samplers->min_lod; - s << ", max_lod: " << range.static_samplers->max_lod; - } - s << ")"; - s << " [" << range_index << "/" << param.descriptor_table.count << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - break; -#endif - default: { - std::stringstream s; - s << "logPipelineLayout("; - s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; - s << " | ??? (0x" << std::hex << static_cast(param.type) << std::dec << ")"; - s << " | " << param.type; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - } - } -} +bool setting_shader_defines_changed = false; void OnInitDevice(reshade::api::device* device) { - std::stringstream s; - s << "init_device("; - s << reinterpret_cast(device); - s << ", api: " << device->get_api(); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); auto& data = device->create_private_data(); - data.device_api = device->get_api(); } void OnDestroyDevice(reshade::api::device* device) { - std::stringstream s; - s << "destroy_device("; - s << reinterpret_cast(device); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); device->destroy_private_data(); } -void OnInitSwapchain(reshade::api::swapchain* swapchain) { - const size_t back_buffer_count = swapchain->get_back_buffer_count(); - - for (uint32_t index = 0; index < back_buffer_count; index++) { - auto buffer = swapchain->get_back_buffer(index); - - std::stringstream s; - s << "init_swapchain("; - s << "buffer:" << reinterpret_cast(buffer.handle); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - - std::stringstream s; - s << "init_swapchain"; - s << "(colorspace: " << swapchain->get_color_space(); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); +void OnInitCommandList(reshade::api::command_list* cmd_list) { + cmd_list->create_private_data(); } -bool OnCreatePipelineLayout( - reshade::api::device* device, - uint32_t& param_count, - reshade::api::pipeline_layout_param*& params) { - // noop - return false; +void OnDestroyCommandList(reshade::api::command_list* cmd_list) { + cmd_list->destroy_private_data(); } -// AfterCreateRootSignature -void OnInitPipelineLayout( - reshade::api::device* device, - const uint32_t param_count, - const reshade::api::pipeline_layout_param* params, - reshade::api::pipeline_layout layout) { - LogLayout(param_count, params, layout); - - uint32_t cbv_index = 0; - uint32_t pc_count = 0; - - for (uint32_t param_index = 0; param_index < param_count; ++param_index) { - auto param = params[param_index]; - if (param.type == reshade::api::pipeline_layout_param_type::descriptor_table) { - for (uint32_t range_index = 0; range_index < param.descriptor_table.count; ++range_index) { - auto range = param.descriptor_table.ranges[range_index]; - if (range.type == reshade::api::descriptor_type::constant_buffer) { - if (cbv_index < range.dx_register_index + range.count) { - cbv_index = range.dx_register_index + range.count; - } - } - } - } else if (param.type == reshade::api::pipeline_layout_param_type::push_constants) { - pc_count++; - if (cbv_index < param.push_constants.dx_register_index + param.push_constants.count) { - cbv_index = param.push_constants.dx_register_index + param.push_constants.count; - } - } else if (param.type == reshade::api::pipeline_layout_param_type::push_descriptors) { - if (param.push_descriptors.type == reshade::api::descriptor_type::constant_buffer) { - if (cbv_index < param.push_descriptors.dx_register_index + param.push_descriptors.count) { - cbv_index = param.push_descriptors.dx_register_index + param.push_descriptors.count; - } - } - } - } - - const uint32_t max_count = 64u - (param_count + 1u) + 1u; - - std::stringstream s; - s << "on_init_pipeline_layout++("; - s << reinterpret_cast(layout.handle); - s << " , max injections: " << (max_count); - s << " )"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} - -// After CreatePipelineState -void OnInitPipeline( - reshade::api::device* device, - reshade::api::pipeline_layout layout, - uint32_t subobject_count, - const reshade::api::pipeline_subobject* subobjects, - reshade::api::pipeline pipeline) { - if (subobject_count == 0) { - std::stringstream s; - s << "on_init_pipeline("; - s << reinterpret_cast(pipeline.handle); - s << ", layout:" << reinterpret_cast(layout.handle); - s << ", subobjects: " << (subobject_count); - s << " )"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - return; - } - - const std::lock_guard lock(s_mutex_generic); - - reshade::api::pipeline_subobject* subobjects_cache = renodx::utils::pipeline::ClonePipelineSubObjects(subobject_count, subobjects); - - auto* cached_pipeline = new CachedPipeline{ - pipeline, - device, - layout, - subobjects_cache, - subobject_count}; - - bool found_replaceable_shader = false; - bool found_custom_shader_file = false; - - for (uint32_t i = 0; i < subobject_count; ++i) { - const auto& subobject = subobjects[i]; - for (uint32_t j = 0; j < subobject.count; ++j) { - std::stringstream s; - s << "on_init_pipeline("; - s << reinterpret_cast(pipeline.handle); - s << "[" << i << "][" << j << "]"; - s << ", layout:" << reinterpret_cast(layout.handle); - s << ", type: " << subobject.type; - switch (subobject.type) { - case reshade::api::pipeline_subobject_type::hull_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::domain_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::geometry_shader: - // reshade::api::shader_desc &desc = static_cast(subobjects[i].data[j]); - break; - case reshade::api::pipeline_subobject_type::blend_state: - break; // Disabled for now - { - auto& desc = static_cast(subobject.data)[j]; - s << ", alpha_to_coverage_enable: " << desc.alpha_to_coverage_enable; - s << ", source_color_blend_factor: " << desc.source_color_blend_factor[0]; - s << ", dest_color_blend_factor: " << desc.dest_color_blend_factor[0]; - s << ", color_blend_op: " << desc.color_blend_op[0]; - s << ", source_alpha_blend_factor: " << desc.source_alpha_blend_factor[0]; - s << ", dest_alpha_blend_factor: " << desc.dest_alpha_blend_factor[0]; - s << ", alpha_blend_op: " << desc.alpha_blend_op[0]; - s << ", render_target_write_mask: " << std::hex << desc.render_target_write_mask[0] << std::dec; - } - break; - case reshade::api::pipeline_subobject_type::vertex_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::compute_shader: - [[fallthrough]]; - case reshade::api::pipeline_subobject_type::pixel_shader: { - // reshade::api::shader_desc* desc = (static_cast(subobject.data))[j]; - auto* new_desc = static_cast(subobjects_cache[i].data); - if (new_desc->code_size == 0) break; - auto shader_hash = compute_crc32(static_cast(new_desc->code), new_desc->code_size); - - { - const std::lock_guard lock_dumping(s_mutex_dumping); - - // Delete any previous shader with the same hash (unlikely to happen, but safer nonetheless) - if (auto previous_shader_pair = shader_cache.find(shader_hash); previous_shader_pair != shader_cache.end() && previous_shader_pair->second != nullptr) { - auto& previous_shader = previous_shader_pair->second; - // Make sure that two shaders have the same hash, their code size also matches (theoretically we could check even more, but the chances hashes overlapping is extremely small) - assert(previous_shader->size == new_desc->code_size); - shader_cache_count--; - shader_cache_size -= previous_shader->size; - free(previous_shader->data); - free(previous_shader); - previous_shader = nullptr; - } - - // Cache shader - auto* cache = new CachedShader{ - malloc(new_desc->code_size), - new_desc->code_size, - subobject.type}; - memcpy(cache->data, new_desc->code, cache->size); - shader_cache_count++; - shader_cache_size += cache->size; - shader_cache[shader_hash] = cache; - shaders_to_dump.emplace(shader_hash); - } - - // Indexes - assert(std::find(cached_pipeline->shader_hashes.begin(), cached_pipeline->shader_hashes.end(), shader_hash) == cached_pipeline->shader_hashes.end()); - cached_pipeline->shader_hashes.emplace_back(shader_hash); - - // Make sure we didn't already have a valid pipeline in there (this should never happen) - auto pipelines_pair = pipeline_caches_by_shader_hash.find(shader_hash); - if (pipelines_pair != pipeline_caches_by_shader_hash.end()) { - pipelines_pair->second.emplace(cached_pipeline); - } else { - pipeline_caches_by_shader_hash[shader_hash] = {cached_pipeline}; - } - found_replaceable_shader = true; - { - const std::lock_guard lock(s_mutex_loading); - found_custom_shader_file |= custom_shaders_cache.contains(shader_hash); - } - - // Metrics - { - std::stringstream s2; - s2 << "caching shader("; - s2 << "hash: " << PRINT_CRC32(shader_hash); - s2 << ", type: " << subobject.type; - s2 << ", pipeline: " << reinterpret_cast(pipeline.handle); - s2 << ")"; - reshade::log_message(reshade::log_level::info, s2.str().c_str()); - } - break; - } - default: - break; - } - - s << " )"; - - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - } - if (!found_replaceable_shader) { - delete cached_pipeline; - cached_pipeline = nullptr; - DestroyPipelineSubojects(subobjects_cache, subobject_count); - subobjects_cache = nullptr; - return; - } - pipeline_cache_by_pipeline_handle[pipeline.handle] = cached_pipeline; - - // Automatically load any custom shaders that might have been bound to this pipeline. - // To avoid this slowing down everything, we only do it if we detect the user already had a matching shader in its custom shaders folder. - if (auto_load && found_custom_shader_file) { - const std::lock_guard lock_loading(s_mutex_loading); - // Immediately cloning and replacing the pipeline might be unsafe, we need to delay it to the next frame. - pipelines_to_reload.emplace(pipeline.handle); - if (PRECOMPILE_CUSTOM_SHADERS) { - // If done with the "immediate" flag, this is unsafe, it hangs the game (even if it seems like it should be safe given it doesn't do anything other than create a cloned pipeline without binding it yet). - // If done without the "immediate" flag, this will cause a hitch due to shader compilation (unless precompile_custom_shaders is true), and still start drawing one frame after, so it's better to rely on the "AutoLoadShaders()" function. - const bool immediate = true; - LoadCustomShaders(pipelines_to_reload, !PRECOMPILE_CUSTOM_SHADERS, immediate); - pipelines_to_reload.clear(); - } - } -} - -void OnDestroyPipeline( - reshade::api::device* device, - reshade::api::pipeline pipeline) { - const std::lock_guard lock(s_mutex_generic); - - uint32_t changed = 0; - changed |= compute_shader_layouts.erase(pipeline.handle); - - { - const std::lock_guard lock_loading(s_mutex_loading); - pipelines_to_reload.erase(pipeline.handle); - } - - if ( - auto pipeline_cache_pair = pipeline_cache_by_pipeline_handle.find(pipeline.handle); - pipeline_cache_pair != pipeline_cache_by_pipeline_handle.end()) { - auto& cached_pipeline = pipeline_cache_pair->second; - - if (cached_pipeline != nullptr) { - // Clean other references to the pipeline - for (auto& pipelines_cache_pair : pipeline_caches_by_shader_hash) { - auto& cached_pipelines = pipelines_cache_pair.second; - cached_pipelines.erase(cached_pipeline); - } - - // Destroy our cloned subojects - DestroyPipelineSubojects(cached_pipeline->subobjects_cache, cached_pipeline->subobject_count); - cached_pipeline->subobjects_cache = nullptr; - - // Destroy our cloned version of the pipeline (and leave the original intact) - if (cached_pipeline->cloned) { - cached_pipeline->cloned = false; - cached_pipeline->device->destroy_pipeline(cached_pipeline->pipeline_clone); - cloned_pipeline_count--; - cloned_pipelines_changed = true; - } - free(cached_pipeline); - cached_pipeline = nullptr; - } - - pipeline_cache_by_pipeline_handle.erase(pipeline.handle); - changed++; - } - - if (changed == 0) return; - - std::stringstream s; - s << "on_destroy_pipeline("; - s << reinterpret_cast(pipeline.handle); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} - -// AfterSetPipelineState void OnBindPipeline( reshade::api::command_list* cmd_list, reshade::api::pipeline_stage stages, reshade::api::pipeline pipeline) { - if (trace_running) { - switch (stages) { - case reshade::api::pipeline_stage::vertex_shader: - case reshade::api::pipeline_stage::pixel_shader: - case reshade::api::pipeline_stage::compute_shader: - break; - default: - case reshade::api::pipeline_stage::input_assembler: - case reshade::api::pipeline_stage::output_merger: { - std::stringstream s; - s << "bind_pipeline(" << reinterpret_cast(pipeline.handle); - s << ", stages: " << stages << " (" << std::hex << static_cast(stages) << std::dec << ")"; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - break; - } - } - } - const std::lock_guard lock(s_mutex_generic); - - auto pair = pipeline_cache_by_pipeline_handle.find(pipeline.handle); - if (pair == pipeline_cache_by_pipeline_handle.end() || pair->second == nullptr) return; - - auto* cached_pipeline = pair->second; - - if (cached_pipeline->test) { - // This will make the shader output black, or skip drawing, so we can easily detect it. This might not be very safe but seems to work in DX11. - // TODO: replace the pipeline with a shader that outputs all "SV_Target" as purple for more visiblity - cmd_list->bind_pipeline(stages, reshade::api::pipeline{0}); - } else if (cached_pipeline->cloned && cached_pipeline->ready_for_binding) { - if (trace_running) { - std::stringstream s; - s << "bind_pipeline(swapping pipeline " << reinterpret_cast(pipeline.handle); - s << " => " << reinterpret_cast(cached_pipeline->pipeline_clone.handle); - s << ", stages: " << stages << "(" << std::hex << static_cast(stages) << ")"; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - - cmd_list->bind_pipeline(stages, cached_pipeline->pipeline_clone); - } + if ((stages + | ((reshade::api::pipeline_stage::vertex_shader + | reshade::api::pipeline_stage::pixel_shader + | reshade::api::pipeline_stage::compute_shader))) + == 0u) return; - if (!trace_running) return; + if (!is_snapshotting) return; - // const bool is_compute_shader = compute_shader_layouts.contains(cached_pipeline->layout.handle); - - bool add_pipeline_trace = true; - if (trace_list_unique_shaders_only) { - auto trace_count = trace_shader_hashes.size(); - for (auto index = 0; index < trace_count; index++) { - auto hash = trace_shader_hashes.at(index); - if (std::find(cached_pipeline->shader_hashes.begin(), cached_pipeline->shader_hashes.end(), hash) != cached_pipeline->shader_hashes.end()) { - trace_shader_hashes.erase(trace_shader_hashes.begin() + index); - add_pipeline_trace = false; - break; - } - } - } + auto& data = cmd_list->get_private_data(); + auto& details = data.GetCurrentDrawDetails(); + details.pipeline_handles_seen.push_back(pipeline.handle); +} - if (trace_ignore_vertex_shaders && (stages == reshade::api::pipeline_stage::vertex_shader || stages == reshade::api::pipeline_stage::input_assembler)) { - add_pipeline_trace = false; - } +bool OnDraw(reshade::api::command_list* cmd_list, DrawDetails::DrawMethods draw_method) { + if (!is_snapshotting) return false; - // Pipelines are always "unique" - if (add_pipeline_trace) { - trace_pipeline_handles.push_back(cached_pipeline->pipeline.handle); - } + auto& command_list_data = cmd_list->get_private_data(); + auto* device = cmd_list->get_device(); + auto& device_data = device->get_private_data(); - for (auto shader_hash : cached_pipeline->shader_hashes) { - if (!trace_list_unique_shaders_only || std::find(trace_shader_hashes.begin(), trace_shader_hashes.end(), shader_hash) == trace_shader_hashes.end()) { - trace_shader_hashes.push_back(shader_hash); - } - // InstructionState state = instructions.at(instructions.size() - 1); - // state.shader = cached_pipeline->shader_hashes; - } + std::unique_lock lock(device_data.mutex); - std::stringstream s; - s << "bind_pipeline("; - s << trace_pipeline_handles.size() << ": "; - s << reinterpret_cast(cached_pipeline->pipeline.handle); - s << ", " << reinterpret_cast(cached_pipeline->layout.handle); - s << ", stages: " << stages << " (" << std::hex << static_cast(stages) << std::dec << ")"; - for (auto shader_hash : cached_pipeline->shader_hashes) { - s << ", " << PRINT_CRC32(shader_hash); + auto& details = command_list_data.GetCurrentDrawDetails(); + details.draw_method = draw_method; + details.render_targets.clear(); + for (auto render_target : renodx::utils::swapchain::GetRenderTargets(cmd_list)) { + if (render_target.handle == 0u) continue; + details.render_targets.push_back( + device_data.GetResourceViewDetails(render_target, device)); } - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} -void OnBindPipelineStates( - reshade::api::command_list* cmd_list, - uint32_t count, - const reshade::api::dynamic_state* states, - const uint32_t* values) { - if (!trace_running) return; - - for (uint32_t i = 0; i < count; i++) { - std::stringstream s; - s << "bind_pipeline_state"; - s << "(" << states[i]; - s << ", " << values[i]; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } -} + device_data.command_list_data.push_back(command_list_data); + command_list_data.draw_details.clear(); -void ResetInstructionState() { - const size_t count = instructions.size(); - const InstructionState old_state = instructions.at(count - 1); - instructions.resize(count + 1); - InstructionState new_state = instructions.at(count); - new_state.render_targets = old_state.render_targets; - new_state.textures = old_state.textures; - new_state.shader = old_state.shader; + return false; } bool OnDraw( @@ -1330,36 +243,11 @@ bool OnDraw( uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) { - if (trace_running) { - std::stringstream s; - s << "on_draw"; - s << "(" << vertex_count; - s << ", " << instance_count; - s << ", " << first_vertex; - s << ", " << first_instance; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - // InstructionState state = instructions.at(instructions.size() - 1); - // state.action = reshade::addon_event::draw; - // resetInstructionState(); - } - return false; + return OnDraw(cmd_list, DrawDetails::DrawMethods::DRAW); } bool OnDispatch(reshade::api::command_list* cmd_list, uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) { - if (trace_running) { - std::stringstream s; - s << "on_dispatch"; - s << "(" << group_count_x; - s << ", " << group_count_y; - s << ", " << group_count_z; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - // InstructionState state = instructions.at(instructions.size() - 1); - // state.action = reshade::addon_event::dispatch; - // resetInstructionState(); - } - return false; + return OnDraw(cmd_list, DrawDetails::DrawMethods::DISPATCH); } bool OnDrawIndexed( @@ -1369,21 +257,7 @@ bool OnDrawIndexed( uint32_t first_index, int32_t vertex_offset, uint32_t first_instance) { - if (trace_running) { - std::stringstream s; - s << "on_draw_indexed"; - s << "(" << index_count; - s << ", " << instance_count; - s << ", " << first_index; - s << ", " << vertex_offset; - s << ", " << first_instance; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - // InstructionState state = instructions.at(instructions.size() - 1); - // state.action = reshade::addon_event::draw_indexed; - // resetInstructionState(); - } - return false; + return OnDraw(cmd_list, DrawDetails::DrawMethods::DRAW_INDEXED); } bool OnDrawOrDispatchIndirect( @@ -1393,1257 +267,561 @@ bool OnDrawOrDispatchIndirect( uint64_t offset, uint32_t draw_count, uint32_t stride) { - if (trace_running) { - std::stringstream s; - s << "on_draw_or_dispatch_indirect(" << type; - s << ", " << reinterpret_cast(buffer.handle); - s << ", " << offset; - s << ", " << draw_count; - s << ", " << stride; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - // InstructionState state = instructions.at(instructions.size() - 1); - // state.action = reshade::addon_event::draw_or_dispatch_indirect; - // resetInstructionState(); - } - return false; -} - -bool OnCopyTextureRegion( - reshade::api::command_list* cmd_list, - reshade::api::resource source, - uint32_t source_subresource, - const reshade::api::subresource_box* source_box, - reshade::api::resource dest, - uint32_t dest_subresource, - const reshade::api::subresource_box* dest_box, - reshade::api::filter_mode filter) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_copy_texture_region"; - s << "(" << reinterpret_cast(source.handle); - s << ", " << (source_subresource); - s << ", " << reinterpret_cast(dest.handle); - s << ", " << static_cast(filter); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - - return false; -} - -bool OnCopyTextureToBuffer( - reshade::api::command_list* cmd_list, - reshade::api::resource source, - uint32_t source_subresource, - const reshade::api::subresource_box* source_box, - reshade::api::resource dest, - uint64_t dest_offset, - uint32_t row_length, - uint32_t slice_height) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_copy_texture_region(" << reinterpret_cast(source.handle); - s << "[" << source_subresource << "]"; - if (source_box != nullptr) { - s << "(" << source_box->top << ", " << source_box->left << ", " << source_box->front << ")"; - } - s << " => " << reinterpret_cast(dest.handle); - s << "[" << dest_offset << "]"; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - - return false; -} - -bool OnCopyBufferToTexture( - reshade::api::command_list* cmd_list, - reshade::api::resource source, - uint64_t source_offset, - uint32_t row_length, - uint32_t slice_height, - reshade::api::resource dest, - uint32_t dest_subresource, - const reshade::api::subresource_box* dest_box) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_copy_texture_region"; - s << "(" << reinterpret_cast(source.handle); - s << "[" << source_offset << "]"; - s << " => " << reinterpret_cast(dest.handle); - s << "[" << dest_subresource << "]"; - if (dest_box != nullptr) { - s << "(" << dest_box->top << ", " << dest_box->left << ", " << dest_box->front << ")"; - } - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - - return false; -} - -bool OnResolveTextureRegion( - reshade::api::command_list* cmd_list, - reshade::api::resource source, - uint32_t source_subresource, - const reshade::api::subresource_box* source_box, - reshade::api::resource dest, - uint32_t dest_subresource, - int32_t dest_x, - int32_t dest_y, - int32_t dest_z, - reshade::api::format format) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_resolve_texture_region"; - s << "(" << reinterpret_cast(source.handle); - s << ": " << (source_subresource); - s << " => " << reinterpret_cast(dest.handle); - s << ": " << (dest_subresource); - s << ", (" << dest_x << ", " << dest_y << ", " << dest_z << ") "; - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - return false; -} - -bool OnCopyResource( - reshade::api::command_list* cmd_list, - reshade::api::resource source, - reshade::api::resource dest) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_copy_resource"; - s << "(" << reinterpret_cast(source.handle); - s << " => " << reinterpret_cast(dest.handle); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - return false; -} - -void OnBarrier( - reshade::api::command_list* cmd_list, - uint32_t count, - const reshade::api::resource* resources, - const reshade::api::resource_usage* old_states, - const reshade::api::resource_usage* new_states) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - for (uint32_t i = 0; i < count; i++) { - std::stringstream s; - s << "on_barrier(" << reinterpret_cast(resources[i].handle); - s << ", " << std::hex << static_cast(old_states[i]) << std::dec << " (" << old_states[i] << ")"; - s << " => " << std::hex << static_cast(new_states[i]) << std::dec << " (" << new_states[i] << ")"; - s << ") [" << i << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } -} - -void OnBindRenderTargetsAndDepthStencil( - reshade::api::command_list* cmd_list, - uint32_t count, - const reshade::api::resource_view* rtvs, - reshade::api::resource_view dsv) { - if (!trace_running) return; - - if (count != 0) { - // InstructionState state = instructions.at(instructions.size() - 1); - // state.renderTargets.clear(); - auto* device = cmd_list->get_device(); - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - for (uint32_t i = 0; i < count; i++) { - auto rtv = rtvs[i]; - // if (rtv.handle) { - // state.renderTargets.push_back(rtv.handle); - // } - std::stringstream s; - s << "on_bind_render_targets("; - s << reinterpret_cast(rtv.handle); - s << ", res: " << reinterpret_cast(GetResourceByViewHandle(data, rtv.handle)); - s << ", name: " << GetResourceNameByViewHandle(data, rtv.handle); - s << ")"; - s << "[" << i << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } - } - if (dsv.handle != 0) { - std::stringstream s; - s << "on_bind_depth_stencil("; - s << reinterpret_cast(dsv.handle); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } + return OnDraw(cmd_list, DrawDetails::DrawMethods::DRAW_INDEXED_OR_INDIRECT); } -void OnInitResource( - reshade::api::device* device, - const reshade::api::resource_desc& desc, - const reshade::api::subresource_data* initial_data, - reshade::api::resource_usage initial_state, - reshade::api::resource resource) { - auto& data = device->get_private_data(); - const std::unique_lock lock(data.mutex); - data.resources.emplace(resource.handle); - - if (!force_all && !trace_running && present_count >= MAX_PRESENT_COUNT) return; - - bool warn = false; - std::stringstream s; - s << "init_resource(" << reinterpret_cast(resource.handle); - s << ", flags: " << std::hex << static_cast(desc.flags) << std::dec; - s << ", state: " << std::hex << static_cast(initial_state) << std::dec; - s << ", type: " << desc.type; - s << ", usage: " << std::hex << static_cast(desc.usage) << std::dec; - - switch (desc.type) { - case reshade::api::resource_type::buffer: - s << ", size: " << desc.buffer.size; - s << ", stride: " << desc.buffer.stride; - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - break; - case reshade::api::resource_type::texture_1d: - case reshade::api::resource_type::texture_2d: - case reshade::api::resource_type::texture_3d: - case reshade::api::resource_type::surface: - s << ", width: " << desc.texture.width; - s << ", height: " << desc.texture.height; - s << ", levels: " << desc.texture.levels; - s << ", format: " << desc.texture.format; - if (desc.texture.format == reshade::api::format::unknown) { - warn = true; - } - break; - default: - case reshade::api::resource_type::unknown: - break; +void PerformShaderReload(reshade::api::device* device) { + if (setting_live_reload) { + if (!renodx::utils::shader::compiler::watcher::HasChanged()) return; + } else { + renodx::utils::shader::compiler::watcher::CompileSync(); } - - s << ")"; - reshade::log_message( - warn - ? reshade::log_level::warning - : reshade::log_level::info, - s.str().c_str()); -} - -void OnDestroyResource(reshade::api::device* device, reshade::api::resource resource) { - auto& data = device->get_private_data(); - const std::unique_lock lock(data.mutex); - data.resources.erase(resource.handle); - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - - std::stringstream s; - s << "on_destroy_resource("; - s << reinterpret_cast(resource.handle); - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); -} - -void OnInitResourceView( - reshade::api::device* device, - reshade::api::resource resource, - reshade::api::resource_usage usage_type, - const reshade::api::resource_view_desc& desc, - reshade::api::resource_view view) { + auto new_shaders = renodx::utils::shader::compiler::watcher::FlushCompiledShaders(); auto& data = device->get_private_data(); - const std::unique_lock lock(data.mutex); - if (data.resource_views.contains(view.handle)) { - if (trace_running || present_count < MAX_PRESENT_COUNT) { - std::stringstream s; - s << "init_resource_view(reused view: "; - s << reinterpret_cast(view.handle); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); + std::unique_lock lock(data.mutex); + for (auto& [shader_hash, custom_shader] : new_shaders) { + renodx::utils::shader::RemoveRuntimeReplacement(shader_hash, device); + if (!custom_shader.removed && custom_shader.IsCompilationOK()) { + renodx::utils::shader::AddRuntimeReplacement(shader_hash, custom_shader.GetCompilationData()); } - if (resource.handle == 0) { - data.resource_views.erase(view.handle); - return; - } - } - if (resource.handle != 0) { - data.resource_views.emplace(view.handle, resource.handle); - } - if (!force_all && !trace_running && present_count >= MAX_PRESENT_COUNT) return; - std::stringstream s; - s << "init_resource_view(" << reinterpret_cast(view.handle); - s << ", view type: " << desc.type << " (0x" << std::hex << static_cast(desc.type) << std::dec << ")"; - s << ", view format: " << desc.format << " (0x" << std::hex << static_cast(desc.format) << std::dec << ")"; - s << ", resource: " << reinterpret_cast(resource.handle); - s << ", resource usage: " << usage_type << " 0x" << std::hex << static_cast(usage_type) << std::dec; - // if (desc.type == reshade::api::resource_view_type::buffer) return; - if (resource.handle != 0) { - const auto resource_desc = device->get_resource_desc(resource); - s << ", resource type: " << resource_desc.type; - - switch (resource_desc.type) { - default: - case reshade::api::resource_type::unknown: - break; - case reshade::api::resource_type::buffer: - // if (!traceRunning) return; - return; - s << ", buffer offset: " << desc.buffer.offset; - s << ", buffer size: " << desc.buffer.size; - break; - case reshade::api::resource_type::texture_1d: - case reshade::api::resource_type::texture_2d: - case reshade::api::resource_type::surface: - s << ", texture format: " << resource_desc.texture.format; - s << ", texture width: " << resource_desc.texture.width; - s << ", texture height: " << resource_desc.texture.height; - break; - case reshade::api::resource_type::texture_3d: - s << ", texture format: " << resource_desc.texture.format; - s << ", texture width: " << resource_desc.texture.width; - s << ", texture height: " << resource_desc.texture.height; - s << ", texture depth: " << resource_desc.texture.depth_or_layers; - break; - } + auto& details = data.GetShaderDetails(shader_hash); + details.custom_shader = custom_shader; } - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} - -void OnDestroyResourceView(reshade::api::device* device, reshade::api::resource_view view) { - std::stringstream s; - s << "on_destroy_resource_view("; - s << reinterpret_cast(view.handle); - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - - auto& data = device->get_private_data(); - const std::unique_lock lock(data.mutex); - data.resource_views.erase(view.handle); } -void OnPushDescriptors( - reshade::api::command_list* cmd_list, - reshade::api::shader_stage stages, - reshade::api::pipeline_layout layout, - uint32_t layout_param, - const reshade::api::descriptor_table_update& update) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - auto* device = cmd_list->get_device(); +// @see https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html +void OnRegisterOverlay(reshade::api::effect_runtime* runtime) { + auto* device = runtime->get_device(); auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - for (uint32_t i = 0; i < update.count; i++) { - std::stringstream s; - s << "push_descriptors(" << reinterpret_cast(layout.handle); - s << "[" << layout_param << "]"; - s << "[" << update.binding + i << "]"; - s << ", type: " << update.type; - - auto log_heap = [=]() { - std::stringstream s2; - uint32_t base_offset = 0; - reshade::api::descriptor_heap heap = {0}; - device->get_descriptor_heap_offset(update.table, update.binding + i, 0, &heap, &base_offset); - s2 << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; - return s2.str(); - }; - switch (update.type) { - case reshade::api::descriptor_type::sampler: { - s << log_heap(); - auto item = static_cast(update.descriptors)[i]; - s << ", sampler: " << reinterpret_cast(item.handle); - break; - } - case reshade::api::descriptor_type::sampler_with_resource_view: { - s << log_heap(); - auto item = static_cast(update.descriptors)[i]; - s << ", sampler: " << reinterpret_cast(item.sampler.handle); - s << ", rsv: " << reinterpret_cast(item.view.handle); - s << ", res: " << reinterpret_cast(GetResourceByViewHandle(data, item.view.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); - break; - } - case reshade::api::descriptor_type::buffer_shader_resource_view: - - case reshade::api::descriptor_type::shader_resource_view: { - s << log_heap(); - auto item = static_cast(update.descriptors)[i]; - s << ", shaderrsv: " << reinterpret_cast(item.handle); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.handle); - break; - } - case reshade::api::descriptor_type::buffer_unordered_access_view: - - case reshade::api::descriptor_type::unordered_access_view: { - s << log_heap(); - auto item = static_cast(update.descriptors)[i]; - s << ", uav: " << reinterpret_cast(item.handle); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.handle); - break; - } - case reshade::api::descriptor_type::acceleration_structure: { - auto item = static_cast(update.descriptors)[i]; - s << ", accl: " << reinterpret_cast(item.handle); - break; - } - case reshade::api::descriptor_type::constant_buffer: { - auto item = static_cast(update.descriptors)[i]; - s << ", buffer: " << reinterpret_cast(item.buffer.handle); - s << ", size: " << item.size; - s << ", offset: " << item.offset; - break; - } - default: - s << ", type: " << update.type; - break; - } - - s << ")"; - s << "[" << update.binding + i << " / " << update.count << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } -} - -void OnBindDescriptorTables( - reshade::api::command_list* cmd_list, - reshade::api::shader_stage stages, - reshade::api::pipeline_layout layout, - uint32_t first, - uint32_t count, - const reshade::api::descriptor_table* tables) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - auto* device = cmd_list->get_device(); - for (uint32_t i = 0; i < count; ++i) { - std::stringstream s; - s << "bind_descriptor_table(" << reinterpret_cast(layout.handle); - s << "[" << (first + i) << "]"; - s << ", stages: " << stages << "(" << std::hex << static_cast(stages) << std::dec << ")"; - s << ", table: " << reinterpret_cast(tables[i].handle); - uint32_t base_offset = 0; - reshade::api::descriptor_heap heap = {0}; - device->get_descriptor_heap_offset(tables[i], 0, 0, &heap, &base_offset); - s << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; - - auto& descriptor_data = device->get_private_data(); - const std::shared_lock decriptor_lock(descriptor_data.mutex); - for (uint32_t j = 0; j < 13; ++j) { - auto origin_primary_key = std::pair(tables[i].handle, j); - if (auto pair = descriptor_data.table_descriptor_resource_views.find(origin_primary_key); - pair != descriptor_data.table_descriptor_resource_views.end()) { - auto update = pair->second; - auto view = renodx::utils::descriptor::GetResourceViewFromDescriptorUpdate(update); - if (view.handle != 0) { - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", rsv[" << j << "]: " << reinterpret_cast(view.handle); - s << ", res[" << j << "]: " << reinterpret_cast(GetResourceByViewHandle(data, view.handle)); - } - } - } - - s << ") [" << i << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - } -} - -bool OnCopyDescriptorTables( - reshade::api::device* device, - uint32_t count, - const reshade::api::descriptor_table_copy* copies) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - - for (uint32_t i = 0; i < count; i++) { - const auto& copy = copies[i]; - - for (uint32_t j = 0; j < copy.count; j++) { - std::stringstream s; - s << "copy_descriptor_tables("; - s << reinterpret_cast(copy.source_table.handle); - s << "[" << copy.source_binding + j << "]"; - s << " => "; - s << reinterpret_cast(copy.dest_table.handle); - s << "[" << copy.dest_binding + j << "]"; - - uint32_t base_offset = 0; - reshade::api::descriptor_heap heap = {0}; - device->get_descriptor_heap_offset( - copy.source_table, copy.source_binding + j, copy.source_array_offset, &heap, &base_offset); - s << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; - device->get_descriptor_heap_offset( - copy.dest_table, copy.dest_binding + j, copy.dest_array_offset, &heap, &base_offset); - s << " => " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; - - auto& descriptor_data = device->get_private_data(); - const std::shared_lock decriptor_lock(descriptor_data.mutex); - auto origin_primary_key = std::pair(copy.source_table.handle, copy.source_binding + j); - if (auto pair = descriptor_data.table_descriptor_resource_views.find(origin_primary_key); - pair != descriptor_data.table_descriptor_resource_views.end()) { - auto update = pair->second; - auto view = renodx::utils::descriptor::GetResourceViewFromDescriptorUpdate(update); - if (view.handle != 0) { - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", rsv: " << reinterpret_cast(view.handle); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, view.handle)); - } - } - - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); + { + ImGui::PushID("##SnapshotButton"); + if (ImGui::Button("Take Snapshot")) { + std::unique_lock lock(data.mutex); + data.StartSnapshot(); } - } - - return false; -} + ImGui::PopID(); -bool OnUpdateDescriptorTables( - reshade::api::device* device, - uint32_t count, - const reshade::api::descriptor_table_update* updates) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - - for (uint32_t i = 0; i < count; i++) { - const auto& update = updates[i]; - - for (uint32_t j = 0; j < update.count; j++) { - std::stringstream s; - s << "update_descriptor_tables("; - s << reinterpret_cast(update.table.handle); - s << "[" << update.binding + j << "]"; - - uint32_t base_offset = 0; - reshade::api::descriptor_heap heap = {0}; - device->get_descriptor_heap_offset(update.table, update.binding + j, 0, &heap, &base_offset); - s << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; - switch (update.type) { - case reshade::api::descriptor_type::sampler: { - auto item = static_cast(update.descriptors)[j]; - s << ", sampler: " << reinterpret_cast(item.handle); - break; - } - case reshade::api::descriptor_type::sampler_with_resource_view: { - auto item = static_cast(update.descriptors)[j]; - s << ", sampler: " << reinterpret_cast(item.sampler.handle); - s << ", rsv: " << reinterpret_cast(item.view.handle); - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.view.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); - break; - } - case reshade::api::descriptor_type::buffer_shader_resource_view: { - auto item = static_cast(update.descriptors)[j]; - s << ", b-srv: " << reinterpret_cast(item.handle); - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); - break; - } - case reshade::api::descriptor_type::buffer_unordered_access_view: { - auto item = static_cast(update.descriptors)[j]; - s << ", b-uav: " << reinterpret_cast(item.handle); - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); - break; - } - case reshade::api::descriptor_type::shader_resource_view: { - auto item = static_cast(update.descriptors)[j]; - s << ", srv: " << reinterpret_cast(item.handle); - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.handle); - break; - } - case reshade::api::descriptor_type::unordered_access_view: { - auto item = static_cast(update.descriptors)[j]; - s << ", uav: " << reinterpret_cast(item.handle); - auto& data = device->get_private_data(); - const std::shared_lock lock(data.mutex); - s << ", res: " << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); - // s << ", name: " << getResourceNameByViewHandle(data, item.handle); - break; - } - case reshade::api::descriptor_type::constant_buffer: { - auto item = static_cast(update.descriptors)[j]; - s << ", buffer: " << reinterpret_cast(item.buffer.handle); - s << ", size: " << item.size; - s << ", offset: " << item.offset; - break; - } - case reshade::api::descriptor_type::shader_storage_buffer: { - auto item = static_cast(update.descriptors)[j]; - s << ", buffer: " << reinterpret_cast(item.buffer.handle); - s << ", size: " << item.size; - s << ", offset: " << item.offset; - break; - } - case reshade::api::descriptor_type::acceleration_structure: - s << ", accl: unknown"; - break; - default: - break; - } - s << ") [" << i << "]"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); + ImGui::SameLine(); + ImGui::PushID("##TraceButton"); + if (ImGui::Button("Log Trace")) { + renodx::utils::trace::trace_scheduled = true; } - } - return false; -} - -bool OnClearRenderTargetView( - reshade::api::command_list* cmd_list, - reshade::api::resource_view rtv, - const float color[4], - uint32_t rect_count, - const reshade::api::rect* rects) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_clear_render_target_view("; - s << reinterpret_cast(rtv.handle); - s << ")"; - - reshade::log_message(reshade::log_level::info, s.str().c_str()); - return false; -} - -bool OnClearUnorderedAccessViewUint( - reshade::api::command_list* cmd_list, - reshade::api::resource_view uav, - const uint32_t values[4], - uint32_t rect_count, - const reshade::api::rect* rects) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; - std::stringstream s; - s << "on_clear_unordered_access_view_uint("; - s << reinterpret_cast(uav.handle); - s << ")"; - - reshade::log_message(reshade::log_level::info, s.str().c_str()); - return false; -} - -void OnPushConstants( - reshade::api::command_list* cmd_list, - reshade::api::shader_stage stages, - reshade::api::pipeline_layout layout, - uint32_t layout_param, - uint32_t first, - uint32_t count, - const void* values) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - std::stringstream s; - s << "push_constants(" << reinterpret_cast(layout.handle); - s << "[" << layout_param << "]"; - s << ", stage: " << std::hex << static_cast(stages) << std::dec << " (" << stages << ")"; - s << ", count: " << count; - s << "{ 0x"; - for (uint32_t i = 0; i < count; i++) { - s << std::hex << static_cast(values)[i] << std::dec << ", "; - } - s << " })"; - - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} - -void OnMapBufferRegion( - reshade::api::device* device, - reshade::api::resource resource, - uint64_t offset, - uint64_t size, - reshade::api::map_access access, - void** data) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - std::stringstream s; - s << "map_buffer_region("; - s << reinterpret_cast(resource.handle); - s << ")"; - - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} - -void OnMapTextureRegion( - reshade::api::device* device, - reshade::api::resource resource, - uint32_t subresource, - const reshade::api::subresource_box* box, - reshade::api::map_access access, - reshade::api::subresource_data* data) { - if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; - std::stringstream s; - s << "map_texture_region("; - s << reinterpret_cast(resource.handle); - s << "[" << subresource << "]"; - s << ")"; - - reshade::log_message(reshade::log_level::info, s.str().c_str()); -} - -void OnReshadePresent(reshade::api::effect_runtime* runtime) { - if (trace_running) { - reshade::log_message(reshade::log_level::info, "present()"); - reshade::log_message(reshade::log_level::info, "--- End Frame ---"); - trace_count = trace_pipeline_handles.size(); - trace_running = false; - } else if (trace_scheduled) { - trace_scheduled = false; - trace_shader_hashes.clear(); - trace_pipeline_handles.clear(); - // instructions.clear(); - // resetInstructionState(); - trace_running = true; - reshade::log_message(reshade::log_level::info, "--- Frame ---"); - } - if (present_count <= MAX_PRESENT_COUNT) { - present_count++; + ImGui::PopID(); } - // TODO: verify this delayed behaviour is actually ever needed and delete it if not { - const std::lock_guard lock(s_mutex_generic); - for (auto& pipeline_pair : pipeline_cache_by_pipeline_handle) { - // Force waiting a frame as replacing the pipeline the first frame it was created could cause hangs - pipeline_pair.second->ready_for_binding = true; - } - } - - // Dump new shaders (checking the "shaders_to_dump" count is theoretically not thread safe but it should work nonetheless as this is run every frame) - if (auto_dump && !thread_auto_dumping_running && !shaders_to_dump.empty()) { - if (thread_auto_dumping.joinable()) { - thread_auto_dumping.join(); + ImGui::BeginDisabled(setting_auto_dump); + ImGui::PushID("##DumpShaders"); + if (ImGui::Button(std::format("Dump Shaders ({})", renodx::utils::shader::dump::pending_dump_count.load()).c_str())) { + renodx::utils::shader::dump::DumpAllPending(); } - thread_auto_dumping_running = true; - thread_auto_dumping = std::thread(AutoDumpShaders); - } + ImGui::PopID(); + ImGui::EndDisabled(); - // Load new shaders (checking the "pipelines_to_reload" count is theoretically not thread safe but it should work nonetheless as this is run every frame) - if (auto_load && !thread_auto_loading_running && !pipelines_to_reload.empty()) { - if (thread_auto_loading.joinable()) { - thread_auto_loading.join(); + ImGui::SameLine(); + ImGui::PushID("##AutoDumpCheckBox"); + if (ImGui::Checkbox("Auto", &setting_auto_dump)) { + // noop } - thread_auto_loading_running = true; - thread_auto_loading = std::thread(AutoLoadShaders); + ImGui::PopID(); } - // Destroy the cloned pipelines in the following frame to avoid crashes { - const std::lock_guard lock(s_mutex_generic); - for (auto pair : pipelines_to_destroy) { - pair.second->destroy_pipeline(reshade::api::pipeline{pair.first}); + ImGui::BeginDisabled(setting_live_reload); + if (ImGui::Button(std::format("Unload Shaders ({})", renodx::utils::shader::runtime_replacement_count.load()).c_str())) { + renodx::utils::shader::RemoveAllRuntimeReplacements(runtime->get_device()); + renodx::utils::shader::compiler::watcher::CompileSync(); } - pipelines_to_destroy.clear(); - } - - if (needs_unload_shaders) { - UnloadCustomShaders(); -#if 1 // Optionally unload all custom shaders data - const std::lock_guard lock(s_mutex_loading); - custom_shaders_cache.clear(); -#endif - needs_unload_shaders = false; - } - if (needs_load_shaders) { - LoadCustomShaders(); - needs_load_shaders = false; - } - - if (needs_live_reload_update) { - ToggleLiveWatching(); - needs_live_reload_update = false; - } - CheckForLiveUpdate(); -} - -void DumpShader(uint32_t shader_hash, bool auto_detect_type = true) { - auto dump_path = GetShaderPath(); - - if (!std::filesystem::exists(dump_path)) { - std::filesystem::create_directory(dump_path); - } - dump_path /= ".\\dump"; - if (!std::filesystem::exists(dump_path)) { - std::filesystem::create_directory(dump_path); - } - - wchar_t hash_string[11]; - swprintf_s(hash_string, L"0x%08X", shader_hash); + ImGui::EndDisabled(); - dump_path /= hash_string; + ImGui::SameLine(); + ImGui::BeginDisabled(setting_live_reload); + if (ImGui::Button(std::format("Load Shaders ({})", renodx::utils::shader::compiler::watcher::custom_shaders_count.load()).c_str())) { + PerformShaderReload(runtime->get_device()); + } + ImGui::EndDisabled(); - auto* cached_shader = shader_cache.find(shader_hash)->second; + ImGui::SameLine(); + ImGui::PushID("##LiveReloadCheckBox"); + ImGui::BeginDisabled(setting_live_reload); - // Automatically find the shader type and append it to the name (a bit hacky). This can make dumping relevantly slower. - if (auto_detect_type) { - if (cached_shader->disasm.empty()) { - auto disasm_code = renodx::utils::shader::compiler::DisassembleShader(cached_shader->data, cached_shader->size); - if (disasm_code.has_value()) { - cached_shader->disasm.assign(disasm_code.value()); + if (ImGui::Checkbox("Auto Compile", &setting_auto_compile)) { + if (setting_auto_compile) { + renodx::utils::shader::compiler::watcher::Start(); } else { - cached_shader->disasm.assign("DECOMPILATION FAILED"); + renodx::utils::shader::compiler::watcher::Stop(); } } + ImGui::EndDisabled(); + ImGui::PopID(); - if (cached_shader->type == reshade::api::pipeline_subobject_type::vertex_shader - || cached_shader->type == reshade::api::pipeline_subobject_type::pixel_shader - || cached_shader->type == reshade::api::pipeline_subobject_type::compute_shader) { - static const std::string TEMPLATE_VERTEX_SHADER_NAME = "vs_"; - static const std::string TEMPLATE_PIXEL_SHADER_NAME = "ps_"; - static const std::string TEMPLATE_COMPUTE_SHADER_NAME = "cs_"; - static const std::string TEMPLATE_SHADER_FULL_NAME = "x_x"; - - std::string_view template_shader_name; - switch (cached_shader->type) { - case reshade::api::pipeline_subobject_type::vertex_shader: { - template_shader_name = TEMPLATE_VERTEX_SHADER_NAME; - break; - } - default: - case reshade::api::pipeline_subobject_type::pixel_shader: { - template_shader_name = TEMPLATE_PIXEL_SHADER_NAME; - break; - } - case reshade::api::pipeline_subobject_type::compute_shader: { - template_shader_name = TEMPLATE_COMPUTE_SHADER_NAME; - break; - } - } - const auto type_index = cached_shader->disasm.find(template_shader_name); - if (type_index != std::string::npos) { - const std::string type = cached_shader->disasm.substr(type_index, template_shader_name.length() + TEMPLATE_SHADER_FULL_NAME.length()); - dump_path += "."; - dump_path += type; - } + ImGui::SameLine(); + ImGui::BeginDisabled(!setting_auto_compile); + ImGui::PushID("##LiveReloadCheckBox"); + if (ImGui::Checkbox("Live Reload", &setting_live_reload)) { + // noop } + ImGui::PopID(); + ImGui::EndDisabled(); } - dump_path += L".cso"; - - std::ofstream file(dump_path, std::ios::binary); - - file.write(static_cast(cached_shader->data), cached_shader->size); - - if (!dumped_shaders.contains(shader_hash)) { - dumped_shaders.emplace(shader_hash); - } -} + bool changed_selected = false; + if (ImGui::BeginTabBar("##MyTabBar", ImGuiTabBarFlags_None)) { + std::unique_lock lock(data.mutex); -void AutoDumpShaders() { - // Copy the "shaders_to_dump" so we don't have to lock "s_mutex_dumping" all the times - std::unordered_set shaders_to_dump_copy; - { - const std::lock_guard lock_dumping(s_mutex_dumping); - if (shaders_to_dump.empty()) { - thread_auto_dumping_running = false; - return; - } - shaders_to_dump_copy = shaders_to_dump; - shaders_to_dump.clear(); - } - for (auto shader_to_dump : shaders_to_dump_copy) { - const std::lock_guard lock_dumping(s_mutex_dumping); - if (!dumped_shaders.contains(shader_to_dump)) { - DumpShader(shader_to_dump, true); - } - } - thread_auto_dumping_running = false; -} + ImGui::PushID("##SnapshotTab"); + auto handle_snapshot_tab = ImGui::BeginTabItem("Capture"); + ImGui::PopID(); + if (handle_snapshot_tab) { + if (ImGui::BeginChild("##SnapshotList", ImVec2(96, 0), ImGuiChildFlags_ResizeX)) { + static ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_SpanFullWidth; + if (ImGui::BeginTable( + "##SnapshotTree", + 5, + ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable + | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY, + ImVec2(-4, -4))) { + static const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_WidthStretch, TEXT_BASE_WIDTH * 24.0f); + ImGui::TableSetupColumn("Ref", ImGuiTableColumnFlags_None, TEXT_BASE_WIDTH * 16.0f); + ImGui::TableSetupColumn("Info", ImGuiTableColumnFlags_None, TEXT_BASE_WIDTH * 24.0f); + ImGui::TableSetupColumn("Tag", ImGuiTableColumnFlags_None, TEXT_BASE_WIDTH * 24.0f); + ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_None, TEXT_BASE_WIDTH * 4.0f); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + uint32_t row_index = 0x2000; + int draw_index = 0; + for (auto& command_list_data : data.command_list_data) { + for (auto& draw_details : command_list_data.draw_details) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(row_index); + bool draw_node_open = ImGui::TreeNodeEx("", tree_node_flags | ImGuiTreeNodeFlags_DefaultOpen, "%s", draw_details.DrawMethodString().c_str()); + ImGui::PopID(); + + ImGui::TableNextColumn(); // Ref + + ImGui::TableNextColumn(); // Info + + ImGui::TableNextColumn(); // Tag + + ImGui::TableNextColumn(); + ImGui::Text("%03d", draw_index); + + for (auto pipeline_handle : draw_details.pipeline_handles_seen) { + auto pipeline_details = renodx::utils::shader::GetPipelineShaderDetails(device, {pipeline_handle}); + if (!pipeline_details.has_value()) continue; + + if (!pipeline_details->tag.has_value()) { + pipeline_details->tag = ""; + auto result = renodx::utils::trace::GetDebugName(device->get_api(), pipeline_handle); + if (result.has_value()) { + pipeline_details->tag = result.value(); + } + } -void AutoLoadShaders() { - // Copy the "pipelines_to_reload_copy" so we don't have to lock "s_mutex_loading" all the times - std::unordered_set pipelines_to_reload_copy; - { - const std::lock_guard lock_loading(s_mutex_loading); - if (pipelines_to_reload.empty()) { - thread_auto_loading_running = false; - return; - } - pipelines_to_reload_copy = pipelines_to_reload; - pipelines_to_reload.clear(); - } - LoadCustomShaders(pipelines_to_reload_copy, !PRECOMPILE_CUSTOM_SHADERS); - thread_auto_loading_running = false; -} + for (auto& [subobject_index, shader_hash] : pipeline_details->shader_hashes_by_index) { + ++row_index; // Count rows regardless of tree node state + if (draw_node_open) { + auto& shader_details = data.GetShaderDetails(shader_hash); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + auto bullet_flags = tree_node_flags | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen + | setting_row_selection.GetTreeNodeFlag(row_index); + + ImGui::PushID(row_index); + if (!shader_details.program_version.has_value()) { + try { + auto shader_data = pipeline_details->GetShaderData(shader_hash, subobject_index); + if (!shader_data.has_value()) throw std::exception("Failed to get shader data"); + shader_details.program_version = renodx::utils::shader::compiler::DecodeShaderVersion(shader_data.value()); + } catch (const std::exception& e) { + reshade::log_message(reshade::log_level::error, e.what()); + } + } + // Fallback to subobject + if (!shader_details.program_version.has_value()) { + static std::unordered_map mapping = { + {reshade::api::pipeline_subobject_type::vertex_shader, "vs"}, + {reshade::api::pipeline_subobject_type::pixel_shader, "ps"}, + {reshade::api::pipeline_subobject_type::compute_shader, "cs"}, + }; + if (auto pair = mapping.find(pipeline_details->subobjects[subobject_index].type); pair != mapping.end()) { + ImGui::TreeNodeEx("", bullet_flags, "%s", pair->second); + } else { + ImGui::TreeNodeEx("", bullet_flags, "%s", "Unknown Shader"); + } + } else { + ImGui::TreeNodeEx("", bullet_flags, "%s_%d_%d", + shader_details.program_version->GetKindAbbr(), + shader_details.program_version->GetMajor(), + shader_details.program_version->GetMinor()); + } + ImGui::PopID(); + if (ImGui::IsItemClicked()) { + setting_row_selection = { + .row_id = row_index, + .pipeline_handle = pipeline_handle, + .shader_hash = shader_hash, + }; + + ImGui::SetItemDefaultFocus(); + } -// @see https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html -void OnRegisterOverlay(reshade::api::effect_runtime* runtime) { - const bool refresh_cloned_pipelines = cloned_pipelines_changed.exchange(false); + ImGui::TableNextColumn(); // Reference + ImGui::Text("0x%08X", shader_hash); + + ImGui::TableNextColumn(); // Name + if (shader_details.custom_shader.has_value()) { + // Has custom shader file + std::string file_alias; + if (shader_details.custom_shader->is_hlsl) { + static const auto CHARACTERS_TO_REMOVE_FROM_END = std::string("0x12345678.xx_x_x.hlsl").length(); + auto filename = shader_details.custom_shader->file_path.filename().string(); + filename.erase(filename.length() - min(CHARACTERS_TO_REMOVE_FROM_END, filename.length())); + if (filename.ends_with("_")) { + filename.erase(filename.length() - 1); + } + file_alias.assign(filename); + } + if (shader_details.custom_shader->IsCompilationOK()) { + if (file_alias.empty()) { + ImGui::TextColored(ImVec4(0, 255, 0, 128), "Custom"); + } else { + ImGui::TextColored(ImVec4(0, 255, 0, 255), "%s", file_alias.c_str()); + } + } else { + if (file_alias.empty()) { + ImGui::TextColored(ImVec4(255, 0, 0, 128), "Custom"); + } else { + ImGui::TextColored(ImVec4(255, 0, 0, 255), "%s", file_alias.c_str()); + } + } + } else { + ImGui::TextUnformatted(""); + } - if (ImGui::Button("Trace")) { - trace_scheduled = true; - } - ImGui::SameLine(); - ImGui::Checkbox("List Unique Shaders Only", &trace_list_unique_shaders_only); - - ImGui::SameLine(); - ImGui::Checkbox("Ignore Vertex Shaders", &trace_ignore_vertex_shaders); - - ImGui::SameLine(); - ImGui::PushID("##DumpShaders"); - if (ImGui::Button(std::format("Dump Shaders ({})", shader_cache_count).c_str())) { - const std::lock_guard lock_dumping(s_mutex_dumping); - // Force dump everything here - for (auto shader : shader_cache) { - DumpShader(shader.first, true); - } - shaders_to_dump.clear(); - } - ImGui::PopID(); + ImGui::TableNextColumn(); // Tag + if (!pipeline_details->tag->empty()) { + ImGui::TextUnformatted(pipeline_details->tag->c_str()); + } - ImGui::SameLine(); - ImGui::PushID("##AutoDumpCheckBox"); - if (ImGui::Checkbox("Auto Dump", &auto_dump)) { - if (!auto_dump && thread_auto_dumping.joinable()) { - thread_auto_dumping.join(); - } - } - ImGui::PopID(); - - if (ImGui::Button(std::format("Unload Shaders ({})", cloned_pipeline_count).c_str())) { - needs_unload_shaders = true; - // For consistency, disable live reload and auto load, it makes no sense for them to be on if we have unloaded shaders - if (live_reload) { - live_reload = false; - needs_live_reload_update = true; - } - if (auto_load) { - auto_load = false; - if (thread_auto_loading.joinable()) { - thread_auto_loading.join(); - } - } - const std::lock_guard lock(s_mutex_loading); - pipelines_to_reload.clear(); - } - ImGui::SameLine(); - if (ImGui::Button("Load Shaders")) { - needs_unload_shaders = false; - needs_load_shaders = true; - const std::lock_guard lock(s_mutex_loading); - pipelines_to_reload.clear(); - } + ImGui::TableNextColumn(); // Index + ImGui::Text("%03d", draw_index); + } + } + } + int render_target_index = 0; + for (auto& render_target : draw_details.render_targets) { + ++row_index; + bool rtv_node_open = false; + if (draw_node_open) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(row_index); + rtv_node_open = ImGui::TreeNodeEx("", tree_node_flags | ImGuiTreeNodeFlags_DefaultOpen, "RTV%d", render_target_index++); + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::Text("0x%016llX", render_target.resource_view.handle); + + ImGui::TableNextColumn(); + std::stringstream s; + s << render_target.resource_view_desc.format; + if (render_target.is_swapchain) { + ImGui::TextColored(ImVec4(0, 255, 0, 255), "%s", s.str().c_str()); + } else { + ImGui::TextUnformatted(s.str().c_str()); + } - ImGui::SameLine(); - ImGui::PushID("##AutoLoadCheckBox"); - if (ImGui::Checkbox("Auto Load", &auto_load)) { - if (!auto_load && thread_auto_loading.joinable()) { - thread_auto_loading.join(); - } - const std::lock_guard lock(s_mutex_loading); - pipelines_to_reload.clear(); - } - ImGui::PopID(); - - ImGui::SameLine(); - ImGui::PushID("##LiveReloadCheckBox"); - if (ImGui::Checkbox("Live Reload", &live_reload)) { - needs_live_reload_update = true; - const std::lock_guard lock(s_mutex_loading); - pipelines_to_reload.clear(); - } - ImGui::PopID(); + ImGui::TableNextColumn(); + if (!render_target.resource_view_tag.empty()) { + ImGui::TextUnformatted(render_target.resource_view_tag.c_str()); + } - ImGui::Text("Cached Shaders Size: %d", shader_cache_size); - static int32_t selected_index = -1; - bool changed_selected = false; - if (ImGui::BeginTabBar("##MyTabBar", ImGuiTabBarFlags_None)) { - ImGui::PushID("##ShadersTab"); - auto handle_shader_tab = ImGui::BeginTabItem(std::format("Traced Shaders ({})", trace_count).c_str()); - ImGui::PopID(); - if (handle_shader_tab) { - if (ImGui::BeginChild("HashList", ImVec2(100, -FLT_MIN), ImGuiChildFlags_ResizeX)) { - if (ImGui::BeginListBox("##HashesListbox", ImVec2(-FLT_MIN, -FLT_MIN))) { - if (!trace_running) { - const std::lock_guard lock(s_mutex_generic); - for (auto index = 0; index < trace_count; index++) { - auto pipeline_handle = trace_pipeline_handles.at(index); - const bool is_selected = (selected_index == index); - const auto pipeline_pair = pipeline_cache_by_pipeline_handle.find(pipeline_handle); - const bool is_valid = pipeline_pair != pipeline_cache_by_pipeline_handle.end() && pipeline_pair->second != nullptr; - std::stringstream name; - auto text_color = IM_COL32(255, 255, 255, 255); - - if (is_valid) { - const auto* pipeline = pipeline_pair->second; - - name << std::setfill('0') << std::setw(3) << index << std::setw(0); - for (auto shader_hash : pipeline->shader_hashes) { - name << " - " << PRINT_CRC32(shader_hash); + ImGui::TableNextColumn(); // Index + ImGui::Text("%03d", draw_index); } + ++row_index; + if (rtv_node_open) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + auto bullet_flags = tree_node_flags | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen + | setting_row_selection.GetTreeNodeFlag(row_index); + ImGui::PushID(row_index); + ImGui::TreeNodeEx("", bullet_flags, "Resource"); + ImGui::PopID(); + if (ImGui::IsItemClicked()) { + setting_row_selection = { + .row_id = row_index, + .resource_handle = render_target.resource.handle, + }; + ImGui::SetItemDefaultFocus(); + } - // Pick the default color by shader type - if (pipeline->HasVertexShader()) { - text_color = IM_COL32(255, 255, 0, 255); // Yellow - } else if (pipeline->HasComputeShader()) { - text_color = IM_COL32(128, 0, 128, 255); // Purple - } + ImGui::TableNextColumn(); + ImGui::Text("0x%016llX", render_target.resource.handle); - const std::lock_guard lock_loading(s_mutex_loading); - const auto* custom_shader = !pipeline->shader_hashes.empty() ? custom_shaders_cache[pipeline->shader_hashes[0]] : nullptr; - - // Find if the shader has been modified - if (pipeline->cloned) { - // For now just force picking the first shader linked to the pipeline, there should always only be one (?) - if (custom_shader != nullptr && custom_shader->is_hlsl && !custom_shader->file_path.empty()) { - name << "* - "; - - // TODO: add support for more name variations - static const std::string FULL_TEMPLATE_NAME = "0x12345678.xx_x_x.hlsl"; - static const auto CHARACTERS_TO_REMOVE_FROM_END = FULL_TEMPLATE_NAME.length(); - auto filename_string = custom_shader->file_path.filename().string(); - filename_string.erase(filename_string.length() - min(CHARACTERS_TO_REMOVE_FROM_END, filename_string.length())); - if (filename_string.ends_with("_")) { - filename_string.erase(filename_string.length() - 1); - } - name << filename_string; + ImGui::TableNextColumn(); + std::stringstream s; + s << render_target.resource_desc.texture.format; + + if (render_target.is_swapchain) { + ImGui::TextColored(ImVec4(0, 255, 0, 255), "%s", s.str().c_str()); } else { - name << "*"; + ImGui::TextUnformatted(s.str().c_str()); } - text_color = IM_COL32(0, 255, 0, 255); - } - // Highlight loading error - if (custom_shader != nullptr && !custom_shader->compilation_error.empty()) { - text_color = IM_COL32(255, 0, 0, 255); - } - } else { - text_color = IM_COL32(255, 0, 0, 255); - name << " - ERROR: CANNOT FIND PIPELINE"; - } + ImGui::TableNextColumn(); + if (!render_target.resource_tag.empty()) { + ImGui::TextUnformatted(render_target.resource_tag.c_str()); + } - ImGui::PushStyleColor(ImGuiCol_Text, text_color); - if (ImGui::Selectable(name.str().c_str(), is_selected)) { - selected_index = index; - changed_selected = true; + ImGui::TableNextColumn(); // Index + ImGui::Text("%03d", draw_index); + + ImGui::TreePop(); + } } - ImGui::PopStyleColor(); - if (is_selected) { - ImGui::SetItemDefaultFocus(); + if (draw_node_open) { + ImGui::TreePop(); } + ++row_index; + ++draw_index; } - } else { - selected_index = max(selected_index, trace_count - 1); } - ImGui::EndListBox(); - } + + ImGui::EndTable(); + } // BeginTable + ImGui::EndChild(); - } + } // BeginChild ##DrawList ImGui::SameLine(); if (ImGui::BeginChild("##ShaderDetails", ImVec2(0, 0))) { - ImGui::BeginDisabled(selected_index == -1); if (ImGui::BeginTabBar("##ShadersCodeTab", ImGuiTabBarFlags_None)) { - const bool open_disassembly_tab_item = ImGui::BeginTabItem("Disassembly"); - static bool opened_disassembly_tab_item = false; - if (open_disassembly_tab_item) { - static std::string disasm_string; - if (selected_index >= 0 && trace_pipeline_handles.size() >= selected_index + 1 && (changed_selected || opened_disassembly_tab_item != open_disassembly_tab_item)) { - const auto pipeline_handle = trace_pipeline_handles.at(selected_index); - const std::lock_guard lock(s_mutex_generic); - if (auto pipeline_pair = pipeline_cache_by_pipeline_handle.find(pipeline_handle); pipeline_pair != pipeline_cache_by_pipeline_handle.end() && pipeline_pair->second != nullptr) { - const std::lock_guard lock_dumping(s_mutex_dumping); - auto* cache = (!pipeline_pair->second->shader_hashes.empty() && shader_cache.contains(pipeline_pair->second->shader_hashes[0])) ? shader_cache[pipeline_pair->second->shader_hashes[0]] : nullptr; - if ((cache != nullptr) && cache->disasm.empty()) { - auto disasm_code = renodx::utils::shader::compiler::DisassembleShader(cache->data, cache->size); - if (disasm_code.has_value()) { - cache->disasm.assign(disasm_code.value()); - } else { - cache->disasm.assign("DECOMPILATION FAILED"); + if (ImGui::BeginTabItem("Disassembly")) { + if (ImGui::BeginChild("DisassemblyCode")) { + std::string disassembly_string; + bool failed = false; + if (setting_row_selection.shader_hash != 0) { + auto shader_details = data.GetShaderDetails(setting_row_selection.shader_hash); + if (std::holds_alternative(shader_details.disassembly)) { + // Never disassembled + try { + auto pipeline_details = renodx::utils::shader::GetPipelineShaderDetails(device, {setting_row_selection.pipeline_handle}); + if (!pipeline_details.has_value()) throw std::exception("Shader blob not found"); + auto shader_data = pipeline_details->GetShaderData(setting_row_selection.shader_hash); + if (!shader_data.has_value()) throw std::exception("Invalid shader selection"); + shader_details.disassembly = renodx::utils::shader::compiler::DisassembleShader(shader_data.value()); + } catch (std::exception& e) { + shader_details.disassembly = e; } } - disasm_string.assign(cache ? cache->disasm : ""); - } - } - if (ImGui::BeginChild("DisassemblyCode")) { + if (std::holds_alternative(shader_details.disassembly)) { + disassembly_string.assign(std::get(shader_details.disassembly).what()); + failed = true; + } else { + disassembly_string.assign(std::get(shader_details.disassembly)); + } + } + if (failed) { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(192, 0, 0, 255)); + } ImGui::InputTextMultiline( "##disassemblyCode", - const_cast(disasm_string.c_str()), - disasm_string.length(), - ImVec2(-FLT_MIN, -FLT_MIN), + const_cast(disassembly_string.c_str()), + disassembly_string.length(), + ImVec2(-4, -4), ImGuiInputTextFlags_ReadOnly); + if (failed) { + ImGui::PopStyleColor(); + } ImGui::EndChild(); // DisassemblyCode - } + } // BeginChild DisassemblyCode ImGui::EndTabItem(); // Disassembly - } - opened_disassembly_tab_item = open_disassembly_tab_item; + } // BeginTabItem Disassembly ImGui::PushID("##LiveTabItem"); const bool open_live_tab_item = ImGui::BeginTabItem("Live"); ImGui::PopID(); static bool opened_live_tab_item = false; if (open_live_tab_item) { - static std::string hlsl_string; - static bool hlsl_error = false; - if (selected_index >= 0 && trace_pipeline_handles.size() >= selected_index + 1 && (changed_selected || opened_live_tab_item != open_live_tab_item || refresh_cloned_pipelines)) { - bool hlsl_set = false; - auto pipeline_handle = trace_pipeline_handles.at(selected_index); - - const std::lock_guard lock(s_mutex_generic); - if ( - auto pipeline_pair = pipeline_cache_by_pipeline_handle.find(pipeline_handle); - pipeline_pair != pipeline_cache_by_pipeline_handle.end() && pipeline_pair->second != nullptr) { - const auto* pipeline = pipeline_pair->second; - const std::lock_guard lock_loading(s_mutex_loading); - const auto* custom_shader = !pipeline->shader_hashes.empty() ? custom_shaders_cache[pipeline->shader_hashes[0]] : nullptr; - // If the custom shader has a compilation error, print that, otherwise read the file text - if (custom_shader != nullptr && !custom_shader->compilation_error.empty()) { - hlsl_string = custom_shader->compilation_error; - hlsl_error = true; - hlsl_set = true; - } else if (custom_shader != nullptr && custom_shader->is_hlsl && !custom_shader->file_path.empty()) { - auto result = ReadTextFile(custom_shader->file_path); - if (result.has_value()) { - hlsl_string.assign(result.value()); - hlsl_error = false; - hlsl_set = true; - } else { - hlsl_string.assign("FAILED TO READ FILE"); - hlsl_error = true; - hlsl_set = true; + std::string live_string; + bool failed = false; + if (setting_row_selection.shader_hash != 0) { + auto shader_details = data.GetShaderDetails(setting_row_selection.shader_hash); + if (shader_details.custom_shader.has_value()) { + if (!shader_details.custom_shader->IsCompilationOK()) { + live_string = shader_details.custom_shader->GetCompilationException().what(); + } else if (shader_details.custom_shader->is_hlsl) { + try { + live_string = renodx::utils::path::ReadTextFile(shader_details.custom_shader->file_path); + } catch (std::exception& e) { + live_string = e.what(); + failed = true; } } } - if (!hlsl_set) { - hlsl_string.clear(); - } + if (ImGui::BeginChild("LiveCode")) { + if (failed) { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(192, 0, 0, 255)); + } + ImGui::InputTextMultiline( + "##liveCode", + const_cast(live_string.c_str()), + live_string.length(), + ImVec2(-4, -4)); + if (failed) { + ImGui::PopStyleColor(); + } + ImGui::EndChild(); + } // BeginChild LiveCode } - opened_live_tab_item = open_live_tab_item; - // Attemping this breaks ImGui - // if (ImGui::BeginChild("##LiveCodeToolbar", ImVec2(-FLT_MIN, 0))) { - // ImGui::EndChild(); - // } + ImGui::EndTabItem(); + } // open_live_tab_item - if (ImGui::BeginChild("LiveCode")) { - ImGui::PushStyleColor(ImGuiCol_Text, hlsl_error ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 255, 255)); - ImGui::InputTextMultiline( - "##liveCode", - const_cast(hlsl_string.c_str()), - hlsl_string.length(), - ImVec2(-FLT_MIN, -FLT_MIN)); - ImGui::PopStyleColor(); - ImGui::EndChild(); + ImGui::EndTabBar(); + } // BeginTabBar ShadersCodeTab + + ImGui::EndChild(); // ##ShaderDetails + } // BeginChild ##ShaderDetails + ImGui::EndTabItem(); + } // handle_capture_tab + + ImGui::PushID("##ShaderDefinesTab"); + auto handle_shader_defines_tab = ImGui::BeginTabItem("Shader Defines"); + ImGui::PopID(); + if (handle_shader_defines_tab) { + if (ImGui::BeginChild("##ShaderDefinesChild", ImVec2(0, 0))) { + static ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_SpanFullWidth; + if (ImGui::BeginTable( + "##ShaderDefinesTable", + 4, + ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable + | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY, + ImVec2(-4, -4))) { + static const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Options", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + int row_index = 0; + int cell_index_id = 0x8000; + static std::vector shader_define_remove_indexes; + + for (auto& [key, value] : setting_shader_defines) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(cell_index_id++); + char temp_key[128] = ""; + key.copy(temp_key, 128); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::InputText("", temp_key, 128, ImGuiInputTextFlags_CharsNoBlank)) { + key.assign(temp_key); + setting_shader_defines_changed = true; } - ImGui::EndTabItem(); // Live + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::PushID(cell_index_id++); + char temp_value[128] = ""; + value.copy(temp_value, 128); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::InputText("", temp_value, 128, ImGuiInputTextFlags_CharsNoBlank)) { + value.assign(temp_value); + setting_shader_defines_changed = true; + } + ImGui::PopID(); + + ImGui::TableNextColumn(); + ImGui::PushID(cell_index_id++); + if (ImGui::Button("Remove")) { + setting_shader_defines_changed = true; + shader_define_remove_indexes.push_back(row_index); + } + ImGui::PopID(); + row_index++; + } + while (shader_define_remove_indexes.size() != 0) { + auto remove_index = shader_define_remove_indexes.rbegin()[0]; + setting_shader_defines.erase(setting_shader_defines.begin() + remove_index); + shader_define_remove_indexes.pop_back(); } - ImGui::PushID("##SettingsTabItem"); - const bool open_settings_tab_item = ImGui::BeginTabItem("Settings"); + ImGui::TableNextRow(); + ImGui::BeginDisabled(); + ImGui::TableNextColumn(); + ImGui::PushID(cell_index_id++); + ImGui::SetNextItemWidth(-FLT_MIN); + char temp_key[128]; + ImGui::InputText("", temp_key, 128, ImGuiInputTextFlags_CharsNoBlank); ImGui::PopID(); - if (open_settings_tab_item && selected_index >= 0 && trace_pipeline_handles.size() >= selected_index + 1) { - auto pipeline_handle = trace_pipeline_handles.at(selected_index); - const std::lock_guard lock(s_mutex_generic); - if (auto pipeline_pair = pipeline_cache_by_pipeline_handle.find(pipeline_handle); pipeline_pair != pipeline_cache_by_pipeline_handle.end() && pipeline_pair->second != nullptr) { - bool test_pipeline = pipeline_pair->second->test; - // TODO: skip showing the setting for vertex shaders - if (ImGui::BeginChild("Settings")) { - ImGui::Checkbox("Test Shader (skips drawing, or draws black)", &test_pipeline); - ImGui::EndChild(); - } - pipeline_pair->second->test = test_pipeline; - } - ImGui::EndTabItem(); // Settings + ImGui::TableNextColumn(); + ImGui::PushID(cell_index_id++); + char temp_value[128] = ""; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::InputText("", temp_value, 128, ImGuiInputTextFlags_CharsNoBlank); + ImGui::PopID(); + ImGui::EndDisabled(); + + ImGui::TableNextColumn(); + ImGui::PushID(cell_index_id++); + if (ImGui::Button("Add")) { + setting_shader_defines.emplace_back(); } + ImGui::PopID(); - ImGui::EndTabBar(); // ShadersCodeTab - } - ImGui::EndDisabled(); - ImGui::EndChild(); // ##ShaderDetails - } - ImGui::EndTabItem(); // Shaders - } + ImGui::EndTable(); + } // ShaderDefinesTable -#if 0 // TODO: implement - if (ImGui::BeginTabItem("Events")) { - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Resources")) { - ImGui::EndTabItem(); - } -#endif - if (ImGui::BeginTabItem("Shader Defines")) { - // TODO: make this dynamic with + and - buttons - static std::string defines_titles[MAX_SHADER_DEFINES * 2]; - static char defines_text[MAX_SHADER_DEFINES * 2][50]; - - for (int i = 0; i < (MAX_SHADER_DEFINES * 2) - 1; i += 2) { - if (defines_titles[i].empty()) { - defines_titles[i] = "Define " + std::to_string(i / 2) + " Name"; - defines_titles[i + 1] = "Define " + std::to_string(i / 2) + " Value"; - } - // ImGUI doesn't work with std::string data, it seems to need c style char arrays. - ImGui::PushID(defines_titles[i].data()); - ImGui::InputTextWithHint("", defines_titles[i].data(), &defines_text[i][0], IM_ARRAYSIZE(defines_text[i]), ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AlwaysOverwrite); - ImGui::PopID(); - ImGui::SameLine(); - ImGui::PushID(defines_titles[i + 1].data()); - ImGui::InputTextWithHint("", defines_titles[i + 1].data(), &defines_text[i + 1][0], IM_ARRAYSIZE(defines_text[i + 1]), ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AlwaysOverwrite); - ImGui::PopID(); - shader_defines[i] = &defines_text[i][0]; - shader_defines[i + 1] = &defines_text[i + 1][0]; + ImGui::EndChild(); } ImGui::EndTabItem(); - } + } // handle_shader_defines_tab ImGui::EndTabBar(); - } + } // BeginTabBar MyTabBar } -void Init() { - // Add all the shaders we have already dumped to the dumped list to avoid live re-dumping them - dumped_shaders.clear(); - auto dump_path = GetShaderPath(); - if (std::filesystem::exists(dump_path)) { - dump_path /= ".\\dump"; - if (std::filesystem::exists(dump_path)) { - const std::lock_guard lock_dumping(s_mutex_dumping); - for (const auto& entry : std::filesystem::directory_iterator(dump_path)) { - if (!entry.is_regular_file()) continue; - const auto& entry_path = entry.path(); - if (entry_path.extension() != ".cso") continue; - const auto& entry_path_string = entry_path.filename().string(); - if (entry_path_string.starts_with("0x") && entry_path_string.length() > 2 + 8) { - const std::string hash = entry_path_string.substr(2, 8); - try { - dumped_shaders.emplace(std::stoul(hash, nullptr, 16)); - } catch (const std::exception& e) { - continue; - } - } - } +void OnPresent( + reshade::api::command_queue* queue, + reshade::api::swapchain* swapchain, + const reshade::api::rect* source_rect, + const reshade::api::rect* dest_rect, + uint32_t dirty_rect_count, + const reshade::api::rect* dirty_rects) { + auto* device = swapchain->get_device(); + if (setting_shader_defines_changed) { + renodx::utils::shader::compiler::watcher::SetShaderDefines(setting_shader_defines); + if (setting_auto_compile) { + renodx::utils::shader::compiler::watcher::RequestCompile(); } + setting_shader_defines_changed = false; } - - // Pre-allocate shader defines and cbuffers - shader_defines.assign(MAX_SHADER_DEFINES * 2, ""); - - // Pre-load all shaders to minimize the wait before replacing them after they are found in game ("auto_load"), - // and to fill the list of shaders we customized, so we can know which ones we need replace on the spot. - if (PRECOMPILE_CUSTOM_SHADERS && !thread_auto_loading_running) { - thread_auto_loading_running = true; - static std::binary_semaphore async_shader_compilation_semaphore{0}; - thread_auto_loading = std::thread([] { - // We need to lock this mutex for the whole async shader loading, so that if the game starts loading shaders, we can already see if we have a custom version and live load it ("live_load"), otherwise the "custom_shaders_cache" list would be incomplete - const std::lock_guard lock(s_mutex_loading); - // This is needed to make sure this thread locks "s_mutex_loading" before any other function could - async_shader_compilation_semaphore.release(); - CompileCustomShaders(); - thread_auto_loading_running = false; - }); - async_shader_compilation_semaphore.acquire(); - } -} - -void Uninit() { - if (thread_auto_dumping.joinable()) { - thread_auto_dumping.join(); + if (setting_live_reload) { + PerformShaderReload(swapchain->get_device()); } - if (thread_auto_loading.joinable()) { - thread_auto_loading.join(); + if (setting_auto_dump) { + renodx::utils::shader::dump::DumpAllPending(); } + + DeviceData::StopSnapshot(); } + } // namespace // NOLINTBEGIN(readability-identifier-naming) @@ -2653,101 +831,50 @@ extern "C" __declspec(dllexport) const char* DESCRIPTION = "RenoDX DevKit Module // NOLINTEND(readability-identifier-naming) -extern "C" __declspec(dllexport) bool AddonInit(HMODULE addon_module, HMODULE reshade_module) { - Init(); - return true; -} -extern "C" __declspec(dllexport) void AddonUninit(HMODULE addon_module, HMODULE reshade_module) { - Uninit(); -} - BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) { switch (fdw_reason) { case DLL_PROCESS_ATTACH: if (!reshade::register_addon(h_module)) return FALSE; renodx::utils::descriptor::Use(fdw_reason); + renodx::utils::shader::Use(fdw_reason); + renodx::utils::shader::dump::Use(fdw_reason); + renodx::utils::trace::Use(fdw_reason); + renodx::utils::swapchain::Use(fdw_reason); + + renodx::utils::shader::use_replace_async = true; reshade::register_event(OnInitDevice); reshade::register_event(OnDestroyDevice); - reshade::register_event(OnInitSwapchain); - - reshade::register_event(OnCreatePipelineLayout); - reshade::register_event(OnInitPipelineLayout); - - reshade::register_event(OnInitPipeline); - reshade::register_event(OnDestroyPipeline); - + reshade::register_event(OnInitCommandList); + reshade::register_event(OnDestroyCommandList); reshade::register_event(OnBindPipeline); - reshade::register_event(OnBindPipelineStates); - - reshade::register_event(OnInitResource); - reshade::register_event(OnDestroyResource); - reshade::register_event(OnInitResourceView); - reshade::register_event(OnDestroyResourceView); - - reshade::register_event(OnPushDescriptors); - reshade::register_event(OnBindDescriptorTables); - reshade::register_event(OnCopyDescriptorTables); - reshade::register_event(OnUpdateDescriptorTables); - reshade::register_event(OnPushConstants); - - reshade::register_event(OnClearRenderTargetView); - reshade::register_event(OnClearUnorderedAccessViewUint); - - reshade::register_event(OnMapBufferRegion); - reshade::register_event(OnMapTextureRegion); - reshade::register_event(OnDraw); - reshade::register_event(OnDispatch); reshade::register_event(OnDrawIndexed); reshade::register_event(OnDrawOrDispatchIndirect); - reshade::register_event(OnBindRenderTargetsAndDepthStencil); - - reshade::register_event(OnCopyTextureRegion); - reshade::register_event(OnCopyTextureToBuffer); - reshade::register_event(OnCopyBufferToTexture); - reshade::register_event(OnResolveTextureRegion); - - reshade::register_event(OnCopyResource); - - reshade::register_event(OnBarrier); - - reshade::register_event(OnReshadePresent); + reshade::register_event(OnDispatch); + reshade::register_event(OnPresent); reshade::register_overlay("RenoDX (DevKit)", OnRegisterOverlay); break; case DLL_PROCESS_DETACH: - renodx::utils::descriptor::Use(fdw_reason); - reshade::unregister_event(OnInitSwapchain); - reshade::unregister_event(OnInitPipelineLayout); - - reshade::unregister_event(OnInitPipeline); - reshade::unregister_event(OnDestroyPipeline); + renodx::utils::descriptor::Use(fdw_reason); + reshade::unregister_event(OnInitDevice); + reshade::unregister_event(OnDestroyDevice); reshade::unregister_event(OnBindPipeline); - - reshade::unregister_event(OnCopyTextureRegion); - - reshade::unregister_event(OnReshadePresent); + reshade::unregister_event(OnDraw); + reshade::unregister_event(OnDrawIndexed); + reshade::unregister_event(OnDrawOrDispatchIndirect); + reshade::unregister_event(OnDispatch); + reshade::unregister_event(OnPresent); reshade::unregister_overlay("RenoDX (DevKit)", OnRegisterOverlay); reshade::unregister_addon(h_module); - if (thread_auto_dumping.joinable()) { - thread_auto_dumping.detach(); - while (thread_auto_dumping_running) { - } - } - if (thread_auto_loading.joinable()) { - thread_auto_loading.detach(); - while (thread_auto_loading_running) { - } - } - break; } diff --git a/src/games/p5r/addon.cpp b/src/games/p5r/addon.cpp index ee1a5ae1..b31b605c 100644 --- a/src/games/p5r/addon.cpp +++ b/src/games/p5r/addon.cpp @@ -304,13 +304,14 @@ bool OnDrawIndexed( auto& shader_state = cmd_list->get_private_data(); - if (shader_state.pixel_shader_hash == 0xC6D14699) return false; // Video - if (shader_state.pixel_shader_hash == 0xB6E26AC7) { + auto pixel_shader_hash = shader_state.GetCurrentPixelShaderHash(); + if (pixel_shader_hash == 0xC6D14699) return false; // Video + if (pixel_shader_hash == 0xB6E26AC7) { g_completed_render = true; return false; } if (!g_completed_render) return false; - if (!g_8bit_hashes.contains(shader_state.pixel_shader_hash)) return false; + if (!g_8bit_hashes.contains(pixel_shader_hash)) return false; auto& swapchain_state = cmd_list->get_private_data(); @@ -469,9 +470,9 @@ void OnInitPipeline( void OnBindPipeline( reshade::api::command_list* cmd_list, - reshade::api::pipeline_stage type, + reshade::api::pipeline_stage stages, reshade::api::pipeline pipeline) { - if (type != reshade::api::pipeline_stage::output_merger) return; + if (stages != reshade::api::pipeline_stage::output_merger) return; auto& data = cmd_list->get_private_data(); data.last_output_merger = pipeline; } diff --git a/src/games/p5r_archive/addon.cpp b/src/games/p5r_archive/addon.cpp index 86907d5b..2bfc3558 100644 --- a/src/games/p5r_archive/addon.cpp +++ b/src/games/p5r_archive/addon.cpp @@ -344,7 +344,7 @@ void OnBindRenderTargetsAndDepthStencil(reshade::api::command_list* cmd_list, ui if (after_tonemapping) { auto& shader_state = cmd_list->get_private_data(); - const uint32_t shader_hash = shader_state.pixel_shader_hash; + const uint32_t shader_hash = shader_state.GetCurrentPixelShaderHash(); if ((rtvs != nullptr) && rtvs->handle != 0) { if (IsUiShader(shader_hash)) { auto* device = cmd_list->get_device(); @@ -405,7 +405,7 @@ void OnBindPipeline(reshade::api::command_list* cmd_list, reshade::api::pipeline state.pipelines[type] = pipeline; auto& shader_state = cmd_list->get_private_data(); - const uint32_t shader_hash = shader_state.pixel_shader_hash; + const uint32_t shader_hash = shader_state.GetCurrentPixelShaderHash(); if (after_tonemapping && IsUiShader(shader_hash) && type == reshade::api::pipeline_stage::output_merger) { ClampAlpha(cmd_list); diff --git a/src/games/starfield/addon.cpp b/src/games/starfield/addon.cpp index e17582b3..596f5595 100644 --- a/src/games/starfield/addon.cpp +++ b/src/games/starfield/addon.cpp @@ -223,15 +223,17 @@ bool HandlePreDraw(reshade::api::command_list* cmd_list, bool is_dispatch = fals // 0x0a152bb1 (tonemapper) (r11g11b10 => rgb8a_unorm tRender) // 0x17FAB08F (sharpen?) (rgb8a_unorm tRender => rgb8a_unorm tComposite) // 0xe9d9e225 (ui) (rgb8a_unorm tUI => rgb8a_unorm tComposite) + + auto pixel_shader_hash = shader_state.GetCurrentPixelShaderHash(); if ( !is_dispatch - && (shader_state.pixel_shader_hash == 0x0a152bb1 // tonemapper - || shader_state.pixel_shader_hash == 0x054D0CB8 // tonemapper - || shader_state.pixel_shader_hash == 0x3B344832 // tonemapper - || shader_state.pixel_shader_hash == 0x17fab08f // sharpener - || shader_state.pixel_shader_hash == 0x32580F53 // movie - || shader_state.pixel_shader_hash == 0xe9d9e225 // ui - || shader_state.pixel_shader_hash == 0x0d5add1f // copy + && (pixel_shader_hash == 0x0a152bb1 // tonemapper + || pixel_shader_hash == 0x054D0CB8 // tonemapper + || pixel_shader_hash == 0x3B344832 // tonemapper + || pixel_shader_hash == 0x17fab08f // sharpener + || pixel_shader_hash == 0x32580F53 // movie + || pixel_shader_hash == 0xe9d9e225 // ui + || pixel_shader_hash == 0x0d5add1f // copy )) { auto& swapchain_state = cmd_list->get_private_data(); diff --git a/src/mods/shader.hpp b/src/mods/shader.hpp index 3308a39b..b83d3586 100644 --- a/src/mods/shader.hpp +++ b/src/mods/shader.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -30,8 +31,7 @@ namespace renodx::mods::shader { struct CustomShader { uint32_t crc32; - const uint8_t* code; - uint32_t code_size; + std::vector code; bool swap_chain_only; int32_t index = -1; }; @@ -39,10 +39,10 @@ struct CustomShader { using CustomShaders = std::unordered_map; // clang-format off -#define CustomSwapchainShader(crc32) { crc32, { crc32, _##crc32, sizeof(_##crc32), true } } -#define CustomShaderEntry(crc32) { crc32, { crc32, _##crc32, sizeof(_##crc32) } } -#define BypassShaderEntry(crc32) { crc32, { crc32, nullptr, 0 } } -#define CustomCountedShader(crc32, index) { crc32, { crc32, _##crc32, sizeof(_##crc32), false, ##index} } +#define CustomSwapchainShader(crc32) { crc32, { crc32, std::vector(_##crc32, _##crc32 + sizeof(_##crc32)), true } } +#define CustomShaderEntry(crc32) { crc32, { crc32, std::vector(_##crc32, _##crc32 + sizeof(_##crc32)) } } +#define BypassShaderEntry(crc32) { crc32, { crc32, std::vector(0) } } +#define CustomCountedShader(crc32, index) { crc32, { crc32, std::vector(_##crc32, _##crc32 + sizeof(_##crc32)), false, ##index} } // clang-format on static thread_local std::vector created_params; @@ -560,7 +560,7 @@ static bool HandlePreDraw(reshade::api::command_list* cmd_list, bool is_dispatch auto& device_data = device->get_private_data(); const std::unique_lock local_device_lock(device_data.mutex); - const auto& shader_state = cmd_list->get_private_data(); + auto& shader_state = renodx::utils::shader::GetCurrentState(cmd_list); float resource_tag = -1; @@ -572,7 +572,8 @@ static bool HandlePreDraw(reshade::api::command_list* cmd_list, bool is_dispatch } } - const uint32_t shader_hash = is_dispatch ? shader_state.compute_shader_hash : shader_state.pixel_shader_hash; + const uint32_t shader_hash = is_dispatch ? shader_state.GetCurrentComputeShaderHash() + : shader_state.GetCurrentPixelShaderHash(); auto custom_shader_info_pair = device_data.custom_shaders.find(shader_hash); if (custom_shader_info_pair == device_data.custom_shaders.end()) { @@ -687,26 +688,6 @@ static bool HandlePreDraw(reshade::api::command_list* cmd_list, bool is_dispatch shader_injection); } } - - // perform bind pipeline (replace shader) - if ( - auto pair = shader_device_state.shader_to_pipeline_replacement.find(shader_hash); - pair != shader_device_state.shader_to_pipeline_replacement.end()) { - // has replacement that can be bound; - const auto& [pipeline_handle, pipeline_type] = pair->second; -#ifdef DEBUG_LEVEL_1 - std::stringstream s; - s << "mods::shader::HandlePreDraw(binding pipeline: "; - s << PRINT_CRC32(shader_hash); - s << ", dispatch: " << (is_dispatch ? "true" : "false"); - s << ", handle: " << reinterpret_cast(pipeline_handle); - s << ", stage: " << shader_state.pipeline_stage; - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); -#endif - - cmd_list->bind_pipeline(shader_state.pipeline_stage, {pipeline_handle}); - } return false; } @@ -779,7 +760,7 @@ static void Use(DWORD fdw_reason, CustomShaders new_custom_shaders, T* new_injec if (!trace_unmodified_shaders) { for (const auto& [hash, shader] : (new_custom_shaders)) { if (shader.swap_chain_only) using_swap_chain_only = true; - if (shader.code_size == 0) using_bypass = true; + if (shader.code.empty()) using_bypass = true; if (shader.index != -1) using_counted_shaders = true; } } @@ -790,15 +771,15 @@ static void Use(DWORD fdw_reason, CustomShaders new_custom_shaders, T* new_injec if (force_pipeline_cloning || use_pipeline_layout_cloning) { for (const auto& [hash, shader] : (new_custom_shaders)) { - renodx::utils::shader::AddInitPipelineReplacement(hash, shader.code_size, shader.code); + renodx::utils::shader::AddRuntimeReplacement(hash, shader.code); } } else { for (const auto& [hash, shader] : (new_custom_shaders)) { - if (!shader.swap_chain_only && shader.code_size && shader.index == -1) { - renodx::utils::shader::AddCreatePipelineReplacement(hash, shader.code_size, shader.code); + if (!shader.swap_chain_only && !shader.code.empty() && shader.index == -1) { + renodx::utils::shader::AddCompileTimeReplacement(hash, shader.code); } - // Use Init as fallback - renodx::utils::shader::AddInitPipelineReplacement(hash, shader.code_size, shader.code); + // Use Runtime as fallback + renodx::utils::shader::AddRuntimeReplacement(hash, shader.code); } } diff --git a/src/utils/path.hpp b/src/utils/path.hpp new file mode 100644 index 00000000..bc05ea6b --- /dev/null +++ b/src/utils/path.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Carlos Lopez + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace renodx::utils::path { + +static std::filesystem::path GetOutputPath() { + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + wchar_t file_prefix[MAX_PATH] = L""; + GetModuleFileNameW(nullptr, file_prefix, ARRAYSIZE(file_prefix)); + + std::filesystem::path dump_path = file_prefix; + dump_path = dump_path.parent_path(); + dump_path /= "renodx-dev"; + return dump_path; +} + +static std::vector ReadBinaryFile(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + file.seekg(0, std::ios::end); + const size_t file_size = file.tellg(); + if (file_size == 0) return {}; + + auto result = std::vector(file_size); + file.seekg(0, std::ios::beg).read(reinterpret_cast(result.data()), file_size); + return result; +} + +static std::string ReadTextFile(const std::filesystem::path& path) { + auto data = ReadBinaryFile(path); + if (data.empty()) return ""; + return {reinterpret_cast(data.data()), data.size()}; +} + +static void WriteBinaryFile(const std::filesystem::path& path, std::span data) { + std::ofstream file(path, std::ios::binary); + file.write(reinterpret_cast(data.data()), data.size()); +} + +} // namespace renodx::utils::path \ No newline at end of file diff --git a/src/utils/pipeline.hpp b/src/utils/pipeline.hpp index f4da84b8..c98061bf 100644 --- a/src/utils/pipeline.hpp +++ b/src/utils/pipeline.hpp @@ -6,11 +6,12 @@ #pragma once #include +#include #include "./format.hpp" namespace renodx::utils::pipeline { -static reshade::api::pipeline_subobject* ClonePipelineSubObjects(uint32_t subobject_count, const reshade::api::pipeline_subobject* subobjects) { +static reshade::api::pipeline_subobject* ClonePipelineSubObjects(const reshade::api::pipeline_subobject* subobjects, uint32_t subobject_count) { auto* new_subobjects = new reshade::api::pipeline_subobject[subobject_count]; memcpy(new_subobjects, subobjects, sizeof(reshade::api::pipeline_subobject) * subobject_count); for (uint32_t i = 0; i < subobject_count; ++i) { @@ -126,4 +127,29 @@ static reshade::api::pipeline_subobject* ClonePipelineSubObjects(uint32_t subobj return new_subobjects; } +static void DestroyPipelineSubobjects(std::span subobjects) { + for (auto& subobject : subobjects) { + switch (subobject.type) { + case reshade::api::pipeline_subobject_type::vertex_shader: + case reshade::api::pipeline_subobject_type::compute_shader: + case reshade::api::pipeline_subobject_type::pixel_shader: { + auto* desc = static_cast(subobject.data); + free(const_cast(desc->code)); + desc->code = nullptr; + break; + } + default: + break; + } + + free(subobject.data); + subobject.data = nullptr; + } +} + +static void DestroyPipelineSubobjects(reshade::api::pipeline_subobject* subobjects, uint32_t subobject_count) { + DestroyPipelineSubobjects({subobjects, subobjects + subobject_count}); + delete[] subobjects; +} + } // namespace renodx::utils::pipeline diff --git a/src/utils/shader.hpp b/src/utils/shader.hpp index 56721ec5..cdd39531 100644 --- a/src/utils/shader.hpp +++ b/src/utils/shader.hpp @@ -9,79 +9,336 @@ #include #include #include +#include +#include #include +#include #include #include #include +#include #include #include #include #include "./format.hpp" +#include "./pipeline.hpp" namespace renodx::utils::shader { +static std::atomic_bool use_replace_on_bind = true; +static std::atomic_bool use_replace_async = false; +static std::atomic_size_t runtime_replacement_count = 0; + +static const auto COMPATIBLE_STAGES = { + reshade::api::pipeline_stage::vertex_shader, + reshade::api::pipeline_stage::pixel_shader, + reshade::api::pipeline_stage::compute_shader, +}; + +static const std::unordered_map COMPATIBLE_STAGE_TO_SUBOBJECT_TYPE = { + {reshade::api::pipeline_stage::vertex_shader, reshade::api::pipeline_subobject_type::vertex_shader}, + {reshade::api::pipeline_stage::pixel_shader, reshade::api::pipeline_subobject_type::pixel_shader}, + {reshade::api::pipeline_stage::compute_shader, reshade::api::pipeline_subobject_type::compute_shader}, +}; + +static const std::unordered_map COMPATIBLE_SUBOBJECT_TYPE_TO_STAGE = { + {reshade::api::pipeline_subobject_type::vertex_shader, reshade::api::pipeline_stage::vertex_shader}, + {reshade::api::pipeline_subobject_type::pixel_shader, reshade::api::pipeline_stage::pixel_shader}, + {reshade::api::pipeline_subobject_type::compute_shader, reshade::api::pipeline_stage::compute_shader}, +}; + +static std::vector GetShaderData(const reshade::api::pipeline_subobject& subobject) { + const reshade::api::shader_desc desc = *static_cast(subobject.data); + if (desc.code_size == 0) return {}; + return { + static_cast(desc.code), + static_cast(desc.code) + desc.code_size}; +} + +struct PipelineShaderDetails { + reshade::api::pipeline_layout layout = {0}; + std::vector subobjects; + std::unordered_set shader_hashes; + std::unordered_set replaced_shader_hashes; + std::unordered_map shader_hashes_by_index; + std::unordered_map shader_hashes_by_type; + std::unordered_map shader_hashes_by_stage; + std::optional replacement_pipeline = std::nullopt; + reshade::api::pipeline_stage replacement_stages = reshade::api::pipeline_stage{0}; + std::optional tag; + bool destroyed = false; + + std::optional> GetShaderData(uint32_t shader_hash, size_t index = -1) { + if (index == -1) { + for (auto& [item_index, item_shader_hash] : shader_hashes_by_index) { + if (item_shader_hash != shader_hash) continue; + index = item_index; + } + } + if (index == -1) return std::nullopt; + return renodx::utils::shader::GetShaderData(subobjects[index]); + } + + [[nodiscard]] bool NeedsReplacementPipeline() const { return !replacement_pipeline.has_value(); } + [[nodiscard]] bool HasReplacementPipeline() const { return replacement_pipeline.has_value() && replacement_pipeline->handle != 0u; } + [[nodiscard]] reshade::api::pipeline GetReplacementPipeline() const { return replacement_pipeline.value(); } + + PipelineShaderDetails() = default; + + PipelineShaderDetails( + reshade::api::pipeline_layout layout, + const reshade::api::pipeline_subobject* subobjects, + uint32_t subobject_count, + std::unordered_map shader_replacements_inverse) { + this->layout = layout; + for (uint32_t i = 0; i < subobject_count; ++i) { + const auto& subobject = subobjects[i]; + + auto pair = COMPATIBLE_SUBOBJECT_TYPE_TO_STAGE.find(subobject.type); + if (pair == COMPATIBLE_SUBOBJECT_TYPE_TO_STAGE.end()) continue; + const auto& stage = pair->second; + + auto shader_data = renodx::utils::shader::GetShaderData(subobject); + if (shader_data.empty()) continue; + // Pipeline has a shader with code. Hash code and check + + uint32_t shader_hash = compute_crc32(shader_data.data(), shader_data.size()); + + // Shader may have been replaced. Get original hash + if (auto pair = shader_replacements_inverse.find(shader_hash); + pair != shader_replacements_inverse.end()) { + shader_hash = pair->second; + replaced_shader_hashes.emplace(shader_hash); + } + + this->shader_hashes.emplace(shader_hash); + this->shader_hashes_by_index.emplace(i, shader_hash); + this->shader_hashes_by_type.emplace(subobject.type, shader_hash); + this->shader_hashes_by_stage.emplace(stage, shader_hash); + } + this->subobjects = std::vector(subobjects, subobjects + subobject_count); + } +}; + struct __declspec(uuid("908f0889-64d8-4e22-bd26-ded3dd0cef77")) DeviceData { - std::unordered_map pipeline_to_layout_map; - std::unordered_map> shader_to_pipeline_replacement; - std::unordered_map> pipeline_to_pipeline_replacement; - std::unordered_map> pipeline_to_shader_hash_map; + std::unordered_map> shader_pipelines; + std::unordered_map pipeline_shader_details; + std::unordered_set incompatible_pipelines; + std::unordered_set ignored_pipelines; + std::unordered_map shader_replacements; // Old => New std::unordered_map shader_replacements_inverse; // New => Old std::shared_mutex mutex; }; struct __declspec(uuid("8707f724-c7e5-420e-89d6-cc032c732d2d")) CommandListData { - uint32_t pixel_shader_hash = 0; - uint32_t compute_shader_hash = 0; reshade::api::pipeline_layout pipeline_layout = {0}; - reshade::api::pipeline_stage pipeline_stage = reshade::api::pipeline_stage::all; + std::unordered_map pending_replacements; + std::unordered_map current_shaders_hashes; + + [[nodiscard]] uint32_t GetCurrentShaderHash(reshade::api::pipeline_stage stage) const { + auto pair = current_shaders_hashes.find(stage); + if (pair == current_shaders_hashes.end()) return 0u; + return pair->second; + } + + [[nodiscard]] uint32_t GetCurrentVertexShaderHash() const { return GetCurrentShaderHash(reshade::api::pipeline_stage::vertex_shader); } + [[nodiscard]] uint32_t GetCurrentPixelShaderHash() const { return GetCurrentShaderHash(reshade::api::pipeline_stage::pixel_shader); } + [[nodiscard]] uint32_t GetCurrentComputeShaderHash() const { return GetCurrentShaderHash(reshade::api::pipeline_stage::compute_shader); } + + void ApplyReplacements(reshade::api::command_list* cmd_list) { + for (auto [stage, pipeline] : pending_replacements) { + cmd_list->bind_pipeline(stage, pipeline); + } + pending_replacements.clear(); + } }; +namespace internal { + static std::shared_mutex mutex; +static std::unordered_map> compile_time_replacements; +static std::unordered_map> runtime_replacements; +static std::unordered_set runtime_replacement_remove_queue; +} // namespace internal + +static bool BuildReplacementPipeline(reshade::api::device* device, PipelineShaderDetails& details) { + reshade::api::pipeline_subobject* replacement_subobjects = nullptr; + auto subobject_count = details.subobjects.size(); + + for (const auto& [index, shader_hash] : details.shader_hashes_by_index) { + // Avoid double-replacement + if (details.replaced_shader_hashes.contains(shader_hash)) { + std::stringstream s; + s << "utils::shader::BuildReplacementPipeline(bypassing "; + s << PRINT_CRC32(shader_hash); + s << ")"; + // reshade::log_message(reshade::log_level::debug, s.str().c_str()); + continue; + } + const std::shared_lock util_lock(internal::mutex); + if (auto new_shader_pair = internal::runtime_replacements.find(shader_hash); + new_shader_pair != internal::runtime_replacements.end()) { + auto& new_shader = new_shader_pair->second; + + if (replacement_subobjects == nullptr) { + replacement_subobjects = renodx::utils::pipeline::ClonePipelineSubObjects(details.subobjects.data(), subobject_count); + } + auto& subobject = replacement_subobjects[index]; + auto* desc = static_cast(subobject.data); + if (desc->code_size != 0u) { + free(const_cast(desc->code)); // Release clone's shader + } + + desc->code_size = new_shader.size(); + if (desc->code_size == 0) { + desc->code = nullptr; + } else { + desc->code = malloc(desc->code_size); + memcpy(const_cast(desc->code), new_shader.data(), desc->code_size); + } + std::stringstream s; + s << "utils::shader::BuildReplacementPipeline(replacing "; + s << PRINT_CRC32(shader_hash); + s << ")"; + // reshade::log_message(reshade::log_level::debug, s.str().c_str()); -static std::unordered_map> create_pipeline_replacements; -static std::unordered_map> init_pipeline_replacements; + if (auto pair = COMPATIBLE_SUBOBJECT_TYPE_TO_STAGE.find(subobject.type); + pair != COMPATIBLE_SUBOBJECT_TYPE_TO_STAGE.end()) { + details.replacement_stages |= pair->second; + } + } + } + + if (replacement_subobjects == nullptr) { + details.replacement_stages = static_cast(0); + details.replacement_pipeline = {0}; + return false; + } + + reshade::api::pipeline new_pipeline; + const bool built_pipeline_ok = device->create_pipeline( + details.layout, + subobject_count, + replacement_subobjects, + &new_pipeline); + renodx::utils::pipeline::DestroyPipelineSubobjects(replacement_subobjects, subobject_count); + if (built_pipeline_ok) { + details.replacement_pipeline = new_pipeline; + } else { + details.replacement_pipeline.reset(); + } + return built_pipeline_ok; +} -static void AddCreatePipelineReplacement( +static void AddCompileTimeReplacement( uint32_t shader_hash, - uint32_t size, - const uint8_t* data) { - const std::unique_lock lock(mutex); - create_pipeline_replacements[shader_hash] = std::vector(data, data + size); + const std::vector& shader_data) { + const std::unique_lock lock(internal::mutex); + internal::compile_time_replacements[shader_hash] = shader_data; } -static void RemoveCreatePipelineReplacement( +static void RemoveCompileTimeReplacement( uint32_t shader_hash) { - const std::unique_lock lock(mutex); - create_pipeline_replacements.erase(shader_hash); + const std::unique_lock lock(internal::mutex); + internal::compile_time_replacements.erase(shader_hash); } -static void AddInitPipelineReplacement( +static void AddRuntimeReplacement( uint32_t shader_hash, - uint32_t size, - const uint8_t* data) { - const std::unique_lock lock(mutex); - init_pipeline_replacements[shader_hash] = std::vector(data, data + size); + const std::vector& shader_data) { + const std::unique_lock lock(internal::mutex); + internal::runtime_replacements[shader_hash] = std::vector(shader_data); + runtime_replacement_count = internal::runtime_replacements.size(); } -static void RemoveInitPipelineReplacement( - uint32_t shader_hash) { - const std::unique_lock lock(mutex); - // Leak - init_pipeline_replacements.erase(shader_hash); +static std::optional GetPipelineShaderDetails(reshade::api::device* device, reshade::api::pipeline pipeline) { + auto& data = device->get_private_data(); + std::unique_lock device_lock(data.mutex); + if (auto details_pair = data.pipeline_shader_details.find(pipeline.handle); + details_pair != data.pipeline_shader_details.end()) { + return details_pair->second; + } + return std::nullopt; } -static uint32_t GetPixelShaderHash(reshade::api::command_list* cmd_list) { - auto& cmd_list_data = cmd_list->get_private_data(); - return cmd_list_data.pixel_shader_hash; +static void RemoveQueuedRuntimeReplacements(reshade::api::device* device) { + std::unique_lock global_lock(internal::mutex); + auto& data = device->get_private_data(); + std::unique_lock device_lock(data.mutex); + for (auto shader_hash : internal::runtime_replacement_remove_queue) { + if (auto entry = data.shader_pipelines.find(shader_hash); + entry != data.shader_pipelines.end()) { + auto& pipeline_set = entry->second; + for (auto pipeline_handle : pipeline_set) { + if (auto details_pair = data.pipeline_shader_details.find(pipeline_handle); + details_pair != data.pipeline_shader_details.end()) { + auto& details = details_pair->second; + if (details.HasReplacementPipeline()) { + device->destroy_pipeline(details.GetReplacementPipeline()); + details.replacement_pipeline.reset(); + } + data.ignored_pipelines.erase(pipeline_handle); + } + } + } + } + internal::runtime_replacement_remove_queue.clear(); } -static uint32_t GetComputeShaderHash(reshade::api::command_list* cmd_list) { - auto& cmd_list_data = cmd_list->get_private_data(); - return cmd_list_data.compute_shader_hash; +static void OnPresentRemoveShaders( + reshade::api::command_queue* queue, + reshade::api::swapchain* swapchain, + const reshade::api::rect* source_rect, + const reshade::api::rect* dest_rect, + uint32_t dirty_rect_count, + const reshade::api::rect* dirty_rects) { + auto* device = swapchain->get_device(); + RemoveQueuedRuntimeReplacements(device); + reshade::unregister_event(OnPresentRemoveShaders); +} + +static void RemoveAllRuntimeReplacements(reshade::api::device* device = nullptr) { + { + const std::unique_lock lock(internal::mutex); + for (auto& [shader_hash, data] : internal::runtime_replacements) { + internal::runtime_replacement_remove_queue.insert(shader_hash); + } + internal::runtime_replacements.clear(); + runtime_replacement_count = 0; + } + + if (device == nullptr) { + // Destroy cloned pipelines on next present + reshade::register_event(OnPresentRemoveShaders); + } else { + RemoveQueuedRuntimeReplacements(device); + } +} + +static void RemoveRuntimeReplacement( + uint32_t shader_hash, + reshade::api::device* device = nullptr) { + { + const std::unique_lock lock(internal::mutex); + internal::runtime_replacements.erase(shader_hash); + internal::runtime_replacement_remove_queue.emplace(shader_hash); + runtime_replacement_count = internal::runtime_replacements.size(); + } + + if (device == nullptr) { + // Destroy cloned pipelines on next present + reshade::register_event(OnPresentRemoveShaders); + } else { + RemoveQueuedRuntimeReplacements(device); + } +} + +static CommandListData& GetCurrentState(reshade::api::command_list* cmd_list) { + return cmd_list->get_private_data(); } static void OnInitDevice(reshade::api::device* device) { @@ -112,7 +369,6 @@ static void OnDestroyCommandList(reshade::api::command_list* cmd_list) { cmd_list->destroy_private_data(); } -// Before CreatePipelineState static bool OnCreatePipeline( reshade::api::device* device, reshade::api::pipeline_layout layout, @@ -123,13 +379,7 @@ static bool OnCreatePipeline( bool changed = false; for (uint32_t i = 0; i < subobject_count; ++i) { - switch (subobjects[i].type) { - case reshade::api::pipeline_subobject_type::compute_shader: - case reshade::api::pipeline_subobject_type::pixel_shader: - break; - default: - continue; - } + if (!COMPATIBLE_SUBOBJECT_TYPE_TO_STAGE.contains(subobjects[i].type)) continue; auto* desc = static_cast(subobjects[i].data); if (desc->code_size == 0) continue; @@ -137,28 +387,26 @@ static bool OnCreatePipeline( static_cast(desc->code), desc->code_size); - const std::shared_lock lock(mutex); + const std::shared_lock lock(internal::mutex); - if ( - auto pair = create_pipeline_replacements.find(shader_hash); - pair != create_pipeline_replacements.end()) { + if (auto pair = internal::compile_time_replacements.find(shader_hash); + pair != internal::compile_time_replacements.end()) { changed = true; - const auto replacement = pair->second; + const auto& replacement = pair->second; auto new_size = replacement.size(); + desc->code_size = new_size; - if (new_size != 0) { - void* copy = malloc(new_size); // leak; - memcpy(copy, replacement.data(), desc->code_size); - desc->code = copy; + if (new_size == 0) { + desc->code = nullptr; + } else { + desc->code = malloc(new_size); + memcpy(const_cast(desc->code), replacement.data(), new_size); const uint32_t new_hash = compute_crc32( replacement.data(), new_size); data.shader_replacements[shader_hash] = new_hash; data.shader_replacements_inverse[new_hash] = shader_hash; - - } else { - desc->code = nullptr; } std::stringstream s; s << "utils::shader::OnCreatePipeline(replacing "; @@ -172,192 +420,74 @@ static bool OnCreatePipeline( return changed; } -static void TraceShader( - reshade::api::device* device, - DeviceData& data, - const reshade::api::pipeline& original_pipeline, - const reshade::api::pipeline_layout& original_layout, - const reshade::api::pipeline_subobject& original_subobject, - const uint32_t original_hash) { - data.pipeline_to_layout_map[original_pipeline.handle] = original_layout.handle; - data.pipeline_to_shader_hash_map[original_pipeline.handle] = { - original_hash, - original_subobject.type, - }; -} - -static reshade::api::pipeline CreateShaderReplacement( - reshade::api::device* device, - DeviceData& data, - reshade::api::pipeline& original_pipeline, - reshade::api::pipeline_layout& original_layout, - size_t subobject_count, - const reshade::api::pipeline_subobject* original_subobjects, - size_t index, - uint32_t original_hash, - size_t new_size, - const uint8_t* new_code) { - auto* new_pipeline_subobjects = new reshade::api::pipeline_subobject[subobject_count]; - memcpy(new_pipeline_subobjects, original_subobjects, sizeof(reshade::api::pipeline_subobject) * subobject_count); - - const reshade::api::pipeline_subobject_type type = new_pipeline_subobjects[index].type; - auto* desc = static_cast(new_pipeline_subobjects[index].data); - - desc->code_size = new_size; - if (new_size != 0) { - void* copy = malloc(new_size); // leak; - memcpy(copy, new_code, new_size); - desc->code = copy; - } else { - desc->code = nullptr; - } - - reshade::api::pipeline new_pipeline; - const bool built_pipeline_ok = device->create_pipeline( - original_layout, - subobject_count, - new_pipeline_subobjects, - &new_pipeline); - if (built_pipeline_ok) { - data.pipeline_to_layout_map[new_pipeline.handle] = original_layout.handle; - data.pipeline_to_shader_hash_map[new_pipeline.handle] = { - original_hash, - type, - }; - data.pipeline_to_pipeline_replacement[original_pipeline.handle] = { - new_pipeline.handle, - type, - }; - data.shader_to_pipeline_replacement[original_hash] = { - new_pipeline.handle, - type, - }; - - std::stringstream s; - s << "utils::shader::OnInitPipeline(clone pipeline "; - s << reinterpret_cast(original_pipeline.handle); - s << " => "; - s << reinterpret_cast(new_pipeline.handle); - s << ", layout: " << reinterpret_cast(original_layout.handle); - s << ")"; - reshade::log_message(reshade::log_level::debug, s.str().c_str()); - } else { - std::stringstream s; - s << "utils::shader::OnInitPipeline(failed to clone pipeline "; - s << reinterpret_cast(original_pipeline.handle); - s << ", layout: " << reinterpret_cast(original_layout.handle); - s << ", hash: " << PRINT_CRC32(original_hash); - s << ", type: " << type; - s << ", size: " << new_size; - s << ", data: " << new_code; - s << ")"; - reshade::log_message(reshade::log_level::error, s.str().c_str()); - // Log error - } - return new_pipeline; -} - -// After CreatePipelineState static void OnInitPipeline( reshade::api::device* device, reshade::api::pipeline_layout layout, uint32_t subobject_count, const reshade::api::pipeline_subobject* subobjects, reshade::api::pipeline pipeline) { + if (pipeline.handle == 0u) return; + auto& data = device->get_private_data(); - for (uint32_t i = 0; i < subobject_count; ++i) { - const auto subobject = subobjects[i]; - switch (subobject.type) { - case reshade::api::pipeline_subobject_type::compute_shader: - case reshade::api::pipeline_subobject_type::pixel_shader: - break; - default: - continue; - } - const reshade::api::shader_desc desc = *static_cast(subobject.data); - if (desc.code_size == 0) continue; - - // Pipeline has a pixel shader with code. Hash code and check - const uint32_t found_shader = compute_crc32(static_cast(desc.code), desc.code_size); - const std::unique_lock lock(data.mutex); - if ( - auto pair = data.shader_replacements_inverse.find(found_shader); - pair != data.shader_replacements_inverse.end()) { - const uint32_t original_hash = pair->second; - TraceShader(device, data, pipeline, layout, subobject, original_hash); + const std::unique_lock lock(data.mutex); + auto details = PipelineShaderDetails(layout, subobjects, subobject_count, data.shader_replacements_inverse); - std::stringstream s; - s << "utils::shader::OnInitPipeline(found replacement: "; - s << PRINT_CRC32(original_hash); - s << " => "; - s << PRINT_CRC32(found_shader); - s << " on " << reinterpret_cast(pipeline.handle); - s << ", layout: " << reinterpret_cast(layout.handle); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); + if (details.shader_hashes.empty()) { + data.incompatible_pipelines.emplace(pipeline.handle); + return; + } + + for (const auto shader_hash : details.shader_hashes) { + if (auto pair = data.shader_pipelines.find(shader_hash); + pair != data.shader_pipelines.end()) { + auto& set = pair->second; + set.emplace(pipeline.handle); } else { - TraceShader(device, data, pipeline, layout, subobject, found_shader); - - if ( - auto pair = init_pipeline_replacements.find(found_shader); - pair != init_pipeline_replacements.end()) { - const auto replacement = pair->second; - const uint32_t new_size = replacement.size(); - const uint32_t new_shader_hash = compute_crc32(replacement.data(), new_size); - - std::stringstream s; - s << "utils::shader::OnInitPipeline(prep replacement: "; - s << PRINT_CRC32(found_shader); - s << ", code_size: " << new_size; - s << ", code: " << reinterpret_cast(replacement.data()); - s << ", layout: " << reinterpret_cast(layout.handle); - s << ", new_shader_hash: " << PRINT_CRC32(new_shader_hash); - s << ")"; - reshade::log_message(reshade::log_level::info, s.str().c_str()); - CreateShaderReplacement( - device, - data, - pipeline, - layout, - subobject_count, - subobjects, - i, - found_shader, - new_size, - replacement.data()); - } + data.shader_pipelines[shader_hash] = {pipeline.handle}; } } + + reshade::api::pipeline_subobject* subobjects_clone = renodx::utils::pipeline::ClonePipelineSubObjects(subobjects, subobject_count); + + // Store clone of subobjects + details.subobjects = std::vector( + subobjects_clone, + subobjects_clone + subobject_count); + + data.pipeline_shader_details[pipeline.handle] = details; } static void OnDestroyPipeline( reshade::api::device* device, reshade::api::pipeline pipeline) { auto& data = device->get_private_data(); + const std::unique_lock lock(data.mutex); - { - if ( - auto pair = data.pipeline_to_shader_hash_map.find(pipeline.handle); - pair != data.pipeline_to_shader_hash_map.end()) { - const uint32_t shader_hash = std::get(pair->second); - data.shader_to_pipeline_replacement.erase(shader_hash); - } - data.pipeline_to_shader_hash_map.erase(pipeline.handle); - } - { - if ( - auto pair = data.pipeline_to_pipeline_replacement.find(pipeline.handle); - pair != data.pipeline_to_pipeline_replacement.end()) { - const uint64_t replacement_handle = std::get(pair->second); - device->destroy_pipeline({replacement_handle}); - data.pipeline_to_layout_map.erase(replacement_handle); - data.pipeline_to_shader_hash_map.erase(replacement_handle); + data.ignored_pipelines.erase(pipeline.handle); + data.incompatible_pipelines.erase(pipeline.handle); + if (auto details_pair = data.pipeline_shader_details.find(pipeline.handle); + details_pair != data.pipeline_shader_details.end()) { + auto& details = details_pair->second; + if (!details.destroyed) { + if (details.HasReplacementPipeline()) { + device->destroy_pipeline(details.GetReplacementPipeline()); + details.replacement_pipeline.reset(); + } + for (auto shader_hash : details.shader_hashes) { + if (auto shader_pipelines_pair = data.shader_pipelines.find(shader_hash); + shader_pipelines_pair != data.shader_pipelines.end()) { + auto& set = shader_pipelines_pair->second; + set.erase(pipeline.handle); + } + } + details.destroyed = true; + } + if (!use_replace_async) { + renodx::utils::pipeline::DestroyPipelineSubobjects(details.subobjects); + data.pipeline_shader_details.erase(details_pair); } - data.pipeline_to_pipeline_replacement.erase(pipeline.handle); } - - data.pipeline_to_layout_map.erase(pipeline.handle); } // AfterSetPipelineState @@ -365,38 +495,59 @@ static void OnBindPipeline( reshade::api::command_list* cmd_list, reshade::api::pipeline_stage stage, reshade::api::pipeline pipeline) { + auto& cmd_list_data = cmd_list->get_private_data(); + + if (pipeline.handle == 0) { + for (auto compatible_stage : COMPATIBLE_STAGES) { + if ((stage | compatible_stage) != 0) { + cmd_list_data.current_shaders_hashes.erase(compatible_stage); + if (use_replace_on_bind) { + cmd_list_data.pending_replacements.erase(compatible_stage); + } + } + } + return; + } + auto* device = cmd_list->get_device(); auto& device_data = device->get_private_data(); - const std::unique_lock lock(device_data.mutex); - if ( - auto pair = device_data.pipeline_to_shader_hash_map.find(pipeline.handle); - pair != device_data.pipeline_to_shader_hash_map.end()) { - auto& cmd_list_data = cmd_list->get_private_data(); - const auto& [shader_hash, shader_type] = pair->second; -#ifdef DEBUG_LEVEL_1 - std::stringstream s; - s << "utils::shader::OnBindPipeline(shader: "; - s << PRINT_CRC32(shader_hash); - s << ", type: " << shader_type; - s << ", pipeline: " << reinterpret_cast(pipeline.handle); - s << ")"; - // reshade::log_message(reshade::log_level::debug, s.str().c_str()); -#endif + std::shared_lock read_lock(device_data.mutex); + if (device_data.incompatible_pipelines.contains(pipeline.handle)) return; - if (shader_type == reshade::api::pipeline_subobject_type::pixel_shader) { - cmd_list_data.pixel_shader_hash = shader_hash; - cmd_list_data.compute_shader_hash = 0; - } else { - cmd_list_data.pixel_shader_hash = 0; - cmd_list_data.compute_shader_hash = shader_hash; + auto details_pair = device_data.pipeline_shader_details.find(pipeline.handle); + if (details_pair == device_data.pipeline_shader_details.end()) { + read_lock.unlock(); + const std::unique_lock write_lock(device_data.mutex); + device_data.incompatible_pipelines.emplace(pipeline.handle); + return; + } + + auto& details = details_pair->second; + + cmd_list_data.pipeline_layout = details.layout; + + if (details.NeedsReplacementPipeline()) { + read_lock.unlock(); + { + const std::unique_lock write_lock(device_data.mutex); + BuildReplacementPipeline(device, details); } - cmd_list_data.pipeline_stage = stage; - if ( - auto pair2 = device_data.pipeline_to_layout_map.find(pipeline.handle); - pair2 != device_data.pipeline_to_layout_map.end()) { - const uint64_t pipeline_layout_handle = pair2->second; - cmd_list_data.pipeline_layout = {pipeline_layout_handle}; + read_lock.lock(); + } + + for (auto compatible_stage : COMPATIBLE_STAGES) { + if ((stage | compatible_stage) == 0) continue; + if (auto pair = details.shader_hashes_by_stage.find(compatible_stage); + pair != details.shader_hashes_by_stage.end()) { + cmd_list_data.current_shaders_hashes[compatible_stage] = pair->second; + } + if (details.HasReplacementPipeline() && ((details.replacement_stages | compatible_stage) != 0)) { + if (use_replace_on_bind) { + cmd_list->bind_pipeline(stage, pipeline); + } else { + cmd_list_data.pending_replacements[compatible_stage] = details.replacement_pipeline.value(); + } } } } diff --git a/src/utils/shader_compiler.hpp b/src/utils/shader_compiler.hpp index 3dd23e53..ab890339 100644 --- a/src/utils/shader_compiler.hpp +++ b/src/utils/shader_compiler.hpp @@ -9,167 +9,60 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include #include -#include #include #include +#include "format.hpp" namespace renodx::utils::shader::compiler { -static std::mutex s_mutex_shader_compiler; +namespace internal { -inline std::optional DisassembleShaderFXC(void* data, size_t size, LPCWSTR library = L"D3DCompiler_47.dll") { - std::optional result; - - // TODO: unify this with "CompileShaderFromFileFXC()", as it loads the same dll. - static std::unordered_map d3d_disassemble; - { - const std::lock_guard lock(s_mutex_shader_compiler); - static std::unordered_map d3d_compiler; - d3d_compiler[library] = LoadLibraryW(library); - if (d3d_compiler[library] != nullptr) { - // NOLINTNEXTLINE(google-readability-casting) - d3d_disassemble[library] = pD3DDisassemble(GetProcAddress(d3d_compiler[library], "D3DDisassemble")); - } - } - - if (d3d_disassemble[library] != nullptr) { - CComPtr out_blob; - if (SUCCEEDED(d3d_disassemble[library]( - data, - size, - D3D_DISASM_ENABLE_INSTRUCTION_NUMBERING | D3D_DISASM_ENABLE_INSTRUCTION_OFFSET, - nullptr, - &out_blob))) { - result = {reinterpret_cast(out_blob->GetBufferPointer()), out_blob->GetBufferSize()}; - } - } - -#if 0 // Not much point in unloading the library, we'd need to keep "s_mutex_shader_compiler" locked the whole time. - FreeLibrary(d3d_compiler[library]); -#endif - - return result; -} +static HMODULE fxc_compiler_library = nullptr; +static HMODULE dxc_compiler_library = nullptr; +static std::shared_mutex mutex_fxc_compiler; +static std::shared_mutex mutex_dxc_compiler; inline HRESULT CreateLibrary(IDxcLibrary** dxc_library) { - const std::lock_guard lock(s_mutex_shader_compiler); // HMODULE dxil_loader = LoadLibraryW(L"dxil.dll"); - HMODULE dx_compiler = LoadLibraryW(L"dxcompiler.dll"); - if (dx_compiler == nullptr) { - reshade::log_message(reshade::log_level::error, "dxcompiler.dll not loaded"); + if (dxc_compiler_library == nullptr) { + dxc_compiler_library = LoadLibraryW(L"dxcompiler.dll"); + } + if (dxc_compiler_library == nullptr) { return -1; } // NOLINTNEXTLINE(google-readability-casting) - auto dxc_create_instance = DxcCreateInstanceProc(GetProcAddress(dx_compiler, "DxcCreateInstance")); + auto dxc_create_instance = DxcCreateInstanceProc(GetProcAddress(dxc_compiler_library, "DxcCreateInstance")); if (dxc_create_instance == nullptr) return -1; return dxc_create_instance(CLSID_DxcLibrary, __uuidof(IDxcLibrary), reinterpret_cast(dxc_library)); } inline HRESULT CreateCompiler(IDxcCompiler** dxc_compiler) { - const std::lock_guard lock(s_mutex_shader_compiler); // HMODULE dxil_loader = LoadLibraryW(L"dxil.dll"); - HMODULE dx_compiler = LoadLibraryW(L"dxcompiler.dll"); - if (dx_compiler == nullptr) { - reshade::log_message(reshade::log_level::error, "dxcompiler.dll not loaded"); + if (dxc_compiler_library == nullptr) { + dxc_compiler_library = LoadLibraryW(L"dxcompiler.dll"); + } + if (dxc_compiler_library == nullptr) { return -1; } // NOLINTNEXTLINE(google-readability-casting) - auto dxc_create_instance = DxcCreateInstanceProc(GetProcAddress(dx_compiler, "DxcCreateInstance")); + auto dxc_create_instance = DxcCreateInstanceProc(GetProcAddress(dxc_compiler_library, "DxcCreateInstance")); if (dxc_create_instance == nullptr) return -1; return dxc_create_instance(CLSID_DxcCompiler, __uuidof(IDxcCompiler), reinterpret_cast(dxc_compiler)); } -inline std::optional DisassembleShaderDXC(void* data, size_t size) { - CComPtr library; - CComPtr compiler; - CComPtr source; - CComPtr disassembly_text; - CComPtr disassembly; - - std::optional result; - - if (FAILED(CreateLibrary(&library))) return result; - if (FAILED(library->CreateBlobWithEncodingFromPinned(data, size, CP_ACP, &source))) return result; - if (FAILED(CreateCompiler(&compiler))) return result; - if (FAILED(compiler->Disassemble(source, &disassembly_text))) return result; - if (FAILED(disassembly_text.QueryInterface(&disassembly))) return result; - - result = {reinterpret_cast(disassembly->GetBufferPointer()), disassembly->GetBufferSize()}; - - return result; -} - -inline std::optional DisassembleShader(void* code, size_t size) { - auto result = DisassembleShaderFXC(code, size); - if (!result.has_value()) { - result = DisassembleShaderDXC(code, size); - } - return result; -} - -inline std::vector CompileShaderFromFileFXC( - LPCWSTR file_path, LPCSTR shader_target, const D3D_SHADER_MACRO* defines = nullptr, - std::string* out_error = nullptr, LPCWSTR library = L"D3DCompiler_47.dll") { - typedef HRESULT(WINAPI * pD3DCompileFromFile)(LPCWSTR, const D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR, LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob**); - static std::unordered_map d3d_compilefromfile; - { - const std::lock_guard lock(s_mutex_shader_compiler); - static std::unordered_map d3d_compiler; - d3d_compiler[library] = LoadLibraryW(library); - if (d3d_compiler[library] != nullptr) { - // NOLINTNEXTLINE(google-readability-casting) - d3d_compilefromfile[library] = pD3DCompileFromFile(GetProcAddress(d3d_compiler[library], "D3DCompileFromFile")); - } - } - - std::vector result; - CComPtr out_blob; - if (d3d_compilefromfile[library] != nullptr) { - CComPtr error_blob; - if (SUCCEEDED(d3d_compilefromfile[library]( - file_path, - defines, - D3D_COMPILE_STANDARD_FILE_INCLUDE, - "main", - shader_target, - D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY, - 0, - &out_blob, - &error_blob))) { - result.assign( - reinterpret_cast(out_blob->GetBufferPointer()), - reinterpret_cast(out_blob->GetBufferPointer()) + out_blob->GetBufferSize()); - } else { - std::stringstream s; - s << "CompileShaderFromFileFXC(Compilation failed"; - if (error_blob != nullptr) { - // auto error_size = error_blob->GetBufferSize(); - auto* error = reinterpret_cast(error_blob->GetBufferPointer()); - s << ": " << error; - if (out_error != nullptr) { - out_error->assign(reinterpret_cast(error)); - } - } else { - s << "."; - } - s << ")"; - reshade::log_message(reshade::log_level::error, s.str().c_str()); - } - } - -#if 0 // Not much point in unloading the library, we'd need to keep "s_mutex_shader_compiler" locked the whole time. - FreeLibrary(d3d_compiler[library]); -#endif - - return result; -} - #define IFR(x) \ { \ const HRESULT __hr = (x); \ @@ -219,8 +112,7 @@ inline HRESULT CompileFromBlob( while (cursor != nullptr && cursor->Name != nullptr) { define_values.emplace_back(CA2W(cursor->Name, CP_UTF8)); if (cursor->Definition != nullptr) { - define_values.emplace_back( - CA2W(cursor->Definition, CP_UTF8)); + define_values.emplace_back(CA2W(cursor->Definition, CP_UTF8)); } else { define_values.emplace_back(/* empty */); } @@ -333,12 +225,69 @@ inline HRESULT WINAPI BridgeD3DCompileFromFile( return CompileFromBlob(source, file_name, defines, include_handler, entrypoint, target, flags1, flags2, code, error_messages); } -inline std::vector CompileShaderFromFileDXC(LPCWSTR file_path, LPCSTR shader_target, const D3D_SHADER_MACRO* defines = nullptr, std::string* out_error = nullptr) { +inline std::vector CompileShaderFromFileFXC( + LPCWSTR file_path, + LPCSTR shader_target, + const D3D_SHADER_MACRO* defines = nullptr) { + CComPtr out_blob; + + if (fxc_compiler_library == nullptr) { + std::stringstream s; + s << "CompileShaderFromFileFXC(Loading D3DCompiler_47.dll)"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + fxc_compiler_library = LoadLibraryW(L"D3DCompiler_47.dll"); + } + if (fxc_compiler_library == nullptr) { + throw std::exception("Could not to load D3DCompiler_47.dll"); + } + + typedef HRESULT(WINAPI * pD3DCompileFromFile)( + LPCWSTR, const D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR, LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob**); + + { + std::stringstream s; + s << "CompileShaderFromFileFXC(GetProcAddress)"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + } + // NOLINTNEXTLINE(google-readability-casting) + auto d3d_compilefromfile = pD3DCompileFromFile(GetProcAddress(fxc_compiler_library, "D3DCompileFromFile")); + if (d3d_compilefromfile == nullptr) { + throw std::exception("Could not to load D3DCompileFromFile from D3DCompiler_47.dll"); + } + + std::unique_lock lock(mutex_fxc_compiler); + CComPtr error_blob; + if (FAILED(d3d_compilefromfile( + file_path, + defines, + D3D_COMPILE_STANDARD_FILE_INCLUDE, + "main", + shader_target, + D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY, + 0, + &out_blob, + &error_blob))) { + if (error_blob == nullptr) throw std::exception("Unknown error occurred."); + + throw std::exception( + std::string(reinterpret_cast(error_blob->GetBufferPointer()), + error_blob->GetBufferSize()) + .c_str()); + } + return {reinterpret_cast(out_blob->GetBufferPointer()), + reinterpret_cast(out_blob->GetBufferPointer()) + out_blob->GetBufferSize()}; +} + +inline std::vector CompileShaderFromFileDXC( + LPCWSTR file_path, + LPCSTR shader_target, + const D3D_SHADER_MACRO* defines = nullptr) { std::vector result; CComPtr out_blob; CComPtr error_blob; - if (SUCCEEDED(BridgeD3DCompileFromFile( + std::unique_lock lock(mutex_dxc_compiler); + if (FAILED(internal::BridgeD3DCompileFromFile( file_path, defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, @@ -348,45 +297,167 @@ inline std::vector CompileShaderFromFileDXC(LPCWSTR file_path, LPCSTR s 0, &out_blob, &error_blob))) { - result.assign( - reinterpret_cast(out_blob->GetBufferPointer()), - reinterpret_cast(out_blob->GetBufferPointer()) + out_blob->GetBufferSize()); - } else { - std::stringstream s; - s << "CompileShaderFromFileDXC(Compilation failed"; - if (error_blob != nullptr) { - // auto error_size = error_blob->GetBufferSize(); - auto* error = reinterpret_cast(error_blob->GetBufferPointer()); - s << ": " << error; - if (out_error != nullptr) { - out_error->assign(reinterpret_cast(error)); - } - } else { - s << "."; + if (error_blob == nullptr) throw std::exception("Unknown error occurred."); + + throw std::exception(std::string(reinterpret_cast(error_blob->GetBufferPointer()), + error_blob->GetBufferSize()) + .c_str()); + } + return {reinterpret_cast(out_blob->GetBufferPointer()), + reinterpret_cast(out_blob->GetBufferPointer()) + out_blob->GetBufferSize()}; +} + +inline std::string DisassembleShaderFXC(std::span blob) { + if (fxc_compiler_library == nullptr) { + fxc_compiler_library = LoadLibraryW(L"D3DCompiler_47.dll"); + } + if (fxc_compiler_library == nullptr) throw std::exception("Could not to load D3DCompiler_47.dll"); + + // NOLINTNEXTLINE(google-readability-casting) + auto d3d_disassemble = pD3DDisassemble(GetProcAddress(fxc_compiler_library, "D3DDisassemble")); + + if (d3d_disassemble == nullptr) throw std::exception("Could not to load D3DDisassemble in D3DCompiler_47.dll"); + + // Function may not be thread-safe + std::unique_lock lock(mutex_fxc_compiler); + CComPtr out_blob; + if (FAILED(d3d_disassemble( + blob.data(), + blob.size(), + D3D_DISASM_ENABLE_INSTRUCTION_NUMBERING | D3D_DISASM_ENABLE_INSTRUCTION_OFFSET, + nullptr, + &out_blob))) { + throw std::exception("Disassembly failed"); + } + + return { + reinterpret_cast(out_blob->GetBufferPointer()), + out_blob->GetBufferSize()}; +} + +inline std::string DisassembleShaderDXC(std::span blob) { + CComPtr library; + CComPtr compiler; + CComPtr source; + CComPtr disassembly_text; + CComPtr disassembly; + + std::unique_lock lock(mutex_dxc_compiler); + if (FAILED(internal::CreateLibrary(&library))) throw std::exception("Could not create library."); + if (FAILED(library->CreateBlobWithEncodingFromPinned(blob.data(), blob.size(), CP_ACP, &source))) throw std::exception("Could not prepare blob."); + if (FAILED(internal::CreateCompiler(&compiler))) throw std::exception("Could not create compiler object."); + if (FAILED(compiler->Disassemble(source, &disassembly_text))) throw std::exception("Could not disassemble."); + if (FAILED(disassembly_text.QueryInterface(&disassembly))) throw std::exception("Could not find diassembly"); + + return { + reinterpret_cast(disassembly->GetBufferPointer()), + disassembly->GetBufferSize(), + }; +} + +} // namespace internal + +// Also compatible with DXBC +struct DxilContainerHeader { + uint32_t four_cc; + uint8_t digest[16]; + uint16_t major_version; + uint16_t minor_version; + uint32_t container_size; + uint32_t part_count; +}; +struct DxilPartHeader { + uint32_t four_cc; + uint32_t part_size; +}; + +struct DxilProgramVersion { + uint32_t value = 0xffff0000; + [[nodiscard]] unsigned int GetMajor() const { return (value & 0xf0) >> 4; } + [[nodiscard]] unsigned int GetMinor() const { return (value & 0xf); } + [[nodiscard]] unsigned int GetKind() const { return (value & 0xffff0000) >> 16; } + [[nodiscard]] const char* GetKindAbbr() const { + switch (this->GetKind()) { + case D3D11_SHVER_PIXEL_SHADER: return "ps"; + case D3D11_SHVER_VERTEX_SHADER: return "vs"; + case D3D11_SHVER_GEOMETRY_SHADER: return "gs"; + case D3D11_SHVER_HULL_SHADER: return "hs"; + case D3D11_SHVER_DOMAIN_SHADER: return "ds"; + case D3D11_SHVER_COMPUTE_SHADER: return "cs"; + default: return "??"; } - s << ")"; + } +}; + +constexpr uint32_t FourCC(char char0, char char1, char char2, char char3) { + return static_cast(static_cast(char0)) + | (static_cast(static_cast(char1)) << 8) + | (static_cast(static_cast(char2)) << 16) + | (static_cast(static_cast(char3)) << 24); +} + +inline DxilProgramVersion DecodeShaderVersion(std::span blob) { + if (blob.size() < sizeof(DxilContainerHeader)) throw std::exception("Invalid shader size."); + auto* header = reinterpret_cast(blob.data()); + + switch (header->four_cc) { + case FourCC('D', 'X', 'B', 'C'): + case FourCC('D', 'X', 'I', 'L'): + break; + default: + throw std::exception("Unrecognized header."); + } + + auto part_offsets = std::span( + reinterpret_cast(blob.data() + sizeof(DxilContainerHeader)), + header->part_count); + + for (auto offset : part_offsets) { + auto* part_header = reinterpret_cast(blob.data() + offset); - reshade::log_message(reshade::log_level::error, s.str().c_str()); + switch (part_header->four_cc) { + case FourCC('S', 'H', 'D', 'R'): + case FourCC('S', 'H', 'E', 'X'): + case FourCC('D', 'X', 'I', 'L'): + break; + default: + continue; + } + auto part_data = blob.subspan( + offset + sizeof(DxilPartHeader), + part_header->part_size); + auto* program_version = reinterpret_cast(part_data.data()); + return *program_version; } + return DxilProgramVersion{}; // No shader (shader layout perhaps) +} - return result; +inline std::string DisassembleShader(std::span blob) { + auto version = DecodeShaderVersion(blob); + if (version.GetMajor() < 6) { + return internal::DisassembleShaderFXC(blob); + } + return internal::DisassembleShaderDXC(blob); } -inline std::vector CompileShaderFromFile(LPCWSTR file_path, LPCSTR shader_target, const std::vector& defines = {}, std::string* out_error = nullptr, LPCWSTR fxc_library = L"D3DCompiler_47.dll") { +inline std::vector CompileShaderFromFile( + LPCWSTR file_path, + LPCSTR shader_target, + const std::vector>& defines = {}) { std::vector local_defines; - for (int i = 0; i < defines.size() && defines.size() > 1; i += 2) { - if (!defines[i].empty() && !defines[i + 1].empty()) { - local_defines.push_back({defines[i].c_str(), defines[i + 1].c_str()}); + for (const auto& [key, value] : defines) { + if (!key.empty() && !value.empty()) { + local_defines.push_back({key.c_str(), value.c_str()}); } } - if (local_defines.size() > 0) { + if (!local_defines.empty()) { local_defines.push_back({nullptr, nullptr}); } if (shader_target[3] < '6') { - return CompileShaderFromFileFXC(file_path, shader_target, local_defines.data(), out_error, fxc_library); + return internal::CompileShaderFromFileFXC(file_path, shader_target, local_defines.empty() ? nullptr : local_defines.data()); } - return CompileShaderFromFileDXC(file_path, shader_target, local_defines.data(), out_error); + return internal::CompileShaderFromFileDXC(file_path, shader_target, local_defines.empty() ? nullptr : local_defines.data()); } } // namespace renodx::utils::shader::compiler diff --git a/src/utils/shader_compiler_watcher.hpp b/src/utils/shader_compiler_watcher.hpp new file mode 100644 index 00000000..71c3a71f --- /dev/null +++ b/src/utils/shader_compiler_watcher.hpp @@ -0,0 +1,388 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "./format.hpp" +#include "./path.hpp" +#include "./shader_compiler.hpp" + +namespace renodx::utils::shader::compiler::watcher { + +static std::atomic_size_t custom_shaders_count = 0; + +struct CustomShader { + std::variant> compilation; + bool is_hlsl = false; + std::filesystem::path file_path; + uint32_t shader_hash; + bool removed = false; + + [[nodiscard]] bool IsCompilationOK() const { + return std::holds_alternative>(compilation); + } + + [[nodiscard]] std::vector GetCompilationData() const { + return std::get>(compilation); + } + + [[nodiscard]] std::exception GetCompilationException() const { + return std::get(compilation); + } +}; + +namespace internal { + +static std::atomic_bool shared_watcher_enabled = true; +static std::atomic_bool shared_watcher_running = false; +static std::atomic_bool shared_shaders_changed = false; +static std::atomic_bool shared_compile_pending = false; +static std::optional worker_thread; + +constexpr uint32_t MAX_SHADER_DEFINES = 4; +const bool PRECOMPILE_CUSTOM_SHADERS = true; + +static std::shared_mutex mutex; +static std::unordered_map custom_shaders_cache; +static std::vector> shared_shader_defines; + +static OVERLAPPED overlapped; +static HANDLE m_target_dir_handle = INVALID_HANDLE_VALUE; +static std::aligned_storage_t<1U << 18, std::max(alignof(FILE_NOTIFY_EXTENDED_INFORMATION), alignof(FILE_NOTIFY_INFORMATION))> watch_buffer; + +static bool CompileCustomShaders() { + const std::unique_lock lock(mutex); + + auto directory = renodx::utils::path::GetOutputPath(); + if (!std::filesystem::exists(directory)) { + std::filesystem::create_directory(directory); + return false; + } + + directory /= "live"; + + if (!std::filesystem::exists(directory)) { + std::filesystem::create_directory(directory); + return false; + } + + std::unordered_set shader_hashes_processed = {}; + std::unordered_set shader_hashes_failed = {}; + std::unordered_set shader_hashes_updated = {}; + + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (!entry.is_regular_file()) continue; + + const auto& entry_path = entry.path(); + if (!entry_path.has_stem() || !entry_path.has_extension()) continue; + + const bool is_hlsl = entry_path.extension().compare(".hlsl") == 0; + const bool is_cso = entry_path.extension().compare(".cso") == 0; + if (!is_hlsl && !is_cso) continue; + + auto basename = entry_path.stem().string(); + std::string hash_string; + std::string shader_target; + + if (is_hlsl) { + auto length = basename.length(); + if (length < strlen("0x12345678.xx_x_x")) continue; + shader_target = basename.substr(length - strlen("xx_x_x"), strlen("xx_x_x")); + if (shader_target[2] != '_') continue; + if (shader_target[4] != '_') continue; + // uint32_t versionMajor = shader_target[3] - '0'; + hash_string = basename.substr(length - strlen("12345678.xx_x_x"), 8); + } else if (is_cso) { + // As long as cso starts from "0x12345678", it's good, they don't need the shader type specified + if (basename.size() < 10) { + std::stringstream s; + s << "CompileCustomShaders(Invalid cso file format: "; + s << basename; + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + continue; + } + hash_string = basename.substr(2, 8); + } + // Any other case (non hlsl non cso) is already earlied out above + + uint32_t shader_hash; + try { + shader_hash = std::stoul(hash_string, nullptr, 16); + } catch (std::exception& e) { + std::stringstream s; + s << "CompileCustomShaders(Invalid shader hash: "; + s << hash_string; + s << ", at " << entry_path; + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + continue; + } + + if (shader_hashes_processed.contains(shader_hash)) { + std::stringstream s; + s << "CompileCustomShaders(Ignoring duplicate shader: "; + s << PRINT_CRC32(shader_hash); + s << ", at " << entry_path; + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + continue; + } + + // Prepare new custom shader entry but hold off unless it actually compiles () + + CustomShader custom_shader = { + .is_hlsl = is_hlsl, + .file_path = entry_path, + }; + + if (is_hlsl) { + { + std::stringstream s; + s << "loadCustomShaders(Compiling file: "; + s << entry_path.string(); + s << ", hash: " << PRINT_CRC32(shader_hash); + s << ", target: " << shader_target; + s << ")"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + } + + try { + custom_shader.compilation = renodx::utils::shader::compiler::CompileShaderFromFile( + entry_path.c_str(), + shader_target.c_str(), + shared_shader_defines); + shader_hashes_processed.emplace(shader_hash); + shader_hashes_failed.erase(shader_hash); + } catch (std::exception& e) { + shader_hashes_failed.emplace(shader_hash); + custom_shader.compilation = e; + std::stringstream s; + s << "loadCustomShaders(Compilation failed: "; + s << entry_path.string(); + s << ", " << custom_shader.GetCompilationException().what(); + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + } + + } else if (is_cso) { + try { + custom_shader.compilation = utils::path::ReadBinaryFile(entry_path); + shader_hashes_processed.emplace(shader_hash); + shader_hashes_failed.erase(shader_hash); + } catch (std::exception& e) { + custom_shader.compilation = e; + shader_hashes_failed.emplace(shader_hash); + continue; + } + } + + // Find previous entry + bool insert_or_replace = true; + if (auto previous_pair = custom_shaders_cache.find(shader_hash); + previous_pair != custom_shaders_cache.end()) { + auto& previous_custom_shader = previous_pair->second; + if (custom_shader.IsCompilationOK() + && previous_custom_shader.IsCompilationOK() + && custom_shader.GetCompilationData() == previous_custom_shader.GetCompilationData()) { + // Same data, update source + previous_custom_shader.is_hlsl = is_hlsl, + previous_custom_shader.file_path = entry_path, + insert_or_replace = false; + } + } + if (insert_or_replace) { + // New entry + custom_shaders_cache[shader_hash] = custom_shader; + shader_hashes_updated.emplace(shader_hash); + } + } + + for (auto& [shader_hash, custom_shader] : custom_shaders_cache) { + if (!shader_hashes_processed.contains(shader_hash)) { + if (!custom_shader.removed) { + custom_shader.removed = true; + shader_hashes_updated.insert(shader_hash); + } + } + } + + if (shader_hashes_updated.size() != 0) { + custom_shaders_count = custom_shaders_cache.size(); + shared_shaders_changed = true; + return true; + } + return false; +} + +static void CALLBACK HandleEventCallback(DWORD error_code, DWORD bytes_transferred, LPOVERLAPPED overlapped) { + shared_watcher_running = false; + shared_compile_pending = true; +} + +static bool EnableLiveWatcher() { + auto directory = utils::path::GetOutputPath(); + if (!std::filesystem::exists(directory)) { + std::filesystem::create_directory(directory); + } + + directory /= "live"; + + if (!std::filesystem::exists(directory)) { + std::filesystem::create_directory(directory); + } + + reshade::log_message(reshade::log_level::info, "Watching live."); + + m_target_dir_handle = CreateFileW( + directory.c_str(), + FILE_LIST_DIRECTORY, + (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), + NULL, // NOLINT + OPEN_EXISTING, + (FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED), + NULL // NOLINT + ); + if (m_target_dir_handle == INVALID_HANDLE_VALUE) { + reshade::log_message(reshade::log_level::error, "ToggleLiveWatching(targetHandle: invalid)"); + return false; + } + { + std::stringstream s; + s << "ToggleLiveWatching(targetHandle: "; + s << reinterpret_cast(m_target_dir_handle); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + + memset(&watch_buffer, 0, sizeof(watch_buffer)); + overlapped = {0}; + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // NOLINT + + const BOOL success = ReadDirectoryChangesExW( + m_target_dir_handle, + &watch_buffer, + sizeof(watch_buffer), + TRUE, + FILE_NOTIFY_CHANGE_FILE_NAME + | FILE_NOTIFY_CHANGE_DIR_NAME + | FILE_NOTIFY_CHANGE_ATTRIBUTES + | FILE_NOTIFY_CHANGE_SIZE + | FILE_NOTIFY_CHANGE_CREATION + | FILE_NOTIFY_CHANGE_LAST_WRITE, + NULL, // NOLINT + &overlapped, + &HandleEventCallback, + ReadDirectoryNotifyExtendedInformation); + + if (success == FALSE) { + CloseHandle(m_target_dir_handle); + std::stringstream s; + s << "ToggleLiveWatching(ReadDirectoryChangesExW: Failed: "; + s << GetLastError(); + s << ")"; + reshade::log_message(reshade::log_level::error, s.str().c_str()); + return false; + } + reshade::log_message(reshade::log_level::info, "ToggleLiveWatching(ReadDirectoryChangesExW: Listening.)"); + return true; +} + +static void DisableLiveWatcher() { + reshade::log_message(reshade::log_level::info, "Cancelling live."); + CancelIoEx(m_target_dir_handle, &overlapped); + CloseHandle(m_target_dir_handle); +} + +static void Watch() { + while (true) { + { + if (!shared_watcher_enabled) { + DisableLiveWatcher(); + shared_watcher_running = false; + return; + } + if (!shared_watcher_running) { + if (EnableLiveWatcher()) { + shared_watcher_running = true; + } + } + if (shared_compile_pending) { + CompileCustomShaders(); + shared_compile_pending = false; + } + } + + if (shared_watcher_running) { + WaitForSingleObjectEx(overlapped.hEvent, 100, TRUE); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + } +} +} // namespace internal + +static void Start() { + internal::shared_compile_pending = true; + if (internal::worker_thread.has_value()) return; + internal::shared_watcher_enabled = true; + internal::worker_thread = std::thread(internal::Watch); +} + +static void Stop() { + if (!internal::worker_thread.has_value()) return; + internal::shared_watcher_enabled = false; + internal::worker_thread->join(); + internal::worker_thread.reset(); +} + +// Retrieves and consumes all compiled shareds +static std::unordered_map FlushCompiledShaders() { + const std::unique_lock lock(internal::mutex); + std::unordered_map copy = internal::custom_shaders_cache; + internal::custom_shaders_cache.clear(); + custom_shaders_count = 0; + internal::shared_shaders_changed = false; + return copy; +} + +static bool HasChanged() { + return internal::shared_shaders_changed; +} + +static bool CompileSync() { + return internal::CompileCustomShaders(); +} + +template +static void SetShaderDefines(T& defines) { + const std::unique_lock lock(internal::mutex); + internal::shared_shader_defines.clear(); + for (auto& pair : defines) { + internal::shared_shader_defines.emplace_back(pair); + } +} + +static void SetShaderDefines(std::vector>& defines) { + const std::unique_lock lock(internal::mutex); + internal::shared_shader_defines = defines; +} + +static void RequestCompile() { + internal::shared_compile_pending = true; +} + +} // namespace renodx::utils::shader::compiler::watcher \ No newline at end of file diff --git a/src/utils/shader_dump.hpp b/src/utils/shader_dump.hpp new file mode 100644 index 00000000..872f7217 --- /dev/null +++ b/src/utils/shader_dump.hpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2024 Carlos Lopez + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "./format.hpp" +#include "./path.hpp" +#include "./shader.hpp" +#include "./shader_compiler.hpp" + +namespace renodx::utils::shader::dump { + +static std::atomic_bool use_auto_dump = true; +static std::atomic_size_t pending_dump_count = 0; + +namespace internal { +static std::unordered_set shaders_dumped; +static std::unordered_set shaders_seen; +static std::unordered_map> shaders_pending; + +static std::shared_mutex mutex; + +// After CreatePipelineState +static void OnInitPipeline( + reshade::api::device* device, + reshade::api::pipeline_layout layout, + uint32_t subobject_count, + const reshade::api::pipeline_subobject* subobjects, + reshade::api::pipeline pipeline) { + if (pipeline.handle == 0u) return; + + auto details = renodx::utils::shader::GetPipelineShaderDetails(device, pipeline); + if (!details.has_value()) return; + + std::unique_lock lock(mutex); + for (auto& [subobject_index, shader_hash] : details->shader_hashes_by_index) { + // Store immediately in case pipeline destroyed before present + if (shaders_seen.contains(shader_hash)) continue; + shaders_seen.emplace(shader_hash); + + if (shaders_dumped.contains(shader_hash)) continue; + if (shaders_pending.contains(shader_hash)) continue; + auto shader_data = details->GetShaderData(shader_hash, subobject_index); + if (!shader_data.has_value()) { + std::stringstream s; + s << "utils::shader::dump(Failed to retreive shader data: "; + s << PRINT_CRC32(shader_hash); + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + continue; + } + try { + auto shader_version = renodx::utils::shader::compiler::DecodeShaderVersion(shader_data.value()); + if (shader_version.GetMajor() == 0) { + // No shader information found + continue; + } + } catch (std::exception& e) { + std::stringstream s; + s << "utils::shader::dump(Failed to decode shader data: "; + s << PRINT_CRC32(shader_hash); + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + continue; + } + std::stringstream s; + s << "utils::shader::dump(storing: "; + s << PRINT_CRC32(shader_hash); + s << ", size: " << shader_data->size(); + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + + shaders_pending[shader_hash] = shader_data.value(); + } +} + +static void OnPresent( + reshade::api::command_queue* queue, + reshade::api::swapchain* swapchain, + const reshade::api::rect* source_rect, + const reshade::api::rect* dest_rect, + uint32_t dirty_rect_count, + const reshade::api::rect* dirty_rects) { + std::shared_lock lock(mutex); + pending_dump_count = shaders_pending.size(); +} + +static bool attached = false; + +static void CheckShadersOnDisk() { + auto dump_path = renodx::utils::path::GetOutputPath(); + + if (!std::filesystem::exists(dump_path)) return; + dump_path /= "dump"; + if (!std::filesystem::exists(dump_path)) return; + std::unordered_set shader_hashes_found = {}; + + for (const auto& entry : std::filesystem::directory_iterator(dump_path)) { + if (!entry.is_regular_file()) continue; + const auto& entry_path = entry.path(); + if (entry_path.extension() != ".cso") continue; + const auto& basename = entry_path.stem().string(); + if (basename.length() < 2 + 8) continue; + if (!basename.starts_with("0x")) continue; + auto hash_string = basename.substr(2, 8); + uint32_t shader_hash; + try { + shader_hash = std::stoul(hash_string, nullptr, 16); + } catch (const std::exception& e) { + std::stringstream s; + s << "utils::shader::dump(Invalid shader string: "; + s << hash_string; + s << " at "; + s << entry_path; + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + continue; + } + auto [iterator, new_insert] = shader_hashes_found.insert(shader_hash); + if (!new_insert) { + std::stringstream s; + s << "utils::shader::dump(Found duplicate shader: "; + s << PRINT_CRC32(shader_hash); + s << " at "; + s << entry_path; + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + } else { + std::stringstream s; + s << "utils::shader::dump(Found shader: "; + s << PRINT_CRC32(shader_hash); + s << " at "; + s << entry_path; + s << ")"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + } + } + + const std::unique_lock lock(mutex); + shaders_dumped.insert(shader_hashes_found.begin(), shader_hashes_found.end()); +} +} // namespace internal + +static bool DumpShader(uint32_t shader_hash, std::span shader_data) { + auto dump_path = renodx::utils::path::GetOutputPath(); + + if (!std::filesystem::exists(dump_path)) { + std::filesystem::create_directory(dump_path); + } + dump_path /= "dump"; + if (!std::filesystem::exists(dump_path)) { + std::filesystem::create_directory(dump_path); + } + + wchar_t hash_string[11]; + swprintf_s(hash_string, L"0x%08X", shader_hash); + + dump_path /= hash_string; + + renodx::utils::shader::compiler::DxilProgramVersion shader_version; + try { + std::stringstream s; + s << "utils::shader::dump(Decoding shader_data: "; + s << PRINT_CRC32(shader_hash); + s << ": " << shader_data.size(); + s << ")"; + reshade::log_message(reshade::log_level::error, s.str().c_str()); + shader_version = renodx::utils::shader::compiler::DecodeShaderVersion(shader_data); + } catch (std::exception& e) { + std::stringstream s; + s << "utils::shader::dump(Failed to parse "; + s << PRINT_CRC32(shader_hash); + s << ": " << e.what(); + s << ")"; + reshade::log_message(reshade::log_level::error, s.str().c_str()); + } + + std::string kind = shader_version.GetKindAbbr(); + if (kind != "??") { + dump_path += L"."; + dump_path += kind; + dump_path += L"_"; + dump_path += std::to_string(shader_version.GetMajor()); + dump_path += L"_"; + dump_path += std::to_string(shader_version.GetMinor()); + } else { + std::stringstream s; + s << "utils::shader::dump(Unknown shader type: "; + s << PRINT_CRC32(shader_hash); + s << ", type: " << shader_version.GetKind(); + s << ")"; + reshade::log_message(reshade::log_level::warning, s.str().c_str()); + } + + dump_path += L".cso"; + + std::stringstream s; + s << "utils::shader::dump(Dumping: "; + s << PRINT_CRC32(shader_hash); + s << " => " << dump_path.string(); + s << ")"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + + renodx::utils::path::WriteBinaryFile(dump_path, shader_data); + return true; +} + +static void DumpAllPending() { + std::unique_lock lock(internal::mutex); + for (auto& [shader_hash, shader_data] : internal::shaders_pending) { + std::stringstream s; + s << "utils::shader::dump(Starting dump: "; + s << PRINT_CRC32(shader_hash); + s << ")"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + + DumpShader(shader_hash, shader_data); + } + internal::shaders_pending.clear(); +} + +static void Use(DWORD fdw_reason) { + renodx::utils::shader::Use(fdw_reason); + switch (fdw_reason) { + case DLL_PROCESS_ATTACH: + if (internal::attached) return; + internal::attached = true; + internal::CheckShadersOnDisk(); + reshade::log_message(reshade::log_level::info, "ShaderDump attached."); + reshade::register_event(internal::OnInitPipeline); + reshade::register_event(internal::OnPresent); + + break; + case DLL_PROCESS_DETACH: + reshade::unregister_event(internal::OnInitPipeline); + reshade::unregister_event(internal::OnPresent); + break; + } +} + +} // namespace renodx::utils::shader::dump \ No newline at end of file diff --git a/src/utils/swapchain.hpp b/src/utils/swapchain.hpp index 3dbdaba6..702c4599 100644 --- a/src/utils/swapchain.hpp +++ b/src/utils/swapchain.hpp @@ -29,7 +29,7 @@ struct __declspec(uuid("4721e307-0cf3-4293-b4a5-40d0a4e62544")) DeviceData { std::shared_mutex mutex; }; -struct __declspec(uuid("25b7ec11-a51f-4884-a6f7-f381d198b9af")) CommandListData { +struct __declspec(uuid("3cf9a628-8518-4509-84c3-9fbe9a295212")) CommandListData { std::vector current_render_targets; reshade::api::resource_view current_depth_stencil; bool has_swapchain_render_target_dirty = true; @@ -175,6 +175,16 @@ static bool HasBackBufferRenderTarget(reshade::api::command_list* cmd_list) { return found_swapchain_rtv; } +static std::vector& GetRenderTargets(reshade::api::command_list* cmd_list) { + auto& cmd_list_data = cmd_list->get_private_data(); + return cmd_list_data.current_render_targets; +}; + +static reshade::api::resource_view& GetDepthStencil(reshade::api::command_list* cmd_list) { + auto& cmd_list_data = cmd_list->get_private_data(); + return cmd_list_data.current_depth_stencil; +}; + static bool attached = false; inline void Use(DWORD fdw_reason) { diff --git a/src/utils/trace.hpp b/src/utils/trace.hpp new file mode 100644 index 00000000..222bea50 --- /dev/null +++ b/src/utils/trace.hpp @@ -0,0 +1,1387 @@ +/* + * Copyright (C) 2024 Carlos Lopez + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include "./descriptor.hpp" +#include "./format.hpp" +#include "./shader.hpp" + +namespace renodx::utils::trace { + +static bool trace_scheduled = false; +static bool trace_running = false; +template + +inline std::optional GetD3DName(T* obj) { + if (obj == nullptr) return std::nullopt; + + char c_name[128] = {}; + UINT size = sizeof(c_name); + if (obj->GetPrivateData(WKPDID_D3DDebugObjectName, &size, c_name) == S_OK) { + if (size > 0) return std::string{c_name, c_name + size}; + } + return std::nullopt; +} + +template +inline std::optional GetD3DNameW(T* obj) { + if (obj == nullptr) return std::nullopt; + + LPCWSTR w_name[128] = {}; + UINT size = sizeof(w_name); + if (obj->GetPrivateData(WKPDID_D3DDebugObjectNameW, &size, w_name) == S_OK) { + if (size > 0) { + auto wstring = std::wstring(reinterpret_cast(w_name), reinterpret_cast(w_name) + size); + return std::string(wstring.begin(), wstring.end()); + } + } + return GetD3DName(obj); +} + +inline std::optional GetDebugName(reshade::api::device_api device_api, reshade::api::resource_view resource_view) { + if (device_api == reshade::api::device_api::d3d11) { + auto* native_resource = reinterpret_cast(resource_view.handle); + return renodx::utils::trace::GetD3DName(native_resource); + } + return std::nullopt; +} +inline std::optional GetDebugName(reshade::api::device_api device_api, uint64_t handle) { + if (device_api == reshade::api::device_api::d3d11) { + auto* native_resource = reinterpret_cast(handle); + return renodx::utils::trace::GetD3DName(native_resource); + } + if (device_api == reshade::api::device_api::d3d12) { + auto* native_resource = reinterpret_cast(handle); + return renodx::utils::trace::GetD3DNameW(native_resource); + } + return std::nullopt; +} + +template +inline std::optional GetDebugName(reshade::api::device_api device_api, T object) { + return GetDebugName(device_api, object.handle); +} + +namespace internal { +struct __declspec(uuid("3b70b2b2-52dc-4637-bd45-c1171c4c322e")) DeviceData { + // + std::unordered_map resource_views; + // > + std::unordered_map> resource_views_by_resource; + std::unordered_map resource_names; + std::unordered_set resources; + std::shared_mutex mutex; + reshade::api::device_api device_api; +}; + +const bool FORCE_ALL = false; +const bool TRACE_NAMES = false; + +static uint32_t present_count = 0; +const uint32_t MAX_PRESENT_COUNT = 60; + +static bool attached = false; + +inline uint64_t GetResourceByViewHandle(DeviceData& data, uint64_t handle) { + if ( + auto pair = data.resource_views.find(handle); + pair != data.resource_views.end()) return pair->second; + + return 0; +} + +inline std::string GetResourceNameByViewHandle(DeviceData& data, uint64_t handle) { + if (!TRACE_NAMES) return "?"; + auto resource_handle = GetResourceByViewHandle(data, handle); + if (resource_handle == 0) return "?"; + if (!data.resources.contains(resource_handle)) return "?"; + + if ( + auto pair = data.resource_names.find(resource_handle); + pair != data.resource_names.end()) return pair->second; + + std::string name; + if (data.device_api == reshade::api::device_api::d3d11) { + auto* native_resource = reinterpret_cast(resource_handle); + auto result = GetD3DName(native_resource); + if (result.has_value()) { + name.assign(result.value()); + } + } else if (data.device_api == reshade::api::device_api::d3d12) { + auto* native_resource = reinterpret_cast(resource_handle); + auto result = GetD3DNameW(native_resource); + if (result.has_value()) { + name.assign(result.value()); + } + } else { + name = "?"; + } + if (!name.empty()) { + data.resource_names[resource_handle] = name; + } + return name; +} + +static void OnInitDevice(reshade::api::device* device) { + std::stringstream s; + s << "init_device("; + s << reinterpret_cast(device); + s << ", api: " << device->get_api(); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + + auto& data = device->create_private_data(); + data.device_api = device->get_api(); +} + +static void OnDestroyDevice(reshade::api::device* device) { + std::stringstream s; + s << "destroy_device("; + s << reinterpret_cast(device); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + device->destroy_private_data(); +} + +static void OnInitSwapchain(reshade::api::swapchain* swapchain) { + const size_t back_buffer_count = swapchain->get_back_buffer_count(); + + for (uint32_t index = 0; index < back_buffer_count; index++) { + auto buffer = swapchain->get_back_buffer(index); + + std::stringstream s; + s << "init_swapchain("; + s << "buffer:" << reinterpret_cast(buffer.handle); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + + std::stringstream s; + s << "init_swapchain"; + s << "(colorspace: " << swapchain->get_color_space(); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +static bool OnCreatePipelineLayout( + reshade::api::device* device, + uint32_t& param_count, + reshade::api::pipeline_layout_param*& params) { + // noop + return false; +} + +static void LogLayout( + const uint32_t param_count, + const reshade::api::pipeline_layout_param* params, + const reshade::api::pipeline_layout layout) { + for (uint32_t param_index = 0; param_index < param_count; ++param_index) { + auto param = params[param_index]; + switch (param.type) { + case reshade::api::pipeline_layout_param_type::descriptor_table: + for (uint32_t range_index = 0; range_index < param.descriptor_table.count; ++range_index) { + auto range = param.descriptor_table.ranges[range_index]; + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | TBL"; + s << " | " << reinterpret_cast(¶m.descriptor_table.ranges); + s << " | "; + switch (range.type) { + case reshade::api::descriptor_type::sampler: + s << "SMP"; + break; + case reshade::api::descriptor_type::sampler_with_resource_view: + s << "SMPRV"; + break; + case reshade::api::descriptor_type::texture_shader_resource_view: + s << "TSRV"; + break; + case reshade::api::descriptor_type::texture_unordered_access_view: + s << "TUAV"; + break; + case reshade::api::descriptor_type::constant_buffer: + s << "CBV"; + break; + case reshade::api::descriptor_type::shader_storage_buffer: + s << "SSB"; + break; + case reshade::api::descriptor_type::acceleration_structure: + s << "ACC"; + break; + default: + s << "??? (0x" << std::hex << static_cast(range.type) << std::dec << ")"; + } + + s << ", array_size: " << range.array_size; + s << ", binding: " << range.binding; + s << ", count: " << range.count; + s << ", register: " << range.dx_register_index; + s << ", space: " << range.dx_register_space; + s << ", visibility: " << range.visibility; + s << ")"; + s << " [" << range_index << "/" << param.descriptor_table.count << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + break; + case reshade::api::pipeline_layout_param_type::push_constants: { + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | PC"; + s << ", binding: " << param.push_constants.binding; + s << ", count " << param.push_constants.count; + s << ", register: " << param.push_constants.dx_register_index; + s << ", space: " << param.push_constants.dx_register_space; + s << ", visibility " << param.push_constants.visibility; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + break; + } + case reshade::api::pipeline_layout_param_type::push_descriptors: { + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | PD |"; + s << " array_size: " << param.push_descriptors.array_size; + s << ", binding: " << param.push_descriptors.binding; + s << ", count " << param.push_descriptors.count; + s << ", register: " << param.push_descriptors.dx_register_index; + s << ", space: " << param.push_descriptors.dx_register_space; + s << ", type: " << param.push_descriptors.type; + s << ", visibility " << param.push_descriptors.visibility; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + break; + } + case reshade::api::pipeline_layout_param_type::push_descriptors_with_ranges: { + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | PDR?? | "; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + break; + } +#if RESHADE_API_VERSION >= 13 + case reshade::api::pipeline_layout_param_type::descriptor_table_with_static_samplers: + for (uint32_t range_index = 0; range_index < param.descriptor_table_with_static_samplers.count; ++range_index) { + auto range = param.descriptor_table_with_static_samplers.ranges[range_index]; + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | TBLSS"; + s << " | " << reinterpret_cast(¶m.descriptor_table.ranges); + s << " | "; + if (range.static_samplers == nullptr) { + s << " null "; + } else { + s << ", filter: " << static_cast(range.static_samplers->filter); + s << ", address_u: " << static_cast(range.static_samplers->address_u); + s << ", address_v: " << static_cast(range.static_samplers->address_v); + s << ", address_w: " << static_cast(range.static_samplers->address_w); + s << ", mip_lod_bias: " << static_cast(range.static_samplers->mip_lod_bias); + s << ", max_anisotropy: " << static_cast(range.static_samplers->max_anisotropy); + s << ", compare_op: " << static_cast(range.static_samplers->compare_op); + s << ", border_color: [" << range.static_samplers->border_color[0] << ", " << range.static_samplers->border_color[1] << ", " << range.static_samplers->border_color[2] << ", " << range.static_samplers->border_color[3] << "]"; + s << ", min_lod: " << range.static_samplers->min_lod; + s << ", max_lod: " << range.static_samplers->max_lod; + } + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + break; + case reshade::api::pipeline_layout_param_type::push_descriptors_with_static_samplers: + for (uint32_t range_index = 0; range_index < param.descriptor_table.count; ++range_index) { + auto range = param.descriptor_table_with_static_samplers.ranges[range_index]; + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | PDSS"; + s << " | " << reinterpret_cast(&range); + s << " | "; + if (range.static_samplers == nullptr) { + s << "not"; + } else { + s << "filter: " << static_cast(range.static_samplers->filter); + s << ", address_u: " << static_cast(range.static_samplers->address_u); + s << ", address_v: " << static_cast(range.static_samplers->address_v); + s << ", address_w: " << static_cast(range.static_samplers->address_w); + s << ", mip_lod_bias: " << static_cast(range.static_samplers->mip_lod_bias); + s << ", max_anisotropy: " << static_cast(range.static_samplers->max_anisotropy); + s << ", compare_op: " << static_cast(range.static_samplers->compare_op); + s << ", border_color: [" << range.static_samplers->border_color[0] << ", " << range.static_samplers->border_color[1] << ", " << range.static_samplers->border_color[2] << ", " << range.static_samplers->border_color[3] << "]"; + s << ", min_lod: " << range.static_samplers->min_lod; + s << ", max_lod: " << range.static_samplers->max_lod; + } + s << ")"; + s << " [" << range_index << "/" << param.descriptor_table.count << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + break; +#endif + default: { + std::stringstream s; + s << "logPipelineLayout("; + s << reinterpret_cast(layout.handle) << "[" << param_index << "]"; + s << " | ??? (0x" << std::hex << static_cast(param.type) << std::dec << ")"; + s << " | " << param.type; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + } + } +} + +// AfterCreateRootSignature +static void OnInitPipelineLayout( + reshade::api::device* device, + const uint32_t param_count, + const reshade::api::pipeline_layout_param* params, + reshade::api::pipeline_layout layout) { + LogLayout(param_count, params, layout); + + const bool found_visiblity = false; + uint32_t cbv_index = 0; + uint32_t pc_count = 0; + + for (uint32_t param_index = 0; param_index < param_count; ++param_index) { + auto param = params[param_index]; + if (param.type == reshade::api::pipeline_layout_param_type::descriptor_table) { + for (uint32_t range_index = 0; range_index < param.descriptor_table.count; ++range_index) { + auto range = param.descriptor_table.ranges[range_index]; + if (range.type == reshade::api::descriptor_type::constant_buffer) { + if (cbv_index < range.dx_register_index + range.count) { + cbv_index = range.dx_register_index + range.count; + } + } + } + } else if (param.type == reshade::api::pipeline_layout_param_type::push_constants) { + pc_count++; + if (cbv_index < param.push_constants.dx_register_index + param.push_constants.count) { + cbv_index = param.push_constants.dx_register_index + param.push_constants.count; + } + } else if (param.type == reshade::api::pipeline_layout_param_type::push_descriptors) { + if (param.push_descriptors.type == reshade::api::descriptor_type::constant_buffer) { + if (cbv_index < param.push_descriptors.dx_register_index + param.push_descriptors.count) { + cbv_index = param.push_descriptors.dx_register_index + param.push_descriptors.count; + } + } + } + } + + const uint32_t max_count = 64u - (param_count + 1u) + 1u; + + std::stringstream s; + s << "on_init_pipeline_layout++("; + s << reinterpret_cast(layout.handle); + s << " , max injections: " << (max_count); + s << " )"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +// After CreatePipelineState +void OnInitPipeline( + reshade::api::device* device, + reshade::api::pipeline_layout layout, + uint32_t subobject_count, + const reshade::api::pipeline_subobject* subobjects, + reshade::api::pipeline pipeline) { + if (subobject_count == 0) { + std::stringstream s; + s << "on_init_pipeline("; + s << reinterpret_cast(pipeline.handle); + s << ", layout:" << reinterpret_cast(layout.handle); + s << ", subobjects: " << (subobject_count); + s << " )"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + return; + } + + for (uint32_t i = 0; i < subobject_count; ++i) { + const auto& subobject = subobjects[i]; + for (uint32_t j = 0; j < subobject.count; ++j) { + std::stringstream s; + s << "on_init_pipeline("; + s << reinterpret_cast(pipeline.handle); + s << "[" << i << "][" << j << "]"; + s << ", layout:" << reinterpret_cast(layout.handle); + s << ", type: " << subobject.type; + switch (subobject.type) { + case reshade::api::pipeline_subobject_type::hull_shader: + case reshade::api::pipeline_subobject_type::domain_shader: + case reshade::api::pipeline_subobject_type::geometry_shader: + // reshade::api::shader_desc &desc = static_cast(subobjects[i].data[j]); + break; + case reshade::api::pipeline_subobject_type::blend_state: + break; // Disabled for now + { + auto& desc = static_cast(subobject.data)[j]; + s << ", alpha_to_coverage_enable: " << desc.alpha_to_coverage_enable; + s << ", source_color_blend_factor: " << desc.source_color_blend_factor[0]; + s << ", dest_color_blend_factor: " << desc.dest_color_blend_factor[0]; + s << ", color_blend_op: " << desc.color_blend_op[0]; + s << ", source_alpha_blend_factor: " << desc.source_alpha_blend_factor[0]; + s << ", dest_alpha_blend_factor: " << desc.dest_alpha_blend_factor[0]; + s << ", alpha_blend_op: " << desc.alpha_blend_op[0]; + s << ", render_target_write_mask: " << std::hex << desc.render_target_write_mask[0] << std::dec; + } + break; + case reshade::api::pipeline_subobject_type::vertex_shader: + case reshade::api::pipeline_subobject_type::compute_shader: + case reshade::api::pipeline_subobject_type::pixel_shader: { + auto& desc = (static_cast(subobject.data))[j]; + s << ", shader: "; + if (desc.code_size == 0) { + s << "(empty)"; + } else { + auto shader_hash = compute_crc32(static_cast(desc.code), desc.code_size); + s << PRINT_CRC32(shader_hash); + } + break; + } + default: + break; + } + + s << " )"; + + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + } +} + +static void OnDestroyPipeline( + reshade::api::device* device, + reshade::api::pipeline pipeline) { + std::stringstream s; + s << "on_destroy_pipeline("; + s << reinterpret_cast(pipeline.handle); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +static void OnPushConstants( + reshade::api::command_list* cmd_list, + reshade::api::shader_stage stages, + reshade::api::pipeline_layout layout, + uint32_t layout_param, + uint32_t first, + uint32_t count, + const void* values) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + std::stringstream s; + s << "push_constants(" << reinterpret_cast(layout.handle); + s << "[" << layout_param << "]"; + s << ", stage: " << std::hex << static_cast(stages) << std::dec << " (" << stages << ")"; + s << ", count: " << count; + s << "{ 0x"; + for (uint32_t i = 0; i < count; i++) { + s << std::hex << static_cast(values)[i] << std::dec << ", "; + } + s << " })"; + + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +// AfterSetPipelineState +static void OnBindPipeline( + reshade::api::command_list* cmd_list, + reshade::api::pipeline_stage stages, + reshade::api::pipeline pipeline) { + if (!trace_running) return; + + std::stringstream s; + s << "bind_pipeline("; + s << (void*)pipeline.handle; + auto details = renodx::utils::shader::GetPipelineShaderDetails(cmd_list->get_device(), pipeline); + if (details.has_value()) { + s << ", layout" << reinterpret_cast(details->layout.handle); + for (auto& [type, shader_hash] : details->shader_hashes_by_type) { + s << ", " << type << ": " << PRINT_CRC32(shader_hash); + } + } + s << ", stages: " << stages << " (" << std::hex << static_cast(stages) << std::dec << ")"; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +static bool OnDraw( + reshade::api::command_list* cmd_list, + uint32_t vertex_count, + uint32_t instance_count, + uint32_t first_vertex, + uint32_t first_instance) { + if (trace_running) { + std::stringstream s; + s << "on_draw"; + s << "(" << vertex_count; + s << ", " << instance_count; + s << ", " << first_vertex; + s << ", " << first_instance; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + return false; +} + +static bool OnDispatch(reshade::api::command_list* cmd_list, uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) { + if (trace_running) { + std::stringstream s; + s << "on_dispatch"; + s << "(" << group_count_x; + s << ", " << group_count_y; + s << ", " << group_count_z; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + // InstructionState state = instructions.at(instructions.size() - 1); + // state.action = reshade::addon_event::dispatch; + // resetInstructionState(); + } + return false; +} + +static bool OnDrawIndexed( + reshade::api::command_list* cmd_list, + uint32_t index_count, + uint32_t instance_count, + uint32_t first_index, + int32_t vertex_offset, + uint32_t first_instance) { + if (trace_running) { + std::stringstream s; + s << "on_draw_indexed"; + s << "(" << index_count; + s << ", " << instance_count; + s << ", " << first_index; + s << ", " << vertex_offset; + s << ", " << first_instance; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + // InstructionState state = instructions.at(instructions.size() - 1); + // state.action = reshade::addon_event::draw_indexed; + // resetInstructionState(); + } + return false; +} + +static bool OnDrawOrDispatchIndirect( + reshade::api::command_list* cmd_list, + reshade::api::indirect_command type, + reshade::api::resource buffer, + uint64_t offset, + uint32_t draw_count, + uint32_t stride) { + if (trace_running) { + std::stringstream s; + s << "on_draw_or_dispatch_indirect(" << type; + s << ", " << reinterpret_cast(buffer.handle); + s << ", " << offset; + s << ", " << draw_count; + s << ", " << stride; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + // InstructionState state = instructions.at(instructions.size() - 1); + // state.action = reshade::addon_event::draw_or_dispatch_indirect; + // resetInstructionState(); + } + return false; +} + +static bool OnCopyTextureRegion( + reshade::api::command_list* cmd_list, + reshade::api::resource source, + uint32_t source_subresource, + const reshade::api::subresource_box* source_box, + reshade::api::resource dest, + uint32_t dest_subresource, + const reshade::api::subresource_box* dest_box, + reshade::api::filter_mode filter) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_copy_texture_region"; + s << "(" << reinterpret_cast(source.handle); + s << ", " << (source_subresource); + s << ", " << reinterpret_cast(dest.handle); + s << ", " << static_cast(filter); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + + return false; +} + +static bool OnCopyTextureToBuffer( + reshade::api::command_list* cmd_list, + reshade::api::resource source, + uint32_t source_subresource, + const reshade::api::subresource_box* source_box, + reshade::api::resource dest, + uint64_t dest_offset, + uint32_t row_length, + uint32_t slice_height) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_copy_texture_region(" << reinterpret_cast(source.handle); + s << "[" << source_subresource << "]"; + if (source_box != nullptr) { + s << "(" << source_box->top << ", " << source_box->left << ", " << source_box->front << ")"; + } + s << " => " << reinterpret_cast(dest.handle); + s << "[" << dest_offset << "]"; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + + return false; +} + +static bool OnCopyBufferToTexture( + reshade::api::command_list* cmd_list, + reshade::api::resource source, + uint64_t source_offset, + uint32_t row_length, + uint32_t slice_height, + reshade::api::resource dest, + uint32_t dest_subresource, + const reshade::api::subresource_box* dest_box) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_copy_texture_region"; + s << "(" << reinterpret_cast(source.handle); + s << "[" << source_offset << "]"; + s << " => " << reinterpret_cast(dest.handle); + s << "[" << dest_subresource << "]"; + if (dest_box != nullptr) { + s << "(" << dest_box->top << ", " << dest_box->left << ", " << dest_box->front << ")"; + } + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + + return false; +} + +static bool OnResolveTextureRegion( + reshade::api::command_list* cmd_list, + reshade::api::resource source, + uint32_t source_subresource, + const reshade::api::subresource_box* source_box, + reshade::api::resource dest, + uint32_t dest_subresource, + int32_t dest_x, + int32_t dest_y, + int32_t dest_z, + reshade::api::format format) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_resolve_texture_region"; + s << "(" << reinterpret_cast(source.handle); + s << ": " << (source_subresource); + s << " => " << reinterpret_cast(dest.handle); + s << ": " << (dest_subresource); + s << ", (" << dest_x << ", " << dest_y << ", " << dest_z << ") "; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + return false; +} + +static bool OnCopyResource( + reshade::api::command_list* cmd_list, + reshade::api::resource source, + reshade::api::resource dest) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_copy_resource"; + s << "(" << reinterpret_cast(source.handle); + s << " => " << reinterpret_cast(dest.handle); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + return false; +} + +static void OnBarrier( + reshade::api::command_list* cmd_list, + uint32_t count, + const reshade::api::resource* resources, + const reshade::api::resource_usage* old_states, + const reshade::api::resource_usage* new_states) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + for (uint32_t i = 0; i < count; i++) { + std::stringstream s; + s << "on_barrier(" << reinterpret_cast(resources[i].handle); + s << ", " << std::hex << static_cast(old_states[i]) << std::dec << " (" << old_states[i] << ")"; + s << " => " << std::hex << static_cast(new_states[i]) << std::dec << " (" << new_states[i] << ")"; + s << ") [" << i << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } +} + +static void OnBindRenderTargetsAndDepthStencil( + reshade::api::command_list* cmd_list, + uint32_t count, + const reshade::api::resource_view* rtvs, + reshade::api::resource_view dsv) { + if (!trace_running) return; + + if (count != 0) { + // InstructionState state = instructions.at(instructions.size() - 1); + // state.renderTargets.clear(); + auto* device = cmd_list->get_device(); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + for (uint32_t i = 0; i < count; i++) { + auto rtv = rtvs[i]; + // if (rtv.handle) { + // state.renderTargets.push_back(rtv.handle); + // } + std::stringstream s; + s << "on_bind_render_targets("; + s << reinterpret_cast(rtv.handle); + s << ", res: " << reinterpret_cast(GetResourceByViewHandle(data, rtv.handle)); + s << ", name: " << GetResourceNameByViewHandle(data, rtv.handle); + s << ")"; + s << "[" << i << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + } + if (dsv.handle != 0) { + std::stringstream s; + s << "on_bind_depth_stencil("; + s << reinterpret_cast(dsv.handle); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } +} + +static void OnInitResource( + reshade::api::device* device, + const reshade::api::resource_desc& desc, + const reshade::api::subresource_data* initial_data, + reshade::api::resource_usage initial_state, + reshade::api::resource resource) { + auto& data = device->get_private_data(); + const std::unique_lock lock(data.mutex); + data.resources.emplace(resource.handle); + + if (!FORCE_ALL && !trace_running && present_count >= MAX_PRESENT_COUNT) return; + + bool warn = false; + std::stringstream s; + s << "init_resource(" << reinterpret_cast(resource.handle); + s << ", flags: " << std::hex << static_cast(desc.flags) << std::dec; + s << ", state: " << std::hex << static_cast(initial_state) << std::dec; + s << ", type: " << desc.type; + s << ", usage: " << std::hex << static_cast(desc.usage) << std::dec; + + switch (desc.type) { + case reshade::api::resource_type::buffer: + s << ", size: " << desc.buffer.size; + s << ", stride: " << desc.buffer.stride; + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + break; + case reshade::api::resource_type::texture_1d: + case reshade::api::resource_type::texture_2d: + case reshade::api::resource_type::texture_3d: + case reshade::api::resource_type::surface: + s << ", width: " << desc.texture.width; + s << ", height: " << desc.texture.height; + s << ", levels: " << desc.texture.levels; + s << ", format: " << desc.texture.format; + if (desc.texture.format == reshade::api::format::unknown) { + warn = true; + } + break; + default: + case reshade::api::resource_type::unknown: + break; + } + + s << ")"; + reshade::log_message( + warn + ? reshade::log_level::warning + : reshade::log_level::info, + s.str().c_str()); +} + +static void OnDestroyResource(reshade::api::device* device, reshade::api::resource resource) { + auto& data = device->get_private_data(); + const std::unique_lock lock(data.mutex); + data.resources.erase(resource.handle); + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + + std::stringstream s; + s << "on_destroy_resource("; + s << reinterpret_cast(resource.handle); + s << ")"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); +} + +static void OnInitResourceView( + reshade::api::device* device, + reshade::api::resource resource, + reshade::api::resource_usage usage_type, + const reshade::api::resource_view_desc& desc, + reshade::api::resource_view view) { + auto& data = device->get_private_data(); + const std::unique_lock lock(data.mutex); + if (data.resource_views.contains(view.handle)) { + if (trace_running || present_count < MAX_PRESENT_COUNT) { + std::stringstream s; + s << "init_resource_view(reused view: "; + s << reinterpret_cast(view.handle); + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + if (resource.handle == 0) { + data.resource_views.erase(view.handle); + return; + } + } + if (resource.handle != 0) { + data.resource_views.emplace(view.handle, resource.handle); + } + + if (!FORCE_ALL && !trace_running && present_count >= MAX_PRESENT_COUNT) return; + std::stringstream s; + s << "init_resource_view(" << reinterpret_cast(view.handle); + s << ", view type: " << desc.type << " (0x" << std::hex << static_cast(desc.type) << std::dec << ")"; + s << ", view format: " << desc.format << " (0x" << std::hex << static_cast(desc.format) << std::dec << ")"; + s << ", resource: " << reinterpret_cast(resource.handle); + s << ", resource usage: " << usage_type << " 0x" << std::hex << static_cast(usage_type) << std::dec; + // if (desc.type == reshade::api::resource_view_type::buffer) return; + if (resource.handle != 0) { + const auto resource_desc = device->get_resource_desc(resource); + s << ", resource type: " << resource_desc.type; + + switch (resource_desc.type) { + default: + case reshade::api::resource_type::unknown: + break; + case reshade::api::resource_type::buffer: + // if (!traceRunning) return; + return; + s << ", buffer offset: " << desc.buffer.offset; + s << ", buffer size: " << desc.buffer.size; + break; + case reshade::api::resource_type::texture_1d: + case reshade::api::resource_type::texture_2d: + case reshade::api::resource_type::surface: + s << ", texture format: " << resource_desc.texture.format; + s << ", texture width: " << resource_desc.texture.width; + s << ", texture height: " << resource_desc.texture.height; + break; + case reshade::api::resource_type::texture_3d: + s << ", texture format: " << resource_desc.texture.format; + s << ", texture width: " << resource_desc.texture.width; + s << ", texture height: " << resource_desc.texture.height; + s << ", texture depth: " << resource_desc.texture.depth_or_layers; + break; + } + } + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +static void OnDestroyResourceView(reshade::api::device* device, reshade::api::resource_view view) { + std::stringstream s; + s << "on_destroy_resource_view("; + s << reinterpret_cast(view.handle); + s << ")"; + reshade::log_message(reshade::log_level::debug, s.str().c_str()); + + auto& data = device->get_private_data(); + const std::unique_lock lock(data.mutex); + data.resource_views.erase(view.handle); +} + +static void OnPushDescriptors( + reshade::api::command_list* cmd_list, + reshade::api::shader_stage stages, + reshade::api::pipeline_layout layout, + uint32_t layout_param, + const reshade::api::descriptor_table_update& update) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + auto* device = cmd_list->get_device(); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + for (uint32_t i = 0; i < update.count; i++) { + std::stringstream s; + s << "push_descriptors(" << reinterpret_cast(layout.handle); + s << "[" << layout_param << "]"; + s << "[" << update.binding + i << "]"; + s << ", type: " << update.type; + + auto log_heap = [=]() { + std::stringstream s2; + uint32_t base_offset = 0; + reshade::api::descriptor_heap heap = {0}; + device->get_descriptor_heap_offset(update.table, update.binding + i, 0, &heap, &base_offset); + s2 << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; + return s2.str(); + }; + + switch (update.type) { + case reshade::api::descriptor_type::sampler: { + s << log_heap(); + auto item = static_cast(update.descriptors)[i]; + s << ", sampler: " << reinterpret_cast(item.handle); + break; + } + case reshade::api::descriptor_type::sampler_with_resource_view: { + s << log_heap(); + auto item = static_cast(update.descriptors)[i]; + s << ", sampler: " << reinterpret_cast(item.sampler.handle); + s << ", rsv: " << reinterpret_cast(item.view.handle); + s << ", res: " << reinterpret_cast(GetResourceByViewHandle(data, item.view.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); + break; + } + case reshade::api::descriptor_type::buffer_shader_resource_view: + + case reshade::api::descriptor_type::shader_resource_view: { + s << log_heap(); + auto item = static_cast(update.descriptors)[i]; + s << ", shaderrsv: " << reinterpret_cast(item.handle); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.handle); + break; + } + case reshade::api::descriptor_type::buffer_unordered_access_view: + + case reshade::api::descriptor_type::unordered_access_view: { + s << log_heap(); + auto item = static_cast(update.descriptors)[i]; + s << ", uav: " << reinterpret_cast(item.handle); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.handle); + break; + } + case reshade::api::descriptor_type::acceleration_structure: { + auto item = static_cast(update.descriptors)[i]; + s << ", accl: " << reinterpret_cast(item.handle); + break; + } + case reshade::api::descriptor_type::constant_buffer: { + auto item = static_cast(update.descriptors)[i]; + s << ", buffer: " << reinterpret_cast(item.buffer.handle); + s << ", size: " << item.size; + s << ", offset: " << item.offset; + break; + } + default: + s << ", type: " << update.type; + break; + } + + s << ")"; + s << "[" << update.binding + i << " / " << update.count << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } +} + +static void OnBindDescriptorTables( + reshade::api::command_list* cmd_list, + reshade::api::shader_stage stages, + reshade::api::pipeline_layout layout, + uint32_t first, + uint32_t count, + const reshade::api::descriptor_table* tables) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + auto* device = cmd_list->get_device(); + for (uint32_t i = 0; i < count; ++i) { + std::stringstream s; + s << "bind_descriptor_table(" << reinterpret_cast(layout.handle); + s << "[" << (first + i) << "]"; + s << ", stages: " << stages << "(" << std::hex << static_cast(stages) << std::dec << ")"; + s << ", table: " << reinterpret_cast(tables[i].handle); + uint32_t base_offset = 0; + reshade::api::descriptor_heap heap = {0}; + device->get_descriptor_heap_offset(tables[i], 0, 0, &heap, &base_offset); + s << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; + + auto& descriptor_data = device->get_private_data(); + const std::shared_lock decriptor_lock(descriptor_data.mutex); + for (uint32_t j = 0; j < 13; ++j) { + auto origin_primary_key = std::pair(tables[i].handle, j); + if (auto pair = descriptor_data.table_descriptor_resource_views.find(origin_primary_key); + pair != descriptor_data.table_descriptor_resource_views.end()) { + auto update = pair->second; + auto view = renodx::utils::descriptor::GetResourceViewFromDescriptorUpdate(update); + if (view.handle != 0) { + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", rsv[" << j << "]: " << reinterpret_cast(view.handle); + s << ", res[" << j << "]: " << reinterpret_cast(GetResourceByViewHandle(data, view.handle)); + } + } + } + + s << ") [" << i << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } +} + +static bool OnCopyDescriptorTables( + reshade::api::device* device, + uint32_t count, + const reshade::api::descriptor_table_copy* copies) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + + for (uint32_t i = 0; i < count; i++) { + const auto& copy = copies[i]; + + for (uint32_t j = 0; j < copy.count; j++) { + std::stringstream s; + s << "copy_descriptor_tables("; + s << reinterpret_cast(copy.source_table.handle); + s << "[" << copy.source_binding + j << "]"; + s << " => "; + s << reinterpret_cast(copy.dest_table.handle); + s << "[" << copy.dest_binding + j << "]"; + + uint32_t base_offset = 0; + reshade::api::descriptor_heap heap = {0}; + device->get_descriptor_heap_offset( + copy.source_table, copy.source_binding + j, copy.source_array_offset, &heap, &base_offset); + s << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; + device->get_descriptor_heap_offset( + copy.dest_table, copy.dest_binding + j, copy.dest_array_offset, &heap, &base_offset); + s << " => " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; + + auto& descriptor_data = device->get_private_data(); + const std::shared_lock decriptor_lock(descriptor_data.mutex); + auto origin_primary_key = std::pair(copy.source_table.handle, copy.source_binding + j); + if (auto pair = descriptor_data.table_descriptor_resource_views.find(origin_primary_key); + pair != descriptor_data.table_descriptor_resource_views.end()) { + auto update = pair->second; + auto view = renodx::utils::descriptor::GetResourceViewFromDescriptorUpdate(update); + if (view.handle != 0) { + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", rsv: " << reinterpret_cast(view.handle); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, view.handle)); + } + } + + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + } + + return false; +} + +static bool OnUpdateDescriptorTables( + reshade::api::device* device, + uint32_t count, + const reshade::api::descriptor_table_update* updates) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + + for (uint32_t i = 0; i < count; i++) { + const auto& update = updates[i]; + + for (uint32_t j = 0; j < update.count; j++) { + std::stringstream s; + s << "update_descriptor_tables("; + s << reinterpret_cast(update.table.handle); + s << "[" << update.binding + j << "]"; + + uint32_t base_offset = 0; + reshade::api::descriptor_heap heap = {0}; + device->get_descriptor_heap_offset(update.table, update.binding + j, 0, &heap, &base_offset); + s << ", heap: " << reinterpret_cast(heap.handle) << "[" << base_offset << "]"; + switch (update.type) { + case reshade::api::descriptor_type::sampler: { + auto item = static_cast(update.descriptors)[j]; + s << ", sampler: " << reinterpret_cast(item.handle); + break; + } + case reshade::api::descriptor_type::sampler_with_resource_view: { + auto item = static_cast(update.descriptors)[j]; + s << ", sampler: " << reinterpret_cast(item.sampler.handle); + s << ", rsv: " << reinterpret_cast(item.view.handle); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.view.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); + break; + } + case reshade::api::descriptor_type::buffer_shader_resource_view: { + auto item = static_cast(update.descriptors)[j]; + s << ", b-srv: " << reinterpret_cast(item.handle); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); + break; + } + case reshade::api::descriptor_type::buffer_unordered_access_view: { + auto item = static_cast(update.descriptors)[j]; + s << ", b-uav: " << reinterpret_cast(item.handle); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.view.handle); + break; + } + case reshade::api::descriptor_type::shader_resource_view: { + auto item = static_cast(update.descriptors)[j]; + s << ", srv: " << reinterpret_cast(item.handle); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", res:" << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.handle); + break; + } + case reshade::api::descriptor_type::unordered_access_view: { + auto item = static_cast(update.descriptors)[j]; + s << ", uav: " << reinterpret_cast(item.handle); + auto& data = device->get_private_data(); + const std::shared_lock lock(data.mutex); + s << ", res: " << reinterpret_cast(GetResourceByViewHandle(data, item.handle)); + // s << ", name: " << getResourceNameByViewHandle(data, item.handle); + break; + } + case reshade::api::descriptor_type::constant_buffer: { + auto item = static_cast(update.descriptors)[j]; + s << ", buffer: " << reinterpret_cast(item.buffer.handle); + s << ", size: " << item.size; + s << ", offset: " << item.offset; + break; + } + case reshade::api::descriptor_type::shader_storage_buffer: { + auto item = static_cast(update.descriptors)[j]; + s << ", buffer: " << reinterpret_cast(item.buffer.handle); + s << ", size: " << item.size; + s << ", offset: " << item.offset; + break; + } + case reshade::api::descriptor_type::acceleration_structure: + s << ", accl: unknown"; + break; + default: + break; + } + s << ") [" << i << "]"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } + } + return false; +} + +static bool OnClearRenderTargetView( + reshade::api::command_list* cmd_list, + reshade::api::resource_view rtv, + const float color[4], + uint32_t rect_count, + const reshade::api::rect* rects) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_clear_render_target_view("; + s << reinterpret_cast(rtv.handle); + s << ")"; + + reshade::log_message(reshade::log_level::info, s.str().c_str()); + return false; +} + +static bool OnClearUnorderedAccessViewUint( + reshade::api::command_list* cmd_list, + reshade::api::resource_view uav, + const uint32_t values[4], + uint32_t rect_count, + const reshade::api::rect* rects) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return false; + std::stringstream s; + s << "on_clear_unordered_access_view_uint("; + s << reinterpret_cast(uav.handle); + s << ")"; + + reshade::log_message(reshade::log_level::info, s.str().c_str()); + return false; +} + +static void OnMapBufferRegion( + reshade::api::device* device, + reshade::api::resource resource, + uint64_t offset, + uint64_t size, + reshade::api::map_access access, + void** data) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + std::stringstream s; + s << "map_buffer_region("; + s << reinterpret_cast(resource.handle); + s << ")"; + + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +static void OnMapTextureRegion( + reshade::api::device* device, + reshade::api::resource resource, + uint32_t subresource, + const reshade::api::subresource_box* box, + reshade::api::map_access access, + reshade::api::subresource_data* data) { + if (!trace_running && present_count >= MAX_PRESENT_COUNT) return; + std::stringstream s; + s << "map_texture_region("; + s << reinterpret_cast(resource.handle); + s << "[" << subresource << "]"; + s << ")"; + + reshade::log_message(reshade::log_level::info, s.str().c_str()); +} + +static void OnBindPipelineStates( + reshade::api::command_list* cmd_list, + uint32_t count, + const reshade::api::dynamic_state* states, + const uint32_t* values) { + if (!trace_running) return; + + for (uint32_t i = 0; i < count; i++) { + std::stringstream s; + s << "bind_pipeline_state"; + s << "(" << states[i]; + s << ", " << values[i]; + s << ")"; + reshade::log_message(reshade::log_level::info, s.str().c_str()); + } +} + +static void OnPresent( + reshade::api::command_queue* queue, + reshade::api::swapchain* swapchain, + const reshade::api::rect* source_rect, + const reshade::api::rect* dest_rect, + uint32_t dirty_rect_count, + const reshade::api::rect* dirty_rects) { + if (trace_running) { + reshade::log_message(reshade::log_level::info, "present()"); + reshade::log_message(reshade::log_level::info, "--- End Frame ---"); + trace_running = false; + } else if (trace_scheduled) { + trace_scheduled = false; + trace_running = true; + reshade::log_message(reshade::log_level::info, "--- Frame ---"); + } + if (present_count <= MAX_PRESENT_COUNT) { + present_count++; + } +} +} // namespace internal + +inline void Use(DWORD fdw_reason) { + renodx::utils::descriptor::Use(fdw_reason); + renodx::utils::shader::Use(fdw_reason); + + switch (fdw_reason) { + case DLL_PROCESS_ATTACH: + if (internal::attached) return; + internal::attached = true; + reshade::register_event(internal::OnInitDevice); + reshade::register_event(internal::OnDestroyDevice); + reshade::register_event(internal::OnInitSwapchain); + + reshade::register_event(internal::OnCreatePipelineLayout); + reshade::register_event(internal::OnInitPipelineLayout); + + reshade::register_event(internal::OnInitPipeline); + reshade::register_event(internal::OnDestroyPipeline); + + reshade::register_event(internal::OnBindPipeline); + reshade::register_event(internal::OnBindPipelineStates); + + reshade::register_event(internal::OnInitResource); + reshade::register_event(internal::OnDestroyResource); + reshade::register_event(internal::OnInitResourceView); + reshade::register_event(internal::OnDestroyResourceView); + + reshade::register_event(internal::OnPushDescriptors); + reshade::register_event(internal::OnBindDescriptorTables); + reshade::register_event(internal::OnCopyDescriptorTables); + reshade::register_event(internal::OnUpdateDescriptorTables); + reshade::register_event(internal::OnPushConstants); + + reshade::register_event(internal::OnClearRenderTargetView); + reshade::register_event(internal::OnClearUnorderedAccessViewUint); + + reshade::register_event(internal::OnMapBufferRegion); + reshade::register_event(internal::OnMapTextureRegion); + + reshade::register_event(internal::OnDraw); + reshade::register_event(internal::OnDispatch); + reshade::register_event(internal::OnDrawIndexed); + reshade::register_event(internal::OnDrawOrDispatchIndirect); + reshade::register_event(internal::OnBindRenderTargetsAndDepthStencil); + + reshade::register_event(internal::OnCopyTextureRegion); + reshade::register_event(internal::OnCopyTextureToBuffer); + reshade::register_event(internal::OnCopyBufferToTexture); + reshade::register_event(internal::OnResolveTextureRegion); + + reshade::register_event(internal::OnCopyResource); + + reshade::register_event(internal::OnBarrier); + + reshade::register_event(internal::OnPresent); + + break; + case DLL_PROCESS_DETACH: + + reshade::unregister_event(internal::OnInitDevice); + reshade::unregister_event(internal::OnDestroyDevice); + reshade::unregister_event(internal::OnInitSwapchain); + + reshade::unregister_event(internal::OnCreatePipelineLayout); + reshade::unregister_event(internal::OnInitPipelineLayout); + + reshade::unregister_event(internal::OnInitPipeline); + reshade::unregister_event(internal::OnDestroyPipeline); + + reshade::unregister_event(internal::OnBindPipeline); + reshade::unregister_event(internal::OnBindPipelineStates); + + reshade::unregister_event(internal::OnInitResource); + reshade::unregister_event(internal::OnDestroyResource); + reshade::unregister_event(internal::OnInitResourceView); + reshade::unregister_event(internal::OnDestroyResourceView); + + reshade::unregister_event(internal::OnPushDescriptors); + reshade::unregister_event(internal::OnBindDescriptorTables); + reshade::unregister_event(internal::OnCopyDescriptorTables); + reshade::unregister_event(internal::OnUpdateDescriptorTables); + reshade::unregister_event(internal::OnPushConstants); + + reshade::unregister_event(internal::OnClearRenderTargetView); + reshade::unregister_event(internal::OnClearUnorderedAccessViewUint); + + reshade::unregister_event(internal::OnMapBufferRegion); + reshade::unregister_event(internal::OnMapTextureRegion); + + reshade::unregister_event(internal::OnDraw); + reshade::unregister_event(internal::OnDispatch); + reshade::unregister_event(internal::OnDrawIndexed); + reshade::unregister_event(internal::OnDrawOrDispatchIndirect); + reshade::unregister_event(internal::OnBindRenderTargetsAndDepthStencil); + + reshade::unregister_event(internal::OnCopyTextureRegion); + reshade::unregister_event(internal::OnCopyTextureToBuffer); + reshade::unregister_event(internal::OnCopyBufferToTexture); + reshade::unregister_event(internal::OnResolveTextureRegion); + + reshade::unregister_event(internal::OnCopyResource); + + reshade::unregister_event(internal::OnBarrier); + + reshade::unregister_event(internal::OnPresent); + break; + } +} +} // namespace renodx::utils::trace