mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Add CiTrace recording support.
This is exposed in the GUI as a new "CiTrace Recording" widget. Playback is implemented by a standalone 3DS homebrew application (which only runs reliably within Citra currently; on an actual 3DS it will often crash still).
This commit is contained in:
		
							parent
							
								
									93d66475d4
								
							
						
					
					
						commit
						902fa4da52
					
				
					 15 changed files with 641 additions and 4 deletions
				
			
		|  | @ -12,6 +12,7 @@ set(SRCS | |||
|             debugger/graphics_breakpoints.cpp | ||||
|             debugger/graphics_cmdlists.cpp | ||||
|             debugger/graphics_framebuffer.cpp | ||||
|             debugger/graphics_tracing.cpp | ||||
|             debugger/graphics_vertex_shader.cpp | ||||
|             debugger/profiler.cpp | ||||
|             debugger/ramview.cpp | ||||
|  | @ -35,6 +36,7 @@ set(HEADERS | |||
|             debugger/graphics_breakpoints_p.h | ||||
|             debugger/graphics_cmdlists.h | ||||
|             debugger/graphics_framebuffer.h | ||||
|             debugger/graphics_tracing.h | ||||
|             debugger/graphics_vertex_shader.h | ||||
|             debugger/profiler.h | ||||
|             debugger/ramview.h | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
|  * This is because the Pica 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, private Pica::DebugContext::BreakPointObserver { | ||||
| class BreakPointObserverDock : public QDockWidget, protected Pica::DebugContext::BreakPointObserver { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|  |  | |||
							
								
								
									
										123
									
								
								src/citra_qt/debugger/graphics_tracing.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/citra_qt/debugger/graphics_tracing.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | |||
| // Copyright 2015 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| #include <QBoxLayout> | ||||
| #include <QComboBox> | ||||
| #include <QFileDialog> | ||||
| #include <QLabel> | ||||
| #include <QPushButton> | ||||
| #include <QSpinBox> | ||||
| 
 | ||||
| #include "core/hw/gpu.h" | ||||
| #include "video_core/pica.h" | ||||
| 
 | ||||
| #include "nihstro/float24.h" | ||||
| 
 | ||||
| #include "graphics_tracing.h" | ||||
| 
 | ||||
| GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, | ||||
|                                              QWidget* parent) | ||||
|     : BreakPointObserverDock(debug_context, tr("CiTrace Recorder"), parent) { | ||||
| 
 | ||||
|     setObjectName("CiTracing"); | ||||
| 
 | ||||
|     QPushButton* start_recording = new QPushButton(tr("Start Recording")); | ||||
|     QPushButton* stop_recording = new QPushButton(QIcon::fromTheme("document-save"), tr("Stop and Save")); | ||||
|     QPushButton* abort_recording = new QPushButton(tr("Abort Recording")); | ||||
| 
 | ||||
|     connect(this, SIGNAL(SetStartTracingButtonEnabled(bool)), start_recording, SLOT(setVisible(bool))); | ||||
|     connect(this, SIGNAL(SetStopTracingButtonEnabled(bool)), stop_recording, SLOT(setVisible(bool))); | ||||
|     connect(this, SIGNAL(SetAbortTracingButtonEnabled(bool)), abort_recording, SLOT(setVisible(bool))); | ||||
|     connect(start_recording, SIGNAL(clicked()), this, SLOT(StartRecording())); | ||||
|     connect(stop_recording, SIGNAL(clicked()), this, SLOT(StopRecording())); | ||||
|     connect(abort_recording, SIGNAL(clicked()), this, SLOT(AbortRecording())); | ||||
| 
 | ||||
|     stop_recording->setVisible(false); | ||||
|     abort_recording->setVisible(false); | ||||
| 
 | ||||
|     auto main_widget = new QWidget; | ||||
|     auto main_layout = new QVBoxLayout; | ||||
|     { | ||||
|         auto sub_layout = new QHBoxLayout; | ||||
|         sub_layout->addWidget(start_recording); | ||||
|         sub_layout->addWidget(stop_recording); | ||||
|         sub_layout->addWidget(abort_recording); | ||||
|         main_layout->addLayout(sub_layout); | ||||
|     } | ||||
|     main_widget->setLayout(main_layout); | ||||
|     setWidget(main_widget); | ||||
| 
 | ||||
|     // TODO: Make sure to have this widget disabled as soon as emulation is started!
 | ||||
| } | ||||
| 
 | ||||
| void GraphicsTracingWidget::StartRecording() { | ||||
|     auto context = context_weak.lock(); | ||||
|     if (!context) | ||||
|         return; | ||||
| 
 | ||||
|     auto shader_binary = Pica::g_state.vs.program_code; | ||||
|     auto swizzle_data = Pica::g_state.vs.swizzle_data; | ||||
| 
 | ||||
|     // Encode floating point numbers to 24-bit values
 | ||||
|     // TODO: Drop this explicit conversion once we store float24 values bit-correctly internally.
 | ||||
|     std::array<Math::Vec4<uint32_t>, 96> vs_float_uniforms; | ||||
|     for (unsigned i = 0; i < 96; ++i) | ||||
|         for (unsigned comp = 0; comp < 3; ++comp) | ||||
|             vs_float_uniforms[i][comp] = nihstro::to_float24(Pica::g_state.vs.uniforms.f[i][comp].ToFloat32()); | ||||
| 
 | ||||
|     auto recorder = new CiTrace::Recorder((u32*)&GPU::g_regs, 0x700, nullptr, 0, (u32*)&Pica::g_state.regs, 0x300, | ||||
|                                           shader_binary.data(), shader_binary.size(), | ||||
|                                           swizzle_data.data(), swizzle_data.size(), | ||||
|                                           (u32*)vs_float_uniforms.data(), vs_float_uniforms.size() * 4, | ||||
|                                           nullptr, 0, nullptr, 0, nullptr, 0 // Geometry shader is not implemented yet, so submit dummy data for now
 | ||||
|                                           ); | ||||
|     context->recorder = std::shared_ptr<CiTrace::Recorder>(recorder); | ||||
| 
 | ||||
|     emit SetStartTracingButtonEnabled(false); | ||||
|     emit SetStopTracingButtonEnabled(true); | ||||
|     emit SetAbortTracingButtonEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GraphicsTracingWidget::StopRecording() { | ||||
|     auto context = context_weak.lock(); | ||||
|     if (!context) | ||||
|         return; | ||||
| 
 | ||||
|     QString filename = QFileDialog::getSaveFileName(this, tr("Save CiTrace"), "citrace.ctf", | ||||
|                                                     tr("CiTrace File (*.ctf)")); | ||||
| 
 | ||||
|     if (filename.isEmpty()) { | ||||
|         // If the user canceled the dialog, keep recording
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     context->recorder->Finish(filename.toStdString()); | ||||
|     context->recorder = nullptr; | ||||
| 
 | ||||
|     emit SetStopTracingButtonEnabled(false); | ||||
|     emit SetAbortTracingButtonEnabled(false); | ||||
|     emit SetStartTracingButtonEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GraphicsTracingWidget::AbortRecording() { | ||||
|     auto context = context_weak.lock(); | ||||
|     if (!context) | ||||
|         return; | ||||
| 
 | ||||
|     context->recorder = nullptr; | ||||
| 
 | ||||
|     emit SetStopTracingButtonEnabled(false); | ||||
|     emit SetAbortTracingButtonEnabled(false); | ||||
|     emit SetStartTracingButtonEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GraphicsTracingWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) { | ||||
|     widget()->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GraphicsTracingWidget::OnResumed() { | ||||
|     widget()->setEnabled(false); | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/citra_qt/debugger/graphics_tracing.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/citra_qt/debugger/graphics_tracing.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| // Copyright 2015 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "graphics_breakpoint_observer.h" | ||||
| 
 | ||||
| class GraphicsTracingWidget : public BreakPointObserverDock { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); | ||||
| 
 | ||||
| private slots: | ||||
|     void StartRecording(); | ||||
|     void StopRecording(); | ||||
|     void AbortRecording(); | ||||
| 
 | ||||
|     void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; | ||||
|     void OnResumed() override; | ||||
| 
 | ||||
| signals: | ||||
|     void SetStartTracingButtonEnabled(bool enable); | ||||
|     void SetStopTracingButtonEnabled(bool enable); | ||||
|     void SetAbortTracingButtonEnabled(bool enable); | ||||
| }; | ||||
|  | @ -32,6 +32,7 @@ | |||
| #include "debugger/graphics_breakpoints.h" | ||||
| #include "debugger/graphics_cmdlists.h" | ||||
| #include "debugger/graphics_framebuffer.h" | ||||
| #include "debugger/graphics_tracing.h" | ||||
| #include "debugger/graphics_vertex_shader.h" | ||||
| #include "debugger/profiler.h" | ||||
| 
 | ||||
|  | @ -94,6 +95,10 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) | |||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); | ||||
|     graphicsVertexShaderWidget->hide(); | ||||
| 
 | ||||
|     auto graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this); | ||||
|     addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); | ||||
|     graphicsTracingWidget->hide(); | ||||
| 
 | ||||
|     QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); | ||||
|     debug_menu->addAction(profilerWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(disasmWidget->toggleViewAction()); | ||||
|  | @ -104,6 +109,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) | |||
|     debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); | ||||
|     debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); | ||||
| 
 | ||||
|     // Set default UI state
 | ||||
|     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
 | ||||
|  |  | |||
|  | @ -115,6 +115,7 @@ set(SRCS | |||
|             loader/elf.cpp | ||||
|             loader/loader.cpp | ||||
|             loader/ncch.cpp | ||||
|             tracer/recorder.cpp | ||||
|             mem_map.cpp | ||||
|             memory.cpp | ||||
|             settings.cpp | ||||
|  | @ -243,6 +244,8 @@ set(HEADERS | |||
|             loader/elf.h | ||||
|             loader/loader.h | ||||
|             loader/ncch.h | ||||
|             tracer/recorder.h | ||||
|             tracer/tracer.h | ||||
|             mem_map.h | ||||
|             memory.h | ||||
|             memory_setup.h | ||||
|  |  | |||
|  | @ -349,7 +349,7 @@ void SignalInterrupt(InterruptId interrupt_id) { | |||
| /// Executes the next GSP command
 | ||||
| static void ExecuteCommand(const Command& command, u32 thread_id) { | ||||
|     // Utility function to convert register ID to address
 | ||||
|     auto WriteGPURegister = [](u32 id, u32 data) { | ||||
|     static auto WriteGPURegister = [](u32 id, u32 data) { | ||||
|         GPU::Write<u32>(0x1EF00000 + 4 * id, data); | ||||
|     }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,12 +21,17 @@ | |||
| #include "core/hw/hw.h" | ||||
| #include "core/hw/gpu.h" | ||||
| 
 | ||||
| #include "core/tracer/recorder.h" | ||||
| 
 | ||||
| #include "video_core/command_processor.h" | ||||
| #include "video_core/hwrasterizer_base.h" | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/utils.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| #include "video_core/debug_utils/debug_utils.h" | ||||
| 
 | ||||
| 
 | ||||
| namespace GPU { | ||||
| 
 | ||||
| Regs g_regs; | ||||
|  | @ -289,6 +294,11 @@ inline void Write(u32 addr, const T data) { | |||
|         if (config.trigger & 1) | ||||
|         { | ||||
|             u32* buffer = (u32*)Memory::GetPhysicalPointer(config.GetPhysicalAddress()); | ||||
| 
 | ||||
|             if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||
|                 Pica::g_debug_context->recorder->MemoryAccessed((u8*)buffer, config.size * sizeof(u32), config.GetPhysicalAddress()); | ||||
|             } | ||||
| 
 | ||||
|             Pica::CommandProcessor::ProcessCommandList(buffer, config.size); | ||||
| 
 | ||||
|             g_regs.command_processor_config.trigger = 0; | ||||
|  | @ -299,6 +309,13 @@ inline void Write(u32 addr, const T data) { | |||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     // Notify tracer about the register write
 | ||||
|     // This is happening *after* handling the write to make sure we properly catch all memory reads.
 | ||||
|     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||
|         // addr + GPU VBase - IO VBase + IO PBase
 | ||||
|         Pica::g_debug_context->recorder->RegisterWritten<T>(addr + 0x1EF00000 - 0x1EC00000 + 0x10100000, data); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Explicitly instantiate template functions because we aren't defining this in the header:
 | ||||
|  |  | |||
|  | @ -10,6 +10,9 @@ | |||
| #include "core/hw/hw.h" | ||||
| #include "core/hw/lcd.h" | ||||
| 
 | ||||
| #include "core/tracer/recorder.h" | ||||
| #include "video_core/debug_utils/debug_utils.h" | ||||
| 
 | ||||
| namespace LCD { | ||||
| 
 | ||||
| Regs g_regs; | ||||
|  | @ -40,6 +43,13 @@ inline void Write(u32 addr, const T data) { | |||
|     } | ||||
| 
 | ||||
|     g_regs[index] = static_cast<u32>(data); | ||||
| 
 | ||||
|     // Notify tracer about the register write
 | ||||
|     // This is happening *after* handling the write to make sure we properly catch all memory reads.
 | ||||
|     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||
|         // addr + GPU VBase - IO VBase + IO PBase
 | ||||
|         Pica::g_debug_context->recorder->RegisterWritten<T>(addr + HW::VADDR_LCD - 0x1EC00000 + 0x10100000, data); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Explicitly instantiate template functions because we aren't defining this in the header:
 | ||||
|  |  | |||
							
								
								
									
										198
									
								
								src/core/tracer/recorder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/core/tracer/recorder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,198 @@ | |||
| // Copyright 2015 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cstring> | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| 
 | ||||
| #include "recorder.h" | ||||
| 
 | ||||
| namespace CiTrace { | ||||
| 
 | ||||
| Recorder::Recorder(u32* gpu_registers,     u32 gpu_registers_size, | ||||
|                    u32* lcd_registers,     u32 lcd_registers_size, | ||||
|                    u32* pica_registers,    u32 pica_registers_size, | ||||
|                    u32* vs_program_binary, u32 vs_program_binary_size, | ||||
|                    u32* vs_swizzle_data,   u32 vs_swizzle_data_size, | ||||
|                    u32* vs_float_uniforms, u32 vs_float_uniforms_size, | ||||
|                    u32* gs_program_binary, u32 gs_program_binary_size, | ||||
|                    u32* gs_swizzle_data,   u32 gs_swizzle_data_size, | ||||
|                    u32* gs_float_uniforms, u32 gs_float_uniforms_size) | ||||
|     : gpu_registers(gpu_registers, gpu_registers + gpu_registers_size), | ||||
|       lcd_registers(lcd_registers, lcd_registers + lcd_registers_size), | ||||
|       pica_registers(pica_registers, pica_registers + pica_registers_size), | ||||
|       vs_program_binary(vs_program_binary, vs_program_binary + vs_program_binary_size), | ||||
|       vs_swizzle_data(vs_swizzle_data, vs_swizzle_data + vs_swizzle_data_size), | ||||
|       vs_float_uniforms(vs_float_uniforms, vs_float_uniforms + vs_float_uniforms_size), | ||||
|       gs_program_binary(gs_program_binary, gs_program_binary + gs_program_binary_size), | ||||
|       gs_swizzle_data(gs_swizzle_data, gs_swizzle_data + gs_swizzle_data_size), | ||||
|       gs_float_uniforms(gs_float_uniforms, gs_float_uniforms + gs_float_uniforms_size) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void Recorder::Finish(const std::string& filename) { | ||||
|     // Setup CiTrace header
 | ||||
|     CTHeader header; | ||||
|     std::memcpy(header.magic, CTHeader::ExpectedMagicWord(), 4); | ||||
|     header.version = CTHeader::ExpectedVersion(); | ||||
|     header.header_size = sizeof(CTHeader); | ||||
| 
 | ||||
|     // Calculate file offsets
 | ||||
|     auto& initial = header.initial_state_offsets; | ||||
| 
 | ||||
|     initial.gpu_registers_size     = gpu_registers.size(); | ||||
|     initial.lcd_registers_size     = lcd_registers.size(); | ||||
|     initial.pica_registers_size    = pica_registers.size(); | ||||
|     initial.vs_program_binary_size = vs_program_binary.size(); | ||||
|     initial.vs_swizzle_data_size   = vs_swizzle_data.size(); | ||||
|     initial.vs_float_uniforms_size = vs_float_uniforms.size(); | ||||
|     initial.gs_program_binary_size = gs_program_binary.size(); | ||||
|     initial.gs_swizzle_data_size   = gs_swizzle_data.size(); | ||||
|     initial.gs_float_uniforms_size = gs_float_uniforms.size(); | ||||
|     header.stream_size             = stream.size(); | ||||
| 
 | ||||
|     initial.gpu_registers     = sizeof(header); | ||||
|     initial.lcd_registers     = initial.gpu_registers     + initial.gpu_registers_size * sizeof(u32); | ||||
|     initial.pica_registers    = initial.lcd_registers     + initial.lcd_registers_size * sizeof(u32);; | ||||
|     initial.vs_program_binary = initial.pica_registers    + initial.pica_registers_size * sizeof(u32); | ||||
|     initial.vs_swizzle_data   = initial.vs_program_binary + initial.vs_program_binary_size * sizeof(u32); | ||||
|     initial.vs_float_uniforms = initial.vs_swizzle_data   + initial.vs_swizzle_data_size * sizeof(u32); | ||||
|     initial.gs_program_binary = initial.vs_float_uniforms + initial.vs_float_uniforms_size * sizeof(u32); | ||||
|     initial.gs_swizzle_data   = initial.gs_program_binary + initial.gs_program_binary_size * sizeof(u32); | ||||
|     initial.gs_float_uniforms = initial.gs_swizzle_data   + initial.gs_swizzle_data_size * sizeof(u32); | ||||
|     header.stream_offset      = initial.gs_float_uniforms + initial.gs_float_uniforms_size * sizeof(u32); | ||||
| 
 | ||||
|     // Iterate through stream elements, update relevant stream element data
 | ||||
|     for (auto& stream_element : stream) { | ||||
|         switch (stream_element.data.type) { | ||||
|         case MemoryLoad: | ||||
|         { | ||||
|             auto& file_offset = memory_regions[stream_element.hash]; | ||||
|             if (!stream_element.uses_existing_data) { | ||||
|                 file_offset = header.stream_offset; | ||||
|             } | ||||
|             stream_element.data.memory_load.file_offset = file_offset; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         default: | ||||
|             // Other commands don't use any extra data
 | ||||
|             DEBUG_ASSERT(stream_element.extra_data.size() == 0); | ||||
|             break; | ||||
|         } | ||||
|         header.stream_offset += stream_element.extra_data.size(); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|         // Open file and write header
 | ||||
|         FileUtil::IOFile file(filename, "wb"); | ||||
|         size_t written = file.WriteObject(header); | ||||
|         if (written != 1 || file.Tell() != initial.gpu_registers) | ||||
|             throw "Failed to write header"; | ||||
| 
 | ||||
|         // Write initial state
 | ||||
|         written = file.WriteArray(gpu_registers.data(), gpu_registers.size()); | ||||
|         if (written != gpu_registers.size() || file.Tell() != initial.lcd_registers) | ||||
|             throw "Failed to write GPU registers"; | ||||
| 
 | ||||
|         written = file.WriteArray(lcd_registers.data(), lcd_registers.size()); | ||||
|         if (written != lcd_registers.size() || file.Tell() != initial.pica_registers) | ||||
|             throw "Failed to write LCD registers"; | ||||
| 
 | ||||
|         written = file.WriteArray(pica_registers.data(), pica_registers.size()); | ||||
|         if (written != pica_registers.size() || file.Tell() != initial.vs_program_binary) | ||||
|             throw "Failed to write Pica registers"; | ||||
| 
 | ||||
|         written = file.WriteArray(vs_program_binary.data(), vs_program_binary.size()); | ||||
|         if (written != vs_program_binary.size() || file.Tell() != initial.vs_swizzle_data) | ||||
|             throw "Failed to write vertex shader program binary"; | ||||
| 
 | ||||
|         written = file.WriteArray(vs_swizzle_data.data(), vs_swizzle_data.size()); | ||||
|         if (written != vs_swizzle_data.size() || file.Tell() != initial.vs_float_uniforms) | ||||
|             throw "Failed to write vertex shader swizzle data"; | ||||
| 
 | ||||
|         written = file.WriteArray(vs_float_uniforms.data(), vs_float_uniforms.size()); | ||||
|         if (written != vs_float_uniforms.size() || file.Tell() != initial.gs_program_binary) | ||||
|             throw "Failed to write vertex shader float uniforms"; | ||||
| 
 | ||||
|         written = file.WriteArray(gs_program_binary.data(), gs_program_binary.size()); | ||||
|         if (written != gs_program_binary.size() || file.Tell() != initial.gs_swizzle_data) | ||||
|             throw "Failed to write geomtry shader program binary"; | ||||
| 
 | ||||
|         written = file.WriteArray(gs_swizzle_data.data(), gs_swizzle_data.size()); | ||||
|         if (written != gs_swizzle_data.size() || file.Tell() != initial.gs_float_uniforms) | ||||
|             throw "Failed to write geometry shader swizzle data"; | ||||
| 
 | ||||
|         written = file.WriteArray(gs_float_uniforms.data(), gs_float_uniforms.size()); | ||||
|         if (written != gs_float_uniforms.size() || file.Tell() != initial.gs_float_uniforms + sizeof(u32) * initial.gs_float_uniforms_size) | ||||
|             throw "Failed to write geometry shader float uniforms"; | ||||
| 
 | ||||
|         // Iterate through stream elements, write "extra data"
 | ||||
|         for (const auto& stream_element : stream) { | ||||
|             if (stream_element.extra_data.size() == 0) | ||||
|                 continue; | ||||
| 
 | ||||
|             written = file.WriteBytes(stream_element.extra_data.data(), stream_element.extra_data.size()); | ||||
|             if (written != stream_element.extra_data.size()) | ||||
|                 throw "Failed to write extra data"; | ||||
|         } | ||||
| 
 | ||||
|         if (file.Tell() != header.stream_offset) | ||||
|             throw "Unexpected end of extra data"; | ||||
| 
 | ||||
|         // Write actual stream elements
 | ||||
|         for (const auto& stream_element : stream) { | ||||
|             if (1 != file.WriteObject(stream_element.data)) | ||||
|                 throw "Failed to write stream element"; | ||||
|         } | ||||
|     } catch(const char* str) { | ||||
|         LOG_ERROR(HW_GPU, "Writing CiTrace file failed: %s", str); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Recorder::FrameFinished() { | ||||
|     stream.push_back( { FrameMarker } ); | ||||
| } | ||||
| 
 | ||||
| void Recorder::MemoryAccessed(const u8* data, u32 size, u32 physical_address) { | ||||
|     StreamElement element = { MemoryLoad }; | ||||
|     element.data.memory_load.size = size; | ||||
|     element.data.memory_load.physical_address = physical_address; | ||||
| 
 | ||||
|     // Compute hash over given memory region to check if the contents are already stored internally
 | ||||
|     boost::crc_32_type result; | ||||
|     result.process_bytes(data, size); | ||||
|     element.hash = result.checksum(); | ||||
| 
 | ||||
|     element.uses_existing_data = (memory_regions.find(element.hash) != memory_regions.end()); | ||||
|     if (!element.uses_existing_data) { | ||||
|         element.extra_data.resize(size); | ||||
|         memcpy(element.extra_data.data(), data, size); | ||||
|         memory_regions.insert({element.hash, 0}); // file offset will be initialized in Finish()
 | ||||
|     } | ||||
| 
 | ||||
|     stream.push_back(element); | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| void Recorder::RegisterWritten(u32 physical_address, T value) { | ||||
|     StreamElement element = { RegisterWrite }; | ||||
|     element.data.register_write.size = (sizeof(T) == 1) ? CTRegisterWrite::SIZE_8 | ||||
|                                      : (sizeof(T) == 2) ? CTRegisterWrite::SIZE_16 | ||||
|                                      : (sizeof(T) == 4) ? CTRegisterWrite::SIZE_32 | ||||
|                                      :                    CTRegisterWrite::SIZE_64; | ||||
|     element.data.register_write.physical_address = physical_address; | ||||
|     element.data.register_write.value = value; | ||||
| 
 | ||||
|     stream.push_back(element); | ||||
| } | ||||
| 
 | ||||
| template void Recorder::RegisterWritten(u32,u8); | ||||
| template void Recorder::RegisterWritten(u32,u16); | ||||
| template void Recorder::RegisterWritten(u32,u32); | ||||
| template void Recorder::RegisterWritten(u32,u64); | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										92
									
								
								src/core/tracer/recorder.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/core/tracer/recorder.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| // Copyright 2015 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <boost/crc.hpp> | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| #include "tracer.h" | ||||
| 
 | ||||
| namespace CiTrace { | ||||
| 
 | ||||
| class Recorder { | ||||
| public: | ||||
|     /**
 | ||||
|      * Recorder constructor | ||||
|      * @param vs_float_uniforms Pointer to an array of 32-bit-aligned 24-bit floating point values. | ||||
|      */ | ||||
|     Recorder(u32* gpu_registers,     u32 gpu_registers_size, | ||||
|              u32* lcd_registers,     u32 lcd_registers_size, | ||||
|              u32* pica_registers,    u32 pica_registers_size, | ||||
|              u32* vs_program_binary, u32 vs_program_binary_size, | ||||
|              u32* vs_swizzle_data,   u32 vs_swizzle_data_size, | ||||
|              u32* vs_float_uniforms, u32 vs_float_uniforms_size, | ||||
|              u32* gs_program_binary, u32 gs_program_binary_size, | ||||
|              u32* gs_swizzle_data,   u32 gs_swizzle_data_size, | ||||
|              u32* gs_float_uniforms, u32 gs_float_uniforms_size); | ||||
| 
 | ||||
|     /// Finish recording of this Citrace and save it using the given filename.
 | ||||
|     void Finish(const std::string& filename); | ||||
| 
 | ||||
|     /// Mark end of a frame
 | ||||
|     void FrameFinished(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Store a copy of the given memory range in the recording. | ||||
|      * @note Use this whenever the GPU is about to access a particular memory region. | ||||
|      * @note The implementation will make sure to minimize redundant memory updates. | ||||
|      */ | ||||
|     void MemoryAccessed(const u8* data, u32 size, u32 physical_address); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Record a register write. | ||||
|      * @note Use this whenever a GPU-related MMIO register has been written to. | ||||
|      */ | ||||
|     template<typename T> | ||||
|     void RegisterWritten(u32 physical_address, T value); | ||||
| 
 | ||||
| private: | ||||
|     // Initial state of recording start
 | ||||
|     std::vector<u32> gpu_registers; | ||||
|     std::vector<u32> lcd_registers; | ||||
|     std::vector<u32> pica_registers; | ||||
|     std::vector<u32> vs_program_binary; | ||||
|     std::vector<u32> vs_swizzle_data; | ||||
|     std::vector<u32> vs_float_uniforms; | ||||
|     std::vector<u32> gs_program_binary; | ||||
|     std::vector<u32> gs_swizzle_data; | ||||
|     std::vector<u32> gs_float_uniforms; | ||||
| 
 | ||||
|     // Command stream
 | ||||
|     struct StreamElement { | ||||
|         CTStreamElement data; | ||||
| 
 | ||||
|         /**
 | ||||
|           * Extra data to store along "core" data. | ||||
|           * This is e.g. used for data used in MemoryUpdates. | ||||
|           */ | ||||
|         std::vector<u8> extra_data; | ||||
| 
 | ||||
|         /// Optional CRC hash (e.g. for hashing memory regions)
 | ||||
|         boost::crc_32_type::value_type hash; | ||||
| 
 | ||||
|         /// If true, refer to data already written to the output file instead of extra_data
 | ||||
|         bool uses_existing_data; | ||||
|     }; | ||||
| 
 | ||||
|     std::vector<StreamElement> stream; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Internal cache which maps hashes of memory contents to file offsets at which those memory | ||||
|      * contents are stored. | ||||
|      */ | ||||
|     std::unordered_map<boost::crc_32_type::value_type /*hash*/, u32 /*file_offset*/> memory_regions; | ||||
| }; | ||||
| 
 | ||||
| } // namespace
 | ||||
							
								
								
									
										98
									
								
								src/core/tracer/tracer.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/core/tracer/tracer.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | |||
| // Copyright 2015 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cstdint> | ||||
| 
 | ||||
| namespace CiTrace { | ||||
| 
 | ||||
| // NOTE: Things are stored in little-endian
 | ||||
| 
 | ||||
| #pragma pack(1) | ||||
| 
 | ||||
| struct CTHeader { | ||||
|     static const char* ExpectedMagicWord() { | ||||
|         return "CiTr"; | ||||
|     } | ||||
| 
 | ||||
|     static uint32_t ExpectedVersion() { | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     char magic[4]; | ||||
|     uint32_t version; | ||||
|     uint32_t header_size; | ||||
| 
 | ||||
|     struct { | ||||
|         // NOTE: Register range sizes are technically hardware-constants, but the actual limits
 | ||||
|         // aren't known. Hence we store the presumed limits along the offsets.
 | ||||
|         // Sizes are given in uint32_t units.
 | ||||
|         uint32_t gpu_registers; | ||||
|         uint32_t gpu_registers_size; | ||||
|         uint32_t lcd_registers; | ||||
|         uint32_t lcd_registers_size; | ||||
|         uint32_t pica_registers; | ||||
|         uint32_t pica_registers_size; | ||||
|         uint32_t vs_program_binary; | ||||
|         uint32_t vs_program_binary_size; | ||||
|         uint32_t vs_swizzle_data; | ||||
|         uint32_t vs_swizzle_data_size; | ||||
|         uint32_t vs_float_uniforms; | ||||
|         uint32_t vs_float_uniforms_size; | ||||
|         uint32_t gs_program_binary; | ||||
|         uint32_t gs_program_binary_size; | ||||
|         uint32_t gs_swizzle_data; | ||||
|         uint32_t gs_swizzle_data_size; | ||||
|         uint32_t gs_float_uniforms; | ||||
|         uint32_t gs_float_uniforms_size; | ||||
| 
 | ||||
|         // Other things we might want to store here:
 | ||||
|         // - Initial framebuffer data, maybe even a full copy of FCRAM/VRAM
 | ||||
|         // - Default vertex attributes
 | ||||
|     } initial_state_offsets; | ||||
| 
 | ||||
|     uint32_t stream_offset; | ||||
|     uint32_t stream_size; | ||||
| }; | ||||
| 
 | ||||
| enum CTStreamElementType : uint32_t { | ||||
|     FrameMarker   = 0xE1, | ||||
|     MemoryLoad    = 0xE2, | ||||
|     RegisterWrite = 0xE3, | ||||
| }; | ||||
| 
 | ||||
| struct CTMemoryLoad { | ||||
|     uint32_t file_offset; | ||||
|     uint32_t size; | ||||
|     uint32_t physical_address; | ||||
|     uint32_t pad; | ||||
| }; | ||||
| 
 | ||||
| struct CTRegisterWrite { | ||||
|     uint32_t physical_address; | ||||
| 
 | ||||
|     enum : uint32_t { | ||||
|         SIZE_8  = 0xD1, | ||||
|         SIZE_16 = 0xD2, | ||||
|         SIZE_32 = 0xD3, | ||||
|         SIZE_64 = 0xD4 | ||||
|     } size; | ||||
| 
 | ||||
|     // TODO: Make it clearer which bits of this member are used for sizes other than 32 bits
 | ||||
|     uint64_t value; | ||||
| }; | ||||
| 
 | ||||
| struct CTStreamElement { | ||||
|     CTStreamElementType type; | ||||
| 
 | ||||
|     union { | ||||
|         CTMemoryLoad memory_load; | ||||
|         CTRegisterWrite register_write; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| #pragma pack() | ||||
| 
 | ||||
| } | ||||
|  | @ -123,12 +123,50 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
|             PrimitiveAssembler<VertexShader::OutputVertex> primitive_assembler(regs.triangle_topology.Value()); | ||||
|             PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex> dumping_primitive_assembler(regs.triangle_topology.Value()); | ||||
| 
 | ||||
|             if (g_debug_context) { | ||||
|                 for (int i = 0; i < 3; ++i) { | ||||
|                     const auto texture = regs.GetTextures()[i]; | ||||
|                     if (!texture.enabled) | ||||
|                         continue; | ||||
| 
 | ||||
|                     u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); | ||||
|                     if (g_debug_context && Pica::g_debug_context->recorder) | ||||
|                         g_debug_context->recorder->MemoryAccessed(texture_data, Pica::Regs::NibblesPerPixel(texture.format) * texture.config.width / 2 * texture.config.height, texture.config.GetPhysicalAddress()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // map physical start address to size
 | ||||
|             std::map<u32, u32> accessed_ranges; | ||||
|             static auto SimplifyRanges = [](std::map<u32, u32>& ranges) { | ||||
|                 for (auto it = ranges.begin(); it != ranges.end(); ++it) { | ||||
| 
 | ||||
|                     // Combine overlapping ranges ... artificially extend first range by 32 bytes to merge "close" ranges
 | ||||
|                     auto it2 = std::next(it); | ||||
|                     while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) { | ||||
|                         it->second = std::max(it->second, it2->first + it2->second - it->first); | ||||
|                         it2 = ranges.erase(it2); | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             static auto AddMemoryAccess = [](std::map<u32, u32>& ranges, u32 paddr, u32 size) { | ||||
|                 // Create new range or extend existing one
 | ||||
|                 ranges[paddr] = std::max(ranges[paddr], size); | ||||
| 
 | ||||
|                 // Simplify ranges...
 | ||||
|                 SimplifyRanges(ranges); | ||||
|             }; | ||||
| 
 | ||||
|             for (unsigned int index = 0; index < regs.num_vertices; ++index) | ||||
|             { | ||||
|                 unsigned int vertex = is_indexed ? (index_u16 ? index_address_16[index] : index_address_8[index]) : index; | ||||
| 
 | ||||
|                 if (is_indexed) { | ||||
|                     // TODO: Implement some sort of vertex cache!
 | ||||
|                     if (g_debug_context && Pica::g_debug_context->recorder) { | ||||
|                         int size = index_u16 ? 2 : 1; | ||||
|                         AddMemoryAccess(accessed_ranges, base_address + index_info.offset + size*index, size); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 // Initialize data for the current vertex
 | ||||
|  | @ -151,7 +189,14 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 
 | ||||
|                     // Load per-vertex data from the loader arrays
 | ||||
|                     for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { | ||||
|                         const u8* srcdata = Memory::GetPhysicalPointer(vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]); | ||||
|                         u32 source_addr = vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]; | ||||
|                         const u8* srcdata = Memory::GetPhysicalPointer(source_addr); | ||||
| 
 | ||||
|                         if (g_debug_context && Pica::g_debug_context->recorder) { | ||||
|                             AddMemoryAccess(accessed_ranges, source_addr, | ||||
|                                             (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 | ||||
|                                             : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1); | ||||
|                         } | ||||
| 
 | ||||
|                         const float srcval = (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::BYTE) ? *(s8*)srcdata : | ||||
|                             (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::UBYTE) ? *(u8*)srcdata : | ||||
|  | @ -213,14 +258,20 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             for (auto& range : accessed_ranges) { | ||||
|                 g_debug_context->recorder->MemoryAccessed(Memory::GetPhysicalPointer(range.first), | ||||
|                                                           range.second, range.first); | ||||
|             } | ||||
| 
 | ||||
|             if (Settings::values.use_hw_renderer) { | ||||
|                 VideoCore::g_renderer->hw_rasterizer->DrawTriangles(); | ||||
|             } | ||||
| 
 | ||||
|             geometry_dumper.Dump(); | ||||
| 
 | ||||
|             if (g_debug_context) | ||||
|             if (g_debug_context) { | ||||
|                 g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||||
|             } | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ | |||
| 
 | ||||
| #include "common/vector_math.h" | ||||
| 
 | ||||
| #include "core/tracer/recorder.h" | ||||
| 
 | ||||
| #include "video_core/pica.h" | ||||
| 
 | ||||
| namespace Pica { | ||||
|  | @ -129,6 +131,8 @@ public: | |||
|     Event active_breakpoint; | ||||
|     bool at_breakpoint = false; | ||||
| 
 | ||||
|     std::shared_ptr<CiTrace::Recorder> recorder = nullptr; | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Private default constructor to make sure people always construct this through Construct() | ||||
|  |  | |||
|  | @ -22,6 +22,8 @@ | |||
| #include "video_core/renderer_opengl/gl_shader_util.h" | ||||
| #include "video_core/renderer_opengl/gl_shaders.h" | ||||
| 
 | ||||
| #include "video_core/debug_utils/debug_utils.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * Vertex structure that the drawn screen rectangles are composed of. | ||||
|  */ | ||||
|  | @ -129,6 +131,10 @@ void RendererOpenGL::SwapBuffers() { | |||
|             hw_rasterizer->Reset(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (Pica::g_debug_context && Pica::g_debug_context->recorder) { | ||||
|         Pica::g_debug_context->recorder->FrameFinished(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue