diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/core/hle/service/gsp_gpu.cpp | 2 | ||||
-rw-r--r-- | src/core/hw/gpu.cpp | 78 | ||||
-rw-r--r-- | src/core/hw/hw.cpp | 30 | ||||
-rw-r--r-- | src/core/hw/lcd.cpp | 10 | ||||
-rw-r--r-- | src/core/tracer/citrace.h | 101 | ||||
-rw-r--r-- | src/core/tracer/recorder.cpp | 187 | ||||
-rw-r--r-- | src/core/tracer/recorder.h | 90 |
8 files changed, 473 insertions, 28 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9b004440c..8267ee586 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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/citrace.h mem_map.h memory.h memory_setup.h diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index f175085e8..3910d0227 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -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); }; diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index a1789f9c7..a3a7d128f 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -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; @@ -101,39 +106,43 @@ inline void Write(u32 addr, const T data) { const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].trigger)); auto& config = g_regs.memory_fill_config[is_second_filler]; - if (config.address_start && config.trigger) { - u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); - u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); - - if (config.fill_24bit) { - // fill with 24-bit values - for (u8* ptr = start; ptr < end; ptr += 3) { - ptr[0] = config.value_24bit_r; - ptr[1] = config.value_24bit_g; - ptr[2] = config.value_24bit_b; + if (config.trigger) { + if (config.address_start) { // Some games pass invalid values here + u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); + u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); + + if (config.fill_24bit) { + // fill with 24-bit values + for (u8* ptr = start; ptr < end; ptr += 3) { + ptr[0] = config.value_24bit_r; + ptr[1] = config.value_24bit_g; + ptr[2] = config.value_24bit_b; + } + } else if (config.fill_32bit) { + // fill with 32-bit values + for (u32* ptr = (u32*)start; ptr < (u32*)end; ++ptr) + *ptr = config.value_32bit; + } else { + // fill with 16-bit values + for (u16* ptr = (u16*)start; ptr < (u16*)end; ++ptr) + *ptr = config.value_16bit; } - } else if (config.fill_32bit) { - // fill with 32-bit values - for (u32* ptr = (u32*)start; ptr < (u32*)end; ++ptr) - *ptr = config.value_32bit; - } else { - // fill with 16-bit values - for (u16* ptr = (u16*)start; ptr < (u16*)end; ++ptr) - *ptr = config.value_16bit; - } - LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); + LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); - config.trigger = 0; - config.finished = 1; + if (!is_second_filler) { + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC0); + } else { + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); + } - if (!is_second_filler) { - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC0); - } else { - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); + VideoCore::g_renderer->hw_rasterizer->NotifyFlush(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress()); } - VideoCore::g_renderer->hw_rasterizer->NotifyFlush(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress()); + // Reset "trigger" flag and set the "finish" flag + // NOTE: This was confirmed to happen on hardware even if "address_start" is zero. + config.trigger = 0; + config.finished = 1; } break; } @@ -270,6 +279,7 @@ inline void Write(u32 addr, const T data) { config.GetPhysicalOutputAddress(), output_width, output_height, config.output_format.Value(), config.flags); + g_regs.display_transfer_config.trigger = 0; GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); VideoCore::g_renderer->hw_rasterizer->NotifyFlush(config.GetPhysicalOutputAddress(), output_size); @@ -284,7 +294,14 @@ 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; } break; } @@ -292,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: diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index c7006a498..b5fdbf9c1 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -15,6 +15,21 @@ template <typename T> inline void Read(T &var, const u32 addr) { switch (addr & 0xFFFFF000) { case VADDR_GPU: + case VADDR_GPU + 0x1000: + case VADDR_GPU + 0x2000: + case VADDR_GPU + 0x3000: + case VADDR_GPU + 0x4000: + case VADDR_GPU + 0x5000: + case VADDR_GPU + 0x6000: + case VADDR_GPU + 0x7000: + case VADDR_GPU + 0x8000: + case VADDR_GPU + 0x9000: + case VADDR_GPU + 0xA000: + case VADDR_GPU + 0xB000: + case VADDR_GPU + 0xC000: + case VADDR_GPU + 0xD000: + case VADDR_GPU + 0xE000: + case VADDR_GPU + 0xF000: GPU::Read(var, addr); break; case VADDR_LCD: @@ -29,6 +44,21 @@ template <typename T> inline void Write(u32 addr, const T data) { switch (addr & 0xFFFFF000) { case VADDR_GPU: + case VADDR_GPU + 0x1000: + case VADDR_GPU + 0x2000: + case VADDR_GPU + 0x3000: + case VADDR_GPU + 0x4000: + case VADDR_GPU + 0x5000: + case VADDR_GPU + 0x6000: + case VADDR_GPU + 0x7000: + case VADDR_GPU + 0x8000: + case VADDR_GPU + 0x9000: + case VADDR_GPU + 0xA000: + case VADDR_GPU + 0xB000: + case VADDR_GPU + 0xC000: + case VADDR_GPU + 0xD000: + case VADDR_GPU + 0xE000: + case VADDR_GPU + 0xF000: GPU::Write(addr, data); break; case VADDR_LCD: diff --git a/src/core/hw/lcd.cpp b/src/core/hw/lcd.cpp index cdb757a18..6f93709e3 100644 --- a/src/core/hw/lcd.cpp +++ b/src/core/hw/lcd.cpp @@ -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: diff --git a/src/core/tracer/citrace.h b/src/core/tracer/citrace.h new file mode 100644 index 000000000..5deb6ce9e --- /dev/null +++ b/src/core/tracer/citrace.h @@ -0,0 +1,101 @@ +// 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 default_attributes; + uint32_t default_attributes_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 + // - Lookup tables for fragment lighting + // - Lookup tables for procedural textures + } 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() + +} diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp new file mode 100644 index 000000000..656706c0c --- /dev/null +++ b/src/core/tracer/recorder.cpp @@ -0,0 +1,187 @@ +// 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(const InitialState& initial_state) : initial_state(initial_state) { + +} + +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 = initial_state.gpu_registers.size(); + initial.lcd_registers_size = initial_state.lcd_registers.size(); + initial.pica_registers_size = initial_state.pica_registers.size(); + initial.default_attributes_size = initial_state.default_attributes.size(); + initial.vs_program_binary_size = initial_state.vs_program_binary.size(); + initial.vs_swizzle_data_size = initial_state.vs_swizzle_data.size(); + initial.vs_float_uniforms_size = initial_state.vs_float_uniforms.size(); + initial.gs_program_binary_size = initial_state.gs_program_binary.size(); + initial.gs_swizzle_data_size = initial_state.gs_swizzle_data.size(); + initial.gs_float_uniforms_size = initial_state.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.default_attributes = initial.pica_registers + initial.pica_registers_size * sizeof(u32); + initial.vs_program_binary = initial.default_attributes + initial.default_attributes_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(initial_state.gpu_registers.data(), initial_state.gpu_registers.size()); + if (written != initial_state.gpu_registers.size() || file.Tell() != initial.lcd_registers) + throw "Failed to write GPU registers"; + + written = file.WriteArray(initial_state.lcd_registers.data(), initial_state.lcd_registers.size()); + if (written != initial_state.lcd_registers.size() || file.Tell() != initial.pica_registers) + throw "Failed to write LCD registers"; + + written = file.WriteArray(initial_state.pica_registers.data(), initial_state.pica_registers.size()); + if (written != initial_state.pica_registers.size() || file.Tell() != initial.default_attributes) + throw "Failed to write Pica registers"; + + written = file.WriteArray(initial_state.default_attributes.data(), initial_state.default_attributes.size()); + if (written != initial_state.default_attributes.size() || file.Tell() != initial.vs_program_binary) + throw "Failed to write default vertex attributes"; + + written = file.WriteArray(initial_state.vs_program_binary.data(), initial_state.vs_program_binary.size()); + if (written != initial_state.vs_program_binary.size() || file.Tell() != initial.vs_swizzle_data) + throw "Failed to write vertex shader program binary"; + + written = file.WriteArray(initial_state.vs_swizzle_data.data(), initial_state.vs_swizzle_data.size()); + if (written != initial_state.vs_swizzle_data.size() || file.Tell() != initial.vs_float_uniforms) + throw "Failed to write vertex shader swizzle data"; + + written = file.WriteArray(initial_state.vs_float_uniforms.data(), initial_state.vs_float_uniforms.size()); + if (written != initial_state.vs_float_uniforms.size() || file.Tell() != initial.gs_program_binary) + throw "Failed to write vertex shader float uniforms"; + + written = file.WriteArray(initial_state.gs_program_binary.data(), initial_state.gs_program_binary.size()); + if (written != initial_state.gs_program_binary.size() || file.Tell() != initial.gs_swizzle_data) + throw "Failed to write geomtry shader program binary"; + + written = file.WriteArray(initial_state.gs_swizzle_data.data(), initial_state.gs_swizzle_data.size()); + if (written != initial_state.gs_swizzle_data.size() || file.Tell() != initial.gs_float_uniforms) + throw "Failed to write geometry shader swizzle data"; + + written = file.WriteArray(initial_state.gs_float_uniforms.data(), initial_state.gs_float_uniforms.size()); + if (written != initial_state.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); + +} diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h new file mode 100644 index 000000000..6e4b70015 --- /dev/null +++ b/src/core/tracer/recorder.h @@ -0,0 +1,90 @@ +// 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 "citrace.h" + +namespace CiTrace { + +class Recorder { +public: + struct InitialState { + std::vector<u32> gpu_registers; + std::vector<u32> lcd_registers; + std::vector<u32> pica_registers; + std::vector<u32> default_attributes; + 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; + }; + + /** + * Recorder constructor + * @param default_attributes Pointer to an array of 32-bit-aligned 24-bit floating point values. + * @param vs_float_uniforms Pointer to an array of 32-bit-aligned 24-bit floating point values. + */ + Recorder(const InitialState& initial_state); + + /// 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 + InitialState initial_state; + + // 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 |