From 71ca62e2ccd160213df3fa1eab81eb8ecb409577 Mon Sep 17 00:00:00 2001 From: wheremyfoodat Date: Wed, 21 Sep 2022 19:10:53 +0300 Subject: [PATCH] [GPU] Get PICA register access working --- CMakeLists.txt | 2 +- include/gpu.hpp | 11 ++++++ src/core/PICA/gpu.cpp | 3 +- src/core/PICA/regs.cpp | 40 +++++++++++++++++++++ src/core/services/gsp_gpu.cpp | 67 ++++++++++++++++++++++++++++++----- 5 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 src/core/PICA/regs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d738a67..49b6d24f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limi set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp ) -set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp) +set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp) set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp include/termcolor.hpp include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/kernel/kernel.hpp diff --git a/include/gpu.hpp b/include/gpu.hpp index e01a4d1d..d1525d8d 100644 --- a/include/gpu.hpp +++ b/include/gpu.hpp @@ -1,12 +1,23 @@ #pragma once +#include #include "helpers.hpp" #include "memory.hpp" class GPU { Memory& mem; + static constexpr u32 regNum = 0x300; + std::array regs; // GPU internal registers public: GPU(Memory& mem) : mem(mem) {} void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control); void reset(); + + // Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked + u32 readReg(u32 address); + void writeReg(u32 address, u32 value); + + // Used when processing GPU command lists + u32 readInternalReg(u32 index); + void writeInternalReg(u32 index, u32 value, u32 mask); }; \ No newline at end of file diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index 85c73384..380c25c7 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -2,7 +2,8 @@ #include void GPU::reset() { - + regs.fill(0); + // TODO: Reset blending, texturing, etc here } void GPU::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { diff --git a/src/core/PICA/regs.cpp b/src/core/PICA/regs.cpp new file mode 100644 index 00000000..144cc0d5 --- /dev/null +++ b/src/core/PICA/regs.cpp @@ -0,0 +1,40 @@ +#include "gpu.hpp" + +u32 GPU::readReg(u32 address) { + printf("Ignoring read from GPU register %08X\n", address); + return 0; +} + +void GPU::writeReg(u32 address, u32 value) { + if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers + const u32 index = (address - 0x1EF01000) / sizeof(u32); + writeInternalReg(index, value, 0xffffffff); + } else { + Helpers::panic("Ignoring write to GPU register %08X. Value: %08X\n", address, value); + } +} + +u32 GPU::readInternalReg(u32 index) { + if (index > regNum) { + printf("Tried to read invalid GPU register. Index: %X\n", index); + return 0; + } + + return regs[index]; +} + +void GPU::writeInternalReg(u32 index, u32 value, u32 mask) { + if (index > regNum) { + Helpers::panic("Tried to write to invalid GPU register. Index: %X, value: %08X\n", index, value); + return; + } + + u32 currentValue = regs[index]; + u32 newValue = (currentValue & ~mask) | (value & mask); // Only overwrite the bits specified by "mask" + + switch (index) { + default: + printf("GPU: Wrote to unimplemented internal reg: %X, value: %08X\n", index, newValue); + break; + } +} \ No newline at end of file diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index c7b31810..a5d5186e 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -116,12 +116,12 @@ void GPUService::writeHwRegs(u32 messagePointer) { Helpers::panic("GSP::GPU::writeHwRegs offset too big"); } + ioAddr += 0x1EB00000; for (u32 i = 0; i < size; i += 4) { const u32 value = mem.read32(dataPointer); - printf("GSP::GPU: Wrote %08X to GPU register %X\n", value, ioAddr); + gpu.writeReg(ioAddr, value); dataPointer += 4; ioAddr += 4; - // TODO: Write the value to the register } mem.write32(messagePointer + 4, Result::Success); } @@ -150,14 +150,16 @@ void GPUService::writeHwRegsWithMask(u32 messagePointer) { if (ioAddr >= 0x420000) { Helpers::panic("GSP::GPU::writeHwRegs offset too big"); } - + + ioAddr += 0x1EB00000; for (u32 i = 0; i < size; i += 4) { - const u32 currentValue = 0; // TODO: Read the actual register value + const u32 current = gpu.readReg(ioAddr); const u32 data = mem.read32(dataPointer); const u32 mask = mem.read32(maskPointer); - u32 newValue = (currentValue & ~mask) | (data & mask); - // TODO: write new value + u32 newValue = (current & ~mask) | (data & mask); + + gpu.writeReg(ioAddr, newValue); maskPointer += 4; dataPointer += 4; ioAddr += 4; @@ -250,9 +252,58 @@ void GPUService::memoryFill(u32* cmd) { // Actually send command list (aka display list) to GPU void GPUService::processCommandList(u32* cmd) { u32 address = cmd[1]; // Buffer address - u32 size = cmd[2]; // Buffer size + u32 size = cmd[2] * 8; // Buffer size in bytes bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update) bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush) - Helpers::panic("GPU::GSP::processCommandList. Address: %08X, size: %08X", address, size); + u32* bufferStart = static_cast(mem.getReadPointer(address)); + if (!bufferStart) Helpers::panic("Couldn't get buffer for command list"); + // TODO: This is very memory unsafe. We get a pointer to FCRAM and just keep writing without checking if we're gonna go OoB + + u32* curr = bufferStart; + u32* bufferEnd = bufferStart + (size / sizeof(u32)); + + // LUT for converting the parameter mask to an actual 32-bit mask + // The parameter mask is 4 bits long, each bit corresponding to one byte of the mask + // If the bit is 0 then the corresponding mask byte is 0, otherwise the mask byte is 0xff + // So for example if the parameter mask is 0b1001, the full mask is 0xff'00'00'ff + static constexpr std::array maskLUT = { + 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, 0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff, + 0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff, + }; + + while (curr < bufferEnd) { + // If the buffer is not aligned to an 8 byte boundary, force align it by moving the pointer up a word + // The curr pointer starts out doubleword-aligned and is increased by 4 bytes each time + // So to check if it is aligned, we get the number of words it's been incremented by + // If that number is an odd value then the buffer is not aligned, otherwise it is + if ((curr - bufferStart) % 2 != 0) { + curr++; + } + + // The first word of a command is the command parameter and the second one is the header + u32 param1 = *curr++; + u32 header = *curr++; + + u32 id = header & 0xffff; + u32 paramMaskIndex = (header >> 16) & 0xf; + u32 paramCount = (header >> 20) & 0xff; // Number of additional parameters + // Bit 31 tells us whether this command is going to write to multiple sequential registers (if the bit is 1) + // Or if all written values will go to the same register (If the bit is 0). It's essentially the value that + // gets added to the "id" field after each register write + bool consecutiveWritingMode = (header >> 31) != 0; + + u32 mask = maskLUT[paramMaskIndex]; // Actual parameter mask + // Increment the ID by 1 after each write if we're in consecutive mode, or 0 otherwise + u32 idIncrement = (consecutiveWritingMode) ? 1 : 0; + + gpu.writeInternalReg(id, param1, mask); + for (u32 i = 0; i < paramCount; i++) { + id += idIncrement; + u32 param = *curr++; + gpu.writeInternalReg(id, param, mask); + } + } + + Helpers::panic("GPU::GSP::processCommandList. Address: %08X, size in bytes: %08X", address, size); } \ No newline at end of file