From 94e7d239dde8b31351a9c67856c55a4c2dc296ff Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 4 Oct 2026 23:22:54 +0400 Subject: [PATCH] gpu: fix zero-source alpha blending on swizzled targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com> --- .../host_gpu/renderer/pipeline/blendMapping.cpp | 7 +++++-- .../host_gpu/renderer/pipeline/blendMapping.h | 1 + .../host_gpu/renderer/pipeline/pipelineCache.cpp | 4 ++++ .../recompiler/backend/spirv/spirvEmitterFlow.cpp | 15 ++++++++++++--- src/graphics/shader/shader.h | 2 +- tests/ShaderRecompilerComputeTests.cpp | 5 +++-- tests/shaderCfgTests.cpp | 11 +++++++++-- 7 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp b/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp index 48d20341d..7a0b2dffc 100644 --- a/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp @@ -32,12 +32,15 @@ BlendMappingSupport ClassifyBlendMapping(const HW::BlendControl& } if (blend.separate_alpha_blend && blend.color_srcblend == static_cast(Prospero::BlendFactor::kSrcAlpha) && - blend.alpha_srcblend == static_cast(Prospero::BlendFactor::kOne) && blend.color_destblend == static_cast(Prospero::BlendFactor::kOneMinusSrcAlpha) && blend.alpha_destblend == blend.color_destblend && blend.color_comb_fcn == static_cast(Prospero::BlendOp::kAdd) && blend.alpha_comb_fcn == blend.color_comb_fcn) { - return BlendMappingSupport::SourceAlphaOne; + switch (static_cast(blend.alpha_srcblend)) { + case Prospero::BlendFactor::kZero: return BlendMappingSupport::SourceAlphaZero; + case Prospero::BlendFactor::kOne: return BlendMappingSupport::SourceAlphaOne; + default: break; + } } // Moving alpha requires the same equation for all channels. if (blend.separate_alpha_blend && (blend.alpha_srcblend != blend.color_srcblend || diff --git a/src/graphics/host_gpu/renderer/pipeline/blendMapping.h b/src/graphics/host_gpu/renderer/pipeline/blendMapping.h index 8bc1f70d9..6fcd076aa 100644 --- a/src/graphics/host_gpu/renderer/pipeline/blendMapping.h +++ b/src/graphics/host_gpu/renderer/pipeline/blendMapping.h @@ -16,6 +16,7 @@ enum class BlendMappingSupport { Direct, SourceAlpha, // Requires logical alpha in the second blend source. SourceAlphaOne, // Source alpha for RGB, one for alpha, with a shared destination equation. + SourceAlphaZero, // Source alpha for RGB, zero for alpha, with a shared destination equation. Unsupported, }; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index aeb66efd2..d40aeb173 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -640,6 +640,9 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( case BlendMappingSupport::SourceAlphaOne: pixel_info.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaOne; break; + case BlendMappingSupport::SourceAlphaZero: + pixel_info.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaZero; + break; default: break; } if (pixel_info.alpha_blend_source != ShaderAlphaBlendSource::None) { @@ -764,6 +767,7 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( blend.separate_alpha_blend = false; break; case ShaderAlphaBlendSource::SourceAlphaOne: + case ShaderAlphaBlendSource::SourceAlphaZero: // The second source carries the mapped source factor; its alpha stays logical Sa. blend.color_srcblend = static_cast(Prospero::BlendFactor::kSrc1Color); blend.color_destblend = diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 9f8b42d63..8940e2521 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -512,14 +512,23 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { auto alpha = state.builder.AllocateId(); state.builder.AddFunction(spv::OpVectorShuffle, vector_type, alpha, value, value, 3u, 3u, 3u, 3u); - if (state.input_info.pixel->alpha_blend_source == - ShaderAlphaBlendSource::SourceAlphaOne) { + uint32_t alpha_factor = 0; + switch (state.input_info.pixel->alpha_blend_source) { + case ShaderAlphaBlendSource::SourceAlphaOne: + alpha_factor = ConstantF32Value(state, 1.0f); + break; + case ShaderAlphaBlendSource::SourceAlphaZero: + alpha_factor = ConstantF32Value(state, 0.0f); + break; + default: break; + } + if (alpha_factor != 0) { const auto mapping = state.input_info.pixel->target_export_mapping[0]; for (uint32_t component = 0; component < 4; component++) { if (mapping.Map(component) != 3u) continue; const auto factors = state.builder.AllocateId(); state.builder.AddFunction(spv::OpCompositeInsert, vector_type, factors, - ConstantF32Value(state, 1.0f), alpha, component); + alpha_factor, alpha, component); alpha = factors; break; } diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index e86a52297..bc39b9a84 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -158,7 +158,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo { ShaderStageRuntime stage; }; -enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne }; +enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne, SourceAlphaZero }; struct ShaderPixelInputInfo { uint32_t interpolator_settings[32] = {0}; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 2c535c5bb..ec6c43556 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -15830,7 +15830,7 @@ public: "wave32 and wave64 pixel programs shared a cache key"); // Reversed RGBA targets must use logical Sa for RGB, with a separate - // unit source factor when alpha accumulates as Sa + Da * (1 - Sa). + // zero or unit source factor for separate alpha attenuation or accumulation. static const auto blend_pixel = [] { std::vector code; constexpr std::array source{0.8f, 0.6f, 0.4f, 0.5f}; @@ -15871,7 +15871,8 @@ public: for (const auto test : { AlphaBlendCase{false, Prospero::BlendFactor::kOne, ShaderAlphaBlendSource::SourceAlpha, 0.4f}, AlphaBlendCase{true, Prospero::BlendFactor::kSrcAlpha, ShaderAlphaBlendSource::SourceAlpha, 0.4f}, - AlphaBlendCase{true, Prospero::BlendFactor::kOne, ShaderAlphaBlendSource::SourceAlphaOne, 0.65f}}) { + AlphaBlendCase{true, Prospero::BlendFactor::kOne, ShaderAlphaBlendSource::SourceAlphaOne, 0.65f}, + AlphaBlendCase{true, Prospero::BlendFactor::kZero, ShaderAlphaBlendSource::SourceAlphaZero, 0.15f}}) { alpha_blend.separate_alpha_blend = test.separate; alpha_blend.alpha_srcblend = static_cast(test.alpha_source); registers.SetBlendControl(0, alpha_blend); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 733a0fcef..46c0ffa47 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -12906,6 +12906,9 @@ void TestBlendMappingClassification() { static_cast(Factor::kOneMinusSrcAlpha); Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlphaOne, "separate alpha accumulation did not preserve its unit source factor"); + blend.alpha_srcblend = static_cast(Factor::kZero); + Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlphaZero, + "separate alpha attenuation did not preserve its zero source factor"); blend.alpha_srcblend = blend.color_srcblend; Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlpha, "equal separate alpha equations did not reuse the broadcast alpha mode"); @@ -12916,7 +12919,7 @@ void TestBlendMappingClassification() { switch (changed) { case 0: blend.color_srcblend = static_cast(Factor::kZero); break; case 1: blend.color_destblend = static_cast(Factor::kOne); break; - case 2: blend.alpha_srcblend = static_cast(Factor::kZero); break; + case 2: blend.alpha_srcblend = static_cast(Factor::kDstAlpha); break; case 3: blend.alpha_destblend = static_cast(Factor::kOne); break; case 4: blend.color_comb_fcn = static_cast(Prospero::BlendOp::kSubtract); break; case 5: blend.alpha_comb_fcn = static_cast(Prospero::BlendOp::kSubtract); break; @@ -12937,9 +12940,13 @@ void TestLogicalAlphaBlendExport() { const auto remapped_key = MakeStageStaticKey(pixel); pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaOne; const auto separate_key = MakeStageStaticKey(pixel); + pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaZero; + const auto zero_key = MakeStageStaticKey(pixel); Check(ordinary_key != guest_key && guest_key != remapped_key && ordinary_key != remapped_key && separate_key != ordinary_key && - separate_key != guest_key && separate_key != remapped_key, + separate_key != guest_key && separate_key != remapped_key && + zero_key != ordinary_key && zero_key != guest_key && + zero_key != remapped_key && zero_key != separate_key, "ordinary, guest dual-source, and logical-alpha modes share a cache key"); auto options = MakeCompileOptions(ShaderType::Pixel);