From 2a348dcff920a7ae20f3dd8cd208b32ebf7d7777 Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Tue, 30 Jun 2026 01:03:30 -0500 Subject: [PATCH] GS:MTL: Add ROV support for RW Textures Tier 1 hardware --- pcsx2/GS/Renderers/Metal/GSDeviceMTL.h | 1 + pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm | 31 ++++++++++++++++---- pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h | 1 + pcsx2/GS/Renderers/Metal/GSTextureMTL.h | 4 ++- pcsx2/GS/Renderers/Metal/GSTextureMTL.mm | 5 +++- pcsx2/GS/Renderers/Metal/tfx.metal | 28 ++++++++++++++---- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index f7743a46e1..5bda6eada1 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -451,6 +451,7 @@ public: void MRESetDSS(id dss); void MRESetSampler(SamplerSelector sel); void MRESetTexture(GSTexture* tex, int pos); + void MRESetTexture(id tex, int pos); void MRESetVertices(id buffer, size_t offset); void MRESetVSIndices(id buffer, size_t offset); void MRESetScissor(const GSVector4i& scissor); diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 9d57f9a641..53f4d9dd51 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -624,6 +624,7 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Usage usage, int width, int hei [desc setStorageMode:MTLStorageModePrivate]; MTLTextureUsage mtl_usage = MTLTextureUsageShaderRead; + bool needs_rov_tex = false; if (GSTexture::IsRenderTargetOrDepthStencil(usage)) mtl_usage |= MTLTextureUsageRenderTarget; @@ -634,6 +635,13 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Usage usage, int width, int hei mtl_usage |= MTLTextureUsagePixelFormatView; // Force color compression off by including PixelFormatView } + if (usage == GSTexture::ShaderWriteTarget && format == GSTexture::Format::Color && m_dev.features.rov_requires_r32) + { + // Need to make an R32 view of the texture for ROV + mtl_usage |= MTLTextureUsagePixelFormatView; + needs_rov_tex = true; + } + if (GSTexture::IsShaderWrite(usage)) mtl_usage |= MTLTextureUsageShaderWrite; @@ -642,7 +650,8 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Usage usage, int width, int hei MRCOwned> tex = MRCTransfer([m_dev.dev newTextureWithDescriptor:desc]); if (tex) { - GSTextureMTL* t = new GSTextureMTL(this, tex, usage, format); + MRCOwned> rov_tex = needs_rov_tex ? MRCTransfer([tex newTextureViewWithPixelFormat:MTLPixelFormatR32Uint]) : nil; + GSTextureMTL* t = new GSTextureMTL(this, tex, rov_tex, usage, format); if (GSTexture::IsRenderTarget(usage)) { ClearRenderTarget(t, 0); @@ -1059,13 +1068,14 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) m_features.test_and_sample_depth = true; m_features.depth_feedback = getDepthFeedback(m_dev, m_features.framebuffer_fetch); m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand; - m_features.rov = m_dev.features.rov && !m_dev.features.rov_requires_r32 && !m_features.framebuffer_fetch; + m_features.rov = m_dev.features.rov && !m_features.framebuffer_fetch; m_max_texture_size = m_dev.features.max_texsize; // Init metal stuff m_fn_constants = MRCTransfer([MTLFunctionConstantValues new]); - setFnConstantB(m_fn_constants, m_features.framebuffer_fetch, GSMTLConstantIndex_FRAMEBUFFER_FETCH); - setFnConstantB(m_fn_constants, m_features.depth_feedback, GSMTLConstantIndex_DEPTH_FEEDBACK); + setFnConstantB(m_fn_constants, m_features.framebuffer_fetch, GSMTLConstantIndex_FRAMEBUFFER_FETCH); + setFnConstantB(m_fn_constants, m_features.depth_feedback, GSMTLConstantIndex_DEPTH_FEEDBACK); + setFnConstantB(m_fn_constants, m_dev.features.rov_requires_r32, GSMTLConstantIndex_ROV_NEEDS_R32); m_draw_sync_fence = MRCTransfer([m_dev.dev newFence]); [m_draw_sync_fence setLabel:@"Draw Sync Fence"]; @@ -2147,6 +2157,15 @@ void GSDeviceMTL::MRESetTexture(GSTexture* tex, int pos) mtex->m_last_read = m_current_draw; } +void GSDeviceMTL::MRESetTexture(id tex, int pos) +{ + GSTexture* gstex = reinterpret_cast(tex); // No one actually dereferences this + if (!tex || gstex == m_current_render.tex[pos]) + return; + m_current_render.tex[pos] = gstex; + [m_current_render.encoder setFragmentTexture:tex atIndex:pos]; +} + void GSDeviceMTL::MRESetVertices(id buffer, size_t offset) { if (m_current_render.vertex_buffer != buffer) @@ -2492,7 +2511,9 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) if (usesStencil(config.destination_alpha)) [mtlenc setStencilReferenceValue:1]; MREInitHWDraw(config, allocation); - if (config.require_one_barrier || config.require_full_barrier || config.ps.HasColorROV()) + if (config.ps.HasColorROV() && m_dev.features.rov_requires_r32) + MRESetTexture(reinterpret_cast(rt)->GetROVTexture(), GSMTLTextureIndexRenderTarget); + else if (config.require_one_barrier || config.require_full_barrier || config.ps.HasColorROV()) MRESetTexture(rt, GSMTLTextureIndexRenderTarget); if (config.ps.HasDepthROV()) { diff --git a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h index 56a36f56d2..344e30b311 100644 --- a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h +++ b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h @@ -165,6 +165,7 @@ enum GSMTLFnConstants GSMTLConstantIndex_CAS_SHARPEN_ONLY, GSMTLConstantIndex_FRAMEBUFFER_FETCH, GSMTLConstantIndex_DEPTH_FEEDBACK, + GSMTLConstantIndex_ROV_NEEDS_R32, GSMTLConstantIndex_FST, GSMTLConstantIndex_IIP, GSMTLConstantIndex_VS_POINT_SIZE, diff --git a/pcsx2/GS/Renderers/Metal/GSTextureMTL.h b/pcsx2/GS/Renderers/Metal/GSTextureMTL.h index 381f8c0d54..c7f87886c7 100644 --- a/pcsx2/GS/Renderers/Metal/GSTextureMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSTextureMTL.h @@ -21,12 +21,13 @@ class GSTextureMTL : public GSTexture { GSDeviceMTL* m_dev; MRCOwned> m_texture; + MRCOwned> m_rov_texture; bool m_has_mipmaps = false; public: u64 m_last_read = 0; ///< Last time this texture was read by a draw u64 m_last_write = 0; ///< Last time this texture was written by a draw - GSTextureMTL(GSDeviceMTL* dev, MRCOwned> texture, Usage usage, Format format); + GSTextureMTL(GSDeviceMTL* dev, MRCOwned> texture, MRCOwned> rov_texture, Usage usage, Format format); ~GSTextureMTL(); /// For making fake backbuffers @@ -42,6 +43,7 @@ public: void Unmap() override; void GenerateMipmap() override; id GetTexture() { return m_texture; } + id GetROVTexture() { return m_rov_texture; } #ifdef PCSX2_DEVBUILD void SetDebugName(std::string_view name) override; diff --git a/pcsx2/GS/Renderers/Metal/GSTextureMTL.mm b/pcsx2/GS/Renderers/Metal/GSTextureMTL.mm index a3394cccaf..e785a927d1 100644 --- a/pcsx2/GS/Renderers/Metal/GSTextureMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSTextureMTL.mm @@ -13,9 +13,10 @@ // Uploads/downloads need 32-byte alignment for AVX2. static constexpr u32 PITCH_ALIGNMENT = 32; -GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned> texture, Usage usage, Format format) +GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned> texture, MRCOwned> rov_texture, Usage usage, Format format) : m_dev(dev) , m_texture(std::move(texture)) + , m_rov_texture(std::move(rov_texture)) { m_usage = usage; m_format = format; @@ -141,6 +142,8 @@ void GSTextureMTL::SetDebugName(std::string_view name) length:static_cast(name.length()) encoding:NSUTF8StringEncoding]; [m_texture setLabel:label]; + if (m_rov_texture) + [m_rov_texture setLabel:[label stringByAppendingString:@".ROV"]]; } m_debug_name = std::move(name); diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 21a8049c53..7ed2eec78e 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -13,6 +13,7 @@ constant uint SHUFFLE_READWRITE = 3; constant bool HAS_FBFETCH [[function_constant(GSMTLConstantIndex_FRAMEBUFFER_FETCH)]]; constant bool DEPTH_FEEDBACK [[function_constant(GSMTLConstantIndex_DEPTH_FEEDBACK)]]; +constant bool ROV_NEEDS_R32 [[function_constant(GSMTLConstantIndex_ROV_NEEDS_R32)]]; constant bool FST [[function_constant(GSMTLConstantIndex_FST)]]; constant bool IIP [[function_constant(GSMTLConstantIndex_IIP)]]; constant bool VS_POINT_SIZE [[function_constant(GSMTLConstantIndex_VS_POINT_SIZE)]]; @@ -1708,7 +1709,8 @@ constant float ds_fbf = 0; #endif constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF && PS_ROV_DEPTH == ROV_DEPTH::NONE; constant bool NEEDS_DS_DEPTH = (SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF) && PS_ROV_DEPTH == ROV_DEPTH::NONE; -constant bool NEEDS_RT_ROV = PS_ROV_COLOR; +constant bool NEEDS_RT_ROV = PS_ROV_COLOR && !ROV_NEEDS_R32; +constant bool NEEDS_RT_U32 = PS_ROV_COLOR && ROV_NEEDS_R32; constant bool NEEDS_DS_ROV = PS_ROV_DEPTH != ROV_DEPTH::NONE; fragment MainPSOut ps_main( @@ -1730,6 +1732,7 @@ fragment MainPSOut ps_main( texture2d ds_tex [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_TEX)]], depth2d ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]], texture2d rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]], + texture2d rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]], texture2d ds_rov [[texture(GSMTLTextureIndexDepthTarget), raster_order_group(1), function_constant(NEEDS_DS_ROV)]]) { PSMain main(in, cb); @@ -1765,7 +1768,10 @@ fragment MainPSOut ps_main( { if (PS_ROV_COLOR) { - main.current_color = rt_rov.read(coord); + if (ROV_NEEDS_R32) + main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x); + else + main.current_color = rt_rov.read(coord); } else { @@ -1788,7 +1794,10 @@ fragment MainPSOut ps_main( { if (!PS_FBMASK) out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff); - rt_rov.write(out.c0, coord); + if (ROV_NEEDS_R32) + rt_u32.write(pack_float_to_unorm4x8(out.c0), coord); + else + rt_rov.write(out.c0, coord); } return out; } @@ -1802,7 +1811,8 @@ fragment void ps_main_rov_eft( texture2d tex [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_COLOR)]], depth2d depth [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_DEPTH)]], texture2d palette [[texture(GSMTLTextureIndexPalette), function_constant(PS_HAS_PALETTE)]], - texture2d rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0)]]) + texture2d rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]], + texture2d rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]]) { PSMain main(in, cb); main.tex_sampler = s; @@ -1814,13 +1824,19 @@ fragment void ps_main_rov_eft( main.palette = palette; uint2 coord = uint2(in.p.xy); - main.current_color = rt_rov.read(coord); + if (ROV_NEEDS_R32) + main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x); + else + main.current_color = rt_rov.read(coord); MainPSOut out = main.ps_main(); if (!main.color_discarded) { if (!PS_FBMASK) out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff); - rt_rov.write(out.c0, coord); + if (ROV_NEEDS_R32) + rt_u32.write(pack_float_to_unorm4x8(out.c0), coord); + else + rt_rov.write(out.c0, coord); } }