diff --git a/Core/HLE/PopsBoot.cpp b/Core/HLE/PopsBoot.cpp index b74b6d3a99..ee0a759e21 100644 --- a/Core/HLE/PopsBoot.cpp +++ b/Core/HLE/PopsBoot.cpp @@ -19,6 +19,7 @@ // This stands in for the parts of the POPS boot PPSSPP doesn't run: the kernel's init, and the // Media Engine the manager's ME loop runs on. See docs/POPS.md. +#include #include #include #include @@ -51,6 +52,8 @@ #include "Core/MemMapHelpers.h" #include "Core/Screenshot.h" #include "Core/System.h" +#include "GPU/GPU.h" +#include "GPU/GPUCommon.h" static bool g_popsBoot = false; static bool g_popsConvertedPBP = false; @@ -303,10 +306,43 @@ static int __PopsReadIcon0(u32 file, u32 buf, u32 offset, u32 size) { return hleLogDebug(Log::sceKernel, (int)size); } +// The emulator's state for a CPU-to-VRAM image transfer through the GP0 port, from its gp: the +// rectangle (x0, y0, x1, y1), then the current x and y. +static const u32 POPS_IMAGE_LOAD_STATE = 0x363C; + +// The end of a row of such a transfer, which the emulator writes pixel by pixel with the CPU. When +// it's the last row, the rectangle goes into the framebuffer. +static void __PopsImageLoadRow() { + // The loop's registers are live across the call. + hleSkipDeadbeef(); + const u32 state = currentMIPS->r[MIPS_REG_GP] + POPS_IMAGE_LOAD_STATE; + const u16 x0 = Memory::ReadUnchecked_U16(state), y0 = Memory::ReadUnchecked_U16(state + 2); + const u16 x1 = Memory::ReadUnchecked_U16(state + 4), y1 = Memory::ReadUnchecked_U16(state + 6); + const u16 y = Memory::ReadUnchecked_U16(state + 10); + if ((u16)(y + 1) != y1 || !gpu) { + return; + } + // VRAM is 1024x512 and the transfer wraps around its edges. + const int w = std::min((u16)(x1 - x0), (u16)1024), h = std::min((u16)(y1 - y0), (u16)512); + for (int ys = 0; ys < h;) { + const int sy = (y0 + ys) & 511; + const int sh = std::min(h - ys, 512 - sy); + for (int xs = 0; xs < w;) { + const int sx = (x0 + xs) & 1023; + const int sw = std::min(w - xs, 1024 - sx); + gpu->PerformWriteRectFromMemory(0x04000000 + (sy * 1024 + sx) * 2, 1024, sw, sh, GE_FORMAT_5551); + xs += sw; + } + ys += sh; + } + DEBUG_LOG(Log::sceKernel, "POPS: image load %d,%d %dx%d", x0, y0, w, h); +} + const HLEFunction PopsBootCalls[] = { { 0x13370006, &WrapI_UUU<__PopsDeflateDecompress>, "__PopsDeflateDecompress", 'i', "xxx" }, { 0x13370007, &WrapI_UUU<__PopsGetVersionKey>, "__PopsGetVersionKey", 'i', "xxx" }, { 0x13370008, &WrapI_UUUU<__PopsReadIcon0>, "__PopsReadIcon0", 'i', "xxxx" }, + { 0x13370009, &WrapV_V<__PopsImageLoadRow>, "__PopsImageLoadRow", 'v', "" }, }; void Register_PopsBootCalls() { @@ -440,6 +476,23 @@ void __PopsBootPatchEmulator(u32 textAddr, u32 textSize, u32 decodeStub) { } currentMIPS->InvalidateICacheRangeDeferred(textAddr, textSize); + // The GP0 port's image transfer ends its rows in two places (after the word's first pixel or its + // second): lhu rN, y1(gp); addiu rM, a2, 1; beq rM, rN; sh rM, y(gp). + int imageLoadRows = 0; + for (u32 addr = textAddr; addr + 16 <= textAddr + textSize; addr += 4) { + const u32 op = Memory::ReadUnchecked_U32(addr); + const u32 next = Memory::ReadUnchecked_U32(addr + 4); + const bool first = op == 0x97983642 && next == 0x24C60001 && (Memory::ReadUnchecked_U32(addr + 8) >> 16) == 0x10D8 && Memory::ReadUnchecked_U32(addr + 12) == 0xA7863646; + const bool second = op == 0x97903642 && next == 0x24C40001 && (Memory::ReadUnchecked_U32(addr + 8) >> 16) == 0x1090 && Memory::ReadUnchecked_U32(addr + 12) == 0xA7843646; + if (first || second) { + PatchTrampoline(addr, "__PopsImageLoadRow", op, addr + 8); + imageLoadRows++; + } + } + if (imageLoadRows != 2) { + WARN_LOG(Log::sceKernel, "POPS: found %d of the 2 GP0 image transfer row ends, hardware renderers may miss its pixels", imageLoadRows); + } + if (!g_popsConvertedPBP) { return; } diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 3749c28f23..0a909fa5b3 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1257,7 +1257,7 @@ void FramebufferManagerCommon::WriteRectFromMemory(u32 addr, int size, int strid VirtualFramebuffer *target = nullptr; for (auto vfb : vfbs_) { - if (!vfb->fbo || vfb->FbStrideInBytes() != byteStride || addr <= vfb->fb_address || addr >= vfb->fb_address + vfb->BufferByteSize(RASTER_COLOR)) { + if (!vfb->fbo || vfb->FbStrideInBytes() != byteStride || addr < vfb->fb_address || addr >= vfb->fb_address + vfb->BufferByteSize(RASTER_COLOR)) { continue; } if (!target || vfb->colorBindSeq > target->colorBindSeq) { diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index a3d3d78ea3..428ffc4c11 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2686,6 +2686,14 @@ void GPUCommon::PerformWriteFormattedFromMemory(u32 addr, int size, int frameWid InvalidateCache(addr, size, GPU_INVALIDATE_SAFE); } +void GPUCommon::PerformWriteRectFromMemory(u32 addr, int stride, int width, int height, GEBufferFormat format) { + const int bpp = BufferFormatBytesPerPixel(format); + if (Memory::IsVRAMAddress(addr) && framebufferManager_) { + framebufferManager_->WriteRectFromMemory(addr, height * stride * bpp, stride, format, width); + } + InvalidateCache(addr, ((height - 1) * stride + width) * bpp, GPU_INVALIDATE_SAFE); +} + bool GPUCommon::PerformReadbackStencilToMemory(u32 dest, int size) { if (Memory::IsVRAMAddress(dest) && framebufferManager_->MayIntersectFramebufferColor(dest)) { return framebufferManager_->ReadbackStencilToMemory(dest, size); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 4346bf1a0f..e2f1f20c6f 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -174,6 +174,8 @@ public: // width is the stride. rectWidth, if set, is how many pixels of each row were written, which lets // a write that starts partway into a framebuffer row be uploaded without clobbering the rest. virtual void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format, int rectWidth = 0); + // The CPU has written a width x height rectangle of pixels at addr, rows stride pixels apart. + virtual void PerformWriteRectFromMemory(u32 addr, int stride, int width, int height, GEBufferFormat format); virtual bool PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags); // Reads the stencil of whole framebuffer rows into the alpha bytes in RAM, leaving the color alone. // For games that test stencil with the CPU. Only 8888 for now. diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 4be76c783f..0ed0ec1fe3 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1506,6 +1506,10 @@ void SoftGPU::PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEB NoteVideoRange(addr, (u32)size); } +void SoftGPU::PerformWriteRectFromMemory(u32 addr, int stride, int width, int height, GEBufferFormat format) { + InvalidateCache(addr, ((height - 1) * stride + width) * BufferFormatBytesPerPixel(format), GPU_INVALIDATE_HINT); +} + bool SoftGPU::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) { // Drawing can still be queued after a list stalled (FinishDeferred). drawEngine_->transformUnit.FlushIfOverlap(this, "memcpy", false, src, size, size, 1); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 4661faeb99..ff250d8eda 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -184,6 +184,7 @@ public: std::vector GetFramebufferList() const override { return std::vector(); } void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override; void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format, int rectWidth) override; + void PerformWriteRectFromMemory(u32 addr, int stride, int width, int height, GEBufferFormat format) override; bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; bool PerformMemorySet(u32 dest, u8 v, int size) override; bool PerformReadbackToMemory(u32 dest, int size) override; diff --git a/docs/POPS.md b/docs/POPS.md index b1ff7f0938..c30cadc083 100644 --- a/docs/POPS.md +++ b/docs/POPS.md @@ -43,7 +43,9 @@ without them. The emulator keeps the PS1's 1024x512 VRAM at `0x04000000` as one 16-bit framebuffer, and reads and writes it outside where it draws. It's a fixed framebuffer: created whole, from memory, and uploaded without -filtering. Its 24-bit video is unpacked three bytes per pixel into `0x04100000`, which is kept at 1x. It +filtering. An image the game sends to VRAM through the GP0 port rather than by DMA is written there by the +CPU, pixel by pixel, which the hardware renderers don't see: the end of each row of that loop is hooked, and +the finished rectangle is uploaded (Ridge Racer's title screen flag). Its 24-bit video is unpacked three bytes per pixel into `0x04100000`, which is kept at 1x. It draws sprites on pixel centers, which above 1x read their neighbours' texels, so those are moved back half a pixel. And it builds video frames by ORing passes together, so draws whose logic op is done in the shader are drawn one at a time.