From fc68cd9457bbc2df4c76ed3b5b8ac2d67e1ca566 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 10 Oct 2022 17:09:14 -0700 Subject: [PATCH] GLES: Add debug readback of stencil data. This allows the existing gpu.buffer.renderStencil to snapshot the state. --- Common/GPU/OpenGL/GLFeatures.cpp | 2 +- Common/GPU/OpenGL/GLFeatures.h | 1 + GPU/Common/FramebufferManagerCommon.cpp | 10 +++ GPU/Common/FramebufferManagerCommon.h | 4 + GPU/GLES/DepthBufferGLES.cpp | 111 ++++++++++++++++++++++++ GPU/GLES/FramebufferManagerGLES.h | 1 + 6 files changed, 128 insertions(+), 1 deletion(-) diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index 103fe00985..ecec49c82e 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -544,8 +544,8 @@ void CheckGLExtensions() { } if (gl_extensions.VersionGEThan(4, 3)) { gl_extensions.ARB_copy_image = true; + gl_extensions.ARB_stencil_texturing = true; // ARB_explicit_uniform_location = true; - // ARB_stencil_texturing = true; // ARB_texture_view = true; // ARB_vertex_attrib_binding = true; } diff --git a/Common/GPU/OpenGL/GLFeatures.h b/Common/GPU/OpenGL/GLFeatures.h index 5fe965c3f0..0e07fd266f 100644 --- a/Common/GPU/OpenGL/GLFeatures.h +++ b/Common/GPU/OpenGL/GLFeatures.h @@ -71,6 +71,7 @@ struct GLExtensions { bool ARB_depth_clamp; bool ARB_uniform_buffer_object; bool ARB_texture_non_power_of_two; + bool ARB_stencil_texturing; // EXT bool EXT_swap_control_tear; diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 397d5ae764..e408798375 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -2578,6 +2578,10 @@ bool FramebufferManagerCommon::GetStencilbuffer(u32 fb_address, int fb_stride, G // No need to free on failure, the caller/destructor will do that. Usually this is a reused buffer, anyway. buffer.Allocate(w, h, GPU_DBG_FORMAT_8BIT, flipY); bool retval = draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_STENCIL_BIT, 0, 0, w,h, Draw::DataFormat::S8, buffer.GetData(), w, "GetStencilbuffer"); + if (!retval) { + // Try ReadbackStencilbufferSync, in case GLES. + retval = ReadbackStencilbufferSync(vfb->fbo, 0, 0, w, h, buffer.GetData(), w); + } // That may have unbound the framebuffer, rebind to avoid crashes when debugging. RebindFramebuffer("RebindFramebuffer - GetStencilbuffer"); return retval; @@ -2652,6 +2656,10 @@ bool FramebufferManagerCommon::ReadbackDepthbufferSync(Draw::Framebuffer *fbo, i return draw_->CopyFramebufferToMemorySync(fbo, Draw::FB_DEPTH_BIT, x, y, w, h, destFormat, pixels, pixelsStride, "ReadbackDepthbufferSync"); } +bool FramebufferManagerCommon::ReadbackStencilbufferSync(Draw::Framebuffer *fbo, int x, int y, int w, int h, uint8_t *pixels, int pixelsStride) { + return draw_->CopyFramebufferToMemorySync(fbo, Draw::FB_DEPTH_BIT, x, y, w, h, Draw::DataFormat::S8, pixels, pixelsStride, "ReadbackStencilbufferSync"); +} + void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel) { // Clamp to bufferWidth. Sometimes block transfers can cause this to hit. if (x + w >= vfb->bufferWidth) { @@ -2809,6 +2817,8 @@ void FramebufferManagerCommon::DeviceLost() { } DoRelease(stencilWriteSampler_); DoRelease(stencilWritePipeline_); + DoRelease(stencilReadbackSampler_); + DoRelease(stencilReadbackPipeline_); DoRelease(depthReadbackSampler_); DoRelease(depthReadbackPipeline_); DoRelease(draw2DPipelineColor_); diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index bb28032be0..ffcab7a1ec 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -444,6 +444,7 @@ protected: virtual void ReadbackFramebufferSync(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel); // Used for when a shader is required, such as GLES. virtual bool ReadbackDepthbufferSync(Draw::Framebuffer *fbo, int x, int y, int w, int h, uint16_t *pixels, int pixelsStride); + virtual bool ReadbackStencilbufferSync(Draw::Framebuffer *fbo, int x, int y, int w, int h, uint8_t *pixels, int pixelsStride); void SetViewport2D(int x, int y, int w, int h); Draw::Texture *MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height); void DrawActiveTexture(float x, float y, float w, float h, float destW, float destH, float u0, float v0, float u1, float v1, int uvRotation, int flags); @@ -571,6 +572,9 @@ protected: Draw::Pipeline *stencilWritePipeline_ = nullptr; Draw::SamplerState *stencilWriteSampler_ = nullptr; + // Used on GLES where we can't directly readback depth or stencil, but here for simplicity. + Draw::Pipeline *stencilReadbackPipeline_ = nullptr; + Draw::SamplerState *stencilReadbackSampler_ = nullptr; Draw::Pipeline *depthReadbackPipeline_ = nullptr; Draw::SamplerState *depthReadbackSampler_ = nullptr; diff --git a/GPU/GLES/DepthBufferGLES.cpp b/GPU/GLES/DepthBufferGLES.cpp index 03e5656ea1..5283612f51 100644 --- a/GPU/GLES/DepthBufferGLES.cpp +++ b/GPU/GLES/DepthBufferGLES.cpp @@ -85,6 +85,45 @@ const UniformBufferDesc depthUBDesc{ sizeof(DepthUB), { { "u_depthTo8", -1, -1, UniformType::FLOAT4, 32 }, } }; +static const char *stencil_dl_fs = R"( +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif +#if __VERSION__ >= 130 +#define varying in +#define texture2D texture +#define gl_FragColor fragColor0 +out vec4 fragColor0; +#endif +varying vec2 v_texcoord; +lowp uniform usampler2D tex; +void main() { + uint stencil = texture2D(tex, v_texcoord).r; + float scaled = float(stencil) / 255.0; + gl_FragColor = vec4(scaled, scaled, scaled, scaled); +} +)"; + +static const char *stencil_vs = R"( +#ifdef GL_ES +precision highp float; +#endif +#if __VERSION__ >= 130 +#define attribute in +#define varying out +#endif +attribute vec2 a_position; +varying vec2 v_texcoord; +void main() { + v_texcoord = a_position * 2.0; + gl_Position = vec4(v_texcoord * 2.0 - vec2(1.0, 1.0), 0.0, 1.0); +} +)"; + static bool SupportsDepthTexturing() { if (gl_extensions.IsGLES) { return gl_extensions.OES_packed_depth_stencil && (gl_extensions.OES_depth_texture || gl_extensions.GLES3); @@ -248,3 +287,75 @@ bool FramebufferManagerGLES::ReadbackDepthbufferSync(Draw::Framebuffer *fbo, int gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); return true; } + +// Well, this is not depth, but it's depth/stencil related. +bool FramebufferManagerGLES::ReadbackStencilbufferSync(Draw::Framebuffer *fbo, int x, int y, int w, int h, uint8_t *pixels, int pixelsStride) { + using namespace Draw; + + if (!fbo) { + ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "ReadbackStencilbufferSync: bad fbo"); + return false; + } + + const bool useColorPath = gl_extensions.IsGLES; + if (!useColorPath) { + return draw_->CopyFramebufferToMemorySync(fbo, FB_STENCIL_BIT, x, y, w, h, DataFormat::S8, pixels, pixelsStride, "ReadbackStencilbufferSync"); + } + + // Unsupported below GLES 3.1 or without ARB_stencil_texturing. + // OES_texture_stencil8 is related, but used to specify texture data. + if ((gl_extensions.IsGLES && !gl_extensions.VersionGEThan(3, 1)) && !gl_extensions.ARB_stencil_texturing) + return false; + + // Pixel size always 4 here because we always request RGBA back. + const u32 bufSize = w * h * 4; + if (!convBuf_ || convBufSize_ < bufSize) { + delete[] convBuf_; + convBuf_ = new u8[bufSize]; + convBufSize_ = bufSize; + } + + if (!stencilReadbackPipeline_) { + stencilReadbackPipeline_ = CreateReadbackPipeline(draw_, "stencil_dl", &depthUBDesc, stencil_dl_fs, "stencil_dl_fs", stencil_vs, "stencil_vs"); + stencilReadbackSampler_ = draw_->CreateSamplerState({}); + } + + shaderManager_->DirtyLastShader(); + auto *blitFBO = GetTempFBO(TempFBO::COPY, fbo->Width(), fbo->Height()); + draw_->BindFramebufferAsRenderTarget(blitFBO, { RPAction::DONT_CARE, RPAction::DONT_CARE, RPAction::DONT_CARE }, "ReadbackStencilbufferSync"); + Draw::Viewport viewport = { 0.0f, 0.0f, (float)fbo->Width(), (float)fbo->Height(), 0.0f, 1.0f }; + draw_->SetViewports(1, &viewport); + + draw_->BindFramebufferAsTexture(fbo, TEX_SLOT_PSP_TEXTURE, FB_STENCIL_BIT, 0); + draw_->BindSamplerStates(TEX_SLOT_PSP_TEXTURE, 1, &stencilReadbackSampler_); + + // We must bind the program after starting the render pass. + draw_->SetScissorRect(0, 0, w, h); + draw_->BindPipeline(stencilReadbackPipeline_); + + // Fullscreen triangle coordinates. + static const float positions[6] = { + 0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + }; + draw_->DrawUP(positions, 3); + + draw_->CopyFramebufferToMemorySync(blitFBO, FB_COLOR_BIT, x, y, w, h, DataFormat::R8G8B8A8_UNORM, convBuf_, w, "ReadbackStencilbufferSync"); + + textureCache_->ForgetLastTexture(); + + // TODO: Use 1/4 width to write all values directly and skip CPU conversion? + uint8_t *dest = pixels; + const u32_le *packed32 = (u32_le *)convBuf_; + for (int yp = 0; yp < h; ++yp) { + for (int xp = 0; xp < w; ++xp) { + dest[xp] = packed32[xp] & 0xFF; + } + dest += pixelsStride; + packed32 += w; + } + + gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); + return true; +} diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index 36aa2545fd..7bbfa1a510 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -39,6 +39,7 @@ public: protected: void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; bool ReadbackDepthbufferSync(Draw::Framebuffer *fbo, int x, int y, int w, int h, uint16_t *pixels, int pixelsStride) override; + bool ReadbackStencilbufferSync(Draw::Framebuffer *fbo, int x, int y, int w, int h, uint8_t *pixels, int pixelsStride) override; private: u8 *convBuf_ = nullptr;