From 71ebc3e90da9882139d2a5695ca1ed59ea77e94f Mon Sep 17 00:00:00 2001 From: Subv Date: Mon, 19 Mar 2018 18:00:29 -0500 Subject: [PATCH 1/9] GPU: Preliminary work for texture decoding. --- src/video_core/CMakeLists.txt | 3 ++ src/video_core/engines/maxwell_3d.cpp | 45 +++++++++++++++++++++ src/video_core/textures/decoders.cpp | 14 +++++++ src/video_core/textures/decoders.h | 20 ++++++++++ src/video_core/textures/texture.h | 57 +++++++++++++++++++++++++++ 5 files changed, 139 insertions(+) create mode 100644 src/video_core/textures/decoders.cpp create mode 100644 src/video_core/textures/decoders.h create mode 100644 src/video_core/textures/texture.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index e56253c4c..8c0e6663b 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -31,6 +31,9 @@ add_library(video_core STATIC renderer_opengl/gl_stream_buffer.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h + textures/decoders.cpp + textures/decoders.h + textures/texture.h utils.h video_core.cpp video_core.h diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 4d9745e48..ca1b150a7 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -2,8 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "common/assert.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" namespace Tegra { namespace Engines { @@ -160,6 +163,48 @@ void Maxwell3D::ProcessQueryGet() { void Maxwell3D::DrawArrays() { LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); + + auto& fragment_shader = state.shader_stages[static_cast(Regs::ShaderStage::Fragment)]; + auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; + ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); + + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; + + for (GPUVAddr current_texture = tex_info_buffer.address + 0x20; + current_texture < tex_info_buffer_end; current_texture += 4) { + + Texture::TextureHandle tex_info{ + Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))}; + + if (tex_info.tic_id != 0 || tex_info.tsc_id != 0) { + GPUVAddr tic_address_gpu = + tic_base_address + tex_info.tic_id * sizeof(Texture::TICEntry); + VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu); + + Texture::TICEntry tic_entry; + Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); + + auto r_type = tic_entry.r_type.Value(); + auto g_type = tic_entry.g_type.Value(); + auto b_type = tic_entry.b_type.Value(); + auto a_type = tic_entry.a_type.Value(); + + // TODO(Subv): Different data types for separate components are not supported + ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + + auto format = tic_entry.format.Value(); + + auto texture = Texture::DecodeTexture( + memory_manager.PhysicalToVirtualAddress(tic_entry.Address()), + tic_entry.format.Value(), tic_entry.Width(), tic_entry.Height()); + + LOG_CRITICAL(HW_GPU, + "Fragment shader using texture TIC %08X TSC %08X at address %016" PRIX64, + tex_info.tic_id.Value(), tex_info.tsc_id.Value(), tic_entry.Address()); + } + } } void Maxwell3D::BindTextureInfoBuffer(const std::vector& parameters) { diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp new file mode 100644 index 000000000..705e2e066 --- /dev/null +++ b/src/video_core/textures/decoders.cpp @@ -0,0 +1,14 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/textures/decoders.h" + +namespace Tegra { +namespace Texture { + +std::vector DecodeTexture(VAddr address, TextureFormat format, u32 width, u32 height) { + return {}; +} +} +} // namespace Tegra diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h new file mode 100644 index 000000000..e0d55600e --- /dev/null +++ b/src/video_core/textures/decoders.h @@ -0,0 +1,20 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "video_core/textures/texture.h" + +namespace Tegra { +namespace Texture { + +/** + * Decodes a swizzled texture into a RGBA8888 texture. + */ +std::vector DecodeTexture(VAddr address, TextureFormat format, u32 width, u32 height); + +} // namespace Texture +} // namespace Tegra diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h new file mode 100644 index 000000000..3306d2ab2 --- /dev/null +++ b/src/video_core/textures/texture.h @@ -0,0 +1,57 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/memory_manager.h" + +namespace Tegra { +namespace Texture { + +enum class TextureFormat : u32 { + DXT1 = 0x24, +}; + +union TextureHandle { + u32 raw; + BitField<0, 20, u32> tic_id; + BitField<20, 12, u32> tsc_id; +}; + +struct TICEntry { + union { + u32 raw; + BitField<0, 7, TextureFormat> format; + BitField<7, 3, u32> r_type; + BitField<10, 3, u32> g_type; + BitField<13, 3, u32> b_type; + BitField<16, 3, u32> a_type; + }; + u32 address_low; + u16 address_high; + INSERT_PADDING_BYTES(6); + u16 width_minus_1; + INSERT_PADDING_BYTES(2); + u16 height_minus_1; + INSERT_PADDING_BYTES(10); + + GPUVAddr Address() const { + return static_cast((static_cast(address_high) << 32) | address_low); + } + + u32 Width() const { + return width_minus_1 + 1; + } + + u32 Height() const { + return height_minus_1 + 1; + } +}; +static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); + +} // namespace Texture +} // namespace Tegra From 1b8d798835c2d39c2867f53d8dcacdc7d0ba0d15 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 15:17:10 -0500 Subject: [PATCH 2/9] GPU: Added a method to unswizzle a texture without decoding it. Allow unswizzling of DXT1 textures. --- src/video_core/engines/maxwell_3d.cpp | 2 +- src/video_core/textures/decoders.cpp | 84 ++++++++++++++++++++++++++- src/video_core/textures/decoders.h | 10 +++- src/video_core/textures/texture.h | 4 ++ 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index ca1b150a7..d1edfe09a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -196,7 +196,7 @@ void Maxwell3D::DrawArrays() { auto format = tic_entry.format.Value(); - auto texture = Texture::DecodeTexture( + auto texture = Texture::UnswizzleTexture( memory_manager.PhysicalToVirtualAddress(tic_entry.Address()), tic_entry.format.Value(), tic_entry.Width(), tic_entry.Height()); diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 705e2e066..300267209 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -2,13 +2,93 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include "common/assert.h" #include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" namespace Tegra { namespace Texture { -std::vector DecodeTexture(VAddr address, TextureFormat format, u32 width, u32 height) { - return {}; +/** + * Calculates the offset of an (x, y) position within a swizzled texture. + * Taken from the Tegra X1 TRM. + */ +static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) { + u32 image_width_in_gobs = image_width * bytes_per_pixel / 64; + u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (x * bytes_per_pixel / 64) * 512 * block_height + + (y % (8 * block_height) / 8) * 512; + x *= bytes_per_pixel; + u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + + (y % 2) * 16 + (x % 16); + + return address; } + +static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, + u8* swizzled_data, u8* unswizzled_data, bool unswizzle, + u32 block_height) { + u8* data_ptrs[2]; + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height); + u32 pixel_index = (x + y * width) * out_bytes_per_pixel; + + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = &unswizzled_data[pixel_index]; + + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } } + +u32 BytesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::DXT1: + // In this case a 'pixel' actually refers to a 4x4 tile. + return 8; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } +} + +std::vector UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) { + u8* data = Memory::GetPointer(address); + u32 bytes_per_pixel = BytesPerPixel(format); + + static constexpr u32 DefaultBlockHeight = 16; + + std::vector unswizzled_data(width * height * bytes_per_pixel); + + switch (format) { + case TextureFormat::DXT1: + // In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values. + CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, DefaultBlockHeight); + break; + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return unswizzled_data; +} + +std::vector DecodeTexture(const std::vector& texture_data, TextureFormat format, u32 width, + u32 height) { + std::vector rgba_data; + + // TODO(Subv): Implement. + switch (format) { + default: + UNIMPLEMENTED_MSG("Format not implemented"); + break; + } + + return rgba_data; +} + +} // namespace Texture } // namespace Tegra diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index e0d55600e..0c21694ff 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -12,9 +12,15 @@ namespace Tegra { namespace Texture { /** - * Decodes a swizzled texture into a RGBA8888 texture. + * Unswizzles a swizzled texture without changing its format. */ -std::vector DecodeTexture(VAddr address, TextureFormat format, u32 width, u32 height); +std::vector UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height); + +/** + * Decodes an unswizzled texture into a A8R8G8B8 texture. + */ +std::vector DecodeTexture(const std::vector& texture_data, TextureFormat format, u32 width, + u32 height); } // namespace Texture } // namespace Tegra diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 3306d2ab2..d969bcdd9 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -13,6 +13,7 @@ namespace Tegra { namespace Texture { enum class TextureFormat : u32 { + A8R8G8B8 = 8, DXT1 = 0x24, }; @@ -53,5 +54,8 @@ struct TICEntry { }; static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size"); +/// Returns the number of bytes per pixel of the input texture format. +u32 BytesPerPixel(TextureFormat format); + } // namespace Texture } // namespace Tegra From 77fd0d47e70968bcbc87a3b5607cd29e6211f656 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 15:19:35 -0500 Subject: [PATCH 3/9] Frontend: Ported the GPU breakpoints and surface viewer widgets from citra. --- src/video_core/CMakeLists.txt | 2 + src/video_core/debug_utils/debug_utils.cpp | 66 +++ src/video_core/debug_utils/debug_utils.h | 165 +++++++ src/video_core/gpu.cpp | 4 + src/video_core/gpu.h | 5 + src/yuzu/CMakeLists.txt | 7 + .../graphics/graphics_breakpoint_observer.cpp | 27 ++ .../graphics/graphics_breakpoint_observer.h | 33 ++ .../graphics/graphics_breakpoints.cpp | 212 +++++++++ .../debugger/graphics/graphics_breakpoints.h | 46 ++ .../graphics/graphics_breakpoints_p.h | 36 ++ .../debugger/graphics/graphics_surface.cpp | 445 ++++++++++++++++++ src/yuzu/debugger/graphics/graphics_surface.h | 97 ++++ src/yuzu/main.cpp | 9 + src/yuzu/main.h | 5 +- 15 files changed, 1155 insertions(+), 4 deletions(-) create mode 100644 src/video_core/debug_utils/debug_utils.cpp create mode 100644 src/video_core/debug_utils/debug_utils.h create mode 100644 src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp create mode 100644 src/yuzu/debugger/graphics/graphics_breakpoint_observer.h create mode 100644 src/yuzu/debugger/graphics/graphics_breakpoints.cpp create mode 100644 src/yuzu/debugger/graphics/graphics_breakpoints.h create mode 100644 src/yuzu/debugger/graphics/graphics_breakpoints_p.h create mode 100644 src/yuzu/debugger/graphics/graphics_surface.cpp create mode 100644 src/yuzu/debugger/graphics/graphics_surface.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 8c0e6663b..3dab81769 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(video_core STATIC command_processor.cpp command_processor.h + debug_utils/debug_utils.cpp + debug_utils/debug_utils.h engines/fermi_2d.cpp engines/fermi_2d.h engines/maxwell_3d.cpp 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..73fd4d7a3 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -0,0 +1,66 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/color.h" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/vector_math.h" +#include "video_core/debug_utils/debug_utils.h" + +namespace Tegra { + +std::shared_ptr g_debug_context; + +void DebugContext::DoOnEvent(Event event, void* data) { + { + std::unique_lock lock(breakpoint_mutex); + + // TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will + // show on debug widgets + + // TODO: Should stop the CPU thread here once we multithread emulation. + + active_breakpoint = event; + at_breakpoint = true; + + // Tell all observers that we hit a breakpoint + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellBreakPointHit(event, data); + } + + // Wait until another thread tells us to Resume() + resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); + } +} + +void DebugContext::Resume() { + { + std::lock_guard lock(breakpoint_mutex); + + // Tell all observers that we are about to resume + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnMaxwellResume(); + } + + // Resume the waiting thread (i.e. OnEvent()) + at_breakpoint = false; + } + + resume_from_breakpoint.notify_one(); +} + +} // 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..98461d6d9 --- /dev/null +++ b/src/video_core/debug_utils/debug_utils.h @@ -0,0 +1,165 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/vector_math.h" + +namespace Tegra { + +class DebugContext { +public: + enum class Event { + FirstEvent = 0, + + MaxwellCommandLoaded = FirstEvent, + MaxwellCommandProcessed, + IncomingPrimitiveBatch, + FinishedPrimitiveBatch, + + NumEvents + }; + + /** + * Inherit from this class to be notified of events registered to some debug context. + * Most importantly this is used for our debugger GUI. + * + * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods. + * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state + * access + * @todo Evaluate an alternative interface, in which there is only one managing observer and + * multiple child observers running (by design) on the same thread. + */ + class BreakPointObserver { + public: + /// Constructs the object such that it observes events of the given DebugContext. + BreakPointObserver(std::shared_ptr debug_context) + : context_weak(debug_context) { + std::unique_lock lock(debug_context->breakpoint_mutex); + debug_context->breakpoint_observers.push_back(this); + } + + virtual ~BreakPointObserver() { + auto context = context_weak.lock(); + if (context) { + std::unique_lock lock(context->breakpoint_mutex); + context->breakpoint_observers.remove(this); + + // If we are the last observer to be destroyed, tell the debugger context that + // it is free to continue. In particular, this is required for a proper yuzu + // shutdown, when the emulation thread is waiting at a breakpoint. + if (context->breakpoint_observers.empty()) + context->Resume(); + } + } + + /** + * Action to perform when a breakpoint was reached. + * @param event Type of event which triggered the breakpoint + * @param data Optional data pointer (if unused, this is a nullptr) + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellBreakPointHit(Event event, void* data) {} + + /** + * Action to perform when emulation is resumed from a breakpoint. + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnMaxwellResume() {} + + protected: + /** + * Weak context pointer. This need not be valid, so when requesting a shared_ptr via + * context_weak.lock(), always compare the result against nullptr. + */ + std::weak_ptr context_weak; + }; + + /** + * Simple structure defining a breakpoint state + */ + struct BreakPoint { + bool enabled = false; + }; + + /** + * Static constructor used to create a shared_ptr of a DebugContext. + */ + static std::shared_ptr Construct() { + return std::shared_ptr(new DebugContext); + } + + /** + * Used by the emulation core when a given event has happened. If a breakpoint has been set + * for this event, OnEvent calls the event handlers of the registered breakpoint observers. + * The current thread then is halted until Resume() is called from another thread (or until + * emulation is stopped). + * @param event Event which has happened + * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until + * Resume() is called. + */ + void OnEvent(Event event, void* data) { + // This check is left in the header to allow the compiler to inline it. + if (!breakpoints[(int)event].enabled) + return; + // For the rest of event handling, call a separate function. + DoOnEvent(event, data); + } + + void DoOnEvent(Event event, void* data); + + /** + * Resume from the current breakpoint. + * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. + * Calling from any other thread is safe. + */ + void Resume(); + + /** + * Delete all set breakpoints and resume emulation. + */ + void ClearBreakpoints() { + for (auto& bp : breakpoints) { + bp.enabled = false; + } + Resume(); + } + + // TODO: Evaluate if access to these members should be hidden behind a public interface. + std::array breakpoints; + Event active_breakpoint; + bool at_breakpoint = false; + +private: + /** + * Private default constructor to make sure people always construct this through Construct() + * instead. + */ + DebugContext() = default; + + /// Mutex protecting current breakpoint state and the observer list. + std::mutex breakpoint_mutex; + + /// Used by OnEvent to wait for resumption. + std::condition_variable resume_from_breakpoint; + + /// List of registered observers + std::list breakpoint_observers; +}; + +extern std::shared_ptr g_debug_context; + +} // namespace Tegra 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() { GPU::~GPU() = default; +const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const { + return *maxwell_3d; +} + } // namespace Tegra diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 206b3e05e..778b63218 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -13,6 +13,8 @@ namespace Tegra { +class DebugContext; + /** * Struct describing framebuffer configuration */ @@ -66,6 +68,9 @@ public: /// Processes a command list stored at the specified address in GPU memory. void ProcessCommandList(GPUVAddr address, u32 size); + /// Returns a reference to the Maxwell3D GPU engine. + const Engines::Maxwell3D& Get3DEngine() const; + std::unique_ptr memory_manager; Engines::Maxwell3D& Maxwell3D() { 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 configuration/configure_input.h configuration/configure_system.cpp configuration/configure_system.h + debugger/graphics/graphics_breakpoint_observer.cpp + debugger/graphics/graphics_breakpoint_observer.h + debugger/graphics/graphics_breakpoints.cpp + debugger/graphics/graphics_breakpoints.h + debugger/graphics/graphics_breakpoints_p.h + debugger/graphics/graphics_surface.cpp + debugger/graphics/graphics_surface.h debugger/profiler.cpp debugger/profiler.h 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 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) { + qRegisterMetaType("Tegra::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnMaxwellResume() { + emit Resumed(); +} 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 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "video_core/debug_utils/debug_utils.h" + +/** + * Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots. + * This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while + * the widget usually wants to perform reactions in the GUI thread. + */ +class BreakPointObserverDock : public QDockWidget, + protected Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +private slots: + virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); +}; 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 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_breakpoints_p.h" + +BreakPointModel::BreakPointModel(std::shared_ptr debug_context, + QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) {} + +int BreakPointModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const { + return static_cast(Tegra::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const { + const auto event = static_cast(index.row()); + + switch (role) { + case Qt::DisplayRole: { + if (index.column() == 0) { + static const std::map map = { + {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")}, + {Tegra::DebugContext::Event::MaxwellCommandProcessed, + tr("Maxwell command processed")}, + {Tegra::DebugContext::Event::IncomingPrimitiveBatch, + tr("Incoming primitive batch")}, + {Tegra::DebugContext::Event::FinishedPrimitiveBatch, + tr("Finished primitive batch")}, + }; + + DEBUG_ASSERT(map.size() == static_cast(Tegra::DebugContext::Event::NumEvents)); + return (map.find(event) != map.end()) ? map.at(event) : QString(); + } + + break; + } + + case Qt::CheckStateRole: { + if (index.column() == 0) + return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked; + break; + } + + case Qt::BackgroundRole: { + if (at_breakpoint && index.row() == static_cast(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: { + auto context = context_weak.lock(); + return context && context->breakpoints[(int)event].enabled; + } + + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const { + if (!index.isValid()) + return 0; + + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if (index.column() == 0) + flags |= Qt::ItemIsUserCheckable; + return flags; +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) { + const auto event = static_cast(index.row()); + + switch (role) { + case Qt::CheckStateRole: { + if (index.column() != 0) + return false; + + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[(int)event].enabled = value == Qt::Checked; + QModelIndex changed_index = createIndex(index.row(), 0); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + +void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) { + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast(event), 0), + createIndex(static_cast(event), 0)); +} + +void BreakPointModel::OnResumed() { + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast(active_breakpoint), 0), + createIndex(static_cast(active_breakpoint), 0)); + active_breakpoint = context->active_breakpoint; +} + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( + std::shared_ptr debug_context, QWidget* parent) + : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver( + debug_context) { + setObjectName("TegraBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setRootIsDecorated(false); + breakpoint_list->setHeaderHidden(true); + breakpoint_list->setModel(breakpoint_model); + + qRegisterMetaType("Tegra::DebugContext::Event"); + + connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, + SLOT(OnItemDoubleClicked(const QModelIndex&))); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, + SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) { + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnMaxwellResume() { + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() { + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() { + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) { + if (!index.isValid()) + return; + + QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0); + QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole); + QVariant new_state = Qt::Unchecked; + if (enabled == Qt::Unchecked) + new_state = Qt::Checked; + breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole); +} 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 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel; + +class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + +public: + explicit GraphicsBreakPointsWidget(std::shared_ptr debug_context, + QWidget* parent = nullptr); + + void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnMaxwellResume() override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); + void OnItemDoubleClicked(const QModelIndex&); + void OnResumeRequested(); + void OnResumed(); + +signals: + void Resumed(); + void BreakPointHit(Tegra::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + QLabel* status_text; + QPushButton* resume_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; 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 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + +public slots: + void OnBreakPointHit(Tegra::DebugContext::Event event); + void OnResumed(); + +private: + std::weak_ptr context_weak; + bool at_breakpoint; + Tegra::DebugContext::Event active_breakpoint; +}; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp new file mode 100644 index 000000000..54b816054 --- /dev/null +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -0,0 +1,445 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core/core.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" +#include "video_core/utils.h" +#include "yuzu/debugger/graphics/graphics_surface.h" +#include "yuzu/util/spinbox.h" + +SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) + : QLabel(parent), surface_widget(surface_widget_) {} +SurfacePicture::~SurfacePicture() {} + +void SurfacePicture::mousePressEvent(QMouseEvent* event) { + // Only do something while the left mouse button is held down + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (pixmap() == nullptr) + return; + + if (surface_widget) + surface_widget->Pick(event->x() * pixmap()->width() / width(), + event->y() * pixmap()->height() / height()); +} + +void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { + // We also want to handle the event if the user moves the mouse while holding down the LMB + mousePressEvent(event); +} + +GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), + surface_source(Source::RenderTarget0) { + setObjectName("MaxwellSurface"); + + surface_source_list = new QComboBox; + surface_source_list->addItem(tr("Render Target 0")); + surface_source_list->addItem(tr("Render Target 1")); + surface_source_list->addItem(tr("Render Target 2")); + surface_source_list->addItem(tr("Render Target 3")); + surface_source_list->addItem(tr("Render Target 4")); + surface_source_list->addItem(tr("Render Target 5")); + surface_source_list->addItem(tr("Render Target 6")); + surface_source_list->addItem(tr("Render Target 7")); + surface_source_list->addItem(tr("Z Buffer")); + surface_source_list->addItem(tr("Custom")); + surface_source_list->setCurrentIndex(static_cast(surface_source)); + + surface_address_control = new CSpinBox; + surface_address_control->SetBase(16); + surface_address_control->SetRange(0, 0xFFFFFFFF); + surface_address_control->SetPrefix("0x"); + + unsigned max_dimension = 16384; // TODO: Find actual maximum + + surface_width_control = new QSpinBox; + surface_width_control->setRange(0, max_dimension); + + surface_height_control = new QSpinBox; + surface_height_control->setRange(0, max_dimension); + + surface_picker_x_control = new QSpinBox; + surface_picker_x_control->setRange(0, max_dimension - 1); + + surface_picker_y_control = new QSpinBox; + surface_picker_y_control->setRange(0, max_dimension - 1); + + surface_format_control = new QComboBox; + + // Color formats sorted by Maxwell texture format index + surface_format_control->addItem(tr("None")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("A8R8G8B8")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("Unknown")); + surface_format_control->addItem(tr("DXT1")); + surface_format_control->addItem(tr("DXT23")); + surface_format_control->addItem(tr("DXT45")); + surface_format_control->addItem(tr("DXN1")); + surface_format_control->addItem(tr("DXN2")); + + surface_info_label = new QLabel(); + surface_info_label->setWordWrap(true); + + surface_picture_label = new SurfacePicture(0, this); + surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); + surface_picture_label->setScaledContents(false); + + auto scroll_area = new QScrollArea(); + scroll_area->setBackgroundRole(QPalette::Dark); + scroll_area->setWidgetResizable(false); + scroll_area->setWidget(surface_picture_label); + + save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceSourceChanged(int))); + connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, + SLOT(OnSurfaceAddressChanged(qint64))); + connect(surface_width_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceWidthChanged(int))); + connect(surface_height_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfaceHeightChanged(int))); + connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, + SLOT(OnSurfaceFormatChanged(int))); + connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerXChanged(int))); + connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, + SLOT(OnSurfacePickerYChanged(int))); + connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(surface_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("GPU Address:"))); + sub_layout->addWidget(surface_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(surface_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(surface_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(surface_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(scroll_area); + + auto info_layout = new QHBoxLayout; + { + auto xy_layout = new QVBoxLayout; + { + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("X:"))); + sub_layout->addWidget(surface_picker_x_control); + xy_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Y:"))); + sub_layout->addWidget(surface_picker_y_control); + xy_layout->addLayout(sub_layout); + } + } + info_layout->addLayout(xy_layout); + surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + info_layout->addWidget(surface_info_label); + } + main_layout->addLayout(info_layout); + + main_layout->addWidget(save_surface); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + if (debug_context && debug_context->at_breakpoint) { + emit Update(); + widget()->setEnabled(debug_context->at_breakpoint); + } else { + widget()->setEnabled(false); + } +} + +void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsSurfaceWidget::OnResumed() { + widget()->setEnabled(false); +} + +void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { + surface_source = static_cast(new_value); + emit Update(); +} + +void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { + if (surface_address != new_value) { + surface_address = static_cast(new_value); + + surface_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { + if (surface_width != static_cast(new_value)) { + surface_width = static_cast(new_value); + + surface_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { + if (surface_height != static_cast(new_value)) { + surface_height = static_cast(new_value); + + surface_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { + if (surface_format != static_cast(new_value)) { + surface_format = static_cast(new_value); + + surface_source_list->setCurrentIndex(static_cast(Source::Custom)); + emit Update(); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { + if (surface_picker_x != new_value) { + surface_picker_x = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { + if (surface_picker_y != new_value) { + surface_picker_y = new_value; + Pick(surface_picker_x, surface_picker_y); + } +} + +void GraphicsSurfaceWidget::Pick(int x, int y) { + surface_picker_x_control->setValue(x); + surface_picker_y_control->setValue(y); + + if (x < 0 || x >= static_cast(surface_width) || y < 0 || + y >= static_cast(surface_height)) { + surface_info_label->setText(tr("Pixel out of bounds")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + return; + } + + u8* buffer = Memory::GetPhysicalPointer(surface_address); + if (buffer == nullptr) { + surface_info_label->setText(tr("(unable to access pixel data)")); + surface_info_label->setAlignment(Qt::AlignCenter); + return; + } + + surface_info_label->setText(QString("Raw: \n(%1)").arg("")); + surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); +} + +void GraphicsSurfaceWidget::OnUpdate() { + auto& gpu = Core::System::GetInstance().GPU(); + + QPixmap pixmap; + + Tegra::GPUVAddr surface_address = 0; + + switch (surface_source) { + case Source::RenderTarget0: + case Source::RenderTarget1: + case Source::RenderTarget2: + case Source::RenderTarget3: + case Source::RenderTarget4: + case Source::RenderTarget5: + case Source::RenderTarget6: + case Source::RenderTarget7: { + // TODO: Store a reference to the registers in the debug context instead of accessing them + // directly... + + auto& registers = gpu.Get3DEngine().regs; + + surface_address = 0; + surface_width = 0; + surface_height = 0; + surface_format = Tegra::Texture::TextureFormat::DXT1; + + break; + } + + case Source::Custom: { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown surface source " << static_cast(surface_source); + break; + } + + surface_address_control->SetValue(surface_address); + surface_width_control->setValue(surface_width); + surface_height_control->setValue(surface_height); + surface_format_control->setCurrentIndex(static_cast(surface_format)); + + if (surface_address == 0) { + surface_picture_label->hide(); + surface_info_label->setText(tr("(invalid surface address)")); + surface_info_label->setAlignment(Qt::AlignCenter); + surface_picker_x_control->setEnabled(false); + surface_picker_y_control->setEnabled(false); + save_surface->setEnabled(false); + return; + } + + // TODO: Implement a good way to visualize alpha components! + + QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); + VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + + auto unswizzled_data = + Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height); + + auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, + surface_width, surface_height); + + ASSERT(texture_data.size() == + surface_width * surface_height * + Tegra::Texture::BytesPerPixel(Tegra::Texture::TextureFormat::A8R8G8B8)); + surface_picture_label->show(); + + for (unsigned int y = 0; y < surface_height; ++y) { + for (unsigned int x = 0; x < surface_width; ++x) { + Math::Vec4 color; + color[0] = texture_data[x + y * surface_width + 0]; + color[1] = texture_data[x + y * surface_width + 1]; + color[2] = texture_data[x + y * surface_width + 2]; + color[3] = texture_data[x + y * surface_width + 3]; + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + pixmap = QPixmap::fromImage(decoded_image); + surface_picture_label->setPixmap(pixmap); + surface_picture_label->resize(pixmap.size()); + + // Update the info with pixel data + surface_picker_x_control->setEnabled(true); + surface_picker_y_control->setEnabled(true); + Pick(surface_picker_x, surface_picker_y); + + // Enable saving the converted pixmap to file + save_surface->setEnabled(true); +} + +void GraphicsSurfaceWidget::SaveSurface() { + QString png_filter = tr("Portable Network Graphic (*.png)"); + QString bin_filter = tr("Binary data (*.bin)"); + + QString selectedFilter; + QString filename = QFileDialog::getSaveFileName( + this, tr("Save Surface"), + QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), + QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); + + if (filename.isEmpty()) { + // If the user canceled the dialog, don't save anything. + return; + } + + if (selectedFilter == png_filter) { + const QPixmap* pixmap = surface_picture_label->pixmap(); + ASSERT_MSG(pixmap != nullptr, "No pixmap set"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + if (pixmap) + pixmap->save(&file, "PNG"); + } else if (selectedFilter == bin_filter) { + const u8* buffer = Memory::GetPhysicalPointer(surface_address); + ASSERT_MSG(buffer != nullptr, "Memory not accessible"); + + QFile file(filename); + file.open(QIODevice::WriteOnly); + int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); + QByteArray data(reinterpret_cast(buffer), size); + file.write(data); + } else { + UNREACHABLE_MSG("Unhandled filter selected"); + } +} 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 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" +#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" + +class QComboBox; +class QSpinBox; +class CSpinBox; + +class GraphicsSurfaceWidget; + +class SurfacePicture : public QLabel { + Q_OBJECT + +public: + explicit SurfacePicture(QWidget* parent = nullptr, + GraphicsSurfaceWidget* surface_widget = nullptr); + ~SurfacePicture(); + +protected slots: + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + +private: + GraphicsSurfaceWidget* surface_widget; +}; + +class GraphicsSurfaceWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Tegra::DebugContext::Event; + + enum class Source { + RenderTarget0 = 0, + RenderTarget1 = 1, + RenderTarget2 = 2, + RenderTarget3 = 3, + RenderTarget4 = 4, + RenderTarget5 = 5, + RenderTarget6 = 6, + RenderTarget7 = 7, + ZBuffer = 8, + Custom = 9, + }; + +public: + explicit GraphicsSurfaceWidget(std::shared_ptr debug_context, + QWidget* parent = nullptr); + void Pick(int x, int y); + +public slots: + void OnSurfaceSourceChanged(int new_value); + void OnSurfaceAddressChanged(qint64 new_value); + void OnSurfaceWidthChanged(int new_value); + void OnSurfaceHeightChanged(int new_value); + void OnSurfaceFormatChanged(int new_value); + void OnSurfacePickerXChanged(int new_value); + void OnSurfacePickerYChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; + void OnResumed() override; + + void SaveSurface(); + +signals: + void Update(); + +private: + QComboBox* surface_source_list; + CSpinBox* surface_address_control; + QSpinBox* surface_width_control; + QSpinBox* surface_height_control; + QComboBox* surface_format_control; + + SurfacePicture* surface_picture_label; + QSpinBox* surface_picker_x_control; + QSpinBox* surface_picker_y_control; + QLabel* surface_info_label; + QPushButton* save_surface; + + Source surface_source; + Tegra::GPUVAddr surface_address; + unsigned surface_width; + unsigned surface_height; + Tegra::Texture::TextureFormat surface_format; + int surface_picker_x = 0; + int surface_picker_y = 0; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index eb22a8ccf..7b065ee7b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -29,6 +29,7 @@ #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/debugger/graphics/graphics_breakpoints.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/registers.h" #include "yuzu/debugger/wait_tree.h" @@ -68,6 +69,9 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { void GMainWindow::ShowCallouts() {} GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { + + Tegra::g_debug_context = Tegra::DebugContext::Construct(); + setAcceptDrops(true); ui.setupUi(this); statusBar()->hide(); @@ -160,6 +164,11 @@ void GMainWindow::InitializeDebugWidgets() { connect(this, &GMainWindow::EmulationStopping, registersWidget, &RegistersWidget::OnEmulationStopping); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Tegra::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4a0d912bb..86528f5b0 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -15,11 +15,7 @@ class Config; class EmuThread; class GameList; class GImageInfo; -class GPUCommandStreamWidget; -class GPUCommandListWidget; class GraphicsBreakPointsWidget; -class GraphicsTracingWidget; -class GraphicsVertexShaderWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; @@ -158,6 +154,7 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; RegistersWidget* registersWidget; + GraphicsBreakPointsWidget* graphicsBreakpointsWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; From 1ad97c75a0fe94000b0f54ff43ca5d29d8a9edc6 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 15:25:17 -0500 Subject: [PATCH 4/9] GPU: Implement the MaxwellCommandLoaded/Processed debug breakpoints. --- src/video_core/engines/maxwell_3d.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index d1edfe09a..ae6a4d5f1 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/textures/decoders.h" #include "video_core/textures/texture.h" @@ -75,6 +76,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { return; } + if (Tegra::g_debug_context) { + Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); + } + regs.reg_array[method] = value; #define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32)) @@ -140,6 +145,11 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { } #undef MAXWELL3D_REG_INDEX + + if (Tegra::g_debug_context) { + Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, + nullptr); + } } void Maxwell3D::ProcessQueryGet() { From 1c31e2b3d2efb3a6425518df62cd1c277367f17a Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 15:27:28 -0500 Subject: [PATCH 5/9] GPU: Implement the Incoming/FinishedPrimitiveBatch debug breakpoints. --- src/video_core/engines/maxwell_3d.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index ae6a4d5f1..aa375f51f 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -173,6 +173,9 @@ void Maxwell3D::ProcessQueryGet() { void Maxwell3D::DrawArrays() { LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); + if (Tegra::g_debug_context) { + Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); + } auto& fragment_shader = state.shader_stages[static_cast(Regs::ShaderStage::Fragment)]; auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; @@ -215,6 +218,10 @@ void Maxwell3D::DrawArrays() { tex_info.tic_id.Value(), tex_info.tsc_id.Value(), tic_entry.Address()); } } + + if (Tegra::g_debug_context) { + Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } } void Maxwell3D::BindTextureInfoBuffer(const std::vector& parameters) { From 025d1113081c2630f453f6cdc837ae98398c9ba5 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 15:30:43 -0500 Subject: [PATCH 6/9] Frontend: Allow opening the Surface View widget in the Qt frontend. --- src/yuzu/main.cpp | 6 ++++++ src/yuzu/main.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7b065ee7b..b8c23ae15 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -30,6 +30,7 @@ #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" #include "yuzu/debugger/graphics/graphics_breakpoints.h" +#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/registers.h" #include "yuzu/debugger/wait_tree.h" @@ -169,6 +170,11 @@ void GMainWindow::InitializeDebugWidgets() { graphicsBreakpointsWidget->hide(); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + graphicsSurfaceWidget = new GraphicsSurfaceWidget(Tegra::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); + graphicsSurfaceWidget->hide(); + debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); + waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 86528f5b0..0f89607c8 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -16,6 +16,7 @@ class EmuThread; class GameList; class GImageInfo; class GraphicsBreakPointsWidget; +class GraphicsSurfaceWidget; class GRenderWindow; class MicroProfileDialog; class ProfilerWidget; @@ -155,6 +156,7 @@ private: MicroProfileDialog* microProfileDialog; RegistersWidget* registersWidget; GraphicsBreakPointsWidget* graphicsBreakpointsWidget; + GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; From 39e60cfeb10ef317521ff1685df3d265d2c9d5ef Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 16:40:11 -0500 Subject: [PATCH 7/9] Frontend: Updated the surface view debug widget to work with Maxwell surfaces. --- src/video_core/gpu.h | 4 ++ src/video_core/textures/decoders.cpp | 11 +++++ .../debugger/graphics/graphics_surface.cpp | 42 ++++++++++--------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 778b63218..8183b12e9 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -13,6 +13,10 @@ namespace Tegra { +enum class RenderTargetFormat { + RGBA8_UNORM = 0xD5, +}; + class DebugContext; /** diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 300267209..2e87281eb 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -48,6 +48,8 @@ u32 BytesPerPixel(TextureFormat format) { case TextureFormat::DXT1: // In this case a 'pixel' actually refers to a 4x4 tile. return 8; + case TextureFormat::A8R8G8B8: + return 4; default: UNIMPLEMENTED_MSG("Format not implemented"); break; @@ -68,6 +70,10 @@ std::vector UnswizzleTexture(VAddr address, TextureFormat format, u32 width, CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data, unswizzled_data.data(), true, DefaultBlockHeight); break; + case TextureFormat::A8R8G8B8: + CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data, + unswizzled_data.data(), true, DefaultBlockHeight); + break; default: UNIMPLEMENTED_MSG("Format not implemented"); break; @@ -82,6 +88,11 @@ std::vector DecodeTexture(const std::vector& texture_data, TextureFormat // TODO(Subv): Implement. switch (format) { + case TextureFormat::DXT1: + case TextureFormat::A8R8G8B8: + // TODO(Subv): For the time being just forward the same data without any decoding. + rgba_data = texture_data; + break; default: UNIMPLEMENTED_MSG("Format not implemented"); break; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 54b816054..d061013da 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -13,12 +13,23 @@ #include #include "core/core.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" #include "video_core/textures/decoders.h" #include "video_core/textures/texture.h" #include "video_core/utils.h" #include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/util/spinbox.h" +static Tegra::Texture::TextureFormat ConvertToTextureFormat( + Tegra::RenderTargetFormat render_target_format) { + switch (render_target_format) { + case Tegra::RenderTargetFormat::RGBA8_UNORM: + return Tegra::Texture::TextureFormat::A8R8G8B8; + default: + UNIMPLEMENTED_MSG("Unimplemented RT format"); + } +} + SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {} SurfacePicture::~SurfacePicture() {} @@ -62,7 +73,7 @@ GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptrSetBase(16); - surface_address_control->SetRange(0, 0xFFFFFFFF); + surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); surface_address_control->SetPrefix("0x"); unsigned max_dimension = 16384; // TODO: Find actual maximum @@ -243,7 +254,7 @@ void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { if (surface_address != new_value) { - surface_address = static_cast(new_value); + surface_address = static_cast(new_value); surface_source_list->setCurrentIndex(static_cast(Source::Custom)); emit Update(); @@ -302,13 +313,6 @@ void GraphicsSurfaceWidget::Pick(int x, int y) { return; } - u8* buffer = Memory::GetPhysicalPointer(surface_address); - if (buffer == nullptr) { - surface_info_label->setText(tr("(unable to access pixel data)")); - surface_info_label->setAlignment(Qt::AlignCenter); - return; - } - surface_info_label->setText(QString("Raw: \n(%1)").arg("")); surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); } @@ -318,8 +322,6 @@ void GraphicsSurfaceWidget::OnUpdate() { QPixmap pixmap; - Tegra::GPUVAddr surface_address = 0; - switch (surface_source) { case Source::RenderTarget0: case Source::RenderTarget1: @@ -333,11 +335,13 @@ void GraphicsSurfaceWidget::OnUpdate() { // directly... auto& registers = gpu.Get3DEngine().regs; + auto& rt = registers.rt[static_cast(surface_source) - + static_cast(Source::RenderTarget0)]; - surface_address = 0; - surface_width = 0; - surface_height = 0; - surface_format = Tegra::Texture::TextureFormat::DXT1; + surface_address = rt.Address(); + surface_width = rt.horiz; + surface_height = rt.vert; + surface_format = ConvertToTextureFormat(static_cast(rt.format)); break; } @@ -378,9 +382,6 @@ void GraphicsSurfaceWidget::OnUpdate() { auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, surface_width, surface_height); - ASSERT(texture_data.size() == - surface_width * surface_height * - Tegra::Texture::BytesPerPixel(Tegra::Texture::TextureFormat::A8R8G8B8)); surface_picture_label->show(); for (unsigned int y = 0; y < surface_height; ++y) { @@ -431,7 +432,10 @@ void GraphicsSurfaceWidget::SaveSurface() { if (pixmap) pixmap->save(&file, "PNG"); } else if (selectedFilter == bin_filter) { - const u8* buffer = Memory::GetPhysicalPointer(surface_address); + auto& gpu = Core::System::GetInstance().GPU(); + VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address); + + const u8* buffer = Memory::GetPointer(address); ASSERT_MSG(buffer != nullptr, "Memory not accessible"); QFile file(filename); From 2c785bd06c8f979fbb869d533204b29d93973d83 Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 23 Mar 2018 18:56:27 -0500 Subject: [PATCH 8/9] GPU: Added a function to retrieve the active textures for a shader stage. TODO: A shader may not use all of these textures at the same time, shader analysis should be performed to determine which textures are actually sampled. --- src/video_core/engines/maxwell_3d.cpp | 93 ++++++++++++++------------- src/video_core/engines/maxwell_3d.h | 16 +++-- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index aa375f51f..c962887ca 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -174,53 +174,13 @@ void Maxwell3D::ProcessQueryGet() { void Maxwell3D::DrawArrays() { LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); if (Tegra::g_debug_context) { - Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); - } - - auto& fragment_shader = state.shader_stages[static_cast(Regs::ShaderStage::Fragment)]; - auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; - ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); - - GPUVAddr tic_base_address = regs.tic.TICAddress(); - - GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; - - for (GPUVAddr current_texture = tex_info_buffer.address + 0x20; - current_texture < tex_info_buffer_end; current_texture += 4) { - - Texture::TextureHandle tex_info{ - Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))}; - - if (tex_info.tic_id != 0 || tex_info.tsc_id != 0) { - GPUVAddr tic_address_gpu = - tic_base_address + tex_info.tic_id * sizeof(Texture::TICEntry); - VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu); - - Texture::TICEntry tic_entry; - Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); - - auto r_type = tic_entry.r_type.Value(); - auto g_type = tic_entry.g_type.Value(); - auto b_type = tic_entry.b_type.Value(); - auto a_type = tic_entry.a_type.Value(); - - // TODO(Subv): Different data types for separate components are not supported - ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); - - auto format = tic_entry.format.Value(); - - auto texture = Texture::UnswizzleTexture( - memory_manager.PhysicalToVirtualAddress(tic_entry.Address()), - tic_entry.format.Value(), tic_entry.Width(), tic_entry.Height()); - - LOG_CRITICAL(HW_GPU, - "Fragment shader using texture TIC %08X TSC %08X at address %016" PRIX64, - tex_info.tic_id.Value(), tex_info.tsc_id.Value(), tic_entry.Address()); - } + Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, + nullptr); } if (Tegra::g_debug_context) { - Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, + nullptr); } } @@ -332,5 +292,50 @@ void Maxwell3D::ProcessCBData(u32 value) { regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; } +std::vector Maxwell3D::GetStageTextures(Regs::ShaderStage stage) { + std::vector textures; + + auto& fragment_shader = state.shader_stages[static_cast(stage)]; + auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; + ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); + + GPUVAddr tic_base_address = regs.tic.TICAddress(); + + GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; + + // Offset into the texture constbuffer where the texture info begins. + static constexpr size_t TextureInfoOffset = 0x20; + + for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; + current_texture < tex_info_buffer_end; current_texture += 4) { + + Texture::TextureHandle tex_info{ + Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))}; + + if (tex_info.tic_id != 0 || tex_info.tsc_id != 0) { + GPUVAddr tic_address_gpu = + tic_base_address + tex_info.tic_id * sizeof(Texture::TICEntry); + VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu); + + Texture::TICEntry tic_entry; + Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry)); + + auto r_type = tic_entry.r_type.Value(); + auto g_type = tic_entry.g_type.Value(); + auto b_type = tic_entry.b_type.Value(); + auto a_type = tic_entry.a_type.Value(); + + // TODO(Subv): Different data types for separate components are not supported + ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + + auto format = tic_entry.format.Value(); + + textures.push_back(tic_entry); + } + } + + return textures; +} + } // namespace Engines } // 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 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "video_core/memory_manager.h" +#include "video_core/textures/texture.h" namespace Tegra { namespace Engines { @@ -21,12 +22,6 @@ public: explicit Maxwell3D(MemoryManager& memory_manager); ~Maxwell3D() = default; - /// Write the value to the register identified by method. - void WriteReg(u32 method, u32 value, u32 remaining_params); - - /// Uploads the code for a GPU macro program associated with the specified entry. - void SubmitMacroCode(u32 entry, std::vector code); - /// Register structure of the Maxwell3D engine. /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. struct Regs { @@ -430,6 +425,15 @@ public: State state{}; + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value, u32 remaining_params); + + /// Uploads the code for a GPU macro program associated with the specified entry. + void SubmitMacroCode(u32 entry, std::vector code); + + /// Returns a list of enabled textures for the specified shader stage. + std::vector GetStageTextures(Regs::ShaderStage stage); + private: MemoryManager& memory_manager; From 0ce52b1da2228f3325d94e52bead7335c8b07d1c Mon Sep 17 00:00:00 2001 From: Subv Date: Sat, 24 Mar 2018 23:35:06 -0500 Subject: [PATCH 9/9] GPU: Make the debug_context variable a member of the frontend instead of a global. --- src/core/core.h | 11 +++++++++ src/video_core/debug_utils/debug_utils.cpp | 2 -- src/video_core/debug_utils/debug_utils.h | 2 -- src/video_core/engines/maxwell_3d.cpp | 24 ++++++++++--------- .../debugger/graphics/graphics_surface.cpp | 5 +++- src/yuzu/main.cpp | 9 ++++--- src/yuzu/main.h | 6 +++++ 7 files changed, 40 insertions(+), 19 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 @@ #include "core/memory.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" class EmuWindow; @@ -135,6 +136,14 @@ public: return *app_loader; } + void SetGPUDebugContext(std::shared_ptr context) { + debug_context = std::move(context); + } + + std::shared_ptr GetGPUDebugContext() const { + return debug_context; + } + private: /** * Initialize the emulated system. @@ -154,6 +163,8 @@ private: std::unique_ptr scheduler; std::unique_ptr gpu_core; + std::shared_ptr debug_context; + Kernel::SharedPtr current_process; /// When true, signals that a reschedule should happen diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 73fd4d7a3..22d44aab2 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -23,8 +23,6 @@ namespace Tegra { -std::shared_ptr g_debug_context; - void DebugContext::DoOnEvent(Event event, void* data) { { std::unique_lock lock(breakpoint_mutex); diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 98461d6d9..bbba8e380 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -160,6 +160,4 @@ private: std::list breakpoint_observers; }; -extern std::shared_ptr g_debug_context; - } // namespace Tegra diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index c962887ca..986165c6d 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" +#include "core/core.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/textures/decoders.h" @@ -50,6 +51,8 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register, increase the size of the Regs structure"); + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + // It is an error to write to a register other than the current macro's ARG register before it // has finished execution. if (executing_macro != 0) { @@ -76,8 +79,8 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { return; } - if (Tegra::g_debug_context) { - Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr); } regs.reg_array[method] = value; @@ -146,9 +149,8 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { #undef MAXWELL3D_REG_INDEX - if (Tegra::g_debug_context) { - Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, - nullptr); + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr); } } @@ -173,14 +175,14 @@ void Maxwell3D::ProcessQueryGet() { void Maxwell3D::DrawArrays() { LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring"); - if (Tegra::g_debug_context) { - Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, - nullptr); + auto debug_context = Core::System::GetInstance().GetGPUDebugContext(); + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); } - if (Tegra::g_debug_context) { - Tegra::g_debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, - nullptr); + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); } } diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index d061013da..8e6509adc 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -341,7 +341,10 @@ void GraphicsSurfaceWidget::OnUpdate() { surface_address = rt.Address(); surface_width = rt.horiz; surface_height = rt.vert; - surface_format = ConvertToTextureFormat(static_cast(rt.format)); + if (rt.format != 0) { + surface_format = + ConvertToTextureFormat(static_cast(rt.format)); + } break; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b8c23ae15..bd323870b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -25,6 +25,7 @@ #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" #include "yuzu/configuration/config.h" @@ -71,7 +72,7 @@ void GMainWindow::ShowCallouts() {} GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { - Tegra::g_debug_context = Tegra::DebugContext::Construct(); + debug_context = Tegra::DebugContext::Construct(); setAcceptDrops(true); ui.setupUi(this); @@ -165,12 +166,12 @@ void GMainWindow::InitializeDebugWidgets() { connect(this, &GMainWindow::EmulationStopping, registersWidget, &RegistersWidget::OnEmulationStopping); - graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Tegra::g_debug_context, this); + graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); graphicsBreakpointsWidget->hide(); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - graphicsSurfaceWidget = new GraphicsSurfaceWidget(Tegra::g_debug_context, this); + graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); graphicsSurfaceWidget->hide(); debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); @@ -339,6 +340,8 @@ bool GMainWindow::LoadROM(const QString& filename) { Core::System& system{Core::System::GetInstance()}; + system.SetGPUDebugContext(debug_context); + const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0f89607c8..2471caf83 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -23,6 +23,10 @@ class ProfilerWidget; class RegistersWidget; class WaitTreeWidget; +namespace Tegra { +class DebugContext; +} + class GMainWindow : public QMainWindow { Q_OBJECT @@ -135,6 +139,8 @@ private: Ui::MainWindow ui; + std::shared_ptr debug_context; + GRenderWindow* render_window; GameList* game_list;