diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 06521cbdca..5c977f4d7f 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -1467,6 +1467,22 @@ static int Hook_everybodysgolf_spinner_background() { return 0; } +static int Hook_codedarms_stencil_check() { + // The crosshair turns red, and lock-on works, when stencil bit 3 is set at the centre of the frame + // just rendered, which the game tests by reading the pixel from VRAM (#17936). Hooked at the load, + // where v0 is the pixel's address. Read back the stencil of the row it's in (the game's stride is + // 512). Only stencil: the alpha the hardware backends keep doesn't follow it everywhere. + const u32 addr = currentMIPS->r[MIPS_REG_V0]; + if (!Memory::IsVRAMAddress(addr)) { + return 0; + } + const u32 row = addr & ~0x7FF; + if (Memory::IsValidRange(row, 0x800)) { + gpu->PerformReadbackStencilToMemory(row, 0x800); + } + return 0; +} + static int Hook_katamari_render_check() { if (!Memory::IsValidRange(currentMIPS->r[MIPS_REG_A0] + 0x3C, 8)) { return 0; @@ -1804,6 +1820,7 @@ static const ReplacementTableEntry entries[] = { { "everybodystennis_copy_portrait", &Hook_everybodys_copy_portrait, 0, REPFLAG_HOOKENTER, 0x54 }, { "everybodysgolf_copy_portrait", &Hook_everybodysgolf_copy_portrait, 0, REPFLAG_HOOKENTER, 0x50 }, { "everybodysgolf_spinner_background", &Hook_everybodysgolf_spinner_background, 0, REPFLAG_HOOKENTER, 0x138 }, + { "codedarms_stencil_check", &Hook_codedarms_stencil_check, 0, REPFLAG_HOOKENTER, 0x8C }, {} }; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 1d25ce177d..fc87bc6701 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -482,6 +482,7 @@ static const HardHashTableEntry hardcodedHashes[] = { { 0xeabb9c1b4f83d2b4, 52, "memset_jak", }, // Crisis Core (US), Jak and Daxter (this is a slow memset and needs to have slow timing) { 0xeb0f7bf63d52ece9, 88, "strncat", }, { 0xeb8c0834d8bbc28c, 416, "fmodf", }, + { 0xec9ad8df944a770b, 180, "codedarms_stencil_check", }, // Coded Arms: Contagion (EUR) { 0xed8918f378e9a563, 628, "sd_gundam_g_generation_download_frame", }, // SD Gundam G Generation Overworld { 0xedbbe9bf9fbceca8, 172, "dl_write_viewport2", }, { 0xedc3f476221f96e6, 148, "tanf", }, diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 5818264215..7c35b73809 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -3487,6 +3487,73 @@ bool FramebufferManagerCommon::ReadbackStencilbuffer(Draw::Framebuffer *fbo, int return draw_->CopyFramebufferToMemory(fbo, Draw::Aspect::DEPTH_BIT, x, y, w, h, Draw::DataFormat::S8, pixels, pixelsStride, mode, "ReadbackStencilbufferSync"); } +bool FramebufferManagerCommon::ReadbackStencilToMemory(u32 addr, int size) { + if (!useBufferedRendering_) { + return false; + } + addr &= 0x3FFFFFFF; + if (Memory::IsVRAMAddress(addr)) + addr &= 0x041FFFFF; + + VirtualFramebuffer *vfb = nullptr; + for (auto v : vfbs_) { + if (!v->fbo || v->fb_stride == 0 || v->fb_format != GE_FORMAT_8888) { + continue; + } + if (addr >= v->fb_address && addr + size <= v->fb_address + v->BufferByteSize(RASTER_COLOR)) { + if (!vfb || v->colorBindSeq > vfb->colorBindSeq) { + vfb = v; + } + } + } + if (!vfb) { + return false; + } + + // Whole rows only. + const u32 byteStride = vfb->fb_stride * 4; + const u32 offset = addr - vfb->fb_address; + if (offset % byteStride != 0 || size % byteStride != 0) { + return false; + } + const int y = offset / byteStride; + const int h = std::min((int)(size / byteStride), (int)vfb->height - y); + const int w = std::min((int)vfb->fb_stride, (int)vfb->width); + if (w <= 0 || h <= 0) { + return false; + } + + FlushBeforeCopy(); + + const int scale = vfb->renderScaleFactor; + const int sw = w * scale; + const int sh = h * scale; + std::vector stencil(sw * sh); + bool ok = draw_->CopyFramebufferToMemory(vfb->fbo, Draw::Aspect::STENCIL_BIT, 0, y * scale, sw, sh, Draw::DataFormat::S8, stencil.data(), sw, Draw::ReadbackMode::BLOCK, "ReadbackStencilToMemory"); + if (!ok) { + ok = ReadbackStencilbuffer(vfb->fbo, 0, y * scale, sw, sh, stencil.data(), sw, Draw::ReadbackMode::BLOCK); + } + draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); + textureCache_->ForgetLastTexture(); + RebindFramebuffer("RebindFramebuffer - ReadbackStencilToMemory"); + gpuStats.perFrame.numBlockingReadbacks++; + if (!ok) { + return false; + } + + // PSP stencil is the alpha byte, so that's all that changes. + u8 *dest = Memory::GetPointerWriteUnchecked(vfb->fb_address + offset); + for (int row = 0; row < h; row++) { + const u8 *src = &stencil[row * scale * sw]; + u8 *dst = dest + row * byteStride; + for (int x = 0; x < w; x++) { + dst[x * 4 + 3] = src[x * scale]; + } + } + NotifyMemInfo(MemBlockFlags::WRITE, vfb->fb_address + offset, h * byteStride, "ReadbackStencil"); + return true; +} + void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel, Draw::ReadbackMode mode) { if (!vfb || !vfb->fbo) { return; diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index f8e5373f21..137f4ebc39 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -351,6 +351,7 @@ public: void UpdateFromMemory(u32 addr, int size); void ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor); bool PerformWriteStencilFromMemory(u32 addr, int size, WriteStencil flags); + bool ReadbackStencilToMemory(u32 addr, int size); void WriteStencilFromFramebufferAlpha(VirtualFramebuffer *dst); // We changed our depth mode, gotta start over. diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 408b2e5aee..011521fa29 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2358,6 +2358,13 @@ void GPUCommon::PerformWriteFormattedFromMemory(u32 addr, int size, int frameWid InvalidateCache(addr, size, GPU_INVALIDATE_SAFE); } +bool GPUCommon::PerformReadbackStencilToMemory(u32 dest, int size) { + if (Memory::IsVRAMAddress(dest) && framebufferManager_->MayIntersectFramebufferColor(dest)) { + return framebufferManager_->ReadbackStencilToMemory(dest, size); + } + return false; +} + bool GPUCommon::PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags) { if (framebufferManager_->MayIntersectFramebufferColor(dest)) { framebufferManager_->PerformWriteStencilFromMemory(dest, size, flags); diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index d78d417277..04a57ba4e2 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -168,6 +168,9 @@ public: // 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); 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. + virtual bool PerformReadbackStencilToMemory(u32 dest, int size); // Tells the texture cache that a block copy moved pixels, so it can carry "this is video" from // the source to the destination. Cheap and safe to call for any copy. diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 1eb0f2d0bb..9375185f8c 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1493,6 +1493,12 @@ bool SoftGPU::PerformReadbackToMemory(u32 dest, int size) return false; } +bool SoftGPU::PerformReadbackStencilToMemory(u32 dest, int size) { + // Stencil is already in RAM, once any queued drawing is done. + drawEngine_->transformUnit.FlushIfOverlap(this, "readback", false, dest, size, size, 1); + return false; +} + bool SoftGPU::PerformWriteColorFromMemory(u32 dest, int size) { drawEngine_->transformUnit.FlushIfOverlap(this, "upload", true, dest, size, size, 1); diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index cb61d33551..baae40a276 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -176,6 +176,7 @@ public: bool PerformReadbackToMemory(u32 dest, int size) override; bool PerformWriteColorFromMemory(u32 dest, int size) override; bool PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags) override; + bool PerformReadbackStencilToMemory(u32 dest, int size) override; void DeviceLost() override; void DeviceRestore(Draw::DrawContext *draw) override;