From 6956b454f3ce35930b8a6ddba0aad651ef026215 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 27 Sep 2026 21:57:39 +0400 Subject: [PATCH] renderer: skip inactive pixel shaders --- src/graphics/host_gpu/renderer/renderDraw.cpp | 30 ++++++++++------ tests/ShaderRecompilerComputeTests.cpp | 34 +++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 879d315af..627e63395 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -575,12 +575,17 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo return state; } -static bool DrawHasActivePixelShader(const CommandBuffer& buffer) { - const auto& ctx = buffer.GetRegisters(); - const auto& sh_regs = ctx.GetShaderRegisters(); - const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0; - return buffer.GetShaders().GetPs().ps_regs.data_addr != 0 && - (has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs)); +static uint32_t DrawColorOutputMask(const HW::Context& ctx) { + const auto& sh_regs = ctx.GetShaderRegisters(); + const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask; + uint32_t output_mask = 0; + for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) { + if (sh_regs.target_output_mode[slot] != 0 && + render_target_mask_slot(write_mask, slot) != 0) { + output_mask |= 1u << slot; + } + } + return output_mask; } enum class CbColorMode : uint8_t { @@ -847,7 +852,7 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer, } static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, - DrawRenderState& state) { + uint32_t color_output_mask, DrawRenderState& state) { auto& ctx = buffer.GetRegisters(); auto& sh_ctx = buffer.GetShaders(); @@ -861,7 +866,7 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, target_export_mapping {}; for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) { const auto& rt = ctx.GetRenderTarget(slot); - if (rt.base.addr != 0 && render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0) { + if ((color_output_mask & (1u << slot)) != 0 && rt.base.addr != 0) { target_export_mapping[slot] = TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type, rt.info.channel_order) @@ -880,8 +885,12 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw, uint32_t render_target_slice_offset, DrawRenderState& state) { - state.ps_active = DrawHasActivePixelShader(buffer); - RefreshShaders(buffer, draw, state); + const auto& shader_regs = buffer.GetRegisters().GetShaderRegisters(); + const auto color_output_mask = DrawColorOutputMask(buffer.GetRegisters()); + state.ps_active = buffer.GetShaders().GetPs().ps_regs.data_addr != 0 && + (color_output_mask != 0 || + PixelShaderHasDepthOrCoverageSideEffects(shader_regs)); + RefreshShaders(buffer, draw, color_output_mask, state); uint32_t mrt_mask = 0; if (state.ps_active) { for (const auto& output: state.ps_input_info.stage.program->info.outputs) { @@ -890,6 +899,7 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal } } } + mrt_mask &= color_output_mask; if (draw.IsIndexed()) { LogDrawPhase(draw.Name(), "ResolveRenderColorTarget"); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index dae62a3c2..17fa2e78b 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -14373,6 +14373,40 @@ public: "a stale color target clipped stencil coverage or the NULL-export shader was skipped"); } + // A depth-only draw retains the previous PS address and write masks, but + // SPI_SHADER_COL_FORMAT disables its exports. Compiling this stale PS + // with the new zero-input metadata would fail before the depth draw. + static const auto stale_pixel = [] { + std::vector code{EncodeVintrp(0, 0, 0, 0, 0), + EncodeVintrp(1, 0, 0, 0, 1), + EncodeExp0(0, 0xf), EncodeExp1(0, 0, 0, 0)}; + AppendEnd(&code); + return code; + }(); + const auto stale_address = reinterpret_cast(stale_pixel.data()); + ShaderMapUserData(stale_address, + {.type = Prospero::ShaderBinaryType::kPs, + .code_size_bytes = static_cast(stale_pixel.size() * sizeof(u32))}); + shaders.SetPsShaderBase(stale_address); + registers.SetPsInControl(0); + registers.SetDepthShaderControl({}); + registers.SetDepthControl({.z_enable = true, .z_write_enable = true, + .zfunc = static_cast(vk::CompareOp::eAlways)}); + for (const auto slot : {0u, 3u}) { + registers.SetTargetOutputMode(slot, 0); + } + RenderExecutorTestAccess::DrawAuto( + executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1}); + const auto depth_only_pixels = ReadCachedTexel( + name, context, depth.image_id, {}, {extent, extent, 1}); + Require(name, "disabled stale PS preserves fixed-function depth", + std::ranges::all_of(depth_only_pixels, [](u32 v) { return v == 0; }), + "disabled color exports invoked the stale PS or clipped fixed-function depth"); + registers.SetPsInControl(0x8000); + for (const auto slot : {0u, 3u}) { + registers.SetTargetOutputMode(slot, 4); + } + // With MRT0 still bound, an MRT3-only export must retain location3 in // rendering attachments, pipeline formats, blend masks and dynamic write enables. static const auto sparse_pixel = [] {