diff options
| author | 2018-03-25 16:28:24 -0400 | |
|---|---|---|
| committer | 2018-03-25 16:28:24 -0400 | |
| commit | e9315ace9f7f541d251a995ff2d4d3513ddc16c4 (patch) | |
| tree | ba32de7358ed98e1230c1f522a5c7ba35d7ab19e /src | |
| parent | Merge pull request #281 from mailwl/sockets-services (diff) | |
| parent | GPU: Make the debug_context variable a member of the frontend instead of a gl... (diff) | |
| download | yuzu-e9315ace9f7f541d251a995ff2d4d3513ddc16c4.tar.gz yuzu-e9315ace9f7f541d251a995ff2d4d3513ddc16c4.tar.xz yuzu-e9315ace9f7f541d251a995ff2d4d3513ddc16c4.zip | |
Merge pull request #273 from Subv/textures
GPU: Added code to unswizzle textures and ported the surface viewer from citra
Diffstat (limited to 'src')
21 files changed, 1464 insertions, 10 deletions
diff --git a/src/core/core.h b/src/core/core.h index 552c8f5ee..ade456cfc 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "core/memory.h" | 13 | #include "core/memory.h" |
| 14 | #include "core/perf_stats.h" | 14 | #include "core/perf_stats.h" |
| 15 | #include "core/telemetry_session.h" | 15 | #include "core/telemetry_session.h" |
| 16 | #include "video_core/debug_utils/debug_utils.h" | ||
| 16 | #include "video_core/gpu.h" | 17 | #include "video_core/gpu.h" |
| 17 | 18 | ||
| 18 | class EmuWindow; | 19 | class EmuWindow; |
| @@ -135,6 +136,14 @@ public: | |||
| 135 | return *app_loader; | 136 | return *app_loader; |
| 136 | } | 137 | } |
| 137 | 138 | ||
| 139 | void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { | ||
| 140 | debug_context = std::move(context); | ||
| 141 | } | ||
| 142 | |||
| 143 | std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const { | ||
| 144 | return debug_context; | ||
| 145 | } | ||
| 146 | |||
| 138 | private: | 147 | private: |
| 139 | /** | 148 | /** |
| 140 | * Initialize the emulated system. | 149 | * Initialize the emulated system. |
| @@ -154,6 +163,8 @@ private: | |||
| 154 | std::unique_ptr<Kernel::Scheduler> scheduler; | 163 | std::unique_ptr<Kernel::Scheduler> scheduler; |
| 155 | std::unique_ptr<Tegra::GPU> gpu_core; | 164 | std::unique_ptr<Tegra::GPU> gpu_core; |
| 156 | 165 | ||
| 166 | std::shared_ptr<Tegra::DebugContext> debug_context; | ||
| 167 | |||
| 157 | Kernel::SharedPtr<Kernel::Process> current_process; | 168 | Kernel::SharedPtr<Kernel::Process> current_process; |
| 158 | 169 | ||
| 159 | /// When true, signals that a reschedule should happen | 170 | /// When true, signals that a reschedule should happen |
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index e56253c4c..3dab81769 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -1,6 +1,8 @@ | |||
| 1 | add_library(video_core STATIC | 1 | add_library(video_core STATIC |
| 2 | command_processor.cpp | 2 | command_processor.cpp |
| 3 | command_processor.h | 3 | command_processor.h |
| 4 | debug_utils/debug_utils.cpp | ||
| 5 | debug_utils/debug_utils.h | ||
| 4 | engines/fermi_2d.cpp | 6 | engines/fermi_2d.cpp |
| 5 | engines/fermi_2d.h | 7 | engines/fermi_2d.h |
| 6 | engines/maxwell_3d.cpp | 8 | engines/maxwell_3d.cpp |
| @@ -31,6 +33,9 @@ add_library(video_core STATIC | |||
| 31 | renderer_opengl/gl_stream_buffer.h | 33 | renderer_opengl/gl_stream_buffer.h |
| 32 | renderer_opengl/renderer_opengl.cpp | 34 | renderer_opengl/renderer_opengl.cpp |
| 33 | renderer_opengl/renderer_opengl.h | 35 | renderer_opengl/renderer_opengl.h |
| 36 | textures/decoders.cpp | ||
| 37 | textures/decoders.h | ||
| 38 | textures/texture.h | ||
| 34 | utils.h | 39 | utils.h |
| 35 | video_core.cpp | 40 | video_core.cpp |
| 36 | video_core.h | 41 | video_core.h |
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp new file mode 100644 index 000000000..22d44aab2 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.cpp | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <condition_variable> | ||
| 7 | #include <cstdint> | ||
| 8 | #include <cstring> | ||
| 9 | #include <fstream> | ||
| 10 | #include <map> | ||
| 11 | #include <mutex> | ||
| 12 | #include <string> | ||
| 13 | |||
| 14 | #include "common/assert.h" | ||
| 15 | #include "common/bit_field.h" | ||
| 16 | #include "common/color.h" | ||
| 17 | #include "common/common_types.h" | ||
| 18 | #include "common/file_util.h" | ||
| 19 | #include "common/logging/log.h" | ||
| 20 | #include "common/math_util.h" | ||
| 21 | #include "common/vector_math.h" | ||
| 22 | #include "video_core/debug_utils/debug_utils.h" | ||
| 23 | |||
| 24 | namespace Tegra { | ||
| 25 | |||
| 26 | void DebugContext::DoOnEvent(Event event, void* data) { | ||
| 27 | { | ||
| 28 | std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||
| 29 | |||
| 30 | // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will | ||
| 31 | // show on debug widgets | ||
| 32 | |||
| 33 | // TODO: Should stop the CPU thread here once we multithread emulation. | ||
| 34 | |||
| 35 | active_breakpoint = event; | ||
| 36 | at_breakpoint = true; | ||
| 37 | |||
| 38 | // Tell all observers that we hit a breakpoint | ||
| 39 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 40 | breakpoint_observer->OnMaxwellBreakPointHit(event, data); | ||
| 41 | } | ||
| 42 | |||
| 43 | // Wait until another thread tells us to Resume() | ||
| 44 | resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | void DebugContext::Resume() { | ||
| 49 | { | ||
| 50 | std::lock_guard<std::mutex> lock(breakpoint_mutex); | ||
| 51 | |||
| 52 | // Tell all observers that we are about to resume | ||
| 53 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 54 | breakpoint_observer->OnMaxwellResume(); | ||
| 55 | } | ||
| 56 | |||
| 57 | // Resume the waiting thread (i.e. OnEvent()) | ||
| 58 | at_breakpoint = false; | ||
| 59 | } | ||
| 60 | |||
| 61 | resume_from_breakpoint.notify_one(); | ||
| 62 | } | ||
| 63 | |||
| 64 | } // namespace Tegra | ||
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h new file mode 100644 index 000000000..bbba8e380 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.h | |||
| @@ -0,0 +1,163 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <algorithm> | ||
| 8 | #include <array> | ||
| 9 | #include <condition_variable> | ||
| 10 | #include <iterator> | ||
| 11 | #include <list> | ||
| 12 | #include <map> | ||
| 13 | #include <memory> | ||
| 14 | #include <mutex> | ||
| 15 | #include <string> | ||
| 16 | #include <utility> | ||
| 17 | #include <vector> | ||
| 18 | #include "common/common_types.h" | ||
| 19 | #include "common/vector_math.h" | ||
| 20 | |||
| 21 | namespace Tegra { | ||
| 22 | |||
| 23 | class DebugContext { | ||
| 24 | public: | ||
| 25 | enum class Event { | ||
| 26 | FirstEvent = 0, | ||
| 27 | |||
| 28 | MaxwellCommandLoaded = FirstEvent, | ||
| 29 | MaxwellCommandProcessed, | ||
| 30 | IncomingPrimitiveBatch, | ||
| 31 | FinishedPrimitiveBatch, | ||
| 32 | |||
| 33 | NumEvents | ||
| 34 | }; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Inherit from this class to be notified of events registered to some debug context. | ||
| 38 | * Most importantly this is used for our debugger GUI. | ||
| 39 | * | ||
| 40 | * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods. | ||
| 41 | * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state | ||
| 42 | * access | ||
| 43 | * @todo Evaluate an alternative interface, in which there is only one managing observer and | ||
| 44 | * multiple child observers running (by design) on the same thread. | ||
| 45 | */ | ||
| 46 | class BreakPointObserver { | ||
| 47 | public: | ||
| 48 | /// Constructs the object such that it observes events of the given DebugContext. | ||
| 49 | BreakPointObserver(std::shared_ptr<DebugContext> debug_context) | ||
| 50 | : context_weak(debug_context) { | ||
| 51 | std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); | ||
| 52 | debug_context->breakpoint_observers.push_back(this); | ||
| 53 | } | ||
| 54 | |||
| 55 | virtual ~BreakPointObserver() { | ||
| 56 | auto context = context_weak.lock(); | ||
| 57 | if (context) { | ||
| 58 | std::unique_lock<std::mutex> lock(context->breakpoint_mutex); | ||
| 59 | context->breakpoint_observers.remove(this); | ||
| 60 | |||
| 61 | // If we are the last observer to be destroyed, tell the debugger context that | ||
| 62 | // it is free to continue. In particular, this is required for a proper yuzu | ||
| 63 | // shutdown, when the emulation thread is waiting at a breakpoint. | ||
| 64 | if (context->breakpoint_observers.empty()) | ||
| 65 | context->Resume(); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | /** | ||
| 70 | * Action to perform when a breakpoint was reached. | ||
| 71 | * @param event Type of event which triggered the breakpoint | ||
| 72 | * @param data Optional data pointer (if unused, this is a nullptr) | ||
| 73 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 74 | */ | ||
| 75 | virtual void OnMaxwellBreakPointHit(Event event, void* data) {} | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Action to perform when emulation is resumed from a breakpoint. | ||
| 79 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 80 | */ | ||
| 81 | virtual void OnMaxwellResume() {} | ||
| 82 | |||
| 83 | protected: | ||
| 84 | /** | ||
| 85 | * Weak context pointer. This need not be valid, so when requesting a shared_ptr via | ||
| 86 | * context_weak.lock(), always compare the result against nullptr. | ||
| 87 | */ | ||
| 88 | std::weak_ptr<DebugContext> context_weak; | ||
| 89 | }; | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Simple structure defining a breakpoint state | ||
| 93 | */ | ||
| 94 | struct BreakPoint { | ||
| 95 | bool enabled = false; | ||
| 96 | }; | ||
| 97 | |||
| 98 | /** | ||
| 99 | * Static constructor used to create a shared_ptr of a DebugContext. | ||
| 100 | */ | ||
| 101 | static std::shared_ptr<DebugContext> Construct() { | ||
| 102 | return std::shared_ptr<DebugContext>(new DebugContext); | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Used by the emulation core when a given event has happened. If a breakpoint has been set | ||
| 107 | * for this event, OnEvent calls the event handlers of the registered breakpoint observers. | ||
| 108 | * The current thread then is halted until Resume() is called from another thread (or until | ||
| 109 | * emulation is stopped). | ||
| 110 | * @param event Event which has happened | ||
| 111 | * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until | ||
| 112 | * Resume() is called. | ||
| 113 | */ | ||
| 114 | void OnEvent(Event event, void* data) { | ||
| 115 | // This check is left in the header to allow the compiler to inline it. | ||
| 116 | if (!breakpoints[(int)event].enabled) | ||
| 117 | return; | ||
| 118 | // For the rest of event handling, call a separate function. | ||
| 119 | DoOnEvent(event, data); | ||
| 120 | } | ||
| 121 | |||
| 122 | void DoOnEvent(Event event, void* data); | ||
| 123 | |||
| 124 | /** | ||
| 125 | * Resume from the current breakpoint. | ||
| 126 | * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. | ||
| 127 | * Calling from any other thread is safe. | ||
| 128 | */ | ||
| 129 | void Resume(); | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Delete all set breakpoints and resume emulation. | ||
| 133 | */ | ||
| 134 | void ClearBreakpoints() { | ||
| 135 | for (auto& bp : breakpoints) { | ||
| 136 | bp.enabled = false; | ||
| 137 | } | ||
| 138 | Resume(); | ||
| 139 | } | ||
| 140 | |||
| 141 | // TODO: Evaluate if access to these members should be hidden behind a public interface. | ||
| 142 | std::array<BreakPoint, (int)Event::NumEvents> breakpoints; | ||
| 143 | Event active_breakpoint; | ||
| 144 | bool at_breakpoint = false; | ||
| 145 | |||
| 146 | private: | ||
| 147 | /** | ||
| 148 | * Private default constructor to make sure people always construct this through Construct() | ||
| 149 | * instead. | ||
| 150 | */ | ||
| 151 | DebugContext() = default; | ||
| 152 | |||
| 153 | /// Mutex protecting current breakpoint state and the observer list. | ||
| 154 | std::mutex breakpoint_mutex; | ||
| 155 | |||
| 156 | /// Used by OnEvent to wait for resumption. | ||
| 157 | std::condition_variable resume_from_breakpoint; | ||
| 158 | |||
| 159 | /// List of registered observers | ||
| 160 | std::list<BreakPointObserver*> breakpoint_observers; | ||
| 161 | }; | ||
| 162 | |||
| 163 | } // namespace Tegra | ||
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 4d9745e48..986165c6d 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -2,8 +2,13 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <cinttypes> | ||
| 5 | #include "common/assert.h" | 6 | #include "common/assert.h" |
| 7 | #include "core/core.h" | ||
| 8 | #include "video_core/debug_utils/debug_utils.h" | ||
| 6 | #include "video_core/engines/maxwell_3d.h" | 9 | #include "video_core/engines/maxwell_3d.h" |
| 10 | #include "video_core/textures/decoders.h" | ||
| 11 | #include "video_core/textures/texture.h" | ||
| 7 | 12 | ||
| 8 | namespace Tegra { | 13 | namespace Tegra { |
| 9 | namespace Engines { | 14 | namespace Engines { |
| @@ -46,6 +51,8 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { | |||
| 46 | ASSERT_MSG(method < Regs::NUM_REGS, | 51 | ASSERT_MSG(method < Regs::NUM_REGS, |
| 47 | "Invalid Maxwell3D register, increase the size of the Regs structure"); | 52 | "Invalid Maxwell3D register, increase the size of the Regs structure"); |
| 48 | 53 | ||
| 54 | auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); | ||
| 55 | |||
| 49 | // It is an error to write to a register other than the current macro's ARG register before it | 56 | // It is an error to write to a register other than the current macro's ARG register before it |
| 50 | // has finished execution. | 57 | // has finished execution. |
| 51 | if (executing_macro != 0) { | 58 | if (executing_macro != 0) { |
| @@ -72,6 +79,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { | |||
| 72 | return; | 79 | return; |
| 73 | } | 80 | } |
| 74 | 81 | ||
| 82 | if (debug_context) { | ||
| 83 | debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); | ||
| 84 | } | ||
| 85 | |||
| 75 | regs.reg_array[method] = value; | 86 | regs.reg_array[method] = value; |
| 76 | 87 | ||
| 77 | #define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32)) | 88 | #define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32)) |
| @@ -137,6 +148,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { | |||
| 137 | } | 148 | } |
| 138 | 149 | ||
| 139 | #undef MAXWELL3D_REG_INDEX | 150 | #undef MAXWELL3D_REG_INDEX |
| 151 | |||
| 152 | if (debug_context) { | ||
| 153 | debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr); | ||
| 154 | } | ||
| 140 | } | 155 | } |
| 141 | 156 | ||
| 142 | void Maxwell3D::ProcessQueryGet() { | 157 | void Maxwell3D::ProcessQueryGet() { |
| @@ -160,6 +175,15 @@ void Maxwell3D::ProcessQueryGet() { | |||
| 160 | 175 | ||
| 161 | void Maxwell3D::DrawArrays() { | 176 | void Maxwell3D::DrawArrays() { |
| 162 | LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); | 177 | LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); |
| 178 | auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); | ||
| 179 | |||
| 180 | if (debug_context) { | ||
| 181 | debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); | ||
| 182 | } | ||
| 183 | |||
| 184 | if (debug_context) { | ||
| 185 | debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||
| 186 | } | ||
| 163 | } | 187 | } |
| 164 | 188 | ||
| 165 | void Maxwell3D::BindTextureInfoBuffer(const std::vector<u32>& parameters) { | 189 | void Maxwell3D::BindTextureInfoBuffer(const std::vector<u32>& parameters) { |
| @@ -270,5 +294,50 @@ void Maxwell3D::ProcessCBData(u32 value) { | |||
| 270 | regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; | 294 | regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; |
| 271 | } | 295 | } |
| 272 | 296 | ||
| 297 | std::vector<Texture::TICEntry> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) { | ||
| 298 | std::vector<Texture::TICEntry> textures; | ||
| 299 | |||
| 300 | auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)]; | ||
| 301 | auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; | ||
| 302 | ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); | ||
| 303 | |||
| 304 | GPUVAddr tic_base_address = regs.tic.TICAddress(); | ||
| 305 | |||
| 306 | GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; | ||
| 307 | |||
| 308 | // Offset into the texture constbuffer where the texture info begins. | ||
| 309 | static constexpr size_t TextureInfoOffset = 0x20; | ||
| 310 | |||
| 311 | for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; | ||
| 312 | current_texture < tex_info_buffer_end; current_texture += 4) { | ||
| 313 | |||
| 314 | Texture::TextureHandle tex_info{ | ||
| 315 | Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))}; | ||
| 316 | |||
| 317 | if (tex_info.tic_id != 0 || tex_info.tsc_id != 0) { | ||
| 318 | GPUVAddr tic_address_gpu = | ||
| 319 | tic_base_address + tex_info.tic_id * sizeof(Texture::TICEntry); | ||
| 320 | VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu); | ||
| 321 | |||
| 322 | Texture::TICEntry tic_entry; | ||
| 323 | Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); | ||
| 324 | |||
| 325 | auto r_type = tic_entry.r_type.Value(); | ||
| 326 | auto g_type = tic_entry.g_type.Value(); | ||
| 327 | auto b_type = tic_entry.b_type.Value(); | ||
| 328 | auto a_type = tic_entry.a_type.Value(); | ||
| 329 | |||
| 330 | // TODO(Subv): Different data types for separate components are not supported | ||
| 331 | ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); | ||
| 332 | |||
| 333 | auto format = tic_entry.format.Value(); | ||
| 334 | |||
| 335 | textures.push_back(tic_entry); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | return textures; | ||
| 340 | } | ||
| 341 | |||
| 273 | } // namespace Engines | 342 | } // namespace Engines |
| 274 | } // namespace Tegra | 343 | } // namespace Tegra |
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 545d7ff35..441cc0c19 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "video_core/memory_manager.h" | 14 | #include "video_core/memory_manager.h" |
| 15 | #include "video_core/textures/texture.h" | ||
| 15 | 16 | ||
| 16 | namespace Tegra { | 17 | namespace Tegra { |
| 17 | namespace Engines { | 18 | namespace Engines { |
| @@ -21,12 +22,6 @@ public: | |||
| 21 | explicit Maxwell3D(MemoryManager& memory_manager); | 22 | explicit Maxwell3D(MemoryManager& memory_manager); |
| 22 | ~Maxwell3D() = default; | 23 | ~Maxwell3D() = default; |
| 23 | 24 | ||
| 24 | /// Write the value to the register identified by method. | ||
| 25 | void WriteReg(u32 method, u32 value, u32 remaining_params); | ||
| 26 | |||
| 27 | /// Uploads the code for a GPU macro program associated with the specified entry. | ||
| 28 | void SubmitMacroCode(u32 entry, std::vector<u32> code); | ||
| 29 | |||
| 30 | /// Register structure of the Maxwell3D engine. | 25 | /// Register structure of the Maxwell3D engine. |
| 31 | /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. | 26 | /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. |
| 32 | struct Regs { | 27 | struct Regs { |
| @@ -430,6 +425,15 @@ public: | |||
| 430 | 425 | ||
| 431 | State state{}; | 426 | State state{}; |
| 432 | 427 | ||
| 428 | /// Write the value to the register identified by method. | ||
| 429 | void WriteReg(u32 method, u32 value, u32 remaining_params); | ||
| 430 | |||
| 431 | /// Uploads the code for a GPU macro program associated with the specified entry. | ||
| 432 | void SubmitMacroCode(u32 entry, std::vector<u32> code); | ||
| 433 | |||
| 434 | /// Returns a list of enabled textures for the specified shader stage. | ||
| 435 | std::vector<Texture::TICEntry> GetStageTextures(Regs::ShaderStage stage); | ||
| 436 | |||
| 433 | private: | 437 | private: |
| 434 | MemoryManager& memory_manager; | 438 | MemoryManager& memory_manager; |
| 435 | 439 | ||
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index c384d236e..9463cd5d6 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp | |||
| @@ -18,4 +18,8 @@ GPU::GPU() { | |||
| 18 | 18 | ||
| 19 | GPU::~GPU() = default; | 19 | GPU::~GPU() = default; |
| 20 | 20 | ||
| 21 | const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const { | ||
| 22 | return *maxwell_3d; | ||
| 23 | } | ||
| 24 | |||
| 21 | } // namespace Tegra | 25 | } // namespace Tegra |
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 206b3e05e..8183b12e9 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h | |||
| @@ -13,6 +13,12 @@ | |||
| 13 | 13 | ||
| 14 | namespace Tegra { | 14 | namespace Tegra { |
| 15 | 15 | ||
| 16 | enum class RenderTargetFormat { | ||
| 17 | RGBA8_UNORM = 0xD5, | ||
| 18 | }; | ||
| 19 | |||
| 20 | class DebugContext; | ||
| 21 | |||
| 16 | /** | 22 | /** |
| 17 | * Struct describing framebuffer configuration | 23 | * Struct describing framebuffer configuration |
| 18 | */ | 24 | */ |
| @@ -66,6 +72,9 @@ public: | |||
| 66 | /// Processes a command list stored at the specified address in GPU memory. | 72 | /// Processes a command list stored at the specified address in GPU memory. |
| 67 | void ProcessCommandList(GPUVAddr address, u32 size); | 73 | void ProcessCommandList(GPUVAddr address, u32 size); |
| 68 | 74 | ||
| 75 | /// Returns a reference to the Maxwell3D GPU engine. | ||
| 76 | const Engines::Maxwell3D& Get3DEngine() const; | ||
| 77 | |||
| 69 | std::unique_ptr<MemoryManager> memory_manager; | 78 | std::unique_ptr<MemoryManager> memory_manager; |
| 70 | 79 | ||
| 71 | Engines::Maxwell3D& Maxwell3D() { | 80 | Engines::Maxwell3D& Maxwell3D() { |
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp new file mode 100644 index 000000000..2e87281eb --- /dev/null +++ b/src/video_core/textures/decoders.cpp | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <cstring> | ||
| 6 | #include "common/assert.h" | ||
| 7 | #include "video_core/textures/decoders.h" | ||
| 8 | #include "video_core/textures/texture.h" | ||
| 9 | |||
| 10 | namespace Tegra { | ||
| 11 | namespace Texture { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Calculates the offset of an (x, y) position within a swizzled texture. | ||
| 15 | * Taken from the Tegra X1 TRM. | ||
| 16 | */ | ||
| 17 | static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { | ||
| 18 | u32 image_width_in_gobs = image_width * bytes_per_pixel / 64; | ||
| 19 | u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + | ||
| 20 | (x * bytes_per_pixel / 64) * 512 * block_height + | ||
| 21 | (y % (8 * block_height) / 8) * 512; | ||
| 22 | x *= bytes_per_pixel; | ||
| 23 | u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + | ||
| 24 | (y % 2) * 16 + (x % 16); | ||
| 25 | |||
| 26 | return address; | ||
| 27 | } | ||
| 28 | |||
| 29 | static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, | ||
| 30 | u8* swizzled_data, u8* unswizzled_data, bool unswizzle, | ||
| 31 | u32 block_height) { | ||
| 32 | u8* data_ptrs[2]; | ||
| 33 | for (unsigned y = 0; y < height; ++y) { | ||
| 34 | for (unsigned x = 0; x < width; ++x) { | ||
| 35 | u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height); | ||
| 36 | u32 pixel_index = (x + y * width) * out_bytes_per_pixel; | ||
| 37 | |||
| 38 | data_ptrs[unswizzle] = swizzled_data + swizzle_offset; | ||
| 39 | data_ptrs[!unswizzle] = &unswizzled_data[pixel_index]; | ||
| 40 | |||
| 41 | std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | u32 BytesPerPixel(TextureFormat format) { | ||
| 47 | switch (format) { | ||
| 48 | case TextureFormat::DXT1: | ||
| 49 | // In this case a 'pixel' actually refers to a 4x4 tile. | ||
| 50 | return 8; | ||
| 51 | case TextureFormat::A8R8G8B8: | ||
| 52 | return 4; | ||
| 53 | default: | ||
| 54 | UNIMPLEMENTED_MSG("Format not implemented"); | ||
| 55 | break; | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) { | ||
| 60 | u8* data = Memory::GetPointer(address); | ||
| 61 | u32 bytes_per_pixel = BytesPerPixel(format); | ||
| 62 | |||
| 63 | static constexpr u32 DefaultBlockHeight = 16; | ||
| 64 | |||
| 65 | std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); | ||
| 66 | |||
| 67 | switch (format) { | ||
| 68 | case TextureFormat::DXT1: | ||
| 69 | // In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values. | ||
| 70 | CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, | ||
| 71 | unswizzled_data.data(), true, DefaultBlockHeight); | ||
| 72 | break; | ||
| 73 | case TextureFormat::A8R8G8B8: | ||
| 74 | CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, | ||
| 75 | unswizzled_data.data(), true, DefaultBlockHeight); | ||
| 76 | break; | ||
| 77 | default: | ||
| 78 | UNIMPLEMENTED_MSG("Format not implemented"); | ||
| 79 | break; | ||
| 80 | } | ||
| 81 | |||
| 82 | return unswizzled_data; | ||
| 83 | } | ||
| 84 | |||
| 85 | std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, | ||
| 86 | u32 height) { | ||
| 87 | std::vector<u8> rgba_data; | ||
| 88 | |||
| 89 | // TODO(Subv): Implement. | ||
| 90 | switch (format) { | ||
| 91 | case TextureFormat::DXT1: | ||
| 92 | case TextureFormat::A8R8G8B8: | ||
| 93 | // TODO(Subv): For the time being just forward the same data without any decoding. | ||
| 94 | rgba_data = texture_data; | ||
| 95 | break; | ||
| 96 | default: | ||
| 97 | UNIMPLEMENTED_MSG("Format not implemented"); | ||
| 98 | break; | ||
| 99 | } | ||
| 100 | |||
| 101 | return rgba_data; | ||
| 102 | } | ||
| 103 | |||
| 104 | } // namespace Texture | ||
| 105 | } // namespace Tegra | ||
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h new file mode 100644 index 000000000..0c21694ff --- /dev/null +++ b/src/video_core/textures/decoders.h | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <vector> | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "video_core/textures/texture.h" | ||
| 10 | |||
| 11 | namespace Tegra { | ||
| 12 | namespace Texture { | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Unswizzles a swizzled texture without changing its format. | ||
| 16 | */ | ||
| 17 | std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Decodes an unswizzled texture into a A8R8G8B8 texture. | ||
| 21 | */ | ||
| 22 | std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, | ||
| 23 | u32 height); | ||
| 24 | |||
| 25 | } // namespace Texture | ||
| 26 | } // namespace Tegra | ||
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h new file mode 100644 index 000000000..d969bcdd9 --- /dev/null +++ b/src/video_core/textures/texture.h | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // Copyright 2018 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "common/bit_field.h" | ||
| 8 | #include "common/common_funcs.h" | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "video_core/memory_manager.h" | ||
| 11 | |||
| 12 | namespace Tegra { | ||
| 13 | namespace Texture { | ||
| 14 | |||
| 15 | enum class TextureFormat : u32 { | ||
| 16 | A8R8G8B8 = 8, | ||
| 17 | DXT1 = 0x24, | ||
| 18 | }; | ||
| 19 | |||
| 20 | union TextureHandle { | ||
| 21 | u32 raw; | ||
| 22 | BitField<0, 20, u32> tic_id; | ||
| 23 | BitField<20, 12, u32> tsc_id; | ||
| 24 | }; | ||
| 25 | |||
| 26 | struct TICEntry { | ||
| 27 | union { | ||
| 28 | u32 raw; | ||
| 29 | BitField<0, 7, TextureFormat> format; | ||
| 30 | BitField<7, 3, u32> r_type; | ||
| 31 | BitField<10, 3, u32> g_type; | ||
| 32 | BitField<13, 3, u32> b_type; | ||
| 33 | BitField<16, 3, u32> a_type; | ||
| 34 | }; | ||
| 35 | u32 address_low; | ||
| 36 | u16 address_high; | ||
| 37 | INSERT_PADDING_BYTES(6); | ||
| 38 | u16 width_minus_1; | ||
| 39 | INSERT_PADDING_BYTES(2); | ||
| 40 | u16 height_minus_1; | ||
| 41 | INSERT_PADDING_BYTES(10); | ||
| 42 | |||
| 43 | GPUVAddr Address() const { | ||
| 44 | return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); | ||
| 45 | } | ||
| 46 | |||
| 47 | u32 Width() const { | ||
| 48 | return width_minus_1 + 1; | ||
| 49 | } | ||
| 50 | |||
| 51 | u32 Height() const { | ||
| 52 | return height_minus_1 + 1; | ||
| 53 | } | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); | ||
| 56 | |||
| 57 | /// Returns the number of bytes per pixel of the input texture format. | ||
| 58 | u32 BytesPerPixel(TextureFormat format); | ||
| 59 | |||
| 60 | } // namespace Texture | ||
| 61 | } // namespace Tegra | ||
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0c4056c49..5af3154d7 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -23,6 +23,13 @@ add_executable(yuzu | |||
| 23 | configuration/configure_input.h | 23 | configuration/configure_input.h |
| 24 | configuration/configure_system.cpp | 24 | configuration/configure_system.cpp |
| 25 | configuration/configure_system.h | 25 | configuration/configure_system.h |
| 26 | debugger/graphics/graphics_breakpoint_observer.cpp | ||
| 27 | debugger/graphics/graphics_breakpoint_observer.h | ||
| 28 | debugger/graphics/graphics_breakpoints.cpp | ||
| 29 | debugger/graphics/graphics_breakpoints.h | ||
| 30 | debugger/graphics/graphics_breakpoints_p.h | ||
| 31 | debugger/graphics/graphics_surface.cpp | ||
| 32 | debugger/graphics/graphics_surface.h | ||
| 26 | debugger/profiler.cpp | 33 | debugger/profiler.cpp |
| 27 | debugger/profiler.h | 34 | debugger/profiler.h |
| 28 | debugger/registers.cpp | 35 | debugger/registers.cpp |
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp new file mode 100644 index 000000000..d6d61a739 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <QMetaType> | ||
| 6 | #include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" | ||
| 7 | |||
| 8 | BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, | ||
| 9 | const QString& title, QWidget* parent) | ||
| 10 | : QDockWidget(title, parent), BreakPointObserver(debug_context) { | ||
| 11 | qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); | ||
| 12 | |||
| 13 | connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); | ||
| 14 | |||
| 15 | // NOTE: This signal is emitted from a non-GUI thread, but connect() takes | ||
| 16 | // care of delaying its handling to the GUI thread. | ||
| 17 | connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, | ||
| 18 | SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); | ||
| 19 | } | ||
| 20 | |||
| 21 | void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { | ||
| 22 | emit BreakPointHit(event, data); | ||
| 23 | } | ||
| 24 | |||
| 25 | void BreakPointObserverDock::OnMaxwellResume() { | ||
| 26 | emit Resumed(); | ||
| 27 | } | ||
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h new file mode 100644 index 000000000..9d05493cf --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <QDockWidget> | ||
| 8 | #include "video_core/debug_utils/debug_utils.h" | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots. | ||
| 12 | * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while | ||
| 13 | * the widget usually wants to perform reactions in the GUI thread. | ||
| 14 | */ | ||
| 15 | class BreakPointObserverDock : public QDockWidget, | ||
| 16 | protected Tegra::DebugContext::BreakPointObserver { | ||
| 17 | Q_OBJECT | ||
| 18 | |||
| 19 | public: | ||
| 20 | BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title, | ||
| 21 | QWidget* parent = nullptr); | ||
| 22 | |||
| 23 | void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; | ||
| 24 | void OnMaxwellResume() override; | ||
| 25 | |||
| 26 | private slots: | ||
| 27 | virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; | ||
| 28 | virtual void OnResumed() = 0; | ||
| 29 | |||
| 30 | signals: | ||
| 31 | void Resumed(); | ||
| 32 | void BreakPointHit(Tegra::DebugContext::Event event, void* data); | ||
| 33 | }; | ||
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp new file mode 100644 index 000000000..f98cc8152 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp | |||
| @@ -0,0 +1,212 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <QLabel> | ||
| 6 | #include <QMetaType> | ||
| 7 | #include <QPushButton> | ||
| 8 | #include <QTreeView> | ||
| 9 | #include <QVBoxLayout> | ||
| 10 | #include "common/assert.h" | ||
| 11 | #include "yuzu/debugger/graphics/graphics_breakpoints.h" | ||
| 12 | #include "yuzu/debugger/graphics/graphics_breakpoints_p.h" | ||
| 13 | |||
| 14 | BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context, | ||
| 15 | QObject* parent) | ||
| 16 | : QAbstractListModel(parent), context_weak(debug_context), | ||
| 17 | at_breakpoint(debug_context->at_breakpoint), | ||
| 18 | active_breakpoint(debug_context->active_breakpoint) {} | ||
| 19 | |||
| 20 | int BreakPointModel::columnCount(const QModelIndex& parent) const { | ||
| 21 | return 1; | ||
| 22 | } | ||
| 23 | |||
| 24 | int BreakPointModel::rowCount(const QModelIndex& parent) const { | ||
| 25 | return static_cast<int>(Tegra::DebugContext::Event::NumEvents); | ||
| 26 | } | ||
| 27 | |||
| 28 | QVariant BreakPointModel::data(const QModelIndex& index, int role) const { | ||
| 29 | const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); | ||
| 30 | |||
| 31 | switch (role) { | ||
| 32 | case Qt::DisplayRole: { | ||
| 33 | if (index.column() == 0) { | ||
| 34 | static const std::map<Tegra::DebugContext::Event, QString> map = { | ||
| 35 | {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")}, | ||
| 36 | {Tegra::DebugContext::Event::MaxwellCommandProcessed, | ||
| 37 | tr("Maxwell command processed")}, | ||
| 38 | {Tegra::DebugContext::Event::IncomingPrimitiveBatch, | ||
| 39 | tr("Incoming primitive batch")}, | ||
| 40 | {Tegra::DebugContext::Event::FinishedPrimitiveBatch, | ||
| 41 | tr("Finished primitive batch")}, | ||
| 42 | }; | ||
| 43 | |||
| 44 | DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents)); | ||
| 45 | return (map.find(event) != map.end()) ? map.at(event) : QString(); | ||
| 46 | } | ||
| 47 | |||
| 48 | break; | ||
| 49 | } | ||
| 50 | |||
| 51 | case Qt::CheckStateRole: { | ||
| 52 | if (index.column() == 0) | ||
| 53 | return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked; | ||
| 54 | break; | ||
| 55 | } | ||
| 56 | |||
| 57 | case Qt::BackgroundRole: { | ||
| 58 | if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { | ||
| 59 | return QBrush(QColor(0xE0, 0xE0, 0x10)); | ||
| 60 | } | ||
| 61 | break; | ||
| 62 | } | ||
| 63 | |||
| 64 | case Role_IsEnabled: { | ||
| 65 | auto context = context_weak.lock(); | ||
| 66 | return context && context->breakpoints[(int)event].enabled; | ||
| 67 | } | ||
| 68 | |||
| 69 | default: | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | return QVariant(); | ||
| 73 | } | ||
| 74 | |||
| 75 | Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const { | ||
| 76 | if (!index.isValid()) | ||
| 77 | return 0; | ||
| 78 | |||
| 79 | Qt::ItemFlags flags = Qt::ItemIsEnabled; | ||
| 80 | if (index.column() == 0) | ||
| 81 | flags |= Qt::ItemIsUserCheckable; | ||
| 82 | return flags; | ||
| 83 | } | ||
| 84 | |||
| 85 | bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) { | ||
| 86 | const auto event = static_cast<Tegra::DebugContext::Event>(index.row()); | ||
| 87 | |||
| 88 | switch (role) { | ||
| 89 | case Qt::CheckStateRole: { | ||
| 90 | if (index.column() != 0) | ||
| 91 | return false; | ||
| 92 | |||
| 93 | auto context = context_weak.lock(); | ||
| 94 | if (!context) | ||
| 95 | return false; | ||
| 96 | |||
| 97 | context->breakpoints[(int)event].enabled = value == Qt::Checked; | ||
| 98 | QModelIndex changed_index = createIndex(index.row(), 0); | ||
| 99 | emit dataChanged(changed_index, changed_index); | ||
| 100 | return true; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | return false; | ||
| 105 | } | ||
| 106 | |||
| 107 | void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) { | ||
| 108 | auto context = context_weak.lock(); | ||
| 109 | if (!context) | ||
| 110 | return; | ||
| 111 | |||
| 112 | active_breakpoint = context->active_breakpoint; | ||
| 113 | at_breakpoint = context->at_breakpoint; | ||
| 114 | emit dataChanged(createIndex(static_cast<int>(event), 0), | ||
| 115 | createIndex(static_cast<int>(event), 0)); | ||
| 116 | } | ||
| 117 | |||
| 118 | void BreakPointModel::OnResumed() { | ||
| 119 | auto context = context_weak.lock(); | ||
| 120 | if (!context) | ||
| 121 | return; | ||
| 122 | |||
| 123 | at_breakpoint = context->at_breakpoint; | ||
| 124 | emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), | ||
| 125 | createIndex(static_cast<int>(active_breakpoint), 0)); | ||
| 126 | active_breakpoint = context->active_breakpoint; | ||
| 127 | } | ||
| 128 | |||
| 129 | GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( | ||
| 130 | std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent) | ||
| 131 | : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( | ||
| 132 | debug_context) { | ||
| 133 | setObjectName("TegraBreakPointsWidget"); | ||
| 134 | |||
| 135 | status_text = new QLabel(tr("Emulation running")); | ||
| 136 | resume_button = new QPushButton(tr("Resume")); | ||
| 137 | resume_button->setEnabled(false); | ||
| 138 | |||
| 139 | breakpoint_model = new BreakPointModel(debug_context, this); | ||
| 140 | breakpoint_list = new QTreeView; | ||
| 141 | breakpoint_list->setRootIsDecorated(false); | ||
| 142 | breakpoint_list->setHeaderHidden(true); | ||
| 143 | breakpoint_list->setModel(breakpoint_model); | ||
| 144 | |||
| 145 | qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); | ||
| 146 | |||
| 147 | connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, | ||
| 148 | SLOT(OnItemDoubleClicked(const QModelIndex&))); | ||
| 149 | |||
| 150 | connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); | ||
| 151 | |||
| 152 | connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, | ||
| 153 | SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); | ||
| 154 | connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); | ||
| 155 | |||
| 156 | connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, | ||
| 157 | SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); | ||
| 158 | connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); | ||
| 159 | |||
| 160 | connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), | ||
| 161 | breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); | ||
| 162 | |||
| 163 | QWidget* main_widget = new QWidget; | ||
| 164 | auto main_layout = new QVBoxLayout; | ||
| 165 | { | ||
| 166 | auto sub_layout = new QHBoxLayout; | ||
| 167 | sub_layout->addWidget(status_text); | ||
| 168 | sub_layout->addWidget(resume_button); | ||
| 169 | main_layout->addLayout(sub_layout); | ||
| 170 | } | ||
| 171 | main_layout->addWidget(breakpoint_list); | ||
| 172 | main_widget->setLayout(main_layout); | ||
| 173 | |||
| 174 | setWidget(main_widget); | ||
| 175 | } | ||
| 176 | |||
| 177 | void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) { | ||
| 178 | // Process in GUI thread | ||
| 179 | emit BreakPointHit(event, data); | ||
| 180 | } | ||
| 181 | |||
| 182 | void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { | ||
| 183 | status_text->setText(tr("Emulation halted at breakpoint")); | ||
| 184 | resume_button->setEnabled(true); | ||
| 185 | } | ||
| 186 | |||
| 187 | void GraphicsBreakPointsWidget::OnMaxwellResume() { | ||
| 188 | // Process in GUI thread | ||
| 189 | emit Resumed(); | ||
| 190 | } | ||
| 191 | |||
| 192 | void GraphicsBreakPointsWidget::OnResumed() { | ||
| 193 | status_text->setText(tr("Emulation running")); | ||
| 194 | resume_button->setEnabled(false); | ||
| 195 | } | ||
| 196 | |||
| 197 | void GraphicsBreakPointsWidget::OnResumeRequested() { | ||
| 198 | if (auto context = context_weak.lock()) | ||
| 199 | context->Resume(); | ||
| 200 | } | ||
| 201 | |||
| 202 | void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) { | ||
| 203 | if (!index.isValid()) | ||
| 204 | return; | ||
| 205 | |||
| 206 | QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0); | ||
| 207 | QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole); | ||
| 208 | QVariant new_state = Qt::Unchecked; | ||
| 209 | if (enabled == Qt::Unchecked) | ||
| 210 | new_state = Qt::Checked; | ||
| 211 | breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole); | ||
| 212 | } | ||
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h new file mode 100644 index 000000000..ae0ede2e8 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include <QDockWidget> | ||
| 9 | #include "video_core/debug_utils/debug_utils.h" | ||
| 10 | |||
| 11 | class QLabel; | ||
| 12 | class QPushButton; | ||
| 13 | class QTreeView; | ||
| 14 | |||
| 15 | class BreakPointModel; | ||
| 16 | |||
| 17 | class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver { | ||
| 18 | Q_OBJECT | ||
| 19 | |||
| 20 | using Event = Tegra::DebugContext::Event; | ||
| 21 | |||
| 22 | public: | ||
| 23 | explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context, | ||
| 24 | QWidget* parent = nullptr); | ||
| 25 | |||
| 26 | void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; | ||
| 27 | void OnMaxwellResume() override; | ||
| 28 | |||
| 29 | public slots: | ||
| 30 | void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); | ||
| 31 | void OnItemDoubleClicked(const QModelIndex&); | ||
| 32 | void OnResumeRequested(); | ||
| 33 | void OnResumed(); | ||
| 34 | |||
| 35 | signals: | ||
| 36 | void Resumed(); | ||
| 37 | void BreakPointHit(Tegra::DebugContext::Event event, void* data); | ||
| 38 | void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); | ||
| 39 | |||
| 40 | private: | ||
| 41 | QLabel* status_text; | ||
| 42 | QPushButton* resume_button; | ||
| 43 | |||
| 44 | BreakPointModel* breakpoint_model; | ||
| 45 | QTreeView* breakpoint_list; | ||
| 46 | }; | ||
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h new file mode 100644 index 000000000..35a6876ae --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include <QAbstractListModel> | ||
| 9 | #include "video_core/debug_utils/debug_utils.h" | ||
| 10 | |||
| 11 | class BreakPointModel : public QAbstractListModel { | ||
| 12 | Q_OBJECT | ||
| 13 | |||
| 14 | public: | ||
| 15 | enum { | ||
| 16 | Role_IsEnabled = Qt::UserRole, | ||
| 17 | }; | ||
| 18 | |||
| 19 | BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent); | ||
| 20 | |||
| 21 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; | ||
| 22 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; | ||
| 23 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; | ||
| 24 | Qt::ItemFlags flags(const QModelIndex& index) const override; | ||
| 25 | |||
| 26 | bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; | ||
| 27 | |||
| 28 | public slots: | ||
| 29 | void OnBreakPointHit(Tegra::DebugContext::Event event); | ||
| 30 | void OnResumed(); | ||
| 31 | |||
| 32 | private: | ||
| 33 | std::weak_ptr<Tegra::DebugContext> context_weak; | ||
| 34 | bool at_breakpoint; | ||
| 35 | Tegra::DebugContext::Event active_breakpoint; | ||
| 36 | }; | ||
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp new file mode 100644 index 000000000..8e6509adc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp | |||
| @@ -0,0 +1,452 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <QBoxLayout> | ||
| 6 | #include <QComboBox> | ||
| 7 | #include <QDebug> | ||
| 8 | #include <QFileDialog> | ||
| 9 | #include <QLabel> | ||
| 10 | #include <QMouseEvent> | ||
| 11 | #include <QPushButton> | ||
| 12 | #include <QScrollArea> | ||
| 13 | #include <QSpinBox> | ||
| 14 | #include "core/core.h" | ||
| 15 | #include "video_core/engines/maxwell_3d.h" | ||
| 16 | #include "video_core/gpu.h" | ||
| 17 | #include "video_core/textures/decoders.h" | ||
| 18 | #include "video_core/textures/texture.h" | ||
| 19 | #include "video_core/utils.h" | ||
| 20 | #include "yuzu/debugger/graphics/graphics_surface.h" | ||
| 21 | #include "yuzu/util/spinbox.h" | ||
| 22 | |||
| 23 | static Tegra::Texture::TextureFormat ConvertToTextureFormat( | ||
| 24 | Tegra::RenderTargetFormat render_target_format) { | ||
| 25 | switch (render_target_format) { | ||
| 26 | case Tegra::RenderTargetFormat::RGBA8_UNORM: | ||
| 27 | return Tegra::Texture::TextureFormat::A8R8G8B8; | ||
| 28 | default: | ||
| 29 | UNIMPLEMENTED_MSG("Unimplemented RT format"); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) | ||
| 34 | : QLabel(parent), surface_widget(surface_widget_) {} | ||
| 35 | SurfacePicture::~SurfacePicture() {} | ||
| 36 | |||
| 37 | void SurfacePicture::mousePressEvent(QMouseEvent* event) { | ||
| 38 | // Only do something while the left mouse button is held down | ||
| 39 | if (!(event->buttons() & Qt::LeftButton)) | ||
| 40 | return; | ||
| 41 | |||
| 42 | if (pixmap() == nullptr) | ||
| 43 | return; | ||
| 44 | |||
| 45 | if (surface_widget) | ||
| 46 | surface_widget->Pick(event->x() * pixmap()->width() / width(), | ||
| 47 | event->y() * pixmap()->height() / height()); | ||
| 48 | } | ||
| 49 | |||
| 50 | void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { | ||
| 51 | // We also want to handle the event if the user moves the mouse while holding down the LMB | ||
| 52 | mousePressEvent(event); | ||
| 53 | } | ||
| 54 | |||
| 55 | GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, | ||
| 56 | QWidget* parent) | ||
| 57 | : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), | ||
| 58 | surface_source(Source::RenderTarget0) { | ||
| 59 | setObjectName("MaxwellSurface"); | ||
| 60 | |||
| 61 | surface_source_list = new QComboBox; | ||
| 62 | surface_source_list->addItem(tr("Render Target 0")); | ||
| 63 | surface_source_list->addItem(tr("Render Target 1")); | ||
| 64 | surface_source_list->addItem(tr("Render Target 2")); | ||
| 65 | surface_source_list->addItem(tr("Render Target 3")); | ||
| 66 | surface_source_list->addItem(tr("Render Target 4")); | ||
| 67 | surface_source_list->addItem(tr("Render Target 5")); | ||
| 68 | surface_source_list->addItem(tr("Render Target 6")); | ||
| 69 | surface_source_list->addItem(tr("Render Target 7")); | ||
| 70 | surface_source_list->addItem(tr("Z Buffer")); | ||
| 71 | surface_source_list->addItem(tr("Custom")); | ||
| 72 | surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); | ||
| 73 | |||
| 74 | surface_address_control = new CSpinBox; | ||
| 75 | surface_address_control->SetBase(16); | ||
| 76 | surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); | ||
| 77 | surface_address_control->SetPrefix("0x"); | ||
| 78 | |||
| 79 | unsigned max_dimension = 16384; // TODO: Find actual maximum | ||
| 80 | |||
| 81 | surface_width_control = new QSpinBox; | ||
| 82 | surface_width_control->setRange(0, max_dimension); | ||
| 83 | |||
| 84 | surface_height_control = new QSpinBox; | ||
| 85 | surface_height_control->setRange(0, max_dimension); | ||
| 86 | |||
| 87 | surface_picker_x_control = new QSpinBox; | ||
| 88 | surface_picker_x_control->setRange(0, max_dimension - 1); | ||
| 89 | |||
| 90 | surface_picker_y_control = new QSpinBox; | ||
| 91 | surface_picker_y_control->setRange(0, max_dimension - 1); | ||
| 92 | |||
| 93 | surface_format_control = new QComboBox; | ||
| 94 | |||
| 95 | // Color formats sorted by Maxwell texture format index | ||
| 96 | surface_format_control->addItem(tr("None")); | ||
| 97 | surface_format_control->addItem(tr("Unknown")); | ||
| 98 | surface_format_control->addItem(tr("Unknown")); | ||
| 99 | surface_format_control->addItem(tr("Unknown")); | ||
| 100 | surface_format_control->addItem(tr("Unknown")); | ||
| 101 | surface_format_control->addItem(tr("Unknown")); | ||
| 102 | surface_format_control->addItem(tr("Unknown")); | ||
| 103 | surface_format_control->addItem(tr("Unknown")); | ||
| 104 | surface_format_control->addItem(tr("A8R8G8B8")); | ||
| 105 | surface_format_control->addItem(tr("Unknown")); | ||
| 106 | surface_format_control->addItem(tr("Unknown")); | ||
| 107 | surface_format_control->addItem(tr("Unknown")); | ||
| 108 | surface_format_control->addItem(tr("Unknown")); | ||
| 109 | surface_format_control->addItem(tr("Unknown")); | ||
| 110 | surface_format_control->addItem(tr("Unknown")); | ||
| 111 | surface_format_control->addItem(tr("Unknown")); | ||
| 112 | surface_format_control->addItem(tr("Unknown")); | ||
| 113 | surface_format_control->addItem(tr("Unknown")); | ||
| 114 | surface_format_control->addItem(tr("Unknown")); | ||
| 115 | surface_format_control->addItem(tr("Unknown")); | ||
| 116 | surface_format_control->addItem(tr("Unknown")); | ||
| 117 | surface_format_control->addItem(tr("Unknown")); | ||
| 118 | surface_format_control->addItem(tr("Unknown")); | ||
| 119 | surface_format_control->addItem(tr("Unknown")); | ||
| 120 | surface_format_control->addItem(tr("Unknown")); | ||
| 121 | surface_format_control->addItem(tr("Unknown")); | ||
| 122 | surface_format_control->addItem(tr("Unknown")); | ||
| 123 | surface_format_control->addItem(tr("Unknown")); | ||
| 124 | surface_format_control->addItem(tr("Unknown")); | ||
| 125 | surface_format_control->addItem(tr("Unknown")); | ||
| 126 | surface_format_control->addItem(tr("Unknown")); | ||
| 127 | surface_format_control->addItem(tr("Unknown")); | ||
| 128 | surface_format_control->addItem(tr("Unknown")); | ||
| 129 | surface_format_control->addItem(tr("Unknown")); | ||
| 130 | surface_format_control->addItem(tr("Unknown")); | ||
| 131 | surface_format_control->addItem(tr("Unknown")); | ||
| 132 | surface_format_control->addItem(tr("DXT1")); | ||
| 133 | surface_format_control->addItem(tr("DXT23")); | ||
| 134 | surface_format_control->addItem(tr("DXT45")); | ||
| 135 | surface_format_control->addItem(tr("DXN1")); | ||
| 136 | surface_format_control->addItem(tr("DXN2")); | ||
| 137 | |||
| 138 | surface_info_label = new QLabel(); | ||
| 139 | surface_info_label->setWordWrap(true); | ||
| 140 | |||
| 141 | surface_picture_label = new SurfacePicture(0, this); | ||
| 142 | surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); | ||
| 143 | surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); | ||
| 144 | surface_picture_label->setScaledContents(false); | ||
| 145 | |||
| 146 | auto scroll_area = new QScrollArea(); | ||
| 147 | scroll_area->setBackgroundRole(QPalette::Dark); | ||
| 148 | scroll_area->setWidgetResizable(false); | ||
| 149 | scroll_area->setWidget(surface_picture_label); | ||
| 150 | |||
| 151 | save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); | ||
| 152 | |||
| 153 | // Connections | ||
| 154 | connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); | ||
| 155 | connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, | ||
| 156 | SLOT(OnSurfaceSourceChanged(int))); | ||
| 157 | connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, | ||
| 158 | SLOT(OnSurfaceAddressChanged(qint64))); | ||
| 159 | connect(surface_width_control, SIGNAL(valueChanged(int)), this, | ||
| 160 | SLOT(OnSurfaceWidthChanged(int))); | ||
| 161 | connect(surface_height_control, SIGNAL(valueChanged(int)), this, | ||
| 162 | SLOT(OnSurfaceHeightChanged(int))); | ||
| 163 | connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, | ||
| 164 | SLOT(OnSurfaceFormatChanged(int))); | ||
| 165 | connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, | ||
| 166 | SLOT(OnSurfacePickerXChanged(int))); | ||
| 167 | connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, | ||
| 168 | SLOT(OnSurfacePickerYChanged(int))); | ||
| 169 | connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); | ||
| 170 | |||
| 171 | auto main_widget = new QWidget; | ||
| 172 | auto main_layout = new QVBoxLayout; | ||
| 173 | { | ||
| 174 | auto sub_layout = new QHBoxLayout; | ||
| 175 | sub_layout->addWidget(new QLabel(tr("Source:"))); | ||
| 176 | sub_layout->addWidget(surface_source_list); | ||
| 177 | main_layout->addLayout(sub_layout); | ||
| 178 | } | ||
| 179 | { | ||
| 180 | auto sub_layout = new QHBoxLayout; | ||
| 181 | sub_layout->addWidget(new QLabel(tr("GPU Address:"))); | ||
| 182 | sub_layout->addWidget(surface_address_control); | ||
| 183 | main_layout->addLayout(sub_layout); | ||
| 184 | } | ||
| 185 | { | ||
| 186 | auto sub_layout = new QHBoxLayout; | ||
| 187 | sub_layout->addWidget(new QLabel(tr("Width:"))); | ||
| 188 | sub_layout->addWidget(surface_width_control); | ||
| 189 | main_layout->addLayout(sub_layout); | ||
| 190 | } | ||
| 191 | { | ||
| 192 | auto sub_layout = new QHBoxLayout; | ||
| 193 | sub_layout->addWidget(new QLabel(tr("Height:"))); | ||
| 194 | sub_layout->addWidget(surface_height_control); | ||
| 195 | main_layout->addLayout(sub_layout); | ||
| 196 | } | ||
| 197 | { | ||
| 198 | auto sub_layout = new QHBoxLayout; | ||
| 199 | sub_layout->addWidget(new QLabel(tr("Format:"))); | ||
| 200 | sub_layout->addWidget(surface_format_control); | ||
| 201 | main_layout->addLayout(sub_layout); | ||
| 202 | } | ||
| 203 | main_layout->addWidget(scroll_area); | ||
| 204 | |||
| 205 | auto info_layout = new QHBoxLayout; | ||
| 206 | { | ||
| 207 | auto xy_layout = new QVBoxLayout; | ||
| 208 | { | ||
| 209 | { | ||
| 210 | auto sub_layout = new QHBoxLayout; | ||
| 211 | sub_layout->addWidget(new QLabel(tr("X:"))); | ||
| 212 | sub_layout->addWidget(surface_picker_x_control); | ||
| 213 | xy_layout->addLayout(sub_layout); | ||
| 214 | } | ||
| 215 | { | ||
| 216 | auto sub_layout = new QHBoxLayout; | ||
| 217 | sub_layout->addWidget(new QLabel(tr("Y:"))); | ||
| 218 | sub_layout->addWidget(surface_picker_y_control); | ||
| 219 | xy_layout->addLayout(sub_layout); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | info_layout->addLayout(xy_layout); | ||
| 223 | surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); | ||
| 224 | info_layout->addWidget(surface_info_label); | ||
| 225 | } | ||
| 226 | main_layout->addLayout(info_layout); | ||
| 227 | |||
| 228 | main_layout->addWidget(save_surface); | ||
| 229 | main_widget->setLayout(main_layout); | ||
| 230 | setWidget(main_widget); | ||
| 231 | |||
| 232 | // Load current data - TODO: Make sure this works when emulation is not running | ||
| 233 | if (debug_context && debug_context->at_breakpoint) { | ||
| 234 | emit Update(); | ||
| 235 | widget()->setEnabled(debug_context->at_breakpoint); | ||
| 236 | } else { | ||
| 237 | widget()->setEnabled(false); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { | ||
| 242 | emit Update(); | ||
| 243 | widget()->setEnabled(true); | ||
| 244 | } | ||
| 245 | |||
| 246 | void GraphicsSurfaceWidget::OnResumed() { | ||
| 247 | widget()->setEnabled(false); | ||
| 248 | } | ||
| 249 | |||
| 250 | void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { | ||
| 251 | surface_source = static_cast<Source>(new_value); | ||
| 252 | emit Update(); | ||
| 253 | } | ||
| 254 | |||
| 255 | void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { | ||
| 256 | if (surface_address != new_value) { | ||
| 257 | surface_address = static_cast<Tegra::GPUVAddr>(new_value); | ||
| 258 | |||
| 259 | surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||
| 260 | emit Update(); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { | ||
| 265 | if (surface_width != static_cast<unsigned>(new_value)) { | ||
| 266 | surface_width = static_cast<unsigned>(new_value); | ||
| 267 | |||
| 268 | surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||
| 269 | emit Update(); | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { | ||
| 274 | if (surface_height != static_cast<unsigned>(new_value)) { | ||
| 275 | surface_height = static_cast<unsigned>(new_value); | ||
| 276 | |||
| 277 | surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||
| 278 | emit Update(); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { | ||
| 283 | if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { | ||
| 284 | surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); | ||
| 285 | |||
| 286 | surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); | ||
| 287 | emit Update(); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { | ||
| 292 | if (surface_picker_x != new_value) { | ||
| 293 | surface_picker_x = new_value; | ||
| 294 | Pick(surface_picker_x, surface_picker_y); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { | ||
| 299 | if (surface_picker_y != new_value) { | ||
| 300 | surface_picker_y = new_value; | ||
| 301 | Pick(surface_picker_x, surface_picker_y); | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | void GraphicsSurfaceWidget::Pick(int x, int y) { | ||
| 306 | surface_picker_x_control->setValue(x); | ||
| 307 | surface_picker_y_control->setValue(y); | ||
| 308 | |||
| 309 | if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || | ||
| 310 | y >= static_cast<int>(surface_height)) { | ||
| 311 | surface_info_label->setText(tr("Pixel out of bounds")); | ||
| 312 | surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); | ||
| 313 | return; | ||
| 314 | } | ||
| 315 | |||
| 316 | surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); | ||
| 317 | surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); | ||
| 318 | } | ||
| 319 | |||
| 320 | void GraphicsSurfaceWidget::OnUpdate() { | ||
| 321 | auto& gpu = Core::System::GetInstance().GPU(); | ||
| 322 | |||
| 323 | QPixmap pixmap; | ||
| 324 | |||
| 325 | switch (surface_source) { | ||
| 326 | case Source::RenderTarget0: | ||
| 327 | case Source::RenderTarget1: | ||
| 328 | case Source::RenderTarget2: | ||
| 329 | case Source::RenderTarget3: | ||
| 330 | case Source::RenderTarget4: | ||
| 331 | case Source::RenderTarget5: | ||
| 332 | case Source::RenderTarget6: | ||
| 333 | case Source::RenderTarget7: { | ||
| 334 | // TODO: Store a reference to the registers in the debug context instead of accessing them | ||
| 335 | // directly... | ||
| 336 | |||
| 337 | auto& registers = gpu.Get3DEngine().regs; | ||
| 338 | auto& rt = registers.rt[static_cast<size_t>(surface_source) - | ||
| 339 | static_cast<size_t>(Source::RenderTarget0)]; | ||
| 340 | |||
| 341 | surface_address = rt.Address(); | ||
| 342 | surface_width = rt.horiz; | ||
| 343 | surface_height = rt.vert; | ||
| 344 | if (rt.format != 0) { | ||
| 345 | surface_format = | ||
| 346 | ConvertToTextureFormat(static_cast<Tegra::RenderTargetFormat>(rt.format)); | ||
| 347 | } | ||
| 348 | |||
| 349 | break; | ||
| 350 | } | ||
| 351 | |||
| 352 | case Source::Custom: { | ||
| 353 | // Keep user-specified values | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | |||
| 357 | default: | ||
| 358 | qDebug() << "Unknown surface source " << static_cast<int>(surface_source); | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | |||
| 362 | surface_address_control->SetValue(surface_address); | ||
| 363 | surface_width_control->setValue(surface_width); | ||
| 364 | surface_height_control->setValue(surface_height); | ||
| 365 | surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); | ||
| 366 | |||
| 367 | if (surface_address == 0) { | ||
| 368 | surface_picture_label->hide(); | ||
| 369 | surface_info_label->setText(tr("(invalid surface address)")); | ||
| 370 | surface_info_label->setAlignment(Qt::AlignCenter); | ||
| 371 | surface_picker_x_control->setEnabled(false); | ||
| 372 | surface_picker_y_control->setEnabled(false); | ||
| 373 | save_surface->setEnabled(false); | ||
| 374 | return; | ||
| 375 | } | ||
| 376 | |||
| 377 | // TODO: Implement a good way to visualize alpha components! | ||
| 378 | |||
| 379 | QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); | ||
| 380 | VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); | ||
| 381 | |||
| 382 | auto unswizzled_data = | ||
| 383 | Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height); | ||
| 384 | |||
| 385 | auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, | ||
| 386 | surface_width, surface_height); | ||
| 387 | |||
| 388 | surface_picture_label->show(); | ||
| 389 | |||
| 390 | for (unsigned int y = 0; y < surface_height; ++y) { | ||
| 391 | for (unsigned int x = 0; x < surface_width; ++x) { | ||
| 392 | Math::Vec4<u8> color; | ||
| 393 | color[0] = texture_data[x + y * surface_width + 0]; | ||
| 394 | color[1] = texture_data[x + y * surface_width + 1]; | ||
| 395 | color[2] = texture_data[x + y * surface_width + 2]; | ||
| 396 | color[3] = texture_data[x + y * surface_width + 3]; | ||
| 397 | decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | pixmap = QPixmap::fromImage(decoded_image); | ||
| 402 | surface_picture_label->setPixmap(pixmap); | ||
| 403 | surface_picture_label->resize(pixmap.size()); | ||
| 404 | |||
| 405 | // Update the info with pixel data | ||
| 406 | surface_picker_x_control->setEnabled(true); | ||
| 407 | surface_picker_y_control->setEnabled(true); | ||
| 408 | Pick(surface_picker_x, surface_picker_y); | ||
| 409 | |||
| 410 | // Enable saving the converted pixmap to file | ||
| 411 | save_surface->setEnabled(true); | ||
| 412 | } | ||
| 413 | |||
| 414 | void GraphicsSurfaceWidget::SaveSurface() { | ||
| 415 | QString png_filter = tr("Portable Network Graphic (*.png)"); | ||
| 416 | QString bin_filter = tr("Binary data (*.bin)"); | ||
| 417 | |||
| 418 | QString selectedFilter; | ||
| 419 | QString filename = QFileDialog::getSaveFileName( | ||
| 420 | this, tr("Save Surface"), | ||
| 421 | QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), | ||
| 422 | QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); | ||
| 423 | |||
| 424 | if (filename.isEmpty()) { | ||
| 425 | // If the user canceled the dialog, don't save anything. | ||
| 426 | return; | ||
| 427 | } | ||
| 428 | |||
| 429 | if (selectedFilter == png_filter) { | ||
| 430 | const QPixmap* pixmap = surface_picture_label->pixmap(); | ||
| 431 | ASSERT_MSG(pixmap != nullptr, "No pixmap set"); | ||
| 432 | |||
| 433 | QFile file(filename); | ||
| 434 | file.open(QIODevice::WriteOnly); | ||
| 435 | if (pixmap) | ||
| 436 | pixmap->save(&file, "PNG"); | ||
| 437 | } else if (selectedFilter == bin_filter) { | ||
| 438 | auto& gpu = Core::System::GetInstance().GPU(); | ||
| 439 | VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); | ||
| 440 | |||
| 441 | const u8* buffer = Memory::GetPointer(address); | ||
| 442 | ASSERT_MSG(buffer != nullptr, "Memory not accessible"); | ||
| 443 | |||
| 444 | QFile file(filename); | ||
| 445 | file.open(QIODevice::WriteOnly); | ||
| 446 | int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); | ||
| 447 | QByteArray data(reinterpret_cast<const char*>(buffer), size); | ||
| 448 | file.write(data); | ||
| 449 | } else { | ||
| 450 | UNREACHABLE_MSG("Unhandled filter selected"); | ||
| 451 | } | ||
| 452 | } | ||
diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h new file mode 100644 index 000000000..6a344bdfc --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.h | |||
| @@ -0,0 +1,97 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <QLabel> | ||
| 8 | #include <QPushButton> | ||
| 9 | #include "video_core/memory_manager.h" | ||
| 10 | #include "video_core/textures/texture.h" | ||
| 11 | #include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" | ||
| 12 | |||
| 13 | class QComboBox; | ||
| 14 | class QSpinBox; | ||
| 15 | class CSpinBox; | ||
| 16 | |||
| 17 | class GraphicsSurfaceWidget; | ||
| 18 | |||
| 19 | class SurfacePicture : public QLabel { | ||
| 20 | Q_OBJECT | ||
| 21 | |||
| 22 | public: | ||
| 23 | explicit SurfacePicture(QWidget* parent = nullptr, | ||
| 24 | GraphicsSurfaceWidget* surface_widget = nullptr); | ||
| 25 | ~SurfacePicture(); | ||
| 26 | |||
| 27 | protected slots: | ||
| 28 | virtual void mouseMoveEvent(QMouseEvent* event); | ||
| 29 | virtual void mousePressEvent(QMouseEvent* event); | ||
| 30 | |||
| 31 | private: | ||
| 32 | GraphicsSurfaceWidget* surface_widget; | ||
| 33 | }; | ||
| 34 | |||
| 35 | class GraphicsSurfaceWidget : public BreakPointObserverDock { | ||
| 36 | Q_OBJECT | ||
| 37 | |||
| 38 | using Event = Tegra::DebugContext::Event; | ||
| 39 | |||
| 40 | enum class Source { | ||
| 41 | RenderTarget0 = 0, | ||
| 42 | RenderTarget1 = 1, | ||
| 43 | RenderTarget2 = 2, | ||
| 44 | RenderTarget3 = 3, | ||
| 45 | RenderTarget4 = 4, | ||
| 46 | RenderTarget5 = 5, | ||
| 47 | RenderTarget6 = 6, | ||
| 48 | RenderTarget7 = 7, | ||
| 49 | ZBuffer = 8, | ||
| 50 | Custom = 9, | ||
| 51 | }; | ||
| 52 | |||
| 53 | public: | ||
| 54 | explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, | ||
| 55 | QWidget* parent = nullptr); | ||
| 56 | void Pick(int x, int y); | ||
| 57 | |||
| 58 | public slots: | ||
| 59 | void OnSurfaceSourceChanged(int new_value); | ||
| 60 | void OnSurfaceAddressChanged(qint64 new_value); | ||
| 61 | void OnSurfaceWidthChanged(int new_value); | ||
| 62 | void OnSurfaceHeightChanged(int new_value); | ||
| 63 | void OnSurfaceFormatChanged(int new_value); | ||
| 64 | void OnSurfacePickerXChanged(int new_value); | ||
| 65 | void OnSurfacePickerYChanged(int new_value); | ||
| 66 | void OnUpdate(); | ||
| 67 | |||
| 68 | private slots: | ||
| 69 | void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; | ||
| 70 | void OnResumed() override; | ||
| 71 | |||
| 72 | void SaveSurface(); | ||
| 73 | |||
| 74 | signals: | ||
| 75 | void Update(); | ||
| 76 | |||
| 77 | private: | ||
| 78 | QComboBox* surface_source_list; | ||
| 79 | CSpinBox* surface_address_control; | ||
| 80 | QSpinBox* surface_width_control; | ||
| 81 | QSpinBox* surface_height_control; | ||
| 82 | QComboBox* surface_format_control; | ||
| 83 | |||
| 84 | SurfacePicture* surface_picture_label; | ||
| 85 | QSpinBox* surface_picker_x_control; | ||
| 86 | QSpinBox* surface_picker_y_control; | ||
| 87 | QLabel* surface_info_label; | ||
| 88 | QPushButton* save_surface; | ||
| 89 | |||
| 90 | Source surface_source; | ||
| 91 | Tegra::GPUVAddr surface_address; | ||
| 92 | unsigned surface_width; | ||
| 93 | unsigned surface_height; | ||
| 94 | Tegra::Texture::TextureFormat surface_format; | ||
| 95 | int surface_picker_x = 0; | ||
| 96 | int surface_picker_y = 0; | ||
| 97 | }; | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index eb22a8ccf..bd323870b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -25,10 +25,13 @@ | |||
| 25 | #include "core/gdbstub/gdbstub.h" | 25 | #include "core/gdbstub/gdbstub.h" |
| 26 | #include "core/loader/loader.h" | 26 | #include "core/loader/loader.h" |
| 27 | #include "core/settings.h" | 27 | #include "core/settings.h" |
| 28 | #include "video_core/debug_utils/debug_utils.h" | ||
| 28 | #include "yuzu/about_dialog.h" | 29 | #include "yuzu/about_dialog.h" |
| 29 | #include "yuzu/bootmanager.h" | 30 | #include "yuzu/bootmanager.h" |
| 30 | #include "yuzu/configuration/config.h" | 31 | #include "yuzu/configuration/config.h" |
| 31 | #include "yuzu/configuration/configure_dialog.h" | 32 | #include "yuzu/configuration/configure_dialog.h" |
| 33 | #include "yuzu/debugger/graphics/graphics_breakpoints.h" | ||
| 34 | #include "yuzu/debugger/graphics/graphics_surface.h" | ||
| 32 | #include "yuzu/debugger/profiler.h" | 35 | #include "yuzu/debugger/profiler.h" |
| 33 | #include "yuzu/debugger/registers.h" | 36 | #include "yuzu/debugger/registers.h" |
| 34 | #include "yuzu/debugger/wait_tree.h" | 37 | #include "yuzu/debugger/wait_tree.h" |
| @@ -68,6 +71,9 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { | |||
| 68 | void GMainWindow::ShowCallouts() {} | 71 | void GMainWindow::ShowCallouts() {} |
| 69 | 72 | ||
| 70 | GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { | 73 | GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { |
| 74 | |||
| 75 | debug_context = Tegra::DebugContext::Construct(); | ||
| 76 | |||
| 71 | setAcceptDrops(true); | 77 | setAcceptDrops(true); |
| 72 | ui.setupUi(this); | 78 | ui.setupUi(this); |
| 73 | statusBar()->hide(); | 79 | statusBar()->hide(); |
| @@ -160,6 +166,16 @@ void GMainWindow::InitializeDebugWidgets() { | |||
| 160 | connect(this, &GMainWindow::EmulationStopping, registersWidget, | 166 | connect(this, &GMainWindow::EmulationStopping, registersWidget, |
| 161 | &RegistersWidget::OnEmulationStopping); | 167 | &RegistersWidget::OnEmulationStopping); |
| 162 | 168 | ||
| 169 | graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); | ||
| 170 | addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); | ||
| 171 | graphicsBreakpointsWidget->hide(); | ||
| 172 | debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); | ||
| 173 | |||
| 174 | graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); | ||
| 175 | addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); | ||
| 176 | graphicsSurfaceWidget->hide(); | ||
| 177 | debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); | ||
| 178 | |||
| 163 | waitTreeWidget = new WaitTreeWidget(this); | 179 | waitTreeWidget = new WaitTreeWidget(this); |
| 164 | addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); | 180 | addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); |
| 165 | waitTreeWidget->hide(); | 181 | waitTreeWidget->hide(); |
| @@ -324,6 +340,8 @@ bool GMainWindow::LoadROM(const QString& filename) { | |||
| 324 | 340 | ||
| 325 | Core::System& system{Core::System::GetInstance()}; | 341 | Core::System& system{Core::System::GetInstance()}; |
| 326 | 342 | ||
| 343 | system.SetGPUDebugContext(debug_context); | ||
| 344 | |||
| 327 | const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; | 345 | const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; |
| 328 | 346 | ||
| 329 | Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); | 347 | Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..2471caf83 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -15,17 +15,18 @@ class Config; | |||
| 15 | class EmuThread; | 15 | class EmuThread; |
| 16 | class GameList; | 16 | class GameList; |
| 17 | class GImageInfo; | 17 | class GImageInfo; |
| 18 | class GPUCommandStreamWidget; | ||
| 19 | class GPUCommandListWidget; | ||
| 20 | class GraphicsBreakPointsWidget; | 18 | class GraphicsBreakPointsWidget; |
| 21 | class GraphicsTracingWidget; | 19 | class GraphicsSurfaceWidget; |
| 22 | class GraphicsVertexShaderWidget; | ||
| 23 | class GRenderWindow; | 20 | class GRenderWindow; |
| 24 | class MicroProfileDialog; | 21 | class MicroProfileDialog; |
| 25 | class ProfilerWidget; | 22 | class ProfilerWidget; |
| 26 | class RegistersWidget; | 23 | class RegistersWidget; |
| 27 | class WaitTreeWidget; | 24 | class WaitTreeWidget; |
| 28 | 25 | ||
| 26 | namespace Tegra { | ||
| 27 | class DebugContext; | ||
| 28 | } | ||
| 29 | |||
| 29 | class GMainWindow : public QMainWindow { | 30 | class GMainWindow : public QMainWindow { |
| 30 | Q_OBJECT | 31 | Q_OBJECT |
| 31 | 32 | ||
| @@ -138,6 +139,8 @@ private: | |||
| 138 | 139 | ||
| 139 | Ui::MainWindow ui; | 140 | Ui::MainWindow ui; |
| 140 | 141 | ||
| 142 | std::shared_ptr<Tegra::DebugContext> debug_context; | ||
| 143 | |||
| 141 | GRenderWindow* render_window; | 144 | GRenderWindow* render_window; |
| 142 | GameList* game_list; | 145 | GameList* game_list; |
| 143 | 146 | ||
| @@ -158,6 +161,8 @@ private: | |||
| 158 | ProfilerWidget* profilerWidget; | 161 | ProfilerWidget* profilerWidget; |
| 159 | MicroProfileDialog* microProfileDialog; | 162 | MicroProfileDialog* microProfileDialog; |
| 160 | RegistersWidget* registersWidget; | 163 | RegistersWidget* registersWidget; |
| 164 | GraphicsBreakPointsWidget* graphicsBreakpointsWidget; | ||
| 165 | GraphicsSurfaceWidget* graphicsSurfaceWidget; | ||
| 161 | WaitTreeWidget* waitTreeWidget; | 166 | WaitTreeWidget* waitTreeWidget; |
| 162 | 167 | ||
| 163 | QAction* actions_recent_files[max_recent_files_item]; | 168 | QAction* actions_recent_files[max_recent_files_item]; |