Devtools IV (#1910)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions

* 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
This commit is contained in:
Vinicius Rangel 2024-12-26 18:08:47 -03:00 committed by GitHub
parent 0bb1c05aff
commit edc027a8bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 159 additions and 55 deletions

View file

@ -11,6 +11,7 @@
#include "libraries/kernel/time.h" #include "libraries/kernel/time.h"
#include "libraries/system/msgdialog.h" #include "libraries/system/msgdialog.h"
#include "video_core/amdgpu/pm4_cmds.h" #include "video_core/amdgpu/pm4_cmds.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
using namespace DebugStateType; 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)) { if ((*dump)->regs.stage_enable.IsStageEnabled(i)) {
auto stage = (*dump)->regs.ProgramForStage(i); auto stage = (*dump)->regs.ProgramForStage(i);
if (stage->address_lo != 0) { if (stage->address_lo != 0) {
const auto& info = AmdGpu::Liverpool::SearchBinaryInfo(stage->Address<u32*>());
auto code = stage->Code(); auto code = stage->Code();
(*dump)->stages[i] = PipelineShaderProgramDump{ (*dump)->stages[i] = PipelineShaderProgramDump{
.name = Vulkan::PipelineCache::GetShaderName(Shader::StageFromIndex(i),
info.shader_hash),
.hash = info.shader_hash,
.user_data = *stage, .user_data = *stage,
.code = std::vector<u32>{code.begin(), code.end()}, .code = std::vector<u32>{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; auto& cs = (*dump)->regs.cs_program;
cs = cs_state; cs = cs_state;
const auto& info = AmdGpu::Liverpool::SearchBinaryInfo(cs.Address<u32*>());
(*dump)->cs_data = PipelineComputerProgramDump{ (*dump)->cs_data = PipelineComputerProgramDump{
.name = Vulkan::PipelineCache::GetShaderName(Shader::Stage::Compute, info.shader_hash),
.hash = info.shader_hash,
.cs_program = cs, .cs_program = cs,
.code = std::vector<u32>{cs.Code().begin(), cs.Code().end()}, .code = std::vector<u32>{cs.Code().begin(), cs.Code().end()},
}; };

View file

@ -50,11 +50,15 @@ struct QueueDump {
}; };
struct PipelineShaderProgramDump { struct PipelineShaderProgramDump {
std::string name;
u64 hash;
Vulkan::Liverpool::ShaderProgram user_data{}; Vulkan::Liverpool::ShaderProgram user_data{};
std::vector<u32> code{}; std::vector<u32> code{};
}; };
struct PipelineComputerProgramDump { struct PipelineComputerProgramDump {
std::string name;
u64 hash;
Vulkan::Liverpool::ComputeProgram cs_program{}; Vulkan::Liverpool::ComputeProgram cs_program{};
std::vector<u32> code{}; std::vector<u32> code{};
}; };

View file

@ -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(); const auto& ctx = *GetCurrentContext();
if (batch_view.open) { if (batch_view.open) {
@ -1285,6 +1285,41 @@ void CmdListViewer::Draw(bool only_batches_view) {
} }
auto& batch = std::get<BatchInfo>(event); auto& batch = std::get<BatchInfo>(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 = auto const* pm4_hdr =
reinterpret_cast<PM4Header const*>(cmdb_addr + batch.start_addr); reinterpret_cast<PM4Header const*>(cmdb_addr + batch.start_addr);

View file

@ -35,6 +35,10 @@ void ParseDepthControl(u32 value, bool begin_table = true);
void ParseEqaa(u32 value, bool begin_table = true); void ParseEqaa(u32 value, bool begin_table = true);
void ParseZInfo(u32 value, bool begin_table = true); void ParseZInfo(u32 value, bool begin_table = true);
struct CmdListFilter {
char shader_name[128]{};
};
class CmdListViewer { class CmdListViewer {
DebugStateType::FrameDump* frame_dump; DebugStateType::FrameDump* frame_dump;
@ -70,7 +74,7 @@ public:
explicit CmdListViewer(DebugStateType::FrameDump* frame_dump, const std::vector<u32>& cmd_list, explicit CmdListViewer(DebugStateType::FrameDump* frame_dump, const std::vector<u32>& cmd_list,
uintptr_t base_addr = 0, std::string name = ""); 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 } // namespace Core::Devtools::Widget

View file

@ -117,7 +117,7 @@ static bool IsDrawCall(AmdGpu::PM4ItOpcode opcode) {
inline std::optional<std::string> exec_cli(const char* cli) { inline std::optional<std::string> exec_cli(const char* cli) {
std::array<char, 64> buffer{}; std::array<char, 64> buffer{};
std::string output; std::string output;
const auto f = popen(cli, "rt"); const auto f = popen(cli, "r");
if (!f) { if (!f) {
pclose(f); pclose(f);
return {}; return {};

View file

@ -132,6 +132,15 @@ void FrameDumpViewer::Draw() {
} }
} }
EndDisabled(); EndDisabled();
SameLine();
if (BeginMenu("Filter")) {
TextUnformatted("Shader name");
SameLine();
InputText("##filter_shader", filter.shader_name, sizeof(filter.shader_name));
ImGui::EndMenu();
}
TextEx("Submit num"); TextEx("Submit num");
SameLine(); SameLine();
@ -187,7 +196,7 @@ void FrameDumpViewer::Draw() {
EndGroup(); EndGroup();
} }
if (is_showing && selected_cmd != -1) { if (is_showing && selected_cmd != -1) {
cmd_list_viewer[selected_cmd].Draw(is_collapsed); cmd_list_viewer[selected_cmd].Draw(is_collapsed, filter);
} }
End(); End();
} }

View file

@ -27,6 +27,8 @@ class FrameDumpViewer {
s32 selected_queue_num2; s32 selected_queue_num2;
s32 selected_cmd = -1; s32 selected_cmd = -1;
CmdListFilter filter;
public: public:
bool is_open = true; bool is_open = true;

View file

@ -19,6 +19,57 @@ constexpr float BAR_HEIGHT_MULT = 1.25f;
constexpr float FRAME_GRAPH_PADDING_Y = 3.0f; constexpr float FRAME_GRAPH_PADDING_Y = 3.0f;
constexpr static float FRAME_GRAPH_HEIGHT = 50.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() { void FrameGraph::Draw() {
if (!is_open) { if (!is_open) {
return; return;
@ -43,55 +94,9 @@ void FrameGraph::Draw() {
Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate); Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate);
Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(), Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(),
DebugState.gnm_frame_count.load()); DebugState.gnm_frame_count.load());
SeparatorText("Frame graph"); SeparatorText("Frame graph");
DrawFrameGraph();
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();
} }
End(); End();
} }

View file

@ -16,6 +16,8 @@ class FrameGraph {
std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{}; std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{};
void DrawFrameGraph();
public: public:
bool is_open = true; bool is_open = true;

View file

@ -292,6 +292,17 @@ void RegView::Draw() {
EndMenuBar(); 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 && if (!data.is_compute &&
BeginChild("STAGES", {}, BeginChild("STAGES", {},
ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeY)) { ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeY)) {

View file

@ -112,6 +112,10 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) {
ReloadShader(value); ReloadShader(value);
} }
} }
SameLine();
if (Button("Copy name")) {
SetClipboardText(value.name.c_str());
}
if (value.is_patched) { if (value.is_patched) {
if (BeginCombo("Shader type", showing_bin ? "SPIRV" : "GLSL", if (BeginCombo("Shader type", showing_bin ? "SPIRV" : "GLSL",
@ -229,9 +233,16 @@ void ShaderList::Draw() {
return; return;
} }
InputTextEx("##search_shader", "Search by name", search_box, sizeof(search_box), {},
ImGuiInputTextFlags_None);
auto width = GetContentRegionAvail().x; auto width = GetContentRegionAvail().x;
int i = 0; int i = 0;
for (const auto& shader : DebugState.shader_dump_list) { for (const auto& shader : DebugState.shader_dump_list) {
if (search_box[0] != '\0' && !shader.name.contains(search_box)) {
i++;
continue;
}
char name[128]; char name[128];
if (shader.is_patched) { if (shader.is_patched) {
snprintf(name, sizeof(name), "%s (PATCH ON)", shader.name.c_str()); snprintf(name, sizeof(name), "%s (PATCH ON)", shader.name.c_str());

View file

@ -31,6 +31,8 @@ class ShaderList {
std::vector<Selection> open_shaders{}; std::vector<Selection> open_shaders{};
char search_box[128]{};
public: public:
bool open = false; bool open = false;

View file

@ -497,7 +497,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim
module = CompileSPV(spv, instance.GetDevice()); 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); Vulkan::SetObjectName(instance.GetDevice(), module, name);
if (Config::collectShadersForDebug()) { if (Config::collectShadersForDebug()) {
DebugState.CollectShader(name, info.l_stage, module, spv, code, DebugState.CollectShader(name, info.l_stage, module, spv, code,
@ -572,6 +572,14 @@ std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule mo
return new_module; return new_module;
} }
std::string PipelineCache::GetShaderName(Shader::Stage stage, u64 hash,
std::optional<size_t> perm) {
if (perm) {
return fmt::format("{}_{:#018x}_{}", stage, hash, *perm);
}
return fmt::format("{}_{:#018x}", stage, hash);
}
void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stage stage, void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stage stage,
size_t perm_idx, std::string_view ext) { size_t perm_idx, std::string_view ext) {
if (!Config::dumpShaders()) { if (!Config::dumpShaders()) {
@ -583,7 +591,7 @@ void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stag
if (!std::filesystem::exists(dump_dir)) { if (!std::filesystem::exists(dump_dir)) {
std::filesystem::create_directories(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}; const auto file = IOFile{dump_dir / filename, FileAccessMode::Write};
file.WriteSpan(code); file.WriteSpan(code);
} }
@ -597,7 +605,7 @@ std::optional<std::vector<u32>> PipelineCache::GetShaderPatch(u64 hash, Shader::
if (!std::filesystem::exists(patch_dir)) { if (!std::filesystem::exists(patch_dir)) {
std::filesystem::create_directories(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; const auto filepath = patch_dir / filename;
if (!std::filesystem::exists(filepath)) { if (!std::filesystem::exists(filepath)) {
return {}; return {};

View file

@ -65,6 +65,9 @@ public:
std::optional<vk::ShaderModule> ReplaceShader(vk::ShaderModule module, std::optional<vk::ShaderModule> ReplaceShader(vk::ShaderModule module,
std::span<const u32> spv_code); std::span<const u32> spv_code);
static std::string GetShaderName(Shader::Stage stage, u64 hash,
std::optional<size_t> perm = {});
private: private:
bool RefreshGraphicsKey(); bool RefreshGraphicsKey();
bool RefreshComputeKey(); bool RefreshComputeKey();