diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 4621115e5f..ac6c6f9559 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -899,7 +899,7 @@ struct alignas(16) GSHWDrawConfig __fi bool HasDepthROV() const { - return rov_depth == PS_ROV_DEPTH::READ_ONLY || rov_depth == PS_ROV_DEPTH::READ_WRITE; + return rov_depth != PS_ROV_DEPTH::NONE; } __fi bool HasDepthROVWrite() const diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index dae95ae80c..f7743a46e1 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -279,6 +279,7 @@ public: std::unordered_map>> m_hw_pipeline; MRCOwned m_render_pass_desc[16]; + MRCOwned m_full_rov_render_pass_desc; MRCOwned> m_sampler_hw[1 << 8]; @@ -316,12 +317,14 @@ public: // Clear line (Things below here are tracked by `has` and don't need to be cleared to reset) SamplerSelector sampler_sel; u8 blend_color; + struct { u32 w, h; } full_rov_size; GSVector4i scissor; PipelineSelectorMTL pipeline_sel; GSHWDrawConfig::VSConstantBuffer cb_vs; GSHWDrawConfig::PSConstantBuffer cb_ps; MainRenderEncoder(const MainRenderEncoder&) = delete; MainRenderEncoder() = default; + bool is_full_rov() const { return encoder && !color_target && !depth_target && !stencil_target; } } m_current_render; MRCOwned> m_texture_upload_cmdbuf; MRCOwned> m_texture_upload_encoder; @@ -330,6 +333,8 @@ public: MRCOwned> m_vertex_upload_encoder; id m_ds_as_rt_texture = nil; GSTexture* m_ds_as_rt_gstexture = nullptr; + MRCOwned> m_rov_dummy_texture; + struct { u32 w, h; } m_rov_dummy_texture_size = {}; struct DebugEntry { @@ -372,8 +377,12 @@ public: void FlushEncodersForReadback(); /// End current render pass without flushing void EndRenderPass(); + /// Prepare to begin a new render pass + void PrepareBeginRenderPass(); /// Begin a new render pass (may reuse existing) void BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil = nullptr, MTLLoadAction stencil_load = MTLLoadActionDontCare, bool rt1 = false); + /// Begin a new full-ROV render pass (may reuse existing) + void BeginFullROV(NSString* name, uint32_t width, uint32_t height); /// Call at the end of each frame void FrameCompleted(); @@ -455,6 +464,7 @@ public: // MARK: Render HW void SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVector4i& r, SetDATM datm); + void PrepareROVTexture(GSTexture** ptex); void RenderHW(GSHWDrawConfig& config) override; void SendHWDraw(GSHWDrawConfig& config, id enc, id buffer, size_t off, bool one_barrier, bool full_barrier); diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index ab3f47db7e..9d57f9a641 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -419,6 +419,19 @@ static GSVector4 GetRTLoadInfo(GSTextureMTL* tex, MTLLoadAction* load_action) return {}; }; +void GSDeviceMTL::PrepareBeginRenderPass() +{ + m_encoders_in_current_cmdbuf++; + + if (m_late_texture_upload_encoder) + { + [m_late_texture_upload_encoder endEncoding]; + m_late_texture_upload_encoder = nullptr; + } + + EndRenderPass(); +} + void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil, MTLLoadAction stencil_load, bool rt1) { @@ -455,14 +468,6 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio return; } - m_encoders_in_current_cmdbuf++; - - if (m_late_texture_upload_encoder) - { - [m_late_texture_upload_encoder endEncoding]; - m_late_texture_upload_encoder = nullptr; - } - int idx = 0; if (mc) idx |= 1; if (md) idx |= 2; @@ -502,7 +507,7 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio color1.clearColor = MTLClearColorMake(depth_load == MTLLoadActionClear ? depth_clear.x : -1, 0, 0, 0); } - EndRenderPass(); + PrepareBeginRenderPass(); m_current_render.encoder = MRCRetain([GetRenderCmdBuf() renderCommandEncoderWithDescriptor:desc]); m_current_render.name = (__bridge void*)name; [m_current_render.encoder setLabel:name]; @@ -516,6 +521,61 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio pxAssertRel(m_current_render.encoder, "Failed to create render encoder!"); } +void GSDeviceMTL::BeginFullROV(NSString* name, uint32_t width, uint32_t height) +{ + bool needs_new = !m_current_render.is_full_rov() + || width > m_current_render.full_rov_size.w + || height > m_current_render.full_rov_size.h; + + if (!needs_new) + { + if (m_current_render.name != (__bridge void*)name) + { + m_current_render.name = (__bridge void*)name; + [m_current_render.encoder setLabel:name]; + } + return; + } + + if (m_current_render.is_full_rov()) + { + // Render was too small. Don't let it shrink in one dimension while growing in the other. + width = std::max(width, m_current_render.full_rov_size.w); + height = std::max(height, m_current_render.full_rov_size.h); + } + + m_current_render.full_rov_size.w = width; + m_current_render.full_rov_size.h = height; + + MTLRenderPassDescriptor* desc = m_full_rov_render_pass_desc; + [desc setRenderTargetWidth:width]; + [desc setRenderTargetHeight:height]; + + if (m_dev.features.rov_requires_rt && (width > m_rov_dummy_texture_size.w || height > m_rov_dummy_texture_size.h)) + { + m_rov_dummy_texture_size.w = std::max(m_rov_dummy_texture_size.w, width); + m_rov_dummy_texture_size.h = std::max(m_rov_dummy_texture_size.h, height); + MTLTextureDescriptor* tdesc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm + width:m_rov_dummy_texture_size.w + height:m_rov_dummy_texture_size.h + mipmapped:NO]; + [tdesc setUsage:MTLTextureUsageRenderTarget]; + [tdesc setStorageMode:MTLStorageModePrivate]; + id tex = m_rov_dummy_texture = MRCTransfer([m_dev.dev newTextureWithDescriptor:tdesc]); + [tex setLabel:@"ROV Dummy Texture"]; + [[[desc colorAttachments] objectAtIndexedSubscript:0] setTexture:tex]; + } + + PrepareBeginRenderPass(); + m_current_render.encoder = MRCRetain([GetRenderCmdBuf() renderCommandEncoderWithDescriptor:desc]); + m_current_render.name = (__bridge void*)name; + [m_current_render.encoder setLabel:name]; + if (!m_dev.features.unified_memory) + [m_current_render.encoder waitForFence:m_draw_sync_fence + beforeStages:MTLRenderStageVertex]; +} + void GSDeviceMTL::FrameCompleted() { if (m_spin_timer) @@ -999,6 +1059,7 @@ 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_max_texture_size = m_dev.features.max_texsize; // Init metal stuff @@ -1062,6 +1123,17 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) } m_render_pass_desc[i] = desc; } + if (m_features.rov) + { + m_full_rov_render_pass_desc = MRCTransfer([MTLRenderPassDescriptor new]); + [m_full_rov_render_pass_desc setDefaultRasterSampleCount:1]; + if (m_dev.features.rov_requires_rt) + { + MTLRenderPassColorAttachmentDescriptor* color0 = [[m_full_rov_render_pass_desc colorAttachments] objectAtIndexedSubscript:0]; + [color0 setLoadAction:MTLLoadActionDontCare]; + [color0 setStoreAction:MTLStoreActionDontCare]; + } + } // Init samplers m_sampler_hw[SamplerSelector::Linear().key] = CreateSampler(m_dev.dev, SamplerSelector::Linear()); @@ -1978,7 +2050,10 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr setFnConstantI(m_fn_constants, pssel.aa1, GSMTLConstantIndex_PS_AA1); setFnConstantB(m_fn_constants, pssel.abe, GSMTLConstantIndex_PS_ABE); setFnConstantI(m_fn_constants, pssel.sw_aniso, GSMTLConstantIndex_PS_SW_ANISO); - auto newps = LoadShader(@"ps_main"); + setFnConstantB(m_fn_constants, pssel.rov_color, GSMTLConstantIndex_PS_ROV_COLOR); + setFnConstantI(m_fn_constants, pssel.rov_depth, GSMTLConstantIndex_PS_ROV_DEPTH); + bool eft = pssel.HasColorROV() && !pssel.HasDepthROV() && !pssel.HasDepthOutput(); + auto newps = LoadShader(eft ? @"ps_main_rov_eft" : @"ps_main"); ps = newps; m_hw_ps.insert(std::make_pair(pssel, std::move(newps))); } @@ -2017,6 +2092,12 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr MTLRenderPipelineColorAttachmentDescriptor* color1 = [[pdesc colorAttachments] objectAtIndexedSubscript:1]; [color1 setPixelFormat:MTLPixelFormatR32Float]; } + if (m_dev.features.rov_requires_rt && extras.rt == GSTexture::Format::Invalid && !extras.has_depth && !extras.has_stencil) + { + // Dummy texture + [color setPixelFormat:MTLPixelFormatR8Unorm]; + [color setWriteMask:MTLColorWriteMaskNone]; + } NSString* pname = [NSString stringWithFormat:@"HW Render %x.%llx.%llx.%x", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey]; auto pipeline = MakePipeline(pdesc, vs, ps, pname); @@ -2108,6 +2189,7 @@ void GSDeviceMTL::MRESetScissor(const GSVector4i& scissor) void GSDeviceMTL::MREClearScissor() { + assert(!m_current_render.is_full_rov()); if (!m_current_render.has.scissor) return; m_current_render.has.scissor = false; @@ -2235,6 +2317,15 @@ void GSDeviceMTL::MREInitHWDraw(GSHWDrawConfig& config, const Map& verts) MRESetVSIndices(verts.gpu_buffer, verts.gpu_offset + config.nverts * sizeof(*config.verts)); } +__fi void GSDeviceMTL::PrepareROVTexture(GSTexture** ptex) +{ + GSTextureMTL* tex = static_cast(*ptex); + FlushClears(tex); + tex->m_last_read = m_current_draw; + tex->m_last_write = m_current_draw; + *ptex = nullptr; +} + void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) { @autoreleasepool { if (config.tex && (config.ds == config.tex || config.rt == config.tex)) @@ -2371,7 +2462,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) } // Try to reduce render pass restarts - if (!config.ds && m_current_render.color_target == rt && stencil == m_current_render.stencil_target && m_current_render.depth_target != config.tex) + if (!config.ps.HasColorROV() && !config.ds && m_current_render.color_target == rt && stencil == m_current_render.stencil_target && m_current_render.depth_target != config.tex) config.ds = m_current_render.depth_target; if (!rt && config.ds == m_current_render.depth_target && m_current_render.color_target != config.tex) rt = m_current_render.color_target; @@ -2386,16 +2477,28 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) } const bool feedback_depth = config.ps.IsFeedbackLoopDepth(); - const bool rt1 = feedback_depth && !m_features.depth_feedback; - BeginRenderPass(@"RenderHW", rt, MTLLoadActionLoad, config.ds, MTLLoadActionLoad, stencil, MTLLoadActionLoad, rt1); + const bool rt1 = feedback_depth && !m_features.depth_feedback && !config.ps.HasDepthROV(); + GSTexture* rt_bind = rt; + GSTexture* ds_bind = config.ds; + GSTexture* rt_size = rt_bind ? rt_bind : ds_bind; + if (config.ps.HasColorROV() && rt_bind) PrepareROVTexture(&rt_bind); + if (config.ps.HasDepthROV() && ds_bind) PrepareROVTexture(&ds_bind); + if (!rt_bind && !ds_bind && !stencil) + BeginFullROV(@"RenderHWROV", rt_size->GetWidth(), rt_size->GetHeight()); + else + BeginRenderPass(@"RenderHW", rt_bind, MTLLoadActionLoad, ds_bind, MTLLoadActionLoad, stencil, MTLLoadActionLoad, rt1); id mtlenc = m_current_render.encoder; FlushDebugEntries(mtlenc); if (usesStencil(config.destination_alpha)) [mtlenc setStencilReferenceValue:1]; MREInitHWDraw(config, allocation); - if (config.require_one_barrier || config.require_full_barrier) + if (config.require_one_barrier || config.require_full_barrier || config.ps.HasColorROV()) MRESetTexture(rt, GSMTLTextureIndexRenderTarget); - if (feedback_depth) + if (config.ps.HasDepthROV()) + { + MRESetTexture(config.ds, GSMTLTextureIndexDepthTarget); + } + else if (feedback_depth) { GSTexture* tex = !m_features.depth_feedback && !m_features.framebuffer_fetch ? m_ds_as_rt : config.ds; MRESetTexture(tex, GSMTLTextureIndexDepthTarget); diff --git a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h index 46719ae692..56a36f56d2 100644 --- a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h +++ b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h @@ -227,4 +227,6 @@ enum GSMTLFnConstants GSMTLConstantIndex_PS_AA1, GSMTLConstantIndex_PS_ABE, GSMTLConstantIndex_PS_SW_ANISO, + GSMTLConstantIndex_PS_ROV_COLOR, + GSMTLConstantIndex_PS_ROV_DEPTH, }; diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index f9c773a8a6..21a8049c53 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -75,17 +75,21 @@ constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SC constant uint PS_AA1_RAW [[function_constant(GSMTLConstantIndex_PS_AA1)]]; constant bool PS_ABE [[function_constant(GSMTLConstantIndex_PS_ABE)]]; constant uint PS_SW_ANISO [[function_constant(GSMTLConstantIndex_PS_SW_ANISO)]]; +constant bool PS_ROV_COLOR [[function_constant(GSMTLConstantIndex_PS_ROV_COLOR)]]; +constant uint PS_ROV_DEPTH_RAW [[function_constant(GSMTLConstantIndex_PS_ROV_DEPTH)]]; using GSShader::VSExpand; using AFAIL = GSShader::PS_AFAIL; using ATST = GSShader::PS_ATST; using GSShader::ZTST; using AA1 = GSShader::PS_AA1; +using ROV_DEPTH = GSShader::PS_ROV_DEPTH; constant VSExpand VS_EXPAND_TYPE = static_cast(VS_EXPAND_TYPE_RAW); constant AFAIL PS_AFAIL = static_cast(PS_AFAIL_RAW); constant ATST PS_ATST = static_cast(PS_ATST_RAW); constant ZTST PS_ZTST = static_cast(PS_ZTST_RAW); constant AA1 PS_AA1 = static_cast(PS_AA1_RAW); +constant ROV_DEPTH PS_ROV_DEPTH = static_cast(PS_ROV_DEPTH_RAW); #if defined(__METAL_MACOS__) && __METAL_VERSION__ >= 220 #define PRIMID_SUPPORT 1 @@ -119,9 +123,9 @@ constant bool NEEDS_DEPTH_FOR_ZTST = PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST constant bool NEEDS_DEPTH_FOR_AA1 = PS_AA1 == AA1::TRIANGLE_SW_Z; constant bool SW_DEPTH = NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1; -constant bool PS_COLOR0 = !PS_NO_COLOR; -constant bool PS_COLOR1 = !PS_NO_COLOR1; -constant bool PS_ZOUTPUT = PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH; +constant bool PS_OUTPUT_COLOR0 = !PS_NO_COLOR && !PS_ROV_COLOR; +constant bool PS_OUTPUT_COLOR1 = !PS_NO_COLOR1 && !PS_ROV_COLOR; +constant bool PS_ZOUTPUT = (PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH) && PS_ROV_DEPTH == ROV_DEPTH::NONE; constant bool PS_ZOUTPUT_LESS = PS_ZOUTPUT && !SW_DEPTH; constant bool PS_ZOUTPUT_ANY = PS_ZOUTPUT && SW_DEPTH; constant bool PS_ZOUTPUT_COLOR = PS_ZOUTPUT_ANY && !DEPTH_FEEDBACK; @@ -165,21 +169,32 @@ struct MainPSIn uint interior [[function_constant(PS_INTERIOR)]]; }; +struct MainResult +{ + float4 c0; + float4 c1; + float depth; +}; + struct MainPSOut { - float4 c0 [[color(0), index(0), function_constant(PS_COLOR0)]]; - float4 c1 [[color(0), index(1), function_constant(PS_COLOR1)]]; + float4 c0 [[color(0), index(0), function_constant(PS_OUTPUT_COLOR0)]]; + float4 c1 [[color(0), index(1), function_constant(PS_OUTPUT_COLOR1)]]; float depthColor [[color(1), function_constant(PS_ZOUTPUT_COLOR)]]; float depthLess [[depth(less), function_constant(PS_ZOUTPUT_LESS)]]; float depthAny [[depth(any), function_constant(PS_ZOUTPUT_ANY)]]; - void setDepth(float depth) + MainPSOut(MainResult res) { + if (PS_OUTPUT_COLOR0) + c0 = res.c0; + if (PS_OUTPUT_COLOR1) + c1 = res.c1; if (PS_ZOUTPUT_LESS) - depthLess = depth; + depthLess = res.depth; if (PS_ZOUTPUT_ANY) - depthAny = depth; + depthAny = res.depth; if (PS_ZOUTPUT_COLOR) - depthColor = depth; + depthColor = res.depth; } }; @@ -564,11 +579,37 @@ struct PSMain float4 current_color; float current_depth; uint prim_id; + bool color_discarded = false; + bool depth_discarded = false; const thread MainPSIn& in; constant GSMTLMainPSUniform& cb; PSMain(const thread MainPSIn& in, constant GSMTLMainPSUniform& cb): in(in), cb(cb) {} + void discard() + { + if (PS_ROV_COLOR || PS_ROV_DEPTH != ROV_DEPTH::NONE) + color_discarded = depth_discarded = true; + else + discard_fragment(); + } + + void discard_color(thread float4& output) + { + if (PS_ROV_COLOR) + color_discarded = true; + else + output = current_color; + } + + void discard_depth(thread float& output) + { + if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE) + depth_discarded = true; + else + output = current_depth; + } + template float4 sample_tex(Args... args) { @@ -1454,9 +1495,9 @@ struct PSMain } } - MainPSOut ps_main() + MainResult ps_main() { - MainPSOut out = {}; + MainResult out = {}; float input_z = in.p.z; if (PS_ZFLOOR) input_z = floor(input_z * 0x1p32) * 0x1p-32; @@ -1464,15 +1505,15 @@ struct PSMain if (PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER) { if (PS_ZTST == ZTST::GEQUAL && input_z < current_depth) - discard_fragment(); + discard(); if (PS_ZTST == ZTST::GREATER && input_z <= current_depth) - discard_fragment(); + discard(); } if (PS_SCANMSK & 2) { if ((uint(in.p.y) & 1) == (PS_SCANMSK & 1)) - discard_fragment(); + discard(); } if (PS_DATE >= 5) @@ -1482,7 +1523,7 @@ struct PSMain bool bad = PS_RTA_CORRECTION ? ((PS_DATE & 3) == 1 ? (rt_a > (254.5f / 255.f)) : (rt_a < (254.5f / 255.f))) : ((PS_DATE & 3) == 1 ? (rt_a > 0.5) : (rt_a < 0.5)); if (bad) - discard_fragment(); + discard(); } if (PS_DATE == 3) @@ -1491,7 +1532,7 @@ struct PSMain // Note prim_id == stencil_ceil will be the primitive that will update // the bad alpha value so we must keep it. if (float(prim_id) > stencil_ceil) - discard_fragment(); + discard(); } float4 C = ps_color(); @@ -1500,9 +1541,9 @@ struct PSMain if (PS_AA1 != AA1::NONE) { - float cov = PS_AA1 == AA1::LINE ? - saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) : // Blur only outer part of the line by scaling coverage. - saturate(1.f - abs(in.inv_cov)); + float cov = PS_AA1 == AA1::LINE + ? saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) // Blur only outer part of the line by scaling coverage. + : saturate(1.f - abs(in.inv_cov)); if (!PS_ABE || floor(C.a) == 128.f) // The coverage is only used if the fragment alpha is 128. C.a = 128.f * cov; } @@ -1514,7 +1555,7 @@ struct PSMain bool atst_pass = atst(C); if (PS_AFAIL == AFAIL::KEEP && !atst_pass) - discard_fragment(); + discard(); float4 alpha_blend = float4(0.f); if (SW_AD_TO_HW) @@ -1620,32 +1661,32 @@ struct PSMain if (PS_AFAIL == AFAIL::RGB_ONLY_DSB) alpha_blend.a = float(atst_pass); - if (PS_COLOR0) + if (!PS_NO_COLOR) { out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f; out.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.f) : C.rgb / 255.f; } - if (PS_COLOR1) + if (!PS_NO_COLOR1) out.c1 = alpha_blend; if (PS_ZCLAMP) input_z = min(input_z, cb.max_depth); if (PS_AA1 == AA1::TRIANGLE_SW_Z && !in.interior) - input_z = current_depth; // No depth update for triangle edges. + discard_depth(input_z); // No depth update for triangle edges. if (!atst_pass) { if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::RGB_ONLY) - out.c0.a = current_color.a; + out.c0.a = current_color.a; // discard alpha else if (PS_AFAIL == AFAIL::ZB_ONLY) - out.c0 = current_color; + discard_color(out.c0); if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::FB_ONLY) - input_z = current_depth; + discard_depth(input_z); } - out.setDepth(input_z); + out.depth = input_z; return out; } @@ -1657,16 +1698,18 @@ fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]]) return in * 2; } -constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH; -constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH; -constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK; +constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH && !PS_ROV_COLOR; +constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH && !PS_ROV_COLOR; +constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK && PS_ROV_DEPTH == ROV_DEPTH::NONE; #else -constant bool NEEDS_RT_TEX = NEEDS_RT; +constant bool NEEDS_RT_TEX = NEEDS_RT && !PS_ROV_COLOR; constant bool NEEDS_DS_FBF = false; constant float ds_fbf = 0; #endif -constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF; -constant bool NEEDS_DS_DEPTH = SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF; +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_DS_ROV = PS_ROV_DEPTH != ROV_DEPTH::NONE; fragment MainPSOut ps_main( MainPSIn in [[stage_in]], @@ -1685,7 +1728,9 @@ fragment MainPSOut ps_main( texture2d rt [[texture(GSMTLTextureIndexRenderTarget), function_constant(NEEDS_RT_TEX)]], texture2d primidtex [[texture(GSMTLTextureIndexPrimIDs), function_constant(PS_PRIM_CHECKING_READ)]], texture2d ds_tex [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_TEX)]], - depth2d ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]]) + 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 ds_rov [[texture(GSMTLTextureIndexDepthTarget), raster_order_group(1), function_constant(NEEDS_DS_ROV)]]) { PSMain main(in, cb); main.tex_sampler = s; @@ -1706,7 +1751,9 @@ fragment MainPSOut ps_main( if (SW_DEPTH) { - if (DEPTH_FEEDBACK) + if (PS_ROV_DEPTH != ROV_DEPTH::NONE) + main.current_depth = ds_rov.read(coord).x; + else if (DEPTH_FEEDBACK) main.current_depth = ds_depth.read(coord); else if (NEEDS_DS_FBF) main.current_depth = ds_fbf < 0 ? ds_depth.read(coord) : ds_fbf; @@ -1714,20 +1761,67 @@ fragment MainPSOut ps_main( main.current_depth = ds_tex.read(coord).x; } - if (NEEDS_RT) + if (NEEDS_RT || (PS_ROV_COLOR && any(cb.fbmask == 0xff))) { + if (PS_ROV_COLOR) + { + main.current_color = rt_rov.read(coord); + } + else + { #if FBFETCH_SUPPORT - main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord); + main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord); #else - main.current_color = rt.read(coord); + main.current_color = rt.read(coord); #endif + } } else { main.current_color = 0; } - return main.ps_main(); + MainResult out = main.ps_main(); + if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE && !main.depth_discarded) + ds_rov.write(out.depth, coord); + if (PS_ROV_COLOR && !main.color_discarded) + { + if (!PS_FBMASK) + out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff); + rt_rov.write(out.c0, coord); + } + return out; +} + +// Metal doesn't let you toggle eft with function constants so we need a separate function for it +[[early_fragment_tests]] +fragment void ps_main_rov_eft( + MainPSIn in [[stage_in]], + constant GSMTLMainPSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]], + sampler s [[sampler(0)]], + 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)]]) +{ + PSMain main(in, cb); + main.tex_sampler = s; + if (PS_TEX_IS_COLOR) + main.tex = tex; + else + main.tex_depth = depth; + if (PS_HAS_PALETTE) + main.palette = palette; + + uint2 coord = uint2(in.p.xy); + 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 PRIMID_SUPPORT