From df847835ad5eea41b2623b977075b05dc8ca6f19 Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sun, 14 May 2023 17:18:03 -0500 Subject: [PATCH] GS:MTL: Implement DrawMultiStretchRects --- pcsx2/GS/Renderers/Metal/GSDeviceMTL.h | 4 +- pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm | 126 ++++++++++++++++++------ 2 files changed, 98 insertions(+), 32 deletions(-) diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index 456f057e8e..96c85d56a2 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -415,13 +415,15 @@ public: void ClearSamplerCache() override; void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override; + void BeginStretchRect(NSString* name, GSTexture* dTex, MTLLoadAction action); void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, id pipeline, bool linear, LoadAction load_action, const void* frag_uniform, size_t frag_uniform_len); - void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds); + void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2& ds); /// Copy from a position in sTex to the same position in the currently active render encoder using the given fs pipeline and rect void RenderCopy(GSTexture* sTex, id pipeline, const GSVector4i& rect); void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override; void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) override; void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) override; + void DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) override; void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override; void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) override; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 12f04e032b..607016e6e6 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -1496,14 +1496,25 @@ void GSDeviceMTL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r [encoder endEncoding]; }} +void GSDeviceMTL::BeginStretchRect(NSString* name, GSTexture* dTex, MTLLoadAction action) +{ + if (dTex->GetFormat() == GSTexture::Format::DepthStencil) + BeginRenderPass(name, nullptr, MTLLoadActionDontCare, dTex, action); + else + BeginRenderPass(name, dTex, action, nullptr, MTLLoadActionDontCare); + + FlushDebugEntries(m_current_render.encoder); + MREClearScissor(); + DepthStencilSelector dsel = DepthStencilSelector::NoDepth(); + dsel.zwe = dTex->GetFormat() == GSTexture::Format::DepthStencil; + MRESetDSS(dsel); +} + void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, id pipeline, bool linear, LoadAction load_action, const void* frag_uniform, size_t frag_uniform_len) { FlushClears(sTex); - GSTextureMTL* sT = static_cast(sTex); - GSTextureMTL* dT = static_cast(dTex); - - GSVector2i ds = dT->GetSize(); + GSVector2i ds = dTex->GetSize(); bool covers_target = static_cast(dRect.x) <= 0 && static_cast(dRect.y) <= 0 @@ -1512,45 +1523,39 @@ void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextu bool dontcare = load_action == LoadAction::DontCare || (load_action == LoadAction::DontCareIfFull && covers_target); MTLLoadAction action = dontcare ? MTLLoadActionDontCare : MTLLoadActionLoad; - if (dT->GetFormat() == GSTexture::Format::DepthStencil) - BeginRenderPass(@"StretchRect", nullptr, MTLLoadActionDontCare, dT, action); - else - BeginRenderPass(@"StretchRect", dT, action, nullptr, MTLLoadActionDontCare); - - FlushDebugEntries(m_current_render.encoder); - MREClearScissor(); - DepthStencilSelector dsel; - dsel.ztst = ZTST_ALWAYS; - dsel.zwe = dT->GetFormat() == GSTexture::Format::DepthStencil; - MRESetDSS(dsel); + BeginStretchRect(@"StretchRect", dTex, action); MRESetPipeline(pipeline); - MRESetTexture(sT, GSMTLTextureIndexNonHW); + MRESetTexture(sTex, GSMTLTextureIndexNonHW); if (frag_uniform && frag_uniform_len) [m_current_render.encoder setFragmentBytes:frag_uniform length:frag_uniform_len atIndex:GSMTLBufferIndexUniforms]; MRESetSampler(linear ? SamplerSelector::Linear() : SamplerSelector::Point()); - DrawStretchRect(sRect, dRect, ds); + DrawStretchRect(sRect, dRect, GSVector2(static_cast(ds.x), static_cast(ds.y))); } -void GSDeviceMTL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds) +static std::array CalcStrechRectPoints(const GSVector4& sRect, const GSVector4& dRect, const GSVector2& ds) { - float left = dRect.x * 2 / ds.x - 1.0f; - float right = dRect.z * 2 / ds.x - 1.0f; - float top = 1.0f - dRect.y * 2 / ds.y; - float bottom = 1.0f - dRect.w * 2 / ds.y; - - ConvertShaderVertex vertices[] = - { - {{left, top}, {sRect.x, sRect.y}}, - {{right, top}, {sRect.z, sRect.y}}, - {{left, bottom}, {sRect.x, sRect.w}}, - {{right, bottom}, {sRect.z, sRect.w}} + static_assert(sizeof(GSDeviceMTL::ConvertShaderVertex) == sizeof(GSVector4), "Using GSVector4 as a ConvertShaderVertex"); + GSVector4 dst = dRect; + dst /= GSVector4(ds.x, ds.y, ds.x, ds.y); + dst *= GSVector4(2, -2, 2, -2); + dst += GSVector4(-1, 1, -1, 1); + return { + dst.xyxy(sRect), + dst.zyzy(sRect), + dst.xwxw(sRect), + dst.zwzw(sRect) }; +} - [m_current_render.encoder setVertexBytes:vertices length:sizeof(vertices) atIndex:GSMTLBufferIndexVertices]; +void GSDeviceMTL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2& ds) +{ + std::array vertices = CalcStrechRectPoints(sRect, dRect, ds); + + [m_current_render.encoder setVertexBytes:&vertices length:sizeof(vertices) atIndex:GSMTLBufferIndexVertices]; [m_current_render.encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 @@ -1622,10 +1627,69 @@ void GSDeviceMTL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture [m_current_render.encoder setFragmentSamplerState:m_sampler_hw[linear ? SamplerSelector::Linear().key : SamplerSelector::Point().key] atIndex:0]; [m_current_render.encoder setFragmentTexture:static_cast(sTex)->GetTexture() atIndex:0]; [m_current_render.encoder setFragmentBytes:&cb length:sizeof(cb) atIndex:GSMTLBufferIndexUniforms]; - DrawStretchRect(sRect, dRect, ds); + DrawStretchRect(sRect, dRect, GSVector2(static_cast(ds.x), static_cast(ds.y))); } }} +void GSDeviceMTL::DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) +{ @autoreleasepool { + BeginStretchRect(@"MultiStretchRect", dTex, MTLLoadActionLoad); + + id pipeline = nullptr; + GSTexture* sTex = rects[0].src; + bool linear = rects[0].linear; + u8 wmask = rects[0].wmask.wrgba; + + const GSVector2 ds(static_cast(dTex->GetWidth()), static_cast(dTex->GetHeight())); + const Map allocation = Allocate(m_vertex_upload_buf, sizeof(ConvertShaderVertex) * 4 * num_rects); + std::array* write = static_cast*>(allocation.cpu_buffer); + const id enc = m_current_render.encoder; + [enc setVertexBuffer:allocation.gpu_buffer + offset:allocation.gpu_offset + atIndex:GSMTLBufferIndexVertices]; + u32 start = 0; + + auto flush = [&](u32 i) { + const u32 end = i * 4; + const u32 vertex_count = end - start; + const u32 index_count = vertex_count + (vertex_count >> 1); // 6 indices per 4 vertices + id new_pipeline = wmask == 0xf ? m_convert_pipeline[static_cast(shader)] + : m_convert_pipeline_copy_mask[wmask]; + if (new_pipeline != pipeline) + { + pipeline = new_pipeline; + pxAssertRel(pipeline, fmt::format("No pipeline for {}", shaderName(shader)).c_str()); + MRESetPipeline(pipeline); + } + MRESetSampler(linear ? SamplerSelector::Linear() : SamplerSelector::Point()); + MRESetTexture(sTex, GSMTLTextureIndexNonHW); + [enc drawIndexedPrimitives:MTLPrimitiveTypeTriangle + indexCount:index_count + indexType:MTLIndexTypeUInt16 + indexBuffer:m_expand_index_buffer + indexBufferOffset:0 + instanceCount:1 + baseVertex:start + baseInstance:0]; + start = end; + }; + + for (u32 i = 0; i < num_rects; i++) + { + const MultiStretchRect& rect = rects[i]; + if (rect.src != sTex || rect.linear != linear || rect.wmask.wrgba != wmask) + { + flush(i); + sTex = rect.src; + linear = rect.linear; + wmask = rect.wmask.wrgba; + } + *write++ = CalcStrechRectPoints(rect.src_rect, rect.dst_rect, ds); + } + + flush(num_rects); +}} + void GSDeviceMTL::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) { GSMTLCLUTConvertPSUniform uniform = { sScale, {offsetX, offsetY}, dOffset };