From af81d8b7dacc809c9bd8ae7a7f1dd0a4ce94fe21 Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Sun, 22 Mar 2026 02:52:46 -0500 Subject: [PATCH] GS:MTL: Faster fbfetch depth feedback --- pcsx2/GS/Renderers/Common/GSDevice.h | 2 +- pcsx2/GS/Renderers/Metal/GSDeviceMTL.h | 3 + pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm | 83 ++++++++++++++++++--- pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.h | 1 + pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.mm | 2 +- pcsx2/GS/Renderers/Metal/tfx.metal | 7 +- 6 files changed, 83 insertions(+), 15 deletions(-) diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index a98a29f86a..b293ec959f 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -1073,7 +1073,7 @@ public: bool IsDSInRTActive() const { return m_ds_as_rt; } /// Create a temporary color clone of depth for depth feedback - void BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea); + virtual void BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea); void EndDSAsRT(); /// Returns a string representing the specified API. diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index 99bf7b379d..56dcf40212 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -318,6 +318,8 @@ public: MRCOwned> m_late_texture_upload_encoder; MRCOwned> m_vertex_upload_cmdbuf; MRCOwned> m_vertex_upload_encoder; + id m_ds_as_rt_texture = nil; + GSTexture* m_ds_as_rt_gstexture = nullptr; struct DebugEntry { @@ -417,6 +419,7 @@ public: 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; void FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect) override; + void BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea) override; void FlushClears(GSTexture* tex); diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 6fba393cc0..88eff02d2f 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -95,6 +95,11 @@ GSDeviceMTL::GSDeviceMTL() GSDeviceMTL::~GSDeviceMTL() { + // m_ds_as_rt_texture is owned if the device has memoryless textures + if (m_dev.features.memoryless_textures) + [m_ds_as_rt_texture release]; + else if (m_ds_as_rt_gstexture) + delete m_ds_as_rt_gstexture; } GSDeviceMTL::Map GSDeviceMTL::Allocate(UploadBuffer& buffer, size_t amt) @@ -247,7 +252,10 @@ id GSDeviceMTL::GetSpinFence() id GSDeviceMTL::GetRT1DepthTexture(GSTextureMTL* depth) { - return static_cast(m_ds_as_rt)->GetTexture(); + if (m_dev.features.framebuffer_fetch) + return m_ds_as_rt_texture; + else + return static_cast(m_ds_as_rt)->GetTexture(); } void GSDeviceMTL::DrawCommandBufferFinished(u64 draw, id buffer) @@ -1023,7 +1031,15 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) { MTLRenderPassColorAttachmentDescriptor* color1 = [[desc colorAttachments] objectAtIndexedSubscript:1]; [color1 setStoreAction:MTLStoreActionDontCare]; - [color1 setLoadAction:MTLLoadActionLoad]; + if (m_features.framebuffer_fetch) + { + [color1 setLoadAction:MTLLoadActionClear]; + [color1 setClearColor:MTLClearColorMake(-1, 0, 0, 0)]; + } + else + { + [color1 setLoadAction:MTLLoadActionLoad]; + } } m_render_pass_desc[i] = desc; } @@ -1311,12 +1327,13 @@ bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; } std::string GSDeviceMTL::GetDriverInfo() const { @autoreleasepool { std::string desc([[m_dev.dev description] UTF8String]); - desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported"); - desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported"); - desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported"); - desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported"); - desc += "\n Shader Version: " + std::string(to_string(m_dev.features.shader_version)); - desc += "\n Max Texture Size: " + std::to_string(m_dev.features.max_texsize); + desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported"); + desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported"); + desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported"); + desc += "\n Memoryless Textures: " + std::string(m_dev.features.memoryless_textures ? "Supported" : "Unsupported"); + desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported"); + desc += "\n Shader Version: " + std::string(to_string(m_dev.features.shader_version)); + desc += "\n Max Texture Size: " + std::to_string(m_dev.features.max_texsize); return desc; }} @@ -1802,6 +1819,50 @@ void GSDeviceMTL::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u3 DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, pipeline, false, LoadAction::DontCareIfFull, &uniform, sizeof(uniform)); }} +static id CreateDSAsRTTexture(id dev, NSUInteger width, NSUInteger height, MTLStorageMode storage, NSString* name) +{ + MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float width:width height:height mipmapped:false]; + [desc setUsage:MTLTextureUsageRenderTarget]; + [desc setStorageMode:storage]; + id result = [dev newTextureWithDescriptor:desc]; + [result setLabel:name]; + return result; +} + +void GSDeviceMTL::BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea) +{ + if (!m_features.framebuffer_fetch) + return GSDevice::BeginDSAsRT(ds, drawarea); + u32 needed_width = ds->GetWidth(); + u32 needed_height = ds->GetHeight(); + u32 current_width = static_cast([m_ds_as_rt_texture width]); + u32 current_height = static_cast([m_ds_as_rt_texture height]); + if (m_dev.features.memoryless_textures) + { + if (needed_width > current_width || needed_height > current_height) [[unlikely]] @autoreleasepool + { + u32 width = std::max(needed_width, current_width); + u32 height = std::max(needed_height, current_height); + [m_ds_as_rt_texture release]; + m_ds_as_rt_texture = CreateDSAsRTTexture(m_dev.dev, width, height, MTLStorageModeMemoryless, @"DS as RT"); + } + } + else + { + if (needed_width == current_width && needed_height == current_height) + return; + if (m_ds_as_rt_gstexture) + Recycle(m_ds_as_rt_gstexture); + m_ds_as_rt_gstexture = CreateRenderTarget(needed_width, needed_height, GSTexture::Format::Float32, false, true); + m_ds_as_rt_texture = static_cast(m_ds_as_rt_gstexture)->GetTexture(); + @autoreleasepool + { + NSString* name = [NSString stringWithFormat:@"DS as RT %dx%d", needed_width, needed_height]; + [m_ds_as_rt_texture setLabel:name]; + } + } +} + void GSDeviceMTL::FlushClears(GSTexture* tex) { if (tex) @@ -2328,10 +2389,10 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) MREInitHWDraw(config, allocation); if (config.require_one_barrier || config.require_full_barrier) MRESetTexture(rt, GSMTLTextureIndexRenderTarget); - if (feedback_depth && !m_features.framebuffer_fetch) + if (feedback_depth) { bool depth_as_rt = m_features.depth_feedback == GSDevice::DepthFeedbackSupport::DepthAsRT; - GSTexture* tex = depth_as_rt ? m_ds_as_rt : config.ds; + GSTexture* tex = depth_as_rt && !m_features.framebuffer_fetch ? m_ds_as_rt : config.ds; MRESetTexture(tex, GSMTLTextureIndexDepthTarget); } if (primid_tex) @@ -2366,7 +2427,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) Recycle(colclip_rt); - g_gs_device->SetColorClipTexture(nullptr); + SetColorClipTexture(nullptr); } } diff --git a/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.h b/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.h index 28d914db3d..055638ddee 100644 --- a/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.h +++ b/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.h @@ -34,6 +34,7 @@ struct GSMTLDevice bool primid : 1; bool slow_color_compression : 1; ///< Color compression seems to slow down rt read on AMD bool has_fast_half : 1; + bool memoryless_textures : 1; MetalVersion shader_version; DepthFeedbackSupport preferred_depth_feedback : 8; int max_texsize; diff --git a/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.mm b/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.mm index 0d6d83aa07..7a14a23b08 100644 --- a/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.mm +++ b/pcsx2/GS/Renderers/Metal/GSMTLDeviceInfo.mm @@ -147,7 +147,7 @@ GSMTLDevice::GSMTLDevice(MRCOwned> dev) if (@available(macOS 11.0, iOS 13.0, *)) if ([dev supportsFamily:MTLGPUFamilyApple1]) - features.framebuffer_fetch = true; + features.framebuffer_fetch = features.memoryless_textures = true; if (@available(macOS 10.15, iOS 13.0, *)) if ([dev supportsFamily:MTLGPUFamilyMac2] || [dev supportsFamily:MTLGPUFamilyApple1]) diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 697ab61043..aefe649a48 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -1413,7 +1413,7 @@ constant bool NEEDS_DS_FBF = false; constant float ds_fbf = 0; #endif constant bool NEEDS_DS_TEX = SW_DEPTH && DEPTH_FEEDBACK == DepthFeedbackSupport::DepthAsRT && !NEEDS_DS_FBF; -constant bool NEEDS_DS_DEPTH = SW_DEPTH && DEPTH_FEEDBACK == DepthFeedbackSupport::Depth; +constant bool NEEDS_DS_DEPTH = SW_DEPTH && DEPTH_FEEDBACK == DepthFeedbackSupport::Depth || NEEDS_DS_FBF; fragment MainPSOut ps_main( MainPSIn in [[stage_in]], @@ -1459,7 +1459,10 @@ fragment MainPSOut ps_main( main.current_depth = ds_depth.read(coord); break; case DepthFeedbackSupport::DepthAsRT: - main.current_depth = HAS_FBFETCH ? ds_fbf : ds_tex.read(coord).x; + if (NEEDS_DS_FBF) + main.current_depth = ds_fbf < 0 ? ds_depth.read(coord) : ds_fbf; + else + main.current_depth = ds_tex.read(coord).x; break; case DepthFeedbackSupport::None: // Should never happen