From edc027a8bcf4c514b071d669e168a4ab31369ef1 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Thu, 26 Dec 2024 18:08:47 -0300 Subject: [PATCH] Devtools IV (#1910) * devtools: fix popen in non-windows environment * devtools: fix frame crash assertion when hidden * devtools: add search to shader list * devtools: add copy name to shader list * devtools: frame dump: search by shader name --- src/core/debug_state.cpp | 8 ++ src/core/debug_state.h | 4 + src/core/devtools/widget/cmd_list.cpp | 37 ++++++- src/core/devtools/widget/cmd_list.h | 6 +- src/core/devtools/widget/common.h | 2 +- src/core/devtools/widget/frame_dump.cpp | 11 +- src/core/devtools/widget/frame_dump.h | 2 + src/core/devtools/widget/frame_graph.cpp | 101 +++++++++--------- src/core/devtools/widget/frame_graph.h | 2 + src/core/devtools/widget/reg_view.cpp | 11 ++ src/core/devtools/widget/shader_list.cpp | 11 ++ src/core/devtools/widget/shader_list.h | 2 + .../renderer_vulkan/vk_pipeline_cache.cpp | 14 ++- .../renderer_vulkan/vk_pipeline_cache.h | 3 + 14 files changed, 159 insertions(+), 55 deletions(-) diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index daf614bd..6508a987 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -11,6 +11,7 @@ #include "libraries/kernel/time.h" #include "libraries/system/msgdialog.h" #include "video_core/amdgpu/pm4_cmds.h" +#include "video_core/renderer_vulkan/vk_pipeline_cache.h" using namespace DebugStateType; @@ -168,8 +169,12 @@ void DebugStateImpl::PushRegsDump(uintptr_t base_addr, uintptr_t header_addr, if ((*dump)->regs.stage_enable.IsStageEnabled(i)) { auto stage = (*dump)->regs.ProgramForStage(i); if (stage->address_lo != 0) { + const auto& info = AmdGpu::Liverpool::SearchBinaryInfo(stage->Address()); auto code = stage->Code(); (*dump)->stages[i] = PipelineShaderProgramDump{ + .name = Vulkan::PipelineCache::GetShaderName(Shader::StageFromIndex(i), + info.shader_hash), + .hash = info.shader_hash, .user_data = *stage, .code = std::vector{code.begin(), code.end()}, }; @@ -191,7 +196,10 @@ void DebugStateImpl::PushRegsDumpCompute(uintptr_t base_addr, uintptr_t header_a auto& cs = (*dump)->regs.cs_program; cs = cs_state; + const auto& info = AmdGpu::Liverpool::SearchBinaryInfo(cs.Address()); (*dump)->cs_data = PipelineComputerProgramDump{ + .name = Vulkan::PipelineCache::GetShaderName(Shader::Stage::Compute, info.shader_hash), + .hash = info.shader_hash, .cs_program = cs, .code = std::vector{cs.Code().begin(), cs.Code().end()}, }; diff --git a/src/core/debug_state.h b/src/core/debug_state.h index a0e428b6..6a8e15ba 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -50,11 +50,15 @@ struct QueueDump { }; struct PipelineShaderProgramDump { + std::string name; + u64 hash; Vulkan::Liverpool::ShaderProgram user_data{}; std::vector code{}; }; struct PipelineComputerProgramDump { + std::string name; + u64 hash; Vulkan::Liverpool::ComputeProgram cs_program{}; std::vector code{}; }; diff --git a/src/core/devtools/widget/cmd_list.cpp b/src/core/devtools/widget/cmd_list.cpp index 7c550cf2..dc3eb9cd 100644 --- a/src/core/devtools/widget/cmd_list.cpp +++ b/src/core/devtools/widget/cmd_list.cpp @@ -1174,7 +1174,7 @@ CmdListViewer::CmdListViewer(DebugStateType::FrameDump* _frame_dump, } } -void CmdListViewer::Draw(bool only_batches_view) { +void CmdListViewer::Draw(bool only_batches_view, CmdListFilter& filter) { const auto& ctx = *GetCurrentContext(); if (batch_view.open) { @@ -1285,6 +1285,41 @@ void CmdListViewer::Draw(bool only_batches_view) { } auto& batch = std::get(event); + + // filtering + { + bool remove = false; + + if (filter.shader_name[0] != '\0') { + remove = true; + std::string_view shader_name{filter.shader_name}; + const auto& data = frame_dump->regs.find(batch.command_addr); + if (data != frame_dump->regs.end()) { + DebugStateType::RegDump& dump = data->second; + if (dump.is_compute) { + if (dump.cs_data.name.contains(shader_name)) { + remove = false; + break; + } + } else { + for (int i = 0; i < DebugStateType::RegDump::MaxShaderStages; ++i) { + if (dump.regs.stage_enable.IsStageEnabled(i)) { + auto& stage = dump.stages[i]; + if (stage.name.contains(shader_name)) { + remove = false; + break; + } + } + } + } + } + } + + if (remove) { + continue; + } + } + auto const* pm4_hdr = reinterpret_cast(cmdb_addr + batch.start_addr); diff --git a/src/core/devtools/widget/cmd_list.h b/src/core/devtools/widget/cmd_list.h index ed71d0b7..e2c61f6b 100644 --- a/src/core/devtools/widget/cmd_list.h +++ b/src/core/devtools/widget/cmd_list.h @@ -35,6 +35,10 @@ void ParseDepthControl(u32 value, bool begin_table = true); void ParseEqaa(u32 value, bool begin_table = true); void ParseZInfo(u32 value, bool begin_table = true); +struct CmdListFilter { + char shader_name[128]{}; +}; + class CmdListViewer { DebugStateType::FrameDump* frame_dump; @@ -70,7 +74,7 @@ public: explicit CmdListViewer(DebugStateType::FrameDump* frame_dump, const std::vector& cmd_list, uintptr_t base_addr = 0, std::string name = ""); - void Draw(bool only_batches_view = false); + void Draw(bool only_batches_view, CmdListFilter& filter); }; } // namespace Core::Devtools::Widget diff --git a/src/core/devtools/widget/common.h b/src/core/devtools/widget/common.h index 75eb5530..4684f6e3 100644 --- a/src/core/devtools/widget/common.h +++ b/src/core/devtools/widget/common.h @@ -117,7 +117,7 @@ static bool IsDrawCall(AmdGpu::PM4ItOpcode opcode) { inline std::optional exec_cli(const char* cli) { std::array buffer{}; std::string output; - const auto f = popen(cli, "rt"); + const auto f = popen(cli, "r"); if (!f) { pclose(f); return {}; diff --git a/src/core/devtools/widget/frame_dump.cpp b/src/core/devtools/widget/frame_dump.cpp index 055ce133..646ccb6d 100644 --- a/src/core/devtools/widget/frame_dump.cpp +++ b/src/core/devtools/widget/frame_dump.cpp @@ -132,6 +132,15 @@ void FrameDumpViewer::Draw() { } } EndDisabled(); + SameLine(); + if (BeginMenu("Filter")) { + + TextUnformatted("Shader name"); + SameLine(); + InputText("##filter_shader", filter.shader_name, sizeof(filter.shader_name)); + + ImGui::EndMenu(); + } TextEx("Submit num"); SameLine(); @@ -187,7 +196,7 @@ void FrameDumpViewer::Draw() { EndGroup(); } if (is_showing && selected_cmd != -1) { - cmd_list_viewer[selected_cmd].Draw(is_collapsed); + cmd_list_viewer[selected_cmd].Draw(is_collapsed, filter); } End(); } diff --git a/src/core/devtools/widget/frame_dump.h b/src/core/devtools/widget/frame_dump.h index cc4fe638..94075112 100644 --- a/src/core/devtools/widget/frame_dump.h +++ b/src/core/devtools/widget/frame_dump.h @@ -27,6 +27,8 @@ class FrameDumpViewer { s32 selected_queue_num2; s32 selected_cmd = -1; + CmdListFilter filter; + public: bool is_open = true; diff --git a/src/core/devtools/widget/frame_graph.cpp b/src/core/devtools/widget/frame_graph.cpp index 952f50c3..0e170db3 100644 --- a/src/core/devtools/widget/frame_graph.cpp +++ b/src/core/devtools/widget/frame_graph.cpp @@ -19,6 +19,57 @@ constexpr float BAR_HEIGHT_MULT = 1.25f; constexpr float FRAME_GRAPH_PADDING_Y = 3.0f; constexpr static float FRAME_GRAPH_HEIGHT = 50.0f; +void FrameGraph::DrawFrameGraph() { + // Frame graph - inspired by + // https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times + const float full_width = GetContentRegionAvail().x; + auto pos = GetCursorScreenPos(); + const ImVec2 size{full_width, FRAME_GRAPH_HEIGHT + FRAME_GRAPH_PADDING_Y * 2.0f}; + ItemSize(size); + if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) { + return; + } + + float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv()); + float cur_pos_x = pos.x + full_width; + pos.y += FRAME_GRAPH_PADDING_Y; + const float final_pos_y = pos.y + FRAME_GRAPH_HEIGHT; + + auto& draw_list = *GetWindowDrawList(); + draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y}, + {pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y}, + IM_COL32(0x33, 0x33, 0x33, 0xFF)); + draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true); + for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) { + const auto& frame_info = frame_list[(DebugState.GetFrameNum() - i) % FRAME_BUFFER_SIZE]; + const float dt_factor = target_dt / frame_info.delta; + + const float width = std::ceil(BAR_WIDTH_MULT / dt_factor); + const float height = + std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * FRAME_GRAPH_HEIGHT; + + ImU32 color; + if (dt_factor >= 0.95f) { // BLUE + color = IM_COL32(0x33, 0x33, 0xFF, 0xFF); + } else if (dt_factor >= 0.5f) { // GREEN <> YELLOW + float t = 1.0f - (dt_factor - 0.5f) * 2.0f; + int r = (int)(0xFF * t); + color = IM_COL32(r, 0xFF, 0, 0xFF); + } else { // YELLOW <> RED + float t = dt_factor * 2.0f; + int g = (int)(0xFF * t); + color = IM_COL32(0xFF, g, 0, 0xFF); + } + draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, {cur_pos_x, final_pos_y}, + color); + cur_pos_x -= width; + if (cur_pos_x < width) { + break; + } + } + draw_list.PopClipRect(); +} + void FrameGraph::Draw() { if (!is_open) { return; @@ -43,55 +94,9 @@ void FrameGraph::Draw() { Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate); Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(), DebugState.gnm_frame_count.load()); + SeparatorText("Frame graph"); - - const float full_width = GetContentRegionAvail().x; - // Frame graph - inspired by - // https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times - auto pos = GetCursorScreenPos(); - const ImVec2 size{full_width, FRAME_GRAPH_HEIGHT + FRAME_GRAPH_PADDING_Y * 2.0f}; - ItemSize(size); - if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) { - return; - } - - float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv()); - float cur_pos_x = pos.x + full_width; - pos.y += FRAME_GRAPH_PADDING_Y; - const float final_pos_y = pos.y + FRAME_GRAPH_HEIGHT; - - draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y}, - {pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y}, - IM_COL32(0x33, 0x33, 0x33, 0xFF)); - draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true); - for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) { - const auto& frame_info = frame_list[(DebugState.GetFrameNum() - i) % FRAME_BUFFER_SIZE]; - const float dt_factor = target_dt / frame_info.delta; - - const float width = std::ceil(BAR_WIDTH_MULT / dt_factor); - const float height = - std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * FRAME_GRAPH_HEIGHT; - - ImU32 color; - if (dt_factor >= 0.95f) { // BLUE - color = IM_COL32(0x33, 0x33, 0xFF, 0xFF); - } else if (dt_factor >= 0.5f) { // GREEN <> YELLOW - float t = 1.0f - (dt_factor - 0.5f) * 2.0f; - int r = (int)(0xFF * t); - color = IM_COL32(r, 0xFF, 0, 0xFF); - } else { // YELLOW <> RED - float t = dt_factor * 2.0f; - int g = (int)(0xFF * t); - color = IM_COL32(0xFF, g, 0, 0xFF); - } - draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, - {cur_pos_x, final_pos_y}, color); - cur_pos_x -= width; - if (cur_pos_x < width) { - break; - } - } - draw_list.PopClipRect(); + DrawFrameGraph(); } End(); } diff --git a/src/core/devtools/widget/frame_graph.h b/src/core/devtools/widget/frame_graph.h index 700b6b2a..40a68ffa 100644 --- a/src/core/devtools/widget/frame_graph.h +++ b/src/core/devtools/widget/frame_graph.h @@ -16,6 +16,8 @@ class FrameGraph { std::array frame_list{}; + void DrawFrameGraph(); + public: bool is_open = true; diff --git a/src/core/devtools/widget/reg_view.cpp b/src/core/devtools/widget/reg_view.cpp index 79b02a84..a1b7937d 100644 --- a/src/core/devtools/widget/reg_view.cpp +++ b/src/core/devtools/widget/reg_view.cpp @@ -292,6 +292,17 @@ void RegView::Draw() { EndMenuBar(); } + const char* shader_name = "_"; + if (data.is_compute) { + shader_name = data.cs_data.name.c_str(); + } else if (selected_shader >= 0) { + shader_name = data.stages[selected_shader].name.c_str(); + } + + TextUnformatted("Shader: "); + SameLine(); + TextUnformatted(shader_name); + if (!data.is_compute && BeginChild("STAGES", {}, ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeY)) { diff --git a/src/core/devtools/widget/shader_list.cpp b/src/core/devtools/widget/shader_list.cpp index 2c97db7f..97d01896 100644 --- a/src/core/devtools/widget/shader_list.cpp +++ b/src/core/devtools/widget/shader_list.cpp @@ -112,6 +112,10 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { ReloadShader(value); } } + SameLine(); + if (Button("Copy name")) { + SetClipboardText(value.name.c_str()); + } if (value.is_patched) { if (BeginCombo("Shader type", showing_bin ? "SPIRV" : "GLSL", @@ -229,9 +233,16 @@ void ShaderList::Draw() { return; } + InputTextEx("##search_shader", "Search by name", search_box, sizeof(search_box), {}, + ImGuiInputTextFlags_None); + auto width = GetContentRegionAvail().x; int i = 0; for (const auto& shader : DebugState.shader_dump_list) { + if (search_box[0] != '\0' && !shader.name.contains(search_box)) { + i++; + continue; + } char name[128]; if (shader.is_patched) { snprintf(name, sizeof(name), "%s (PATCH ON)", shader.name.c_str()); diff --git a/src/core/devtools/widget/shader_list.h b/src/core/devtools/widget/shader_list.h index 2534ded3..fbb8d207 100644 --- a/src/core/devtools/widget/shader_list.h +++ b/src/core/devtools/widget/shader_list.h @@ -31,6 +31,8 @@ class ShaderList { std::vector open_shaders{}; + char search_box[128]{}; + public: bool open = false; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 74ae6b61..c880cad7 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -497,7 +497,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim module = CompileSPV(spv, instance.GetDevice()); } - const auto name = fmt::format("{}_{:#018x}_{}", info.stage, info.pgm_hash, perm_idx); + const auto name = GetShaderName(info.stage, info.pgm_hash, perm_idx); Vulkan::SetObjectName(instance.GetDevice(), module, name); if (Config::collectShadersForDebug()) { DebugState.CollectShader(name, info.l_stage, module, spv, code, @@ -572,6 +572,14 @@ std::optional PipelineCache::ReplaceShader(vk::ShaderModule mo return new_module; } +std::string PipelineCache::GetShaderName(Shader::Stage stage, u64 hash, + std::optional perm) { + if (perm) { + return fmt::format("{}_{:#018x}_{}", stage, hash, *perm); + } + return fmt::format("{}_{:#018x}", stage, hash); +} + void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, std::string_view ext) { if (!Config::dumpShaders()) { @@ -583,7 +591,7 @@ void PipelineCache::DumpShader(std::span code, u64 hash, Shader::Stag if (!std::filesystem::exists(dump_dir)) { std::filesystem::create_directories(dump_dir); } - const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); + const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext); const auto file = IOFile{dump_dir / filename, FileAccessMode::Write}; file.WriteSpan(code); } @@ -597,7 +605,7 @@ std::optional> PipelineCache::GetShaderPatch(u64 hash, Shader:: if (!std::filesystem::exists(patch_dir)) { std::filesystem::create_directories(patch_dir); } - const auto filename = fmt::format("{}_{:#018x}_{}.{}", stage, hash, perm_idx, ext); + const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext); const auto filepath = patch_dir / filename; if (!std::filesystem::exists(filepath)) { return {}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index d4a51efd..b3bccd51 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -65,6 +65,9 @@ public: std::optional ReplaceShader(vk::ShaderModule module, std::span spv_code); + static std::string GetShaderName(Shader::Stage stage, u64 hash, + std::optional perm = {}); + private: bool RefreshGraphicsKey(); bool RefreshComputeKey();