diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index f36b43835..643dd9272 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -1079,8 +1079,20 @@ bool CommandProcessor::ExecutePacketType3_IM_LOAD(RingbufferReader* reader, uint32_t size_dwords = start_size & 0xFFFF; // dwords assert_true(start == 0); trace_writer_.WriteMemoryRead(CpuToGpu(addr), size_dwords * 4); - LoadShader(shader_type, addr, memory_->TranslatePhysical(addr), - size_dwords); + auto shader = + LoadShader(shader_type, addr, memory_->TranslatePhysical(addr), + size_dwords); + switch (shader_type) { + case ShaderType::kVertex: + active_vertex_shader_ = shader; + break; + case ShaderType::kPixel: + active_pixel_shader_ = shader; + break; + default: + assert_unhandled_case(shader_type); + return false; + } return true; } @@ -1095,8 +1107,20 @@ bool CommandProcessor::ExecutePacketType3_IM_LOAD_IMMEDIATE( uint32_t size_dwords = start_size & 0xFFFF; // dwords assert_true(start == 0); reader->CheckRead(size_dwords); - LoadShader(shader_type, reader->ptr(), - memory_->TranslatePhysical(reader->ptr()), size_dwords); + auto shader = LoadShader(shader_type, reader->ptr(), + memory_->TranslatePhysical(reader->ptr()), + size_dwords); + switch (shader_type) { + case ShaderType::kVertex: + active_vertex_shader_ = shader; + break; + case ShaderType::kPixel: + active_pixel_shader_ = shader; + break; + default: + assert_unhandled_case(shader_type); + return false; + } reader->Advance(size_dwords); return true; } diff --git a/src/xenia/gpu/command_processor.h b/src/xenia/gpu/command_processor.h index f26f4749e..92dc1523d 100644 --- a/src/xenia/gpu/command_processor.h +++ b/src/xenia/gpu/command_processor.h @@ -31,6 +31,7 @@ namespace xe { namespace gpu { class GraphicsSystem; +class Shader; struct SwapState { // Lock must be held when changing data in this structure. @@ -60,6 +61,9 @@ class CommandProcessor { uint32_t counter() const { return counter_; } void increment_counter() { counter_++; } + Shader* active_vertex_shader() const { return active_vertex_shader_; } + Shader* active_pixel_shader() const { return active_pixel_shader_; } + virtual bool Initialize(std::unique_ptr context); virtual void Shutdown(); @@ -160,9 +164,9 @@ class CommandProcessor { bool ExecutePacketType3_INVALIDATE_STATE(RingbufferReader* reader, uint32_t packet, uint32_t count); - virtual bool LoadShader(ShaderType shader_type, uint32_t guest_address, - const uint32_t* host_address, - uint32_t dword_count) = 0; + virtual Shader* LoadShader(ShaderType shader_type, uint32_t guest_address, + const uint32_t* host_address, + uint32_t dword_count) = 0; virtual bool IssueDraw(PrimitiveType prim_type, uint32_t index_count, IndexBufferInfo* index_buffer_info) = 0; @@ -205,6 +209,9 @@ class CommandProcessor { uint64_t bin_select_ = 0xFFFFFFFFull; uint64_t bin_mask_ = 0xFFFFFFFFull; + + Shader* active_vertex_shader_ = nullptr; + Shader* active_pixel_shader_ = nullptr; }; } // namespace gpu diff --git a/src/xenia/gpu/gl4/gl4_command_processor.cc b/src/xenia/gpu/gl4/gl4_command_processor.cc index c56905b7f..046c6159d 100644 --- a/src/xenia/gpu/gl4/gl4_command_processor.cc +++ b/src/xenia/gpu/gl4/gl4_command_processor.cc @@ -463,10 +463,10 @@ void GL4CommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, texture_cache_.Scavenge(); } -bool GL4CommandProcessor::LoadShader(ShaderType shader_type, - uint32_t guest_address, - const uint32_t* host_address, - uint32_t dword_count) { +Shader* GL4CommandProcessor::LoadShader(ShaderType shader_type, + uint32_t guest_address, + const uint32_t* host_address, + uint32_t dword_count) { // Hash the input memory and lookup the shader. GL4Shader* shader_ptr = nullptr; uint64_t hash = XXH64(host_address, dword_count * sizeof(uint32_t), 0); @@ -489,18 +489,7 @@ bool GL4CommandProcessor::LoadShader(ShaderType shader_type, guest_address, dword_count * 4, shader_ptr->ucode_disassembly().c_str()); } - switch (shader_type) { - case ShaderType::kVertex: - active_vertex_shader_ = shader_ptr; - break; - case ShaderType::kPixel: - active_pixel_shader_ = shader_ptr; - break; - default: - assert_unhandled_case(shader_type); - return false; - } - return true; + return shader_ptr; } bool GL4CommandProcessor::IssueDraw(PrimitiveType prim_type, @@ -622,8 +611,8 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateShaders( if (!dirty) { return UpdateStatus::kCompatible; } - regs.vertex_shader = active_vertex_shader_; - regs.pixel_shader = active_pixel_shader_; + regs.vertex_shader = static_cast(active_vertex_shader_); + regs.pixel_shader = static_cast(active_pixel_shader_); regs.prim_type = prim_type; SCOPE_profile_cpu_f("gpu"); @@ -632,30 +621,30 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateShaders( xe_gpu_program_cntl_t program_cntl; program_cntl.dword_0 = regs.sq_program_cntl; - if (!active_vertex_shader_->has_prepared()) { - if (!active_vertex_shader_->PrepareVertexShader(&shader_translator_, - program_cntl)) { + if (!regs.vertex_shader->has_prepared()) { + if (!regs.vertex_shader->PrepareVertexShader(&shader_translator_, + program_cntl)) { XELOGE("Unable to prepare vertex shader"); return UpdateStatus::kError; } - } else if (!active_vertex_shader_->is_valid()) { + } else if (!regs.vertex_shader->is_valid()) { XELOGE("Vertex shader invalid"); return UpdateStatus::kError; } - if (!active_pixel_shader_->has_prepared()) { - if (!active_pixel_shader_->PreparePixelShader(&shader_translator_, - program_cntl)) { + if (!regs.pixel_shader->has_prepared()) { + if (!regs.pixel_shader->PreparePixelShader(&shader_translator_, + program_cntl)) { XELOGE("Unable to prepare pixel shader"); return UpdateStatus::kError; } - } else if (!active_pixel_shader_->is_valid()) { + } else if (!regs.pixel_shader->is_valid()) { XELOGE("Pixel shader invalid"); return UpdateStatus::kError; } - GLuint vertex_program = active_vertex_shader_->program(); - GLuint fragment_program = active_pixel_shader_->program(); + GLuint vertex_program = regs.vertex_shader->program(); + GLuint fragment_program = regs.pixel_shader->program(); uint64_t key = (uint64_t(vertex_program) << 32) | fragment_program; CachedPipeline* cached_pipeline = nullptr; @@ -707,7 +696,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateShaders( cached_pipeline->handles.line_quad_list_pipeline = pipelines[4]; // This can be set once, as the buffer never changes. - glVertexArrayElementBuffer(active_vertex_shader_->vao(), + glVertexArrayElementBuffer(regs.vertex_shader->vao(), scratch_buffer_.handle()); } @@ -741,11 +730,11 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateShaders( } } - draw_batcher_.ReconfigurePipeline(active_vertex_shader_, active_pixel_shader_, + draw_batcher_.ReconfigurePipeline(regs.vertex_shader, regs.pixel_shader, pipeline); glBindProgramPipeline(pipeline); - glBindVertexArray(active_vertex_shader_->vao()); + glBindVertexArray(regs.vertex_shader->vao()); return UpdateStatus::kMismatch; } @@ -1394,6 +1383,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateVertexBuffers() { trace_writer_.WriteMemoryRead(fetch->address << 2, valid_range); + auto vertex_shader = static_cast(active_vertex_shader_); CircularBuffer::Allocation allocation; if (!scratch_buffer_.AcquireCached(fetch->address << 2, valid_range, &allocation)) { @@ -1408,7 +1398,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateVertexBuffers() { // TODO(benvanik): if we could find a way to avoid this, we could use // multidraw without flushing. - glVertexArrayVertexBuffer(active_vertex_shader_->vao(), buffer_index, + glVertexArrayVertexBuffer(vertex_shader->vao(), buffer_index, scratch_buffer_.handle(), allocation.offset, desc.stride_words * 4); @@ -1416,7 +1406,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateVertexBuffers() { } else { // TODO(benvanik): if we could find a way to avoid this, we could use // multidraw without flushing. - glVertexArrayVertexBuffer(active_vertex_shader_->vao(), buffer_index, + glVertexArrayVertexBuffer(vertex_shader->vao(), buffer_index, scratch_buffer_.handle(), allocation.offset, desc.stride_words * 4); } diff --git a/src/xenia/gpu/gl4/gl4_command_processor.h b/src/xenia/gpu/gl4/gl4_command_processor.h index b18e0c200..14b031b60 100644 --- a/src/xenia/gpu/gl4/gl4_command_processor.h +++ b/src/xenia/gpu/gl4/gl4_command_processor.h @@ -49,8 +49,6 @@ class GL4CommandProcessor : public CommandProcessor { // HACK: for debugging; would be good to have this in a base type. TextureCache* texture_cache() { return &texture_cache_; } - GL4Shader* active_vertex_shader() const { return active_vertex_shader_; } - GL4Shader* active_pixel_shader() const { return active_pixel_shader_; } GLuint GetColorRenderTarget(uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, @@ -111,8 +109,9 @@ class GL4CommandProcessor : public CommandProcessor { void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width, uint32_t frontbuffer_height) override; - bool LoadShader(ShaderType shader_type, uint32_t guest_address, - const uint32_t* host_address, uint32_t dword_count) override; + Shader* LoadShader(ShaderType shader_type, uint32_t guest_address, + const uint32_t* host_address, + uint32_t dword_count) override; bool IssueDraw(PrimitiveType prim_type, uint32_t index_count, IndexBufferInfo* index_buffer_info) override; @@ -135,8 +134,6 @@ class GL4CommandProcessor : public CommandProcessor { GL4ShaderTranslator shader_translator_; std::vector> all_shaders_; std::unordered_map shader_cache_; - GL4Shader* active_vertex_shader_ = nullptr; - GL4Shader* active_pixel_shader_ = nullptr; CachedFramebuffer* active_framebuffer_ = nullptr; GLuint last_framebuffer_texture_ = 0; diff --git a/src/xenia/gpu/gl4/gl4_trace_viewer_main.cc b/src/xenia/gpu/gl4/gl4_trace_viewer_main.cc index 56ea3b062..48739ba1f 100644 --- a/src/xenia/gpu/gl4/gl4_trace_viewer_main.cc +++ b/src/xenia/gpu/gl4/gl4_trace_viewer_main.cc @@ -11,27 +11,9 @@ #include -#include "third_party/imgui/imgui.h" -#include "xenia/base/clock.h" #include "xenia/base/logging.h" #include "xenia/base/main.h" -#include "xenia/base/mapped_memory.h" -#include "xenia/base/math.h" -#include "xenia/base/platform_win.h" -#include "xenia/base/profiling.h" -#include "xenia/emulator.h" -#include "xenia/gpu/command_processor.h" -#include "xenia/gpu/graphics_system.h" -#include "xenia/gpu/packet_disassembler.h" -#include "xenia/gpu/register_file.h" -#include "xenia/gpu/sampler_info.h" -#include "xenia/gpu/texture_info.h" -#include "xenia/gpu/trace_player.h" -#include "xenia/gpu/trace_protocol.h" -#include "xenia/gpu/xenos.h" -#include "xenia/ui/gl/gl_context.h" -#include "xenia/ui/imgui_drawer.h" -#include "xenia/ui/window.h" +#include "xenia/gpu/trace_viewer.h" // HACK: until we have another impl, we just use gl4 directly. #include "xenia/gpu/gl4/gl4_command_processor.h" @@ -42,1384 +24,49 @@ DEFINE_string(target_trace_file, "", "Specifies the trace file to load."); namespace xe { namespace gpu { +namespace gl4 { using namespace xe::gpu::xenos; -void DrawControllerUI(xe::ui::Window* window, TracePlayer& player, - Memory* memory) { - ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCond_FirstUseEver); - if (!ImGui::Begin("Controller", nullptr, ImVec2(340, 60))) { - ImGui::End(); - return; +class GL4TraceViewer : public TraceViewer { + public: + uintptr_t GetColorRenderTarget(uint32_t pitch, MsaaSamples samples, + uint32_t base, + ColorRenderTargetFormat format) override { + auto command_processor = static_cast( + graphics_system_->command_processor()); + return command_processor->GetColorRenderTarget(pitch, samples, base, + format); } - int target_frame = player.current_frame_index(); - if (ImGui::Button("|<<")) { - target_frame = 0; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Reset to first frame"); - } - ImGui::SameLine(); - ImGui::PushButtonRepeat(true); - if (ImGui::Button(">>", ImVec2(0, 0))) { - if (target_frame + 1 < player.frame_count()) { - ++target_frame; - } - } - ImGui::PopButtonRepeat(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Next frame (hold for continuous)"); - } - ImGui::SameLine(); - if (ImGui::Button(">>|")) { - target_frame = player.frame_count() - 1; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip to last frame"); - } - ImGui::SameLine(); - ImGui::SliderInt("", &target_frame, 0, player.frame_count() - 1); - if (target_frame != player.current_frame_index()) { - player.SeekFrame(target_frame); - } - ImGui::End(); -} - -void DrawCommandListUI(xe::ui::Window* window, TracePlayer& player, - Memory* memory) { - ImGui::SetNextWindowPos(ImVec2(5, 70), ImGuiSetCond_FirstUseEver); - if (!ImGui::Begin("Command List", nullptr, ImVec2(200, 640))) { - ImGui::End(); - return; + uintptr_t GetDepthRenderTarget(uint32_t pitch, MsaaSamples samples, + uint32_t base, + DepthRenderTargetFormat format) override { + auto command_processor = static_cast( + graphics_system_->command_processor()); + return command_processor->GetDepthRenderTarget(pitch, samples, base, + format); } - static const TracePlayer::Frame* previous_frame = nullptr; - auto frame = player.current_frame(); - if (!frame) { - ImGui::End(); - return; - } - bool did_seek = false; - if (previous_frame != frame) { - did_seek = true; - previous_frame = frame; - } - int command_count = int(frame->commands.size()); - int target_command = player.current_command_index(); - int column_width = int(ImGui::GetContentRegionMax().x); - ImGui::Text("Frame #%d", player.current_frame_index()); - ImGui::Separator(); - if (ImGui::Button("reset")) { - target_command = -1; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Reset to before any frame commands"); - } - ImGui::SameLine(); - ImGui::PushButtonRepeat(true); - if (ImGui::Button("prev", ImVec2(0, 0))) { - if (target_command >= 0) { - --target_command; - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move to the previous command (hold)"); - } - ImGui::SameLine(); - if (ImGui::Button("next", ImVec2(0, 0))) { - if (target_command < command_count - 1) { - ++target_command; - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move to the next command (hold)"); - } - ImGui::PopButtonRepeat(); - ImGui::SameLine(); - if (ImGui::Button("end")) { - target_command = command_count - 1; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Move to the last command"); - } - ImGui::PushItemWidth(float(column_width - 15)); - ImGui::SliderInt("", &target_command, -1, command_count - 1); - ImGui::PopItemWidth(); - if (target_command != player.current_command_index()) { - did_seek = true; - player.SeekCommand(target_command); - } - ImGui::Separator(); - ImGui::BeginChild("command_list"); - ImGui::PushID(-1); - bool is_selected = player.current_command_index() == -1; - if (ImGui::Selectable("", &is_selected)) { - player.SeekCommand(-1); - } - ImGui::PopID(); - if (did_seek && target_command == -1) { - ImGui::SetScrollPosHere(); - } + uintptr_t GetTextureEntry(const TextureInfo& texture_info, + const SamplerInfo& sampler_info) override { + auto command_processor = static_cast( + graphics_system_->command_processor()); - for (int i = 0; i < int(frame->commands.size()); ++i) { - ImGui::PushID(i); - is_selected = i == player.current_command_index(); - const auto& command = frame->commands[i]; - const char* label; - switch (command.type) { - case TraceReader::Frame::Command::Type::kDraw: - label = "Draw"; - break; - case TraceReader::Frame::Command::Type::kSwap: - label = "Swap"; - break; - } - if (ImGui::Selectable(label, &is_selected)) { - player.SeekCommand(i); - } - ImGui::SameLine(column_width - 60.0f); - ImGui::Text("%d", i); - ImGui::PopID(); - if (did_seek && target_command == i) { - ImGui::SetScrollPosHere(); + auto entry_view = + command_processor->texture_cache()->Demand(texture_info, sampler_info); + if (!entry_view) { + return 0; } + auto texture = entry_view->texture; + return static_cast(texture->handle); } - ImGui::EndChild(); - ImGui::End(); -} - -static const ImVec4 kColorError = - ImVec4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 255 / 255.0f); -static const ImVec4 kColorComment = - ImVec4(42 / 255.0f, 179 / 255.0f, 0 / 255.0f, 255 / 255.0f); -static const ImVec4 kColorIgnored = - ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f); - -void DrawMultilineString(const std::string& str) { - size_t i = 0; - bool done = false; - while (!done && i < str.size()) { - size_t next_i = str.find('\n', i); - if (next_i == std::string::npos) { - done = true; - next_i = str.size() - 1; - } - auto line = str.substr(i, next_i - i); - ImGui::Text("%s", line.c_str()); - i = next_i + 1; - } -} - -enum class ShaderDisplayType : int { - kUcode, - kTranslated, - kHostDisasm, }; -ShaderDisplayType DrawShaderTypeUI() { - static ShaderDisplayType shader_display_type = ShaderDisplayType::kUcode; - ImGui::RadioButton("ucode", reinterpret_cast(&shader_display_type), - static_cast(ShaderDisplayType::kUcode)); - ImGui::SameLine(); - ImGui::RadioButton("translated", reinterpret_cast(&shader_display_type), - static_cast(ShaderDisplayType::kTranslated)); - ImGui::SameLine(); - ImGui::RadioButton("disasm", reinterpret_cast(&shader_display_type), - static_cast(ShaderDisplayType::kHostDisasm)); - return shader_display_type; -} - -void DrawShaderUI(xe::ui::Window* window, TracePlayer& player, Memory* memory, - gl4::GL4Shader* shader, ShaderDisplayType display_type) { - // Must be prepared for advanced display modes. - if (display_type != ShaderDisplayType::kUcode) { - if (!shader->has_prepared()) { - ImGui::TextColored(kColorError, - "ERROR: shader not prepared (not used this frame?)"); - return; - } - } - - switch (display_type) { - case ShaderDisplayType::kUcode: { - DrawMultilineString(shader->ucode_disassembly()); - break; - } - case ShaderDisplayType::kTranslated: { - const auto& str = shader->translated_disassembly(); - size_t i = 0; - bool done = false; - while (!done && i < str.size()) { - size_t next_i = str.find('\n', i); - if (next_i == std::string::npos) { - done = true; - next_i = str.size() - 1; - } - auto line = str.substr(i, next_i - i); - if (line.find("//") != std::string::npos) { - ImGui::TextColored(kColorComment, "%s", line.c_str()); - } else { - ImGui::Text("%s", line.c_str()); - } - i = next_i + 1; - } - break; - } - case ShaderDisplayType::kHostDisasm: { - DrawMultilineString(shader->host_disassembly()); - break; - } - } -} - -// glBlendEquationSeparatei(i, blend_op, blend_op_alpha); -// glBlendFuncSeparatei(i, src_blend, dest_blend, src_blend_alpha, -// dest_blend_alpha); -void DrawBlendMode(uint32_t src_blend, uint32_t dest_blend, uint32_t blend_op) { - static const char* kBlendNames[] = { - /* 0 */ "ZERO", - /* 1 */ "ONE", - /* 2 */ "UNK2", // ? - /* 3 */ "UNK3", // ? - /* 4 */ "SRC_COLOR", - /* 5 */ "ONE_MINUS_SRC_COLOR", - /* 6 */ "SRC_ALPHA", - /* 7 */ "ONE_MINUS_SRC_ALPHA", - /* 8 */ "DST_COLOR", - /* 9 */ "ONE_MINUS_DST_COLOR", - /* 10 */ "DST_ALPHA", - /* 11 */ "ONE_MINUS_DST_ALPHA", - /* 12 */ "CONSTANT_COLOR", - /* 13 */ "ONE_MINUS_CONSTANT_COLOR", - /* 14 */ "CONSTANT_ALPHA", - /* 15 */ "ONE_MINUS_CONSTANT_ALPHA", - /* 16 */ "SRC_ALPHA_SATURATE", - }; - const char* src_str = kBlendNames[src_blend]; - const char* dest_str = kBlendNames[dest_blend]; - const char* op_template; - switch (blend_op) { - case 0: // add - op_template = "%s + %s"; - break; - case 1: // subtract - op_template = "%s - %s"; - break; - case 2: // min - op_template = "min(%s, %s)"; - break; - case 3: // max - op_template = "max(%s, %s)"; - break; - case 4: // reverse subtract - op_template = "-(%s) + %s"; - break; - default: - op_template = "%s ? %s"; - break; - } - ImGui::Text(op_template, src_str, dest_str); -} - -void DrawFailedTextureInfo(const Shader::SamplerDesc& desc, - const char* message) { - // TODO(benvanik): better error info/etc. - ImGui::TextColored(kColorError, "ERROR: %s", message); -} -void DrawTextureInfo(TracePlayer& player, const Shader::SamplerDesc& desc) { - auto gs = static_cast(player.graphics_system()); - auto cp = static_cast(gs->command_processor()); - auto& regs = *gs->register_file(); - - int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + desc.fetch_slot * 6; - auto group = reinterpret_cast(®s.values[r]); - auto& fetch = group->texture_fetch; - if (fetch.type != 0x2) { - DrawFailedTextureInfo(desc, "Invalid fetch type"); - return; - } - TextureInfo texture_info; - if (!TextureInfo::Prepare(fetch, &texture_info)) { - DrawFailedTextureInfo(desc, "Unable to parse texture fetcher info"); - return; - } - SamplerInfo sampler_info; - if (!SamplerInfo::Prepare(fetch, desc.tex_fetch, &sampler_info)) { - DrawFailedTextureInfo(desc, "Unable to parse sampler info"); - return; - } - auto entry_view = cp->texture_cache()->Demand(texture_info, sampler_info); - if (!entry_view) { - DrawFailedTextureInfo(desc, "Failed to demand texture"); - return; - } - auto texture = entry_view->texture; - - ImGui::Columns(2); - ImVec2 button_size(256, 256); - if (ImGui::ImageButton(ImTextureID(GLuint64(texture->handle)), button_size, - ImVec2(0, 0), ImVec2(1, 1))) { - // show viewer - } - ImGui::NextColumn(); - ImGui::Text("Fetch Slot: %d", desc.fetch_slot); - switch (texture_info.dimension) { - case Dimension::k1D: - ImGui::Text("1D: %dpx", texture_info.width + 1); - break; - case Dimension::k2D: - ImGui::Text("2D: %dx%dpx", texture_info.width + 1, - texture_info.height + 1); - break; - case Dimension::k3D: - ImGui::Text("3D: %dx%dx%dpx", texture_info.width + 1, - texture_info.height + 1, texture_info.depth + 1); - break; - case Dimension::kCube: - ImGui::Text("Cube: ?"); - break; - } - ImGui::Columns(1); -} - -void DrawVertexFetcher(const Memory* memory, gl4::GL4Shader* shader, - const Shader::BufferDesc& desc, - const xe_gpu_vertex_fetch_t* fetch) { - const uint8_t* addr = memory->TranslatePhysical(fetch->address << 2); - uint32_t vertex_count = (fetch->size * 4) / desc.stride_words; - int column_count = 0; - for (uint32_t el_index = 0; el_index < desc.element_count; ++el_index) { - const auto& el = desc.elements[el_index]; - switch (el.format) { - case VertexFormat::k_32: - case VertexFormat::k_32_FLOAT: - ++column_count; - break; - case VertexFormat::k_16_16: - case VertexFormat::k_16_16_FLOAT: - case VertexFormat::k_32_32: - case VertexFormat::k_32_32_FLOAT: - column_count += 2; - break; - case VertexFormat::k_10_11_11: - case VertexFormat::k_11_11_10: - case VertexFormat::k_32_32_32_FLOAT: - column_count += 3; - break; - case VertexFormat::k_8_8_8_8: - ++column_count; - break; - case VertexFormat::k_2_10_10_10: - case VertexFormat::k_16_16_16_16: - case VertexFormat::k_32_32_32_32: - case VertexFormat::k_16_16_16_16_FLOAT: - case VertexFormat::k_32_32_32_32_FLOAT: - column_count += 4; - break; - } - } - ImGui::BeginChild("#indices", ImVec2(0, 300)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - int display_start, display_end; - ImGui::CalcListClipping(1 + vertex_count, ImGui::GetTextLineHeight(), - &display_start, &display_end); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + - (display_start)*ImGui::GetTextLineHeight()); - ImGui::Columns(column_count); - if (display_start <= 1) { - for (uint32_t el_index = 0; el_index < desc.element_count; ++el_index) { - const auto& el = desc.elements[el_index]; - switch (el.format) { - case VertexFormat::k_32: - case VertexFormat::k_32_FLOAT: - ImGui::Text("e%d.x", el_index); - ImGui::NextColumn(); - break; - case VertexFormat::k_16_16: - case VertexFormat::k_16_16_FLOAT: - case VertexFormat::k_32_32: - case VertexFormat::k_32_32_FLOAT: - ImGui::Text("e%d.x", el_index); - ImGui::NextColumn(); - ImGui::Text("e%d.y", el_index); - ImGui::NextColumn(); - break; - case VertexFormat::k_10_11_11: - case VertexFormat::k_11_11_10: - case VertexFormat::k_32_32_32_FLOAT: - ImGui::Text("e%d.x", el_index); - ImGui::NextColumn(); - ImGui::Text("e%d.y", el_index); - ImGui::NextColumn(); - ImGui::Text("e%d.z", el_index); - ImGui::NextColumn(); - break; - case VertexFormat::k_8_8_8_8: - ImGui::Text("e%d.xyzw", el_index); - ImGui::NextColumn(); - break; - case VertexFormat::k_2_10_10_10: - case VertexFormat::k_16_16_16_16: - case VertexFormat::k_32_32_32_32: - case VertexFormat::k_16_16_16_16_FLOAT: - case VertexFormat::k_32_32_32_32_FLOAT: - ImGui::Text("e%d.x", el_index); - ImGui::NextColumn(); - ImGui::Text("e%d.y", el_index); - ImGui::NextColumn(); - ImGui::Text("e%d.z", el_index); - ImGui::NextColumn(); - ImGui::Text("e%d.w", el_index); - ImGui::NextColumn(); - break; - } - } - ImGui::Separator(); - } - for (int i = display_start; i < display_end; ++i) { - const uint8_t* vstart = addr + i * desc.stride_words * 4; - for (uint32_t el_index = 0; el_index < desc.element_count; ++el_index) { - const auto& el = desc.elements[el_index]; -#define LOADEL(type, wo) \ - GpuSwap(xe::load(vstart + (el.offset_words + wo) * 4), \ - Endian(fetch->endian)) - switch (el.format) { - case VertexFormat::k_32: - ImGui::Text("%.8X", LOADEL(uint32_t, 0)); - ImGui::NextColumn(); - break; - case VertexFormat::k_32_FLOAT: - ImGui::Text("%.2f", LOADEL(float, 0)); - ImGui::NextColumn(); - break; - case VertexFormat::k_16_16: - case VertexFormat::k_16_16_FLOAT: - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - break; - case VertexFormat::k_32_32: - ImGui::Text("%.8X", LOADEL(uint32_t, 0)); - ImGui::NextColumn(); - ImGui::Text("%.8X", LOADEL(uint32_t, 1)); - ImGui::NextColumn(); - break; - case VertexFormat::k_32_32_FLOAT: - ImGui::Text("%.2f", LOADEL(float, 0)); - ImGui::NextColumn(); - ImGui::Text("%.2f", LOADEL(float, 1)); - ImGui::NextColumn(); - break; - case VertexFormat::k_10_11_11: - case VertexFormat::k_11_11_10: - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - break; - case VertexFormat::k_32_32_32_FLOAT: - ImGui::Text("%.2f", LOADEL(float, 0)); - ImGui::NextColumn(); - ImGui::Text("%.2f", LOADEL(float, 1)); - ImGui::NextColumn(); - ImGui::Text("%.2f", LOADEL(float, 2)); - ImGui::NextColumn(); - break; - case VertexFormat::k_8_8_8_8: - ImGui::Text("%.8X", LOADEL(uint32_t, 0)); - ImGui::NextColumn(); - break; - case VertexFormat::k_2_10_10_10: - case VertexFormat::k_16_16_16_16: - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - break; - case VertexFormat::k_32_32_32_32: - ImGui::Text("%.8X", LOADEL(uint32_t, 0)); - ImGui::NextColumn(); - ImGui::Text("%.8X", LOADEL(uint32_t, 1)); - ImGui::NextColumn(); - ImGui::Text("%.8X", LOADEL(uint32_t, 2)); - ImGui::NextColumn(); - ImGui::Text("%.8X", LOADEL(uint32_t, 3)); - ImGui::NextColumn(); - break; - case VertexFormat::k_16_16_16_16_FLOAT: - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - ImGui::Text("??"); - ImGui::NextColumn(); - break; - case VertexFormat::k_32_32_32_32_FLOAT: - ImGui::Text("%.2f", LOADEL(float, 0)); - ImGui::NextColumn(); - ImGui::Text("%.2f", LOADEL(float, 1)); - ImGui::NextColumn(); - ImGui::Text("%.2f", LOADEL(float, 2)); - ImGui::NextColumn(); - ImGui::Text("%.2f", LOADEL(float, 3)); - ImGui::NextColumn(); - break; - } - } - } - ImGui::Columns(1); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + - (vertex_count - display_end) * - ImGui::GetTextLineHeight()); - ImGui::PopStyleVar(); - ImGui::EndChild(); -} - -static const char* kCompareFuncNames[] = { - "", "<", "==", "<=", ">", "!=", ">=", "", -}; -static const char* kIndexFormatNames[] = { - "uint16", "uint32", -}; -static const char* kEndiannessNames[] = { - "unspecified endianness", "8-in-16", "8-in-32", "16-in-32", -}; - -void DrawStateUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) { - auto gs = static_cast(player.graphics_system()); - auto cp = static_cast(gs->command_processor()); - auto& regs = *gs->register_file(); - - ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 30), - ImGuiSetCond_FirstUseEver); - if (!ImGui::Begin("State", nullptr, ImVec2(500, 680))) { - ImGui::End(); - return; - } - - if (!player.current_frame() || player.current_command_index() == -1) { - ImGui::Text("No frame/command selected"); - ImGui::End(); - return; - } - - auto frame = player.current_frame(); - const auto& command = frame->commands[player.current_command_index()]; - auto packet_head = command.head_ptr + sizeof(PacketStartCommand); - uint32_t packet = xe::load_and_swap(packet_head); - uint32_t packet_type = packet >> 30; - assert_true(packet_type == 0x03); - uint32_t opcode = (packet >> 8) & 0x7F; - struct { - PrimitiveType prim_type; - bool is_auto_index; - uint32_t index_count; - uint32_t index_buffer_ptr; - uint32_t index_buffer_size; - Endian index_endianness; - IndexFormat index_format; - } draw_info; - std::memset(&draw_info, 0, sizeof(draw_info)); - switch (opcode) { - case PM4_DRAW_INDX: { - uint32_t dword0 = xe::load_and_swap(packet_head + 4); - uint32_t dword1 = xe::load_and_swap(packet_head + 8); - draw_info.index_count = dword1 >> 16; - draw_info.prim_type = static_cast(dword1 & 0x3F); - uint32_t src_sel = (dword1 >> 6) & 0x3; - if (src_sel == 0x0) { - // Indexed draw. - draw_info.is_auto_index = false; - draw_info.index_buffer_ptr = - xe::load_and_swap(packet_head + 12); - uint32_t index_size = xe::load_and_swap(packet_head + 16); - draw_info.index_endianness = static_cast(index_size >> 30); - index_size &= 0x00FFFFFF; - bool index_32bit = (dword1 >> 11) & 0x1; - draw_info.index_format = - index_32bit ? IndexFormat::kInt32 : IndexFormat::kInt16; - draw_info.index_buffer_size = index_size * (index_32bit ? 4 : 2); - } else if (src_sel == 0x2) { - // Auto draw. - draw_info.is_auto_index = true; - } else { - // Unknown source select. - assert_always(); - } - break; - } - case PM4_DRAW_INDX_2: { - uint32_t dword0 = xe::load_and_swap(packet_head + 4); - uint32_t src_sel = (dword0 >> 6) & 0x3; - assert_true(src_sel == 0x2); // 'SrcSel=AutoIndex' - draw_info.prim_type = static_cast(dword0 & 0x3F); - draw_info.is_auto_index = true; - draw_info.index_count = dword0 >> 16; - break; - } - } - - auto enable_mode = - static_cast(regs[XE_GPU_REG_RB_MODECONTROL].u32 & 0x7); - const char* mode_name = "Unknown"; - switch (enable_mode) { - case ModeControl::kIgnore: - ImGui::Text("Ignored Command %d", player.current_command_index()); - break; - case ModeControl::kColorDepth: - case ModeControl::kDepth: { - static const char* kPrimNames[] = { - "", "point list", "line list", "line strip", - "triangle list", "triangle fan", "triangle strip", "unknown 0x7", - "rectangle list", "unknown 0x9", "unknown 0xA", "unknown 0xB", - "line loop", "quad list", "quad strip", "unknown 0xF", - }; - ImGui::Text("%s Command %d: %s, %d indices", - enable_mode == ModeControl::kColorDepth ? "Color-Depth" - : "Depth-only", - player.current_command_index(), - kPrimNames[int(draw_info.prim_type)], draw_info.index_count); - break; - } - case ModeControl::kCopy: - ImGui::Text("Copy Command %d", player.current_command_index()); - break; - } - - ImGui::Columns(2); - ImGui::BulletText("Viewport State:"); - if (true) { - ImGui::TreePush((const void*)0); - uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32; - if ((pa_su_sc_mode_cntl >> 16) & 1) { - uint32_t window_offset = regs[XE_GPU_REG_PA_SC_WINDOW_OFFSET].u32; - int16_t window_offset_x = window_offset & 0x7FFF; - int16_t window_offset_y = (window_offset >> 16) & 0x7FFF; - if (window_offset_x & 0x4000) { - window_offset_x |= 0x8000; - } - if (window_offset_y & 0x4000) { - window_offset_y |= 0x8000; - } - ImGui::BulletText("Window Offset: %d, %d", window_offset_x, - window_offset_y); - } else { - ImGui::BulletText("Window Offset: disabled"); - } - uint32_t window_scissor_tl = regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL].u32; - uint32_t window_scissor_br = regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_BR].u32; - ImGui::BulletText( - "Window Scissor: %d,%d to %d,%d (%d x %d)", window_scissor_tl & 0x7FFF, - (window_scissor_tl >> 16) & 0x7FFF, window_scissor_br & 0x7FFF, - (window_scissor_br >> 16) & 0x7FFF, - (window_scissor_br & 0x7FFF) - (window_scissor_tl & 0x7FFF), - ((window_scissor_br >> 16) & 0x7FFF) - - ((window_scissor_tl >> 16) & 0x7FFF)); - uint32_t surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32; - uint32_t surface_pitch = surface_info & 0x3FFF; - auto surface_msaa = (surface_info >> 16) & 0x3; - static const char* kMsaaNames[] = { - "1X", "2X", "4X", - }; - ImGui::BulletText("Surface MSAA: %s", kMsaaNames[surface_msaa]); - uint32_t vte_control = regs[XE_GPU_REG_PA_CL_VTE_CNTL].u32; - bool vport_xscale_enable = (vte_control & (1 << 0)) > 0; - bool vport_xoffset_enable = (vte_control & (1 << 1)) > 0; - bool vport_yscale_enable = (vte_control & (1 << 2)) > 0; - bool vport_yoffset_enable = (vte_control & (1 << 3)) > 0; - bool vport_zscale_enable = (vte_control & (1 << 4)) > 0; - bool vport_zoffset_enable = (vte_control & (1 << 5)) > 0; - assert_true(vport_xscale_enable == vport_yscale_enable == - vport_zscale_enable == vport_xoffset_enable == - vport_yoffset_enable == vport_zoffset_enable); - ImGui::BulletText( - "Viewport Offset: %f, %f, %f", - vport_xoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32 : 0, - vport_yoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32 : 0, - vport_zoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32 : 0); - ImGui::BulletText( - "Viewport Scale: %f, %f, %f", - vport_xscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32 : 1, - vport_yscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32 : 1, - vport_zscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32 : 1); - ImGui::BulletText("Vertex Format: %s, %s, %s, %s", - ((vte_control >> 8) & 0x1) ? "x/w0" : "x", - ((vte_control >> 8) & 0x1) ? "y/w0" : "y", - ((vte_control >> 9) & 0x1) ? "z/w0" : "z", - ((vte_control >> 10) & 0x1) ? "w0" : "1/w0"); - uint32_t clip_control = regs[XE_GPU_REG_PA_CL_CLIP_CNTL].u32; - bool clip_enabled = ((clip_control >> 17) & 0x1) == 0; - bool dx_clip = ((clip_control >> 20) & 0x1) == 0x1; - ImGui::BulletText("Clip Enabled: %s, DX Clip: %s", - clip_enabled ? "true" : "false", - dx_clip ? "true" : "false"); - ImGui::TreePop(); - } - ImGui::NextColumn(); - ImGui::BulletText("Rasterizer State:"); - if (true) { - ImGui::TreePush((const void*)0); - uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32; - uint32_t pa_sc_screen_scissor_tl = - regs[XE_GPU_REG_PA_SC_SCREEN_SCISSOR_TL].u32; - uint32_t pa_sc_screen_scissor_br = - regs[XE_GPU_REG_PA_SC_SCREEN_SCISSOR_BR].u32; - if (pa_sc_screen_scissor_tl != 0 && pa_sc_screen_scissor_br != 0x20002000) { - int32_t screen_scissor_x = pa_sc_screen_scissor_tl & 0x7FFF; - int32_t screen_scissor_y = (pa_sc_screen_scissor_tl >> 16) & 0x7FFF; - int32_t screen_scissor_w = - (pa_sc_screen_scissor_br & 0x7FFF) - screen_scissor_x; - int32_t screen_scissor_h = - ((pa_sc_screen_scissor_br >> 16) & 0x7FFF) - screen_scissor_y; - ImGui::BulletText("Scissor: %d,%d to %d,%d (%d x %d)", screen_scissor_x, - screen_scissor_y, screen_scissor_x + screen_scissor_w, - screen_scissor_y + screen_scissor_h, screen_scissor_w, - screen_scissor_h); - } else { - ImGui::BulletText("Scissor: disabled"); - } - switch (pa_su_sc_mode_cntl & 0x3) { - case 0: - ImGui::BulletText("Culling: disabled"); - break; - case 1: - ImGui::BulletText("Culling: front-face"); - break; - case 2: - ImGui::BulletText("Culling: back-face"); - break; - } - if (pa_su_sc_mode_cntl & 0x4) { - ImGui::BulletText("Front-face: clockwise"); - } else { - ImGui::BulletText("Front-face: counter-clockwise"); - } - static const char* kFillModeNames[3] = { - "point", "line", "fill", - }; - bool poly_mode = ((pa_su_sc_mode_cntl >> 3) & 0x3) != 0; - if (poly_mode) { - uint32_t front_poly_mode = (pa_su_sc_mode_cntl >> 5) & 0x7; - uint32_t back_poly_mode = (pa_su_sc_mode_cntl >> 8) & 0x7; - // GL only supports both matching. - assert_true(front_poly_mode == back_poly_mode); - ImGui::BulletText("Polygon Mode: %s", kFillModeNames[front_poly_mode]); - } else { - ImGui::BulletText("Polygon Mode: fill"); - } - if (pa_su_sc_mode_cntl & (1 << 19)) { - ImGui::BulletText("Provoking Vertex: last"); - } else { - ImGui::BulletText("Provoking Vertex: first"); - } - ImGui::TreePop(); - } - ImGui::Columns(1); - - auto rb_surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32; - uint32_t surface_pitch = rb_surface_info & 0x3FFF; - auto surface_msaa = static_cast((rb_surface_info >> 16) & 0x3); - - if (ImGui::CollapsingHeader("Color Targets")) { - if (enable_mode != ModeControl::kDepth) { - // Alpha testing -- ALPHAREF, ALPHAFUNC, ALPHATESTENABLE - // if(ALPHATESTENABLE && frag_out.a [<=/ALPHAFUNC] ALPHAREF) discard; - uint32_t color_control = regs[XE_GPU_REG_RB_COLORCONTROL].u32; - if ((color_control & 0x4) != 0) { - ImGui::BulletText("Alpha Test: %s %.2f", - kCompareFuncNames[color_control & 0x7], - regs[XE_GPU_REG_RB_ALPHA_REF].f32); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::BulletText("Alpha Test: disabled"); - ImGui::PopStyleColor(); - } - - auto blend_color = ImVec4(regs[XE_GPU_REG_RB_BLEND_RED].f32, - regs[XE_GPU_REG_RB_BLEND_GREEN].f32, - regs[XE_GPU_REG_RB_BLEND_BLUE].f32, - regs[XE_GPU_REG_RB_BLEND_ALPHA].f32); - ImGui::BulletText("Blend Color: (%.2f,%.2f,%.2f,%.2f)", blend_color.x, - blend_color.y, blend_color.z, blend_color.w); - ImGui::SameLine(); - ImGui::ColorButton(blend_color, true); - - uint32_t rb_color_mask = regs[XE_GPU_REG_RB_COLOR_MASK].u32; - uint32_t color_info[4] = { - regs[XE_GPU_REG_RB_COLOR_INFO].u32, - regs[XE_GPU_REG_RB_COLOR1_INFO].u32, - regs[XE_GPU_REG_RB_COLOR2_INFO].u32, - regs[XE_GPU_REG_RB_COLOR3_INFO].u32, - }; - uint32_t rb_blendcontrol[4] = { - regs[XE_GPU_REG_RB_BLENDCONTROL_0].u32, - regs[XE_GPU_REG_RB_BLENDCONTROL_1].u32, - regs[XE_GPU_REG_RB_BLENDCONTROL_2].u32, - regs[XE_GPU_REG_RB_BLENDCONTROL_3].u32, - }; - ImGui::Columns(2); - for (int i = 0; i < xe::countof(color_info); ++i) { - uint32_t blend_control = rb_blendcontrol[i]; - // A2XX_RB_BLEND_CONTROL_COLOR_SRCBLEND - auto src_blend = (blend_control & 0x0000001F) >> 0; - // A2XX_RB_BLEND_CONTROL_COLOR_DESTBLEND - auto dest_blend = (blend_control & 0x00001F00) >> 8; - // A2XX_RB_BLEND_CONTROL_COLOR_COMB_FCN - auto blend_op = (blend_control & 0x000000E0) >> 5; - // A2XX_RB_BLEND_CONTROL_ALPHA_SRCBLEND - auto src_blend_alpha = (blend_control & 0x001F0000) >> 16; - // A2XX_RB_BLEND_CONTROL_ALPHA_DESTBLEND - auto dest_blend_alpha = (blend_control & 0x1F000000) >> 24; - // A2XX_RB_BLEND_CONTROL_ALPHA_COMB_FCN - auto blend_op_alpha = (blend_control & 0x00E00000) >> 21; - // A2XX_RB_COLORCONTROL_BLEND_DISABLE ?? Can't find this! - // Just guess based on actions. - bool blend_enable = !((src_blend == 1) && (dest_blend == 0) && - (blend_op == 0) && (src_blend_alpha == 1) && - (dest_blend_alpha == 0) && (blend_op_alpha == 0)); - if (blend_enable) { - if (src_blend == src_blend_alpha && dest_blend == dest_blend_alpha && - blend_op == blend_op_alpha) { - ImGui::BulletText("Blend %d: ", i); - ImGui::SameLine(); - DrawBlendMode(src_blend, dest_blend, blend_op); - } else { - ImGui::BulletText("Blend %d:", i); - ImGui::BulletText(" Color: "); - ImGui::SameLine(); - DrawBlendMode(src_blend, dest_blend, blend_op); - ImGui::BulletText(" Alpha: "); - ImGui::SameLine(); - DrawBlendMode(src_blend_alpha, dest_blend_alpha, blend_op_alpha); - } - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::BulletText("Blend %d: disabled", i); - ImGui::PopStyleColor(); - } - ImGui::NextColumn(); - uint32_t write_mask = (rb_color_mask >> (i * 4)) & 0xF; - if (write_mask) { - ImGui::BulletText("Write Mask %d: %s, %s, %s, %s", i, - !!(write_mask & 0x1) ? "true" : "false", - !!(write_mask & 0x2) ? "true" : "false", - !!(write_mask & 0x4) ? "true" : "false", - !!(write_mask & 0x8) ? "true" : "false"); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::BulletText("Write Mask %d: disabled", i); - ImGui::PopStyleColor(); - } - ImGui::NextColumn(); - } - ImGui::Columns(1); - - ImGui::Columns(4); - for (int i = 0; i < xe::countof(color_info); ++i) { - uint32_t write_mask = (rb_color_mask >> (i * 4)) & 0xF; - uint32_t color_base = color_info[i] & 0xFFF; - auto color_format = - static_cast((color_info[i] >> 16) & 0xF); - ImVec2 button_size(256, 256); - if (write_mask) { - GLuint color_target = cp->GetColorRenderTarget( - surface_pitch, surface_msaa, color_base, color_format); - if (ImGui::ImageButton(ImTextureID(GLuint64(color_target)), - button_size, ImVec2(0, 0), ImVec2(1, 1))) { - // show viewer - } - } else { - ImGui::ImageButton(ImTextureID(GLuint64(0)), button_size, - ImVec2(0, 0), ImVec2(1, 1), -1, ImVec4(0, 0, 0, 0), - ImVec4(0, 0, 0, 0)); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Color Target %d (%s), base %.4X, pitch %d", i, - write_mask ? "enabled" : "disabled", color_base, - surface_pitch); - } - ImGui::NextColumn(); - } - ImGui::Columns(1); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::Text("Depth-only mode, no color targets"); - ImGui::PopStyleColor(); - } - } - - if (ImGui::CollapsingHeader("Depth/Stencil Target")) { - auto rb_depthcontrol = regs[XE_GPU_REG_RB_DEPTHCONTROL].u32; - auto rb_stencilrefmask = regs[XE_GPU_REG_RB_STENCILREFMASK].u32; - auto rb_depth_info = regs[XE_GPU_REG_RB_DEPTH_INFO].u32; - bool uses_depth = - (rb_depthcontrol & 0x00000002) || (rb_depthcontrol & 0x00000004); - uint32_t stencil_write_mask = (rb_stencilrefmask & 0x00FF0000) >> 16; - bool uses_stencil = - (rb_depthcontrol & 0x00000001) || (stencil_write_mask != 0); - - ImGui::Columns(2); - - if (rb_depthcontrol & 0x00000002) { - ImGui::BulletText("Depth Test: enabled"); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::BulletText("Depth Test: disabled"); - } - ImGui::BulletText("Depth Func: %s", - kCompareFuncNames[(rb_depthcontrol & 0x00000070) >> 4]); - if (!(rb_depthcontrol & 0x00000002)) { - ImGui::PopStyleColor(); - } - if (rb_depthcontrol & 0x00000004) { - ImGui::BulletText("Depth Write: enabled"); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::BulletText("Depth Write: disabled"); - ImGui::PopStyleColor(); - } - if (rb_depthcontrol & 0x00000001) { - ImGui::BulletText("Stencil Test: enabled"); - } else { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - ImGui::BulletText("Stencil Test: disabled"); - } - // TODO(benvanik): stencil stuff. - ImGui::BulletText("TODO: stencil stuff"); - if (!(rb_depthcontrol & 0x00000001)) { - ImGui::PopStyleColor(); - } - - ImGui::NextColumn(); - - if (uses_depth || uses_stencil) { - uint32_t depth_base = rb_depth_info & 0xFFF; - auto depth_format = - static_cast((rb_depth_info >> 16) & 0x1); - GLuint depth_target = cp->GetDepthRenderTarget( - surface_pitch, surface_msaa, depth_base, depth_format); - ImVec2 button_size(256, 256); - if (ImGui::ImageButton(ImTextureID(GLuint64(depth_target)), button_size, - ImVec2(0, 0), ImVec2(1, 1))) { - // show viewer - } - } else { - ImGui::Text("No depth target"); - } - - ImGui::Columns(1); - } - - if (ImGui::CollapsingHeader("Vertex Shader")) { - ShaderDisplayType shader_display_type = DrawShaderTypeUI(); - ImGui::BeginChild("#vertex_shader_text", ImVec2(0, 400)); - auto shader = cp->active_vertex_shader(); - if (shader) { - DrawShaderUI(window, player, memory, shader, shader_display_type); - } else { - ImGui::TextColored(kColorError, "ERROR: no vertex shader set"); - } - ImGui::EndChild(); - } - if (ImGui::CollapsingHeader("Pixel Shader")) { - ShaderDisplayType shader_display_type = DrawShaderTypeUI(); - ImGui::BeginChild("#pixel_shader_text", ImVec2(0, 400)); - auto shader = cp->active_pixel_shader(); - if (shader) { - DrawShaderUI(window, player, memory, shader, shader_display_type); - } else { - ImGui::TextColored(kColorError, "ERROR: no pixel shader set"); - } - ImGui::EndChild(); - } - if (ImGui::CollapsingHeader("Index Buffer")) { - if (draw_info.is_auto_index) { - ImGui::Text("%d indices, auto-indexed", draw_info.index_count); - } else { - ImGui::Text("%d indices from buffer %.8X (%db), %s, %s", - draw_info.index_count, draw_info.index_buffer_ptr, - draw_info.index_buffer_size, - kIndexFormatNames[int(draw_info.index_format)], - kEndiannessNames[int(draw_info.index_endianness)]); - uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32; - if (pa_su_sc_mode_cntl & (1 << 21)) { - uint32_t reset_index = - regs[XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX].u32; - if (draw_info.index_format == IndexFormat::kInt16) { - ImGui::Text("Reset Index: %.4X", reset_index & 0xFFFF); - } else { - ImGui::Text("Reset Index: %.8X", reset_index); - } - } else { - ImGui::Text("Reset Index: disabled"); - } - ImGui::BeginChild("#indices", ImVec2(0, 300)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - int display_start, display_end; - ImGui::CalcListClipping(1 + draw_info.index_count, - ImGui::GetTextLineHeight(), &display_start, - &display_end); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + - (display_start)*ImGui::GetTextLineHeight()); - ImGui::Columns(2, "#indices", true); - ImGui::SetColumnOffset(1, 60); - if (display_start <= 1) { - ImGui::Text("Ordinal"); - ImGui::NextColumn(); - ImGui::Text(" Value"); - ImGui::NextColumn(); - ImGui::Separator(); - } - uint32_t element_size = - draw_info.index_format == IndexFormat::kInt32 ? 4 : 2; - const uint8_t* data_ptr = memory->TranslatePhysical( - draw_info.index_buffer_ptr + (display_start * element_size)); - for (int i = display_start; i < display_end; - ++i, data_ptr += element_size) { - if (i < 10) { - ImGui::Text(" %d", i); - } else if (i < 100) { - ImGui::Text(" %d", i); - } else if (i < 1000) { - ImGui::Text(" %d", i); - } else { - ImGui::Text(" %d", i); - } - ImGui::NextColumn(); - uint32_t value = element_size == 4 - ? GpuSwap(xe::load(data_ptr), - draw_info.index_endianness) - : GpuSwap(xe::load(data_ptr), - draw_info.index_endianness); - ImGui::Text(" %d", value); - ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + - (draw_info.index_count - display_end) * - ImGui::GetTextLineHeight()); - ImGui::PopStyleVar(); - ImGui::EndChild(); - } - } - if (ImGui::CollapsingHeader("Vertex Buffers")) { - auto shader = cp->active_vertex_shader(); - if (shader) { - const auto& buffer_inputs = shader->buffer_inputs(); - for (uint32_t buffer_index = 0; buffer_index < buffer_inputs.count; - ++buffer_index) { - const auto& desc = buffer_inputs.descs[buffer_index]; - int r = - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + (desc.fetch_slot / 3) * 6; - const auto group = - reinterpret_cast(®s.values[r]); - const xe_gpu_vertex_fetch_t* fetch = nullptr; - switch (desc.fetch_slot % 3) { - case 0: - fetch = &group->vertex_fetch_0; - break; - case 1: - fetch = &group->vertex_fetch_1; - break; - case 2: - fetch = &group->vertex_fetch_2; - break; - } - assert_true(fetch->endian == 2); - char tree_root_id[32]; - sprintf(tree_root_id, "#vertices_root_%d", desc.fetch_slot); - if (ImGui::TreeNode(tree_root_id, "vf%d: 0x%.8X (%db), %s", - desc.fetch_slot, fetch->address << 2, - fetch->size * 4, - kEndiannessNames[int(fetch->endian)])) { - ImGui::BeginChild("#vertices", ImVec2(0, 300)); - DrawVertexFetcher(memory, shader, desc, fetch); - ImGui::EndChild(); - ImGui::TreePop(); - } - } - } else { - ImGui::TextColored(kColorError, "ERROR: no vertex shader set"); - } - } - if (ImGui::CollapsingHeader("Vertex Textures")) { - auto shader = cp->active_vertex_shader(); - if (shader) { - const auto& sampler_inputs = shader->sampler_inputs(); - if (sampler_inputs.count) { - for (size_t i = 0; i < sampler_inputs.count; ++i) { - DrawTextureInfo(player, sampler_inputs.descs[i]); - } - } else { - ImGui::Text("No vertex shader samplers"); - } - } else { - ImGui::TextColored(kColorError, "ERROR: no vertex shader set"); - } - } - if (ImGui::CollapsingHeader("Textures")) { - auto shader = cp->active_pixel_shader(); - if (shader) { - const auto& sampler_inputs = shader->sampler_inputs(); - if (sampler_inputs.count) { - for (size_t i = 0; i < sampler_inputs.count; ++i) { - DrawTextureInfo(player, sampler_inputs.descs[i]); - } - } else { - ImGui::Text("No pixel shader samplers"); - } - } else { - ImGui::TextColored(kColorError, "ERROR: no pixel shader set"); - } - } - if (ImGui::CollapsingHeader("Fetch Constants (raw)")) { - ImGui::Columns(2); - ImGui::SetColumnOffset(1, 85.0f); - for (int i = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0; - i <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5; ++i) { - ImGui::Text("f%02d_%d", (i - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) / 6, - (i - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) % 6); - ImGui::NextColumn(); - ImGui::Text("%.8X", regs[i].u32); - ImGui::NextColumn(); - } - ImGui::Columns(1); - } - if (ImGui::CollapsingHeader("ALU Constants")) { - ImGui::Columns(2); - for (int i = XE_GPU_REG_SHADER_CONSTANT_000_X; - i <= XE_GPU_REG_SHADER_CONSTANT_511_X; i += 4) { - ImGui::Text("c%d", (i - XE_GPU_REG_SHADER_CONSTANT_000_X) / 4); - ImGui::NextColumn(); - ImGui::Text("%f, %f, %f, %f", regs[i + 0].f32, regs[i + 1].f32, - regs[i + 2].f32, regs[i + 3].f32); - ImGui::NextColumn(); - } - ImGui::Columns(1); - } - if (ImGui::CollapsingHeader("Bool Constants")) { - ImGui::Columns(2); - for (int i = XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031; - i <= XE_GPU_REG_SHADER_CONSTANT_BOOL_224_255; ++i) { - ImGui::Text("b%03d-%03d", - (i - XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031) * 32, - (i - XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031) * 32 + 31); - ImGui::NextColumn(); - ImGui::Text("%.8X", regs[i].u32); - ImGui::NextColumn(); - } - ImGui::Columns(1); - } - if (ImGui::CollapsingHeader("Loop Constants")) { - ImGui::Columns(2); - for (int i = XE_GPU_REG_SHADER_CONSTANT_LOOP_00; - i <= XE_GPU_REG_SHADER_CONSTANT_LOOP_31; ++i) { - ImGui::Text("l%d", i - XE_GPU_REG_SHADER_CONSTANT_LOOP_00); - ImGui::NextColumn(); - ImGui::Text("%.8X", regs[i].u32); - ImGui::NextColumn(); - } - ImGui::Columns(1); - } - ImGui::End(); -} - -void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player, - Memory* memory) { - ImGui::SetNextWindowCollapsed(true, ImGuiSetCond_FirstUseEver); - ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 5), - ImGuiSetCond_FirstUseEver); - if (!ImGui::Begin("Packet Disassembler", nullptr, ImVec2(500, 300))) { - ImGui::End(); - return; - } - if (!player.current_frame() || player.current_command_index() == -1) { - ImGui::Text("No frame/command selected"); - ImGui::End(); - return; - } - - auto frame = player.current_frame(); - const auto& command = frame->commands[player.current_command_index()]; - const uint8_t* start_ptr = command.start_ptr; - const uint8_t* end_ptr = command.end_ptr; - - ImGui::Text("Frame #%d, command %d", player.current_frame_index(), - player.current_command_index()); - ImGui::Separator(); - ImGui::BeginChild("packet_disassembler_list"); - const PacketStartCommand* pending_packet = nullptr; - auto trace_ptr = start_ptr; - while (trace_ptr < end_ptr) { - auto type = static_cast(xe::load(trace_ptr)); - switch (type) { - case TraceCommandType::kPrimaryBufferStart: { - auto cmd = - reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd) + cmd->count * 4; - ImGui::BulletText("PrimaryBufferStart"); - break; - } - case TraceCommandType::kPrimaryBufferEnd: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - ImGui::BulletText("PrimaryBufferEnd"); - break; - } - case TraceCommandType::kIndirectBufferStart: { - auto cmd = - reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd) + cmd->count * 4; - ImGui::BulletText("IndirectBufferStart"); - break; - } - case TraceCommandType::kIndirectBufferEnd: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - ImGui::BulletText("IndirectBufferEnd"); - break; - } - case TraceCommandType::kPacketStart: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd) + cmd->count * 4; - pending_packet = cmd; - break; - } - case TraceCommandType::kPacketEnd: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - if (pending_packet) { - PacketInfo packet_info = {0}; - if (PacketDisassembler::DisasmPacket( - reinterpret_cast(pending_packet) + - sizeof(PacketStartCommand), - &packet_info)) { - if (packet_info.predicated) { - ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); - } - ImGui::BulletText(packet_info.type_info->name); - ImGui::TreePush((const char*)0); - for (auto action : packet_info.actions) { - switch (action.type) { - case PacketAction::Type::kRegisterWrite: { - auto register_info = xe::gpu::RegisterFile::GetRegisterInfo( - action.register_write.index); - ImGui::Columns(2); - ImGui::Text("%.4X %s", action.register_write.index, - register_info ? register_info->name : "???"); - ImGui::NextColumn(); - if (!register_info || - register_info->type == RegisterInfo::Type::kDword) { - ImGui::Text("%.8X", action.register_write.value.u32); - } else { - ImGui::Text("%8f", action.register_write.value.f32); - } - ImGui::Columns(1); - break; - } - case PacketAction::Type::kSetBinMask: { - ImGui::Text("%.16llX", action.set_bin_mask.value); - break; - } - case PacketAction::Type::kSetBinSelect: { - ImGui::Text("%.16llX", action.set_bin_select.value); - break; - } - } - } - ImGui::TreePop(); - if (packet_info.predicated) { - ImGui::PopStyleColor(); - } - } else { - ImGui::BulletText(""); - } - pending_packet = nullptr; - } - break; - } - case TraceCommandType::kMemoryRead: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd) + cmd->length; - // ImGui::BulletText("MemoryRead"); - break; - } - case TraceCommandType::kMemoryWrite: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd) + cmd->length; - // ImGui::BulletText("MemoryWrite"); - break; - } - case TraceCommandType::kEvent: { - auto cmd = reinterpret_cast(trace_ptr); - trace_ptr += sizeof(*cmd); - switch (cmd->event_type) { - case EventType::kSwap: { - ImGui::BulletText(""); - break; - } - } - break; - } - } - } - ImGui::EndChild(); - ImGui::End(); -} - -void DrawUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) { - // ImGui::ShowTestWindow(); - - DrawControllerUI(window, player, memory); - DrawCommandListUI(window, player, memory); - DrawStateUI(window, player, memory); - DrawPacketDisassemblerUI(window, player, memory); -} - -std::unique_ptr imgui_drawer_; - int trace_viewer_main(const std::vector& args) { - // Create the emulator but don't initialize so we can setup the window. - auto emulator = std::make_unique(L""); - - // Main emulator display window. - auto loop = ui::Loop::Create(); - auto window = xe::ui::Window::Create(loop.get(), L"xenia-gpu-trace-viewer"); - loop->PostSynchronous([&window]() { - xe::threading::set_name("Win32 Loop"); - if (!window->Initialize()) { - FatalError("Failed to initialize main window"); - return; - } - }); - window->on_closed.AddListener([&loop](xe::ui::UIEvent* e) { - loop->Quit(); - XELOGI("User-initiated death!"); - imgui_drawer_.reset(); - exit(1); - }); - loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); }); - window->Resize(1920, 1200); - - X_STATUS result = emulator->Setup(window.get()); - if (XFAILED(result)) { - XELOGE("Failed to setup emulator: %.8X", result); - return 1; - } - // Grab path from the flag or unnamed argument. if (FLAGS_target_trace_file.empty() && args.size() < 2) { - XELOGE("No trace file specified"); + xe::FatalError("No trace file specified"); return 1; } @@ -1436,70 +83,23 @@ int trace_viewer_main(const std::vector& args) { // Normalize the path and make absolute. auto abs_path = xe::to_absolute_path(path); - auto file_name = xe::find_name_from_path(path); - window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name); - - auto graphics_system = emulator->graphics_system(); - - imgui_drawer_ = std::make_unique(window.get()); - imgui_drawer_->SetupDefaultInput(); - - TracePlayer player(loop.get(), graphics_system); - if (!player.Open(abs_path)) { - XELOGE("Could not load trace file"); + GL4TraceViewer trace_viewer; + if (!trace_viewer.Setup()) { + xe::FatalError("Unable to setup trace viewer"); return 1; } - - window->on_key_char.AddListener([graphics_system](xe::ui::KeyEvent* e) { - if (e->key_code() == 0x74 /* VK_F5 */) { - graphics_system->ClearCaches(); - e->set_handled(true); - } - }); - - window->on_painting.AddListener([&](xe::ui::UIEvent* e) { - auto& io = ImGui::GetIO(); - auto current_ticks = Clock::QueryHostTickCount(); - static uint64_t last_ticks = 0; - io.DeltaTime = - (current_ticks - last_ticks) / float(Clock::host_tick_frequency()); - last_ticks = current_ticks; - - io.DisplaySize = - ImVec2(float(e->target()->width()), float(e->target()->height())); - - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - - ImGui::NewFrame(); - - DrawUI(window.get(), player, emulator->memory()); - - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - ImGui::Render(); - - // Continuous paint. - window->Invalidate(); - }); - window->Invalidate(); - - // Wait until we are exited. - loop->AwaitQuit(); - - imgui_drawer_.reset(); - - emulator.reset(); - window.reset(); - loop.reset(); + if (!trace_viewer.Load(std::move(abs_path))) { + xe::FatalError("Unable to load trace file; not found?"); + return 1; + } + trace_viewer.Run(); return 0; } +} // namespace gl4 } // namespace gpu } // namespace xe DEFINE_ENTRY_POINT(L"xenia-gpu-gl4-trace-viewer", L"xenia-gpu-gl4-trace-viewer some.trace", - xe::gpu::trace_viewer_main); + xe::gpu::gl4::trace_viewer_main); diff --git a/src/xenia/gpu/trace_viewer.cc b/src/xenia/gpu/trace_viewer.cc new file mode 100644 index 000000000..f73fc5714 --- /dev/null +++ b/src/xenia/gpu/trace_viewer.cc @@ -0,0 +1,1460 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/gpu/trace_viewer.h" + +#include + +#include "third_party/imgui/imgui.h" +#include "xenia/base/clock.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" +#include "xenia/base/string.h" +#include "xenia/base/threading.h" +#include "xenia/gpu/command_processor.h" +#include "xenia/gpu/graphics_system.h" +#include "xenia/gpu/packet_disassembler.h" +#include "xenia/gpu/register_file.h" +#include "xenia/gpu/sampler_info.h" +#include "xenia/gpu/texture_info.h" +#include "xenia/memory.h" +#include "xenia/ui/imgui_drawer.h" +#include "xenia/ui/window.h" +#include "xenia/xbox.h" + +namespace xe { +namespace gpu { + +using namespace xe::gpu::xenos; + +static const ImVec4 kColorError = + ImVec4(255 / 255.0f, 0 / 255.0f, 0 / 255.0f, 255 / 255.0f); +static const ImVec4 kColorComment = + ImVec4(42 / 255.0f, 179 / 255.0f, 0 / 255.0f, 255 / 255.0f); +static const ImVec4 kColorIgnored = + ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f); + +TraceViewer::TraceViewer() = default; + +TraceViewer::~TraceViewer() = default; + +bool TraceViewer::Setup() { + // Main display window. + loop_ = ui::Loop::Create(); + window_ = xe::ui::Window::Create(loop_.get(), L"xenia-gpu-trace-viewer"); + loop_->PostSynchronous([&]() { + xe::threading::set_name("Win32 Loop"); + if (!window_->Initialize()) { + xe::FatalError("Failed to initialize main window"); + return; + } + }); + window_->on_closed.AddListener([&](xe::ui::UIEvent* e) { + loop_->Quit(); + XELOGI("User-initiated death!"); + imgui_drawer_.reset(); + exit(1); + }); + loop_->on_quit.AddListener([&](xe::ui::UIEvent* e) { window_.reset(); }); + window_->Resize(1920, 1200); + + // Create the emulator but don't initialize so we can setup the window. + emulator_ = std::make_unique(L""); + X_STATUS result = emulator_->Setup(window_.get()); + if (XFAILED(result)) { + XELOGE("Failed to setup emulator: %.8X", result); + return false; + } + memory_ = emulator_->memory(); + graphics_system_ = emulator_->graphics_system(); + + window_->on_key_char.AddListener([&](xe::ui::KeyEvent* e) { + if (e->key_code() == 0x74 /* VK_F5 */) { + graphics_system_->ClearCaches(); + e->set_handled(true); + } + }); + + player_ = std::make_unique(loop_.get(), graphics_system_); + + imgui_drawer_ = std::make_unique(window_.get()); + imgui_drawer_->SetupDefaultInput(); + + window_->on_painting.AddListener([&](xe::ui::UIEvent* e) { + auto& io = ImGui::GetIO(); + auto current_ticks = Clock::QueryHostTickCount(); + static uint64_t last_ticks = 0; + io.DeltaTime = + (current_ticks - last_ticks) / float(Clock::host_tick_frequency()); + last_ticks = current_ticks; + + io.DisplaySize = + ImVec2(float(e->target()->width()), float(e->target()->height())); + + ImGui::NewFrame(); + + DrawUI(); + + ImGui::Render(); + + // Continuous paint. + window_->Invalidate(); + }); + window_->Invalidate(); + + return true; +} + +bool TraceViewer::Load(std::wstring trace_file_path) { + auto file_name = xe::find_name_from_path(trace_file_path); + window_->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name); + + if (!player_->Open(trace_file_path)) { + XELOGE("Could not load trace file"); + return false; + } + + return true; +} + +void TraceViewer::Run() { + // Wait until we are exited. + loop_->AwaitQuit(); + + player_.reset(); + emulator_.reset(); + imgui_drawer_.reset(); + window_.reset(); + loop_.reset(); +} + +void TraceViewer::DrawMultilineString(const std::string& str) { + size_t i = 0; + bool done = false; + while (!done && i < str.size()) { + size_t next_i = str.find('\n', i); + if (next_i == std::string::npos) { + done = true; + next_i = str.size() - 1; + } + auto line = str.substr(i, next_i - i); + ImGui::Text("%s", line.c_str()); + i = next_i + 1; + } +} + +void TraceViewer::DrawUI() { + // ImGui::ShowTestWindow(); + + DrawControllerUI(); + DrawCommandListUI(); + DrawStateUI(); + DrawPacketDisassemblerUI(); +} + +void TraceViewer::DrawControllerUI() { + ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCond_FirstUseEver); + if (!ImGui::Begin("Controller", nullptr, ImVec2(340, 60))) { + ImGui::End(); + return; + } + + int target_frame = player_->current_frame_index(); + if (ImGui::Button("|<<")) { + target_frame = 0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reset to first frame"); + } + ImGui::SameLine(); + ImGui::PushButtonRepeat(true); + if (ImGui::Button(">>", ImVec2(0, 0))) { + if (target_frame + 1 < player_->frame_count()) { + ++target_frame; + } + } + ImGui::PopButtonRepeat(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Next frame (hold for continuous)"); + } + ImGui::SameLine(); + if (ImGui::Button(">>|")) { + target_frame = player_->frame_count() - 1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip to last frame"); + } + ImGui::SameLine(); + ImGui::SliderInt("", &target_frame, 0, player_->frame_count() - 1); + if (target_frame != player_->current_frame_index()) { + player_->SeekFrame(target_frame); + } + ImGui::End(); +} + +void TraceViewer::DrawPacketDisassemblerUI() { + ImGui::SetNextWindowCollapsed(true, ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(float(window_->width()) - 500 - 5, 5), + ImGuiSetCond_FirstUseEver); + if (!ImGui::Begin("Packet Disassembler", nullptr, ImVec2(500, 300))) { + ImGui::End(); + return; + } + if (!player_->current_frame() || player_->current_command_index() == -1) { + ImGui::Text("No frame/command selected"); + ImGui::End(); + return; + } + + auto frame = player_->current_frame(); + const auto& command = frame->commands[player_->current_command_index()]; + const uint8_t* start_ptr = command.start_ptr; + const uint8_t* end_ptr = command.end_ptr; + + ImGui::Text("Frame #%d, command %d", player_->current_frame_index(), + player_->current_command_index()); + ImGui::Separator(); + ImGui::BeginChild("packet_disassembler_list"); + const PacketStartCommand* pending_packet = nullptr; + auto trace_ptr = start_ptr; + while (trace_ptr < end_ptr) { + auto type = static_cast(xe::load(trace_ptr)); + switch (type) { + case TraceCommandType::kPrimaryBufferStart: { + auto cmd = + reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->count * 4; + ImGui::BulletText("PrimaryBufferStart"); + break; + } + case TraceCommandType::kPrimaryBufferEnd: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + ImGui::BulletText("PrimaryBufferEnd"); + break; + } + case TraceCommandType::kIndirectBufferStart: { + auto cmd = + reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->count * 4; + ImGui::BulletText("IndirectBufferStart"); + break; + } + case TraceCommandType::kIndirectBufferEnd: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + ImGui::BulletText("IndirectBufferEnd"); + break; + } + case TraceCommandType::kPacketStart: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->count * 4; + pending_packet = cmd; + break; + } + case TraceCommandType::kPacketEnd: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + if (pending_packet) { + PacketInfo packet_info = {0}; + if (PacketDisassembler::DisasmPacket( + reinterpret_cast(pending_packet) + + sizeof(PacketStartCommand), + &packet_info)) { + if (packet_info.predicated) { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + } + ImGui::BulletText(packet_info.type_info->name); + ImGui::TreePush((const char*)0); + for (auto action : packet_info.actions) { + switch (action.type) { + case PacketAction::Type::kRegisterWrite: { + auto register_info = xe::gpu::RegisterFile::GetRegisterInfo( + action.register_write.index); + ImGui::Columns(2); + ImGui::Text("%.4X %s", action.register_write.index, + register_info ? register_info->name : "???"); + ImGui::NextColumn(); + if (!register_info || + register_info->type == RegisterInfo::Type::kDword) { + ImGui::Text("%.8X", action.register_write.value.u32); + } else { + ImGui::Text("%8f", action.register_write.value.f32); + } + ImGui::Columns(1); + break; + } + case PacketAction::Type::kSetBinMask: { + ImGui::Text("%.16llX", action.set_bin_mask.value); + break; + } + case PacketAction::Type::kSetBinSelect: { + ImGui::Text("%.16llX", action.set_bin_select.value); + break; + } + } + } + ImGui::TreePop(); + if (packet_info.predicated) { + ImGui::PopStyleColor(); + } + } else { + ImGui::BulletText(""); + } + pending_packet = nullptr; + } + break; + } + case TraceCommandType::kMemoryRead: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->length; + // ImGui::BulletText("MemoryRead"); + break; + } + case TraceCommandType::kMemoryWrite: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd) + cmd->length; + // ImGui::BulletText("MemoryWrite"); + break; + } + case TraceCommandType::kEvent: { + auto cmd = reinterpret_cast(trace_ptr); + trace_ptr += sizeof(*cmd); + switch (cmd->event_type) { + case EventType::kSwap: { + ImGui::BulletText(""); + break; + } + } + break; + } + } + } + ImGui::EndChild(); + ImGui::End(); +} + +void TraceViewer::DrawCommandListUI() { + ImGui::SetNextWindowPos(ImVec2(5, 70), ImGuiSetCond_FirstUseEver); + if (!ImGui::Begin("Command List", nullptr, ImVec2(200, 640))) { + ImGui::End(); + return; + } + + static const TracePlayer::Frame* previous_frame = nullptr; + auto frame = player_->current_frame(); + if (!frame) { + ImGui::End(); + return; + } + bool did_seek = false; + if (previous_frame != frame) { + did_seek = true; + previous_frame = frame; + } + int command_count = int(frame->commands.size()); + int target_command = player_->current_command_index(); + int column_width = int(ImGui::GetContentRegionMax().x); + ImGui::Text("Frame #%d", player_->current_frame_index()); + ImGui::Separator(); + if (ImGui::Button("reset")) { + target_command = -1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reset to before any frame commands"); + } + ImGui::SameLine(); + ImGui::PushButtonRepeat(true); + if (ImGui::Button("prev", ImVec2(0, 0))) { + if (target_command >= 0) { + --target_command; + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move to the previous command (hold)"); + } + ImGui::SameLine(); + if (ImGui::Button("next", ImVec2(0, 0))) { + if (target_command < command_count - 1) { + ++target_command; + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move to the next command (hold)"); + } + ImGui::PopButtonRepeat(); + ImGui::SameLine(); + if (ImGui::Button("end")) { + target_command = command_count - 1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move to the last command"); + } + ImGui::PushItemWidth(float(column_width - 15)); + ImGui::SliderInt("", &target_command, -1, command_count - 1); + ImGui::PopItemWidth(); + if (target_command != player_->current_command_index()) { + did_seek = true; + player_->SeekCommand(target_command); + } + ImGui::Separator(); + ImGui::BeginChild("command_list"); + ImGui::PushID(-1); + bool is_selected = player_->current_command_index() == -1; + if (ImGui::Selectable("", &is_selected)) { + player_->SeekCommand(-1); + } + ImGui::PopID(); + if (did_seek && target_command == -1) { + ImGui::SetScrollPosHere(); + } + + for (int i = 0; i < int(frame->commands.size()); ++i) { + ImGui::PushID(i); + is_selected = i == player_->current_command_index(); + const auto& command = frame->commands[i]; + const char* label; + switch (command.type) { + case TraceReader::Frame::Command::Type::kDraw: + label = "Draw"; + break; + case TraceReader::Frame::Command::Type::kSwap: + label = "Swap"; + break; + } + if (ImGui::Selectable(label, &is_selected)) { + player_->SeekCommand(i); + } + ImGui::SameLine(column_width - 60.0f); + ImGui::Text("%d", i); + ImGui::PopID(); + if (did_seek && target_command == i) { + ImGui::SetScrollPosHere(); + } + } + ImGui::EndChild(); + ImGui::End(); +} + +TraceViewer::ShaderDisplayType TraceViewer::DrawShaderTypeUI() { + static ShaderDisplayType shader_display_type = ShaderDisplayType::kUcode; + ImGui::RadioButton("ucode", reinterpret_cast(&shader_display_type), + static_cast(ShaderDisplayType::kUcode)); + ImGui::SameLine(); + ImGui::RadioButton("translated", reinterpret_cast(&shader_display_type), + static_cast(ShaderDisplayType::kTranslated)); + ImGui::SameLine(); + ImGui::RadioButton("disasm", reinterpret_cast(&shader_display_type), + static_cast(ShaderDisplayType::kHostDisasm)); + return shader_display_type; +} + +void TraceViewer::DrawShaderUI(Shader* shader, ShaderDisplayType display_type) { + // Must be prepared for advanced display modes. + if (display_type != ShaderDisplayType::kUcode) { + if (!shader->has_prepared()) { + ImGui::TextColored(kColorError, + "ERROR: shader not prepared (not used this frame?)"); + return; + } + } + + switch (display_type) { + case ShaderDisplayType::kUcode: { + DrawMultilineString(shader->ucode_disassembly()); + break; + } + case ShaderDisplayType::kTranslated: { + const auto& str = shader->translated_disassembly(); + size_t i = 0; + bool done = false; + while (!done && i < str.size()) { + size_t next_i = str.find('\n', i); + if (next_i == std::string::npos) { + done = true; + next_i = str.size() - 1; + } + auto line = str.substr(i, next_i - i); + if (line.find("//") != std::string::npos) { + ImGui::TextColored(kColorComment, "%s", line.c_str()); + } else { + ImGui::Text("%s", line.c_str()); + } + i = next_i + 1; + } + break; + } + case ShaderDisplayType::kHostDisasm: { + DrawMultilineString(shader->host_disassembly()); + break; + } + } +} + +// glBlendEquationSeparatei(i, blend_op, blend_op_alpha); +// glBlendFuncSeparatei(i, src_blend, dest_blend, src_blend_alpha, +// dest_blend_alpha); +void TraceViewer::DrawBlendMode(uint32_t src_blend, uint32_t dest_blend, + uint32_t blend_op) { + static const char* kBlendNames[] = { + /* 0 */ "ZERO", + /* 1 */ "ONE", + /* 2 */ "UNK2", // ? + /* 3 */ "UNK3", // ? + /* 4 */ "SRC_COLOR", + /* 5 */ "ONE_MINUS_SRC_COLOR", + /* 6 */ "SRC_ALPHA", + /* 7 */ "ONE_MINUS_SRC_ALPHA", + /* 8 */ "DST_COLOR", + /* 9 */ "ONE_MINUS_DST_COLOR", + /* 10 */ "DST_ALPHA", + /* 11 */ "ONE_MINUS_DST_ALPHA", + /* 12 */ "CONSTANT_COLOR", + /* 13 */ "ONE_MINUS_CONSTANT_COLOR", + /* 14 */ "CONSTANT_ALPHA", + /* 15 */ "ONE_MINUS_CONSTANT_ALPHA", + /* 16 */ "SRC_ALPHA_SATURATE", + }; + const char* src_str = kBlendNames[src_blend]; + const char* dest_str = kBlendNames[dest_blend]; + const char* op_template; + switch (blend_op) { + case 0: // add + op_template = "%s + %s"; + break; + case 1: // subtract + op_template = "%s - %s"; + break; + case 2: // min + op_template = "min(%s, %s)"; + break; + case 3: // max + op_template = "max(%s, %s)"; + break; + case 4: // reverse subtract + op_template = "-(%s) + %s"; + break; + default: + op_template = "%s ? %s"; + break; + } + ImGui::Text(op_template, src_str, dest_str); +} + +void TraceViewer::DrawTextureInfo(const Shader::SamplerDesc& desc) { + auto& regs = *graphics_system_->register_file(); + + int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + desc.fetch_slot * 6; + auto group = reinterpret_cast(®s.values[r]); + auto& fetch = group->texture_fetch; + if (fetch.type != 0x2) { + DrawFailedTextureInfo(desc, "Invalid fetch type"); + return; + } + TextureInfo texture_info; + if (!TextureInfo::Prepare(fetch, &texture_info)) { + DrawFailedTextureInfo(desc, "Unable to parse texture fetcher info"); + return; + } + SamplerInfo sampler_info; + if (!SamplerInfo::Prepare(fetch, desc.tex_fetch, &sampler_info)) { + DrawFailedTextureInfo(desc, "Unable to parse sampler info"); + return; + } + auto texture = GetTextureEntry(texture_info, sampler_info); + if (!texture) { + DrawFailedTextureInfo(desc, "Failed to demand texture"); + return; + } + + ImGui::Columns(2); + ImVec2 button_size(256, 256); + if (ImGui::ImageButton(ImTextureID(texture), button_size, ImVec2(0, 0), + ImVec2(1, 1))) { + // show viewer + } + ImGui::NextColumn(); + ImGui::Text("Fetch Slot: %d", desc.fetch_slot); + switch (texture_info.dimension) { + case Dimension::k1D: + ImGui::Text("1D: %dpx", texture_info.width + 1); + break; + case Dimension::k2D: + ImGui::Text("2D: %dx%dpx", texture_info.width + 1, + texture_info.height + 1); + break; + case Dimension::k3D: + ImGui::Text("3D: %dx%dx%dpx", texture_info.width + 1, + texture_info.height + 1, texture_info.depth + 1); + break; + case Dimension::kCube: + ImGui::Text("Cube: ?"); + break; + } + ImGui::Columns(1); +} + +void TraceViewer::DrawFailedTextureInfo(const Shader::SamplerDesc& desc, + const char* message) { + // TODO(benvanik): better error info/etc. + ImGui::TextColored(kColorError, "ERROR: %s", message); +} + +void TraceViewer::DrawVertexFetcher(Shader* shader, + const Shader::BufferDesc& desc, + const xe_gpu_vertex_fetch_t* fetch) { + const uint8_t* addr = memory_->TranslatePhysical(fetch->address << 2); + uint32_t vertex_count = (fetch->size * 4) / desc.stride_words; + int column_count = 0; + for (uint32_t el_index = 0; el_index < desc.element_count; ++el_index) { + const auto& el = desc.elements[el_index]; + switch (el.format) { + case VertexFormat::k_32: + case VertexFormat::k_32_FLOAT: + ++column_count; + break; + case VertexFormat::k_16_16: + case VertexFormat::k_16_16_FLOAT: + case VertexFormat::k_32_32: + case VertexFormat::k_32_32_FLOAT: + column_count += 2; + break; + case VertexFormat::k_10_11_11: + case VertexFormat::k_11_11_10: + case VertexFormat::k_32_32_32_FLOAT: + column_count += 3; + break; + case VertexFormat::k_8_8_8_8: + ++column_count; + break; + case VertexFormat::k_2_10_10_10: + case VertexFormat::k_16_16_16_16: + case VertexFormat::k_32_32_32_32: + case VertexFormat::k_16_16_16_16_FLOAT: + case VertexFormat::k_32_32_32_32_FLOAT: + column_count += 4; + break; + } + } + ImGui::BeginChild("#indices", ImVec2(0, 300)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + int display_start, display_end; + ImGui::CalcListClipping(1 + vertex_count, ImGui::GetTextLineHeight(), + &display_start, &display_end); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + + (display_start)*ImGui::GetTextLineHeight()); + ImGui::Columns(column_count); + if (display_start <= 1) { + for (uint32_t el_index = 0; el_index < desc.element_count; ++el_index) { + const auto& el = desc.elements[el_index]; + switch (el.format) { + case VertexFormat::k_32: + case VertexFormat::k_32_FLOAT: + ImGui::Text("e%d.x", el_index); + ImGui::NextColumn(); + break; + case VertexFormat::k_16_16: + case VertexFormat::k_16_16_FLOAT: + case VertexFormat::k_32_32: + case VertexFormat::k_32_32_FLOAT: + ImGui::Text("e%d.x", el_index); + ImGui::NextColumn(); + ImGui::Text("e%d.y", el_index); + ImGui::NextColumn(); + break; + case VertexFormat::k_10_11_11: + case VertexFormat::k_11_11_10: + case VertexFormat::k_32_32_32_FLOAT: + ImGui::Text("e%d.x", el_index); + ImGui::NextColumn(); + ImGui::Text("e%d.y", el_index); + ImGui::NextColumn(); + ImGui::Text("e%d.z", el_index); + ImGui::NextColumn(); + break; + case VertexFormat::k_8_8_8_8: + ImGui::Text("e%d.xyzw", el_index); + ImGui::NextColumn(); + break; + case VertexFormat::k_2_10_10_10: + case VertexFormat::k_16_16_16_16: + case VertexFormat::k_32_32_32_32: + case VertexFormat::k_16_16_16_16_FLOAT: + case VertexFormat::k_32_32_32_32_FLOAT: + ImGui::Text("e%d.x", el_index); + ImGui::NextColumn(); + ImGui::Text("e%d.y", el_index); + ImGui::NextColumn(); + ImGui::Text("e%d.z", el_index); + ImGui::NextColumn(); + ImGui::Text("e%d.w", el_index); + ImGui::NextColumn(); + break; + } + } + ImGui::Separator(); + } + for (int i = display_start; i < display_end; ++i) { + const uint8_t* vstart = addr + i * desc.stride_words * 4; + for (uint32_t el_index = 0; el_index < desc.element_count; ++el_index) { + const auto& el = desc.elements[el_index]; +#define LOADEL(type, wo) \ + GpuSwap(xe::load(vstart + (el.offset_words + wo) * 4), \ + Endian(fetch->endian)) + switch (el.format) { + case VertexFormat::k_32: + ImGui::Text("%.8X", LOADEL(uint32_t, 0)); + ImGui::NextColumn(); + break; + case VertexFormat::k_32_FLOAT: + ImGui::Text("%.2f", LOADEL(float, 0)); + ImGui::NextColumn(); + break; + case VertexFormat::k_16_16: + case VertexFormat::k_16_16_FLOAT: + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + break; + case VertexFormat::k_32_32: + ImGui::Text("%.8X", LOADEL(uint32_t, 0)); + ImGui::NextColumn(); + ImGui::Text("%.8X", LOADEL(uint32_t, 1)); + ImGui::NextColumn(); + break; + case VertexFormat::k_32_32_FLOAT: + ImGui::Text("%.2f", LOADEL(float, 0)); + ImGui::NextColumn(); + ImGui::Text("%.2f", LOADEL(float, 1)); + ImGui::NextColumn(); + break; + case VertexFormat::k_10_11_11: + case VertexFormat::k_11_11_10: + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + break; + case VertexFormat::k_32_32_32_FLOAT: + ImGui::Text("%.2f", LOADEL(float, 0)); + ImGui::NextColumn(); + ImGui::Text("%.2f", LOADEL(float, 1)); + ImGui::NextColumn(); + ImGui::Text("%.2f", LOADEL(float, 2)); + ImGui::NextColumn(); + break; + case VertexFormat::k_8_8_8_8: + ImGui::Text("%.8X", LOADEL(uint32_t, 0)); + ImGui::NextColumn(); + break; + case VertexFormat::k_2_10_10_10: + case VertexFormat::k_16_16_16_16: + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + break; + case VertexFormat::k_32_32_32_32: + ImGui::Text("%.8X", LOADEL(uint32_t, 0)); + ImGui::NextColumn(); + ImGui::Text("%.8X", LOADEL(uint32_t, 1)); + ImGui::NextColumn(); + ImGui::Text("%.8X", LOADEL(uint32_t, 2)); + ImGui::NextColumn(); + ImGui::Text("%.8X", LOADEL(uint32_t, 3)); + ImGui::NextColumn(); + break; + case VertexFormat::k_16_16_16_16_FLOAT: + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + ImGui::Text("??"); + ImGui::NextColumn(); + break; + case VertexFormat::k_32_32_32_32_FLOAT: + ImGui::Text("%.2f", LOADEL(float, 0)); + ImGui::NextColumn(); + ImGui::Text("%.2f", LOADEL(float, 1)); + ImGui::NextColumn(); + ImGui::Text("%.2f", LOADEL(float, 2)); + ImGui::NextColumn(); + ImGui::Text("%.2f", LOADEL(float, 3)); + ImGui::NextColumn(); + break; + } + } + } + ImGui::Columns(1); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + + (vertex_count - display_end) * + ImGui::GetTextLineHeight()); + ImGui::PopStyleVar(); + ImGui::EndChild(); +} + +static const char* kCompareFuncNames[] = { + "", "<", "==", "<=", ">", "!=", ">=", "", +}; +static const char* kIndexFormatNames[] = { + "uint16", "uint32", +}; +static const char* kEndiannessNames[] = { + "unspecified endianness", "8-in-16", "8-in-32", "16-in-32", +}; + +void TraceViewer::DrawStateUI() { + auto command_processor = graphics_system_->command_processor(); + auto& regs = *graphics_system_->register_file(); + + ImGui::SetNextWindowPos(ImVec2(float(window_->width()) - 500 - 5, 30), + ImGuiSetCond_FirstUseEver); + if (!ImGui::Begin("State", nullptr, ImVec2(500, 680))) { + ImGui::End(); + return; + } + + if (!player_->current_frame() || player_->current_command_index() == -1) { + ImGui::Text("No frame/command selected"); + ImGui::End(); + return; + } + + auto frame = player_->current_frame(); + const auto& command = frame->commands[player_->current_command_index()]; + auto packet_head = command.head_ptr + sizeof(PacketStartCommand); + uint32_t packet = xe::load_and_swap(packet_head); + uint32_t packet_type = packet >> 30; + assert_true(packet_type == 0x03); + uint32_t opcode = (packet >> 8) & 0x7F; + struct { + PrimitiveType prim_type; + bool is_auto_index; + uint32_t index_count; + uint32_t index_buffer_ptr; + uint32_t index_buffer_size; + Endian index_endianness; + IndexFormat index_format; + } draw_info; + std::memset(&draw_info, 0, sizeof(draw_info)); + switch (opcode) { + case PM4_DRAW_INDX: { + uint32_t dword0 = xe::load_and_swap(packet_head + 4); + uint32_t dword1 = xe::load_and_swap(packet_head + 8); + draw_info.index_count = dword1 >> 16; + draw_info.prim_type = static_cast(dword1 & 0x3F); + uint32_t src_sel = (dword1 >> 6) & 0x3; + if (src_sel == 0x0) { + // Indexed draw. + draw_info.is_auto_index = false; + draw_info.index_buffer_ptr = + xe::load_and_swap(packet_head + 12); + uint32_t index_size = xe::load_and_swap(packet_head + 16); + draw_info.index_endianness = static_cast(index_size >> 30); + index_size &= 0x00FFFFFF; + bool index_32bit = (dword1 >> 11) & 0x1; + draw_info.index_format = + index_32bit ? IndexFormat::kInt32 : IndexFormat::kInt16; + draw_info.index_buffer_size = index_size * (index_32bit ? 4 : 2); + } else if (src_sel == 0x2) { + // Auto draw. + draw_info.is_auto_index = true; + } else { + // Unknown source select. + assert_always(); + } + break; + } + case PM4_DRAW_INDX_2: { + uint32_t dword0 = xe::load_and_swap(packet_head + 4); + uint32_t src_sel = (dword0 >> 6) & 0x3; + assert_true(src_sel == 0x2); // 'SrcSel=AutoIndex' + draw_info.prim_type = static_cast(dword0 & 0x3F); + draw_info.is_auto_index = true; + draw_info.index_count = dword0 >> 16; + break; + } + } + + auto enable_mode = + static_cast(regs[XE_GPU_REG_RB_MODECONTROL].u32 & 0x7); + const char* mode_name = "Unknown"; + switch (enable_mode) { + case ModeControl::kIgnore: + ImGui::Text("Ignored Command %d", player_->current_command_index()); + break; + case ModeControl::kColorDepth: + case ModeControl::kDepth: { + static const char* kPrimNames[] = { + "", "point list", "line list", "line strip", + "triangle list", "triangle fan", "triangle strip", "unknown 0x7", + "rectangle list", "unknown 0x9", "unknown 0xA", "unknown 0xB", + "line loop", "quad list", "quad strip", "unknown 0xF", + }; + ImGui::Text("%s Command %d: %s, %d indices", + enable_mode == ModeControl::kColorDepth ? "Color-Depth" + : "Depth-only", + player_->current_command_index(), + kPrimNames[int(draw_info.prim_type)], draw_info.index_count); + break; + } + case ModeControl::kCopy: + ImGui::Text("Copy Command %d", player_->current_command_index()); + break; + } + + ImGui::Columns(2); + ImGui::BulletText("Viewport State:"); + if (true) { + ImGui::TreePush((const void*)0); + uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32; + if ((pa_su_sc_mode_cntl >> 16) & 1) { + uint32_t window_offset = regs[XE_GPU_REG_PA_SC_WINDOW_OFFSET].u32; + int16_t window_offset_x = window_offset & 0x7FFF; + int16_t window_offset_y = (window_offset >> 16) & 0x7FFF; + if (window_offset_x & 0x4000) { + window_offset_x |= 0x8000; + } + if (window_offset_y & 0x4000) { + window_offset_y |= 0x8000; + } + ImGui::BulletText("Window Offset: %d, %d", window_offset_x, + window_offset_y); + } else { + ImGui::BulletText("Window Offset: disabled"); + } + uint32_t window_scissor_tl = regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL].u32; + uint32_t window_scissor_br = regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_BR].u32; + ImGui::BulletText( + "Window Scissor: %d,%d to %d,%d (%d x %d)", window_scissor_tl & 0x7FFF, + (window_scissor_tl >> 16) & 0x7FFF, window_scissor_br & 0x7FFF, + (window_scissor_br >> 16) & 0x7FFF, + (window_scissor_br & 0x7FFF) - (window_scissor_tl & 0x7FFF), + ((window_scissor_br >> 16) & 0x7FFF) - + ((window_scissor_tl >> 16) & 0x7FFF)); + uint32_t surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32; + uint32_t surface_pitch = surface_info & 0x3FFF; + auto surface_msaa = (surface_info >> 16) & 0x3; + static const char* kMsaaNames[] = { + "1X", "2X", "4X", + }; + ImGui::BulletText("Surface MSAA: %s", kMsaaNames[surface_msaa]); + uint32_t vte_control = regs[XE_GPU_REG_PA_CL_VTE_CNTL].u32; + bool vport_xscale_enable = (vte_control & (1 << 0)) > 0; + bool vport_xoffset_enable = (vte_control & (1 << 1)) > 0; + bool vport_yscale_enable = (vte_control & (1 << 2)) > 0; + bool vport_yoffset_enable = (vte_control & (1 << 3)) > 0; + bool vport_zscale_enable = (vte_control & (1 << 4)) > 0; + bool vport_zoffset_enable = (vte_control & (1 << 5)) > 0; + assert_true(vport_xscale_enable == vport_yscale_enable == + vport_zscale_enable == vport_xoffset_enable == + vport_yoffset_enable == vport_zoffset_enable); + ImGui::BulletText( + "Viewport Offset: %f, %f, %f", + vport_xoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32 : 0, + vport_yoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32 : 0, + vport_zoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32 : 0); + ImGui::BulletText( + "Viewport Scale: %f, %f, %f", + vport_xscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32 : 1, + vport_yscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32 : 1, + vport_zscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32 : 1); + ImGui::BulletText("Vertex Format: %s, %s, %s, %s", + ((vte_control >> 8) & 0x1) ? "x/w0" : "x", + ((vte_control >> 8) & 0x1) ? "y/w0" : "y", + ((vte_control >> 9) & 0x1) ? "z/w0" : "z", + ((vte_control >> 10) & 0x1) ? "w0" : "1/w0"); + uint32_t clip_control = regs[XE_GPU_REG_PA_CL_CLIP_CNTL].u32; + bool clip_enabled = ((clip_control >> 17) & 0x1) == 0; + bool dx_clip = ((clip_control >> 20) & 0x1) == 0x1; + ImGui::BulletText("Clip Enabled: %s, DX Clip: %s", + clip_enabled ? "true" : "false", + dx_clip ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::NextColumn(); + ImGui::BulletText("Rasterizer State:"); + if (true) { + ImGui::TreePush((const void*)0); + uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32; + uint32_t pa_sc_screen_scissor_tl = + regs[XE_GPU_REG_PA_SC_SCREEN_SCISSOR_TL].u32; + uint32_t pa_sc_screen_scissor_br = + regs[XE_GPU_REG_PA_SC_SCREEN_SCISSOR_BR].u32; + if (pa_sc_screen_scissor_tl != 0 && pa_sc_screen_scissor_br != 0x20002000) { + int32_t screen_scissor_x = pa_sc_screen_scissor_tl & 0x7FFF; + int32_t screen_scissor_y = (pa_sc_screen_scissor_tl >> 16) & 0x7FFF; + int32_t screen_scissor_w = + (pa_sc_screen_scissor_br & 0x7FFF) - screen_scissor_x; + int32_t screen_scissor_h = + ((pa_sc_screen_scissor_br >> 16) & 0x7FFF) - screen_scissor_y; + ImGui::BulletText("Scissor: %d,%d to %d,%d (%d x %d)", screen_scissor_x, + screen_scissor_y, screen_scissor_x + screen_scissor_w, + screen_scissor_y + screen_scissor_h, screen_scissor_w, + screen_scissor_h); + } else { + ImGui::BulletText("Scissor: disabled"); + } + switch (pa_su_sc_mode_cntl & 0x3) { + case 0: + ImGui::BulletText("Culling: disabled"); + break; + case 1: + ImGui::BulletText("Culling: front-face"); + break; + case 2: + ImGui::BulletText("Culling: back-face"); + break; + } + if (pa_su_sc_mode_cntl & 0x4) { + ImGui::BulletText("Front-face: clockwise"); + } else { + ImGui::BulletText("Front-face: counter-clockwise"); + } + static const char* kFillModeNames[3] = { + "point", "line", "fill", + }; + bool poly_mode = ((pa_su_sc_mode_cntl >> 3) & 0x3) != 0; + if (poly_mode) { + uint32_t front_poly_mode = (pa_su_sc_mode_cntl >> 5) & 0x7; + uint32_t back_poly_mode = (pa_su_sc_mode_cntl >> 8) & 0x7; + // GL only supports both matching. + assert_true(front_poly_mode == back_poly_mode); + ImGui::BulletText("Polygon Mode: %s", kFillModeNames[front_poly_mode]); + } else { + ImGui::BulletText("Polygon Mode: fill"); + } + if (pa_su_sc_mode_cntl & (1 << 19)) { + ImGui::BulletText("Provoking Vertex: last"); + } else { + ImGui::BulletText("Provoking Vertex: first"); + } + ImGui::TreePop(); + } + ImGui::Columns(1); + + auto rb_surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32; + uint32_t surface_pitch = rb_surface_info & 0x3FFF; + auto surface_msaa = static_cast((rb_surface_info >> 16) & 0x3); + + if (ImGui::CollapsingHeader("Color Targets")) { + if (enable_mode != ModeControl::kDepth) { + // Alpha testing -- ALPHAREF, ALPHAFUNC, ALPHATESTENABLE + // if(ALPHATESTENABLE && frag_out.a [<=/ALPHAFUNC] ALPHAREF) discard; + uint32_t color_control = regs[XE_GPU_REG_RB_COLORCONTROL].u32; + if ((color_control & 0x4) != 0) { + ImGui::BulletText("Alpha Test: %s %.2f", + kCompareFuncNames[color_control & 0x7], + regs[XE_GPU_REG_RB_ALPHA_REF].f32); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::BulletText("Alpha Test: disabled"); + ImGui::PopStyleColor(); + } + + auto blend_color = ImVec4(regs[XE_GPU_REG_RB_BLEND_RED].f32, + regs[XE_GPU_REG_RB_BLEND_GREEN].f32, + regs[XE_GPU_REG_RB_BLEND_BLUE].f32, + regs[XE_GPU_REG_RB_BLEND_ALPHA].f32); + ImGui::BulletText("Blend Color: (%.2f,%.2f,%.2f,%.2f)", blend_color.x, + blend_color.y, blend_color.z, blend_color.w); + ImGui::SameLine(); + ImGui::ColorButton(blend_color, true); + + uint32_t rb_color_mask = regs[XE_GPU_REG_RB_COLOR_MASK].u32; + uint32_t color_info[4] = { + regs[XE_GPU_REG_RB_COLOR_INFO].u32, + regs[XE_GPU_REG_RB_COLOR1_INFO].u32, + regs[XE_GPU_REG_RB_COLOR2_INFO].u32, + regs[XE_GPU_REG_RB_COLOR3_INFO].u32, + }; + uint32_t rb_blendcontrol[4] = { + regs[XE_GPU_REG_RB_BLENDCONTROL_0].u32, + regs[XE_GPU_REG_RB_BLENDCONTROL_1].u32, + regs[XE_GPU_REG_RB_BLENDCONTROL_2].u32, + regs[XE_GPU_REG_RB_BLENDCONTROL_3].u32, + }; + ImGui::Columns(2); + for (int i = 0; i < xe::countof(color_info); ++i) { + uint32_t blend_control = rb_blendcontrol[i]; + // A2XX_RB_BLEND_CONTROL_COLOR_SRCBLEND + auto src_blend = (blend_control & 0x0000001F) >> 0; + // A2XX_RB_BLEND_CONTROL_COLOR_DESTBLEND + auto dest_blend = (blend_control & 0x00001F00) >> 8; + // A2XX_RB_BLEND_CONTROL_COLOR_COMB_FCN + auto blend_op = (blend_control & 0x000000E0) >> 5; + // A2XX_RB_BLEND_CONTROL_ALPHA_SRCBLEND + auto src_blend_alpha = (blend_control & 0x001F0000) >> 16; + // A2XX_RB_BLEND_CONTROL_ALPHA_DESTBLEND + auto dest_blend_alpha = (blend_control & 0x1F000000) >> 24; + // A2XX_RB_BLEND_CONTROL_ALPHA_COMB_FCN + auto blend_op_alpha = (blend_control & 0x00E00000) >> 21; + // A2XX_RB_COLORCONTROL_BLEND_DISABLE ?? Can't find this! + // Just guess based on actions. + bool blend_enable = !((src_blend == 1) && (dest_blend == 0) && + (blend_op == 0) && (src_blend_alpha == 1) && + (dest_blend_alpha == 0) && (blend_op_alpha == 0)); + if (blend_enable) { + if (src_blend == src_blend_alpha && dest_blend == dest_blend_alpha && + blend_op == blend_op_alpha) { + ImGui::BulletText("Blend %d: ", i); + ImGui::SameLine(); + DrawBlendMode(src_blend, dest_blend, blend_op); + } else { + ImGui::BulletText("Blend %d:", i); + ImGui::BulletText(" Color: "); + ImGui::SameLine(); + DrawBlendMode(src_blend, dest_blend, blend_op); + ImGui::BulletText(" Alpha: "); + ImGui::SameLine(); + DrawBlendMode(src_blend_alpha, dest_blend_alpha, blend_op_alpha); + } + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::BulletText("Blend %d: disabled", i); + ImGui::PopStyleColor(); + } + ImGui::NextColumn(); + uint32_t write_mask = (rb_color_mask >> (i * 4)) & 0xF; + if (write_mask) { + ImGui::BulletText("Write Mask %d: %s, %s, %s, %s", i, + !!(write_mask & 0x1) ? "true" : "false", + !!(write_mask & 0x2) ? "true" : "false", + !!(write_mask & 0x4) ? "true" : "false", + !!(write_mask & 0x8) ? "true" : "false"); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::BulletText("Write Mask %d: disabled", i); + ImGui::PopStyleColor(); + } + ImGui::NextColumn(); + } + ImGui::Columns(1); + + ImGui::Columns(4); + for (int i = 0; i < xe::countof(color_info); ++i) { + uint32_t write_mask = (rb_color_mask >> (i * 4)) & 0xF; + uint32_t color_base = color_info[i] & 0xFFF; + auto color_format = + static_cast((color_info[i] >> 16) & 0xF); + ImVec2 button_size(256, 256); + if (write_mask) { + auto color_target = GetColorRenderTarget(surface_pitch, surface_msaa, + color_base, color_format); + if (ImGui::ImageButton(ImTextureID(color_target), button_size, + ImVec2(0, 0), ImVec2(1, 1))) { + // show viewer + } + } else { + ImGui::ImageButton(ImTextureID(0), button_size, ImVec2(0, 0), + ImVec2(1, 1), -1, ImVec4(0, 0, 0, 0), + ImVec4(0, 0, 0, 0)); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Color Target %d (%s), base %.4X, pitch %d", i, + write_mask ? "enabled" : "disabled", color_base, + surface_pitch); + } + ImGui::NextColumn(); + } + ImGui::Columns(1); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::Text("Depth-only mode, no color targets"); + ImGui::PopStyleColor(); + } + } + + if (ImGui::CollapsingHeader("Depth/Stencil Target")) { + auto rb_depthcontrol = regs[XE_GPU_REG_RB_DEPTHCONTROL].u32; + auto rb_stencilrefmask = regs[XE_GPU_REG_RB_STENCILREFMASK].u32; + auto rb_depth_info = regs[XE_GPU_REG_RB_DEPTH_INFO].u32; + bool uses_depth = + (rb_depthcontrol & 0x00000002) || (rb_depthcontrol & 0x00000004); + uint32_t stencil_write_mask = (rb_stencilrefmask & 0x00FF0000) >> 16; + bool uses_stencil = + (rb_depthcontrol & 0x00000001) || (stencil_write_mask != 0); + + ImGui::Columns(2); + + if (rb_depthcontrol & 0x00000002) { + ImGui::BulletText("Depth Test: enabled"); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::BulletText("Depth Test: disabled"); + } + ImGui::BulletText("Depth Func: %s", + kCompareFuncNames[(rb_depthcontrol & 0x00000070) >> 4]); + if (!(rb_depthcontrol & 0x00000002)) { + ImGui::PopStyleColor(); + } + if (rb_depthcontrol & 0x00000004) { + ImGui::BulletText("Depth Write: enabled"); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::BulletText("Depth Write: disabled"); + ImGui::PopStyleColor(); + } + if (rb_depthcontrol & 0x00000001) { + ImGui::BulletText("Stencil Test: enabled"); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored); + ImGui::BulletText("Stencil Test: disabled"); + } + // TODO(benvanik): stencil stuff. + ImGui::BulletText("TODO: stencil stuff"); + if (!(rb_depthcontrol & 0x00000001)) { + ImGui::PopStyleColor(); + } + + ImGui::NextColumn(); + + if (uses_depth || uses_stencil) { + uint32_t depth_base = rb_depth_info & 0xFFF; + auto depth_format = + static_cast((rb_depth_info >> 16) & 0x1); + auto depth_target = GetDepthRenderTarget(surface_pitch, surface_msaa, + depth_base, depth_format); + ImVec2 button_size(256, 256); + if (ImGui::ImageButton(ImTextureID(depth_target), button_size, + ImVec2(0, 0), ImVec2(1, 1))) { + // show viewer + } + } else { + ImGui::Text("No depth target"); + } + + ImGui::Columns(1); + } + + if (ImGui::CollapsingHeader("Vertex Shader")) { + ShaderDisplayType shader_display_type = DrawShaderTypeUI(); + ImGui::BeginChild("#vertex_shader_text", ImVec2(0, 400)); + auto shader = command_processor->active_vertex_shader(); + if (shader) { + DrawShaderUI(shader, shader_display_type); + } else { + ImGui::TextColored(kColorError, "ERROR: no vertex shader set"); + } + ImGui::EndChild(); + } + if (ImGui::CollapsingHeader("Pixel Shader")) { + ShaderDisplayType shader_display_type = DrawShaderTypeUI(); + ImGui::BeginChild("#pixel_shader_text", ImVec2(0, 400)); + auto shader = command_processor->active_pixel_shader(); + if (shader) { + DrawShaderUI(shader, shader_display_type); + } else { + ImGui::TextColored(kColorError, "ERROR: no pixel shader set"); + } + ImGui::EndChild(); + } + if (ImGui::CollapsingHeader("Index Buffer")) { + if (draw_info.is_auto_index) { + ImGui::Text("%d indices, auto-indexed", draw_info.index_count); + } else { + ImGui::Text("%d indices from buffer %.8X (%db), %s, %s", + draw_info.index_count, draw_info.index_buffer_ptr, + draw_info.index_buffer_size, + kIndexFormatNames[int(draw_info.index_format)], + kEndiannessNames[int(draw_info.index_endianness)]); + uint32_t pa_su_sc_mode_cntl = regs[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32; + if (pa_su_sc_mode_cntl & (1 << 21)) { + uint32_t reset_index = + regs[XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX].u32; + if (draw_info.index_format == IndexFormat::kInt16) { + ImGui::Text("Reset Index: %.4X", reset_index & 0xFFFF); + } else { + ImGui::Text("Reset Index: %.8X", reset_index); + } + } else { + ImGui::Text("Reset Index: disabled"); + } + ImGui::BeginChild("#indices", ImVec2(0, 300)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + int display_start, display_end; + ImGui::CalcListClipping(1 + draw_info.index_count, + ImGui::GetTextLineHeight(), &display_start, + &display_end); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + + (display_start)*ImGui::GetTextLineHeight()); + ImGui::Columns(2, "#indices", true); + ImGui::SetColumnOffset(1, 60); + if (display_start <= 1) { + ImGui::Text("Ordinal"); + ImGui::NextColumn(); + ImGui::Text(" Value"); + ImGui::NextColumn(); + ImGui::Separator(); + } + uint32_t element_size = + draw_info.index_format == IndexFormat::kInt32 ? 4 : 2; + const uint8_t* data_ptr = memory_->TranslatePhysical( + draw_info.index_buffer_ptr + (display_start * element_size)); + for (int i = display_start; i < display_end; + ++i, data_ptr += element_size) { + if (i < 10) { + ImGui::Text(" %d", i); + } else if (i < 100) { + ImGui::Text(" %d", i); + } else if (i < 1000) { + ImGui::Text(" %d", i); + } else { + ImGui::Text(" %d", i); + } + ImGui::NextColumn(); + uint32_t value = element_size == 4 + ? GpuSwap(xe::load(data_ptr), + draw_info.index_endianness) + : GpuSwap(xe::load(data_ptr), + draw_info.index_endianness); + ImGui::Text(" %d", value); + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + + (draw_info.index_count - display_end) * + ImGui::GetTextLineHeight()); + ImGui::PopStyleVar(); + ImGui::EndChild(); + } + } + if (ImGui::CollapsingHeader("Vertex Buffers")) { + auto shader = command_processor->active_vertex_shader(); + if (shader) { + const auto& buffer_inputs = shader->buffer_inputs(); + for (uint32_t buffer_index = 0; buffer_index < buffer_inputs.count; + ++buffer_index) { + const auto& desc = buffer_inputs.descs[buffer_index]; + int r = + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + (desc.fetch_slot / 3) * 6; + const auto group = + reinterpret_cast(®s.values[r]); + const xe_gpu_vertex_fetch_t* fetch = nullptr; + switch (desc.fetch_slot % 3) { + case 0: + fetch = &group->vertex_fetch_0; + break; + case 1: + fetch = &group->vertex_fetch_1; + break; + case 2: + fetch = &group->vertex_fetch_2; + break; + } + assert_true(fetch->endian == 2); + char tree_root_id[32]; + sprintf(tree_root_id, "#vertices_root_%d", desc.fetch_slot); + if (ImGui::TreeNode(tree_root_id, "vf%d: 0x%.8X (%db), %s", + desc.fetch_slot, fetch->address << 2, + fetch->size * 4, + kEndiannessNames[int(fetch->endian)])) { + ImGui::BeginChild("#vertices", ImVec2(0, 300)); + DrawVertexFetcher(shader, desc, fetch); + ImGui::EndChild(); + ImGui::TreePop(); + } + } + } else { + ImGui::TextColored(kColorError, "ERROR: no vertex shader set"); + } + } + if (ImGui::CollapsingHeader("Vertex Textures")) { + auto shader = command_processor->active_vertex_shader(); + if (shader) { + const auto& sampler_inputs = shader->sampler_inputs(); + if (sampler_inputs.count) { + for (size_t i = 0; i < sampler_inputs.count; ++i) { + DrawTextureInfo(sampler_inputs.descs[i]); + } + } else { + ImGui::Text("No vertex shader samplers"); + } + } else { + ImGui::TextColored(kColorError, "ERROR: no vertex shader set"); + } + } + if (ImGui::CollapsingHeader("Textures")) { + auto shader = command_processor->active_pixel_shader(); + if (shader) { + const auto& sampler_inputs = shader->sampler_inputs(); + if (sampler_inputs.count) { + for (size_t i = 0; i < sampler_inputs.count; ++i) { + DrawTextureInfo(sampler_inputs.descs[i]); + } + } else { + ImGui::Text("No pixel shader samplers"); + } + } else { + ImGui::TextColored(kColorError, "ERROR: no pixel shader set"); + } + } + if (ImGui::CollapsingHeader("Fetch Constants (raw)")) { + ImGui::Columns(2); + ImGui::SetColumnOffset(1, 85.0f); + for (int i = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0; + i <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5; ++i) { + ImGui::Text("f%02d_%d", (i - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) / 6, + (i - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) % 6); + ImGui::NextColumn(); + ImGui::Text("%.8X", regs[i].u32); + ImGui::NextColumn(); + } + ImGui::Columns(1); + } + if (ImGui::CollapsingHeader("ALU Constants")) { + ImGui::Columns(2); + for (int i = XE_GPU_REG_SHADER_CONSTANT_000_X; + i <= XE_GPU_REG_SHADER_CONSTANT_511_X; i += 4) { + ImGui::Text("c%d", (i - XE_GPU_REG_SHADER_CONSTANT_000_X) / 4); + ImGui::NextColumn(); + ImGui::Text("%f, %f, %f, %f", regs[i + 0].f32, regs[i + 1].f32, + regs[i + 2].f32, regs[i + 3].f32); + ImGui::NextColumn(); + } + ImGui::Columns(1); + } + if (ImGui::CollapsingHeader("Bool Constants")) { + ImGui::Columns(2); + for (int i = XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031; + i <= XE_GPU_REG_SHADER_CONSTANT_BOOL_224_255; ++i) { + ImGui::Text("b%03d-%03d", + (i - XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031) * 32, + (i - XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031) * 32 + 31); + ImGui::NextColumn(); + ImGui::Text("%.8X", regs[i].u32); + ImGui::NextColumn(); + } + ImGui::Columns(1); + } + if (ImGui::CollapsingHeader("Loop Constants")) { + ImGui::Columns(2); + for (int i = XE_GPU_REG_SHADER_CONSTANT_LOOP_00; + i <= XE_GPU_REG_SHADER_CONSTANT_LOOP_31; ++i) { + ImGui::Text("l%d", i - XE_GPU_REG_SHADER_CONSTANT_LOOP_00); + ImGui::NextColumn(); + ImGui::Text("%.8X", regs[i].u32); + ImGui::NextColumn(); + } + ImGui::Columns(1); + } + ImGui::End(); +} + +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/trace_viewer.h b/src/xenia/gpu/trace_viewer.h new file mode 100644 index 000000000..eaa298df3 --- /dev/null +++ b/src/xenia/gpu/trace_viewer.h @@ -0,0 +1,97 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_TRACE_VIEWER_H_ +#define XENIA_GPU_TRACE_VIEWER_H_ + +#include + +#include "xenia/emulator.h" +#include "xenia/gpu/shader.h" +#include "xenia/gpu/trace_player.h" +#include "xenia/gpu/trace_protocol.h" +#include "xenia/gpu/xenos.h" +#include "xenia/memory.h" + +namespace xe { +namespace ui { +class ImGuiDrawer; +class Loop; +class Window; +} // namespace ui +} // namespace xe + +namespace xe { +namespace gpu { + +struct SamplerInfo; +struct TextureInfo; + +class TraceViewer { + public: + virtual ~TraceViewer(); + + bool Setup(); + bool Load(std::wstring trace_file_path); + void Run(); + + protected: + TraceViewer(); + + void DrawMultilineString(const std::string& str); + + virtual uintptr_t GetColorRenderTarget( + uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, + xenos::ColorRenderTargetFormat format) = 0; + virtual uintptr_t GetDepthRenderTarget( + uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, + xenos::DepthRenderTargetFormat format) = 0; + virtual uintptr_t GetTextureEntry(const TextureInfo& texture_info, + const SamplerInfo& sampler_info) = 0; + + std::unique_ptr loop_; + std::unique_ptr window_; + std::unique_ptr emulator_; + Memory* memory_ = nullptr; + GraphicsSystem* graphics_system_ = nullptr; + std::unique_ptr player_; + + std::unique_ptr imgui_drawer_; + + private: + enum class ShaderDisplayType : int { + kUcode, + kTranslated, + kHostDisasm, + }; + + void DrawUI(); + void DrawControllerUI(); + void DrawPacketDisassemblerUI(); + void DrawCommandListUI(); + void DrawStateUI(); + + ShaderDisplayType DrawShaderTypeUI(); + void DrawShaderUI(Shader* shader, ShaderDisplayType display_type); + + void DrawBlendMode(uint32_t src_blend, uint32_t dest_blend, + uint32_t blend_op); + + void DrawTextureInfo(const Shader::SamplerDesc& desc); + void DrawFailedTextureInfo(const Shader::SamplerDesc& desc, + const char* message); + + void DrawVertexFetcher(Shader* shader, const Shader::BufferDesc& desc, + const xenos::xe_gpu_vertex_fetch_t* fetch); +}; + +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_TRACE_VIEWER_H_