From 77fd0d47e70968bcbc87a3b5607cd29e6211f656 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 22 Mar 2018 15:19:35 -0500 Subject: Frontend: Ported the GPU breakpoints and surface viewer widgets from citra. --- src/video_core/debug_utils/debug_utils.cpp | 66 ++++++++++++ src/video_core/debug_utils/debug_utils.h | 165 +++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 src/video_core/debug_utils/debug_utils.cpp create mode 100644 src/video_core/debug_utils/debug_utils.h (limited to 'src/video_core/debug_utils') 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 -- cgit v1.2.3 From 0ce52b1da2228f3325d94e52bead7335c8b07d1c Mon Sep 17 00:00:00 2001 From: Subv Date: Sat, 24 Mar 2018 23:35:06 -0500 Subject: 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 +++++++++++++----------- src/yuzu/debugger/graphics/graphics_surface.cpp | 5 ++++- src/yuzu/main.cpp | 9 ++++++--- src/yuzu/main.h | 6 ++++++ 7 files changed, 40 insertions(+), 19 deletions(-) (limited to 'src/video_core/debug_utils') 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; -- cgit v1.2.3