diff --git a/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp b/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp index 856aee0d0..48d20341d 100644 --- a/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/blendMapping.cpp @@ -30,6 +30,15 @@ BlendMappingSupport ClassifyBlendMapping(const HW::BlendControl& if (mapping.Map(3) == 3u) { return BlendMappingSupport::Direct; } + 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; + } // Moving alpha requires the same equation for all channels. if (blend.separate_alpha_blend && (blend.alpha_srcblend != blend.color_srcblend || blend.alpha_destblend != blend.color_destblend || diff --git a/src/graphics/host_gpu/renderer/pipeline/blendMapping.h b/src/graphics/host_gpu/renderer/pipeline/blendMapping.h index bcc4ac5fa..8bc1f70d9 100644 --- a/src/graphics/host_gpu/renderer/pipeline/blendMapping.h +++ b/src/graphics/host_gpu/renderer/pipeline/blendMapping.h @@ -15,6 +15,7 @@ struct ColorComponentMapping; 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. Unsupported, }; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 57e465b09..6f60780d0 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -39,6 +39,16 @@ namespace Libs::Graphics { namespace { +uint8_t RemapSourceAlphaFactor(uint8_t factor) { + switch (static_cast(factor)) { + case Prospero::BlendFactor::kSrcAlpha: + return static_cast(Prospero::BlendFactor::kSrc1Alpha); + case Prospero::BlendFactor::kOneMinusSrcAlpha: + return static_cast(Prospero::BlendFactor::kOneMinusSrc1Alpha); + default: return factor; + } +} + vk::PolygonMode ResolvePolygonMode(const HW::ModeControl& mode, bool cull_front, bool cull_back) { // CxPrimitiveSetup::PolygonMode disables both per-face modes when it is zero. if (mode.poly_mode == 0) { @@ -619,14 +629,21 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( pixel_info.target_output_mode[0] != 0 && pixel_info.target_output_mode[0] != 7 && std::all_of(std::begin(pixel_info.target_output_mode) + 1, std::end(pixel_info.target_output_mode), - [](uint8_t mode) { return mode == 0; }) && - ClassifyBlendMapping(blend, pixel_info.target_export_mapping[0]) == - BlendMappingSupport::SourceAlpha) { - // Preserve logical alpha when the export mapping moves it. - pixel_info.alpha_blend_source_remap = true; - pixel_info.dual_source_blending = true; - pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0]; - pixel_info.target_export_mapping[1] = {}; + [](uint8_t mode) { return mode == 0; })) { + switch (ClassifyBlendMapping(blend, pixel_info.target_export_mapping[0])) { + case BlendMappingSupport::SourceAlpha: + pixel_info.alpha_blend_source = ShaderAlphaBlendSource::SourceAlpha; + break; + case BlendMappingSupport::SourceAlphaOne: + pixel_info.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaOne; + break; + default: break; + } + if (pixel_info.alpha_blend_source != ShaderAlphaBlendSource::None) { + pixel_info.dual_source_blending = true; + pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0]; + pixel_info.target_export_mapping[1] = {}; + } } } if (context.GetClipControl().clip_disable) { @@ -729,10 +746,12 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( } const auto& rt = ctx.GetRenderTarget(colors[i].target_slot); const auto& bc = ctx.GetBlendControl(colors[i].target_slot); - const bool alpha_remap = - slot == 0 && ps_input_info != nullptr && ps_input_info->alpha_blend_source_remap; + auto alpha_source = ShaderAlphaBlendSource::None; + if (slot == 0 && ps_input_info != nullptr) { + alpha_source = ps_input_info->alpha_blend_source; + } static_params.blend_enable[slot] = bc.enable && !rt.info.blend_bypass; - if (static_params.blend_enable[slot] && !alpha_remap && + if (static_params.blend_enable[slot] && alpha_source == ShaderAlphaBlendSource::None && ClassifyBlendMapping(bc, colors[i].export_mapping) != BlendMappingSupport::Direct) { static_params.blend_enable[slot] = false; static std::atomic_bool warned = false; @@ -744,18 +763,34 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline( bc.alpha_srcblend, bc.alpha_destblend, bc.separate_alpha_blend ? 1 : 0)); } } - if (alpha_remap) { - static_params.blend_alpha_source_remap = true; - } if (static_params.blend_enable[slot]) { - static_params.color_srcblend[slot] = bc.color_srcblend; - static_params.color_comb_fcn[slot] = bc.color_comb_fcn; - static_params.color_destblend[slot] = bc.color_destblend; - static_params.separate_alpha_blend[slot] = bc.separate_alpha_blend; - if (bc.separate_alpha_blend) { - static_params.alpha_srcblend[slot] = bc.alpha_srcblend; - static_params.alpha_comb_fcn[slot] = bc.alpha_comb_fcn; - static_params.alpha_destblend[slot] = bc.alpha_destblend; + auto blend = bc; + switch (alpha_source) { + case ShaderAlphaBlendSource::SourceAlpha: + blend.color_srcblend = RemapSourceAlphaFactor(blend.color_srcblend); + blend.color_destblend = RemapSourceAlphaFactor(blend.color_destblend); + if (blend.separate_alpha_blend) { + blend.alpha_srcblend = RemapSourceAlphaFactor(blend.alpha_srcblend); + blend.alpha_destblend = RemapSourceAlphaFactor(blend.alpha_destblend); + } + break; + case ShaderAlphaBlendSource::SourceAlphaOne: + // The second source carries the mapped source factor; its alpha stays logical Sa. + blend.color_srcblend = static_cast(Prospero::BlendFactor::kSrc1Color); + blend.color_destblend = + static_cast(Prospero::BlendFactor::kOneMinusSrc1Alpha); + blend.separate_alpha_blend = false; + break; + case ShaderAlphaBlendSource::None: break; + } + static_params.color_srcblend[slot] = blend.color_srcblend; + static_params.color_comb_fcn[slot] = blend.color_comb_fcn; + static_params.color_destblend[slot] = blend.color_destblend; + static_params.separate_alpha_blend[slot] = blend.separate_alpha_blend; + if (blend.separate_alpha_blend) { + static_params.alpha_srcblend[slot] = blend.alpha_srcblend; + static_params.alpha_comb_fcn[slot] = blend.alpha_comb_fcn; + static_params.alpha_destblend[slot] = blend.alpha_destblend; } } } diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h index 49dc7abfb..4be29854d 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h @@ -55,7 +55,6 @@ struct PipelineStaticParameters { uint8_t alpha_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {}; bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {}; bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {}; - bool blend_alpha_source_remap = false; bool operator==(const PipelineStaticParameters& other) const noexcept; }; @@ -65,7 +64,7 @@ struct PipelineStaticParameters { static_assert(std::is_trivially_copyable_v); static_assert(std::is_standard_layout_v); static_assert(alignof(PipelineStaticParameters) == 1); -static_assert(sizeof(PipelineStaticParameters) == 126); +static_assert(sizeof(PipelineStaticParameters) == 125); struct PipelineRenderingState { std::array color_formats {}; diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index 05fa894d7..b56b702b2 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -137,17 +137,14 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format, } } -static vk::BlendFactor GetBlendFactor(uint32_t factor, bool remap_source_alpha) { +static vk::BlendFactor GetBlendFactor(uint32_t factor) { switch (static_cast(factor)) { case Prospero::BlendFactor::kZero: return vk::BlendFactor::eZero; case Prospero::BlendFactor::kOne: return vk::BlendFactor::eOne; case Prospero::BlendFactor::kSrcColor: return vk::BlendFactor::eSrcColor; case Prospero::BlendFactor::kOneMinusSrcColor: return vk::BlendFactor::eOneMinusSrcColor; - case Prospero::BlendFactor::kSrcAlpha: - return remap_source_alpha ? vk::BlendFactor::eSrc1Color : vk::BlendFactor::eSrcAlpha; - case Prospero::BlendFactor::kOneMinusSrcAlpha: - return remap_source_alpha ? vk::BlendFactor::eOneMinusSrc1Color - : vk::BlendFactor::eOneMinusSrcAlpha; + case Prospero::BlendFactor::kSrcAlpha: return vk::BlendFactor::eSrcAlpha; + case Prospero::BlendFactor::kOneMinusSrcAlpha: return vk::BlendFactor::eOneMinusSrcAlpha; case Prospero::BlendFactor::kDstAlpha: return vk::BlendFactor::eDstAlpha; case Prospero::BlendFactor::kOneMinusDstAlpha: return vk::BlendFactor::eOneMinusDstAlpha; case Prospero::BlendFactor::kDstColor: return vk::BlendFactor::eDstColor; @@ -397,20 +394,18 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p color_blend_attachment[i].colorWriteMask = vk::ColorComponentFlags {static_params.color_mask[i]}; color_blend_attachment[i].blendEnable = static_params.blend_enable[i] ? VK_TRUE : VK_FALSE; - // Only target 0 can use the second blend source carrying logical alpha. - const bool remap_source_alpha = i == 0 && static_params.blend_alpha_source_remap; color_blend_attachment[i].srcColorBlendFactor = - GetBlendFactor(static_params.color_srcblend[i], remap_source_alpha); + GetBlendFactor(static_params.color_srcblend[i]); color_blend_attachment[i].dstColorBlendFactor = - GetBlendFactor(static_params.color_destblend[i], remap_source_alpha); + GetBlendFactor(static_params.color_destblend[i]); color_blend_attachment[i].colorBlendOp = GetBlendOp(static_params.color_comb_fcn[i]); color_blend_attachment[i].srcAlphaBlendFactor = (static_params.separate_alpha_blend[i] - ? GetBlendFactor(static_params.alpha_srcblend[i], remap_source_alpha) + ? GetBlendFactor(static_params.alpha_srcblend[i]) : color_blend_attachment[i].srcColorBlendFactor); color_blend_attachment[i].dstAlphaBlendFactor = (static_params.separate_alpha_blend[i] - ? GetBlendFactor(static_params.alpha_destblend[i], remap_source_alpha) + ? GetBlendFactor(static_params.alpha_destblend[i]) : color_blend_attachment[i].dstColorBlendFactor); color_blend_attachment[i].alphaBlendOp = (static_params.separate_alpha_blend[i] ? GetBlendOp(static_params.alpha_comb_fcn[i]) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 09d22a699..2a1d1ee1f 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -459,7 +459,7 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { } // Skip dormant color exports after their valid mask; MRT1 is reserved for logical alpha. if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt && - exp.index != 0 && state.input_info.pixel->alpha_blend_source_remap) { + exp.index != 0 && state.input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None) { return; } EmitIfCondition(state, exec, [&]() { @@ -503,14 +503,27 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) { const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4); auto value = ExportVector(ctx, data, exp, uint_output); if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt && - exp.index == 0 && !uint_output && state.input_info.pixel->alpha_blend_source_remap) { + exp.index == 0 && !uint_output && + state.input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None) { // Broadcast logical alpha before swizzling the primary output. const auto blend_output = OutputVariableForExport(state, {.kind = IR::ExportTargetKind::Mrt, .index = 1}); if (blend_output != 0) { - const auto alpha = state.builder.AllocateId(); + 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) { + 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 = factors; + break; + } + } state.builder.AddFunction(spv::OpStore, blend_output, alpha); } } diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 315c5c0f9..30151b6ba 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -327,7 +327,7 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { void CollectOutputs(const Program& program, ShaderStageInputInfo input_info, ShaderInfo& info) { const bool alpha_remap = program.stage == ShaderType::Pixel && input_info.pixel != nullptr && - input_info.pixel->alpha_blend_source_remap; + input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None; for (const auto* block: program.blocks) { for (const auto& inst: *block) { if (inst.GetOpcode() != ValueOpcode::SetAttribute) { diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index ba5bf46b5..ba849a7a7 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -660,7 +660,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector key.push_back(static_cast(info.ps_sample_mask_export_enable)); key.push_back(static_cast(info.ps_early_z)); key.push_back(static_cast(info.dual_source_blending)); - key.push_back(static_cast(info.alpha_blend_source_remap)); + key.push_back(static_cast(info.alpha_blend_source)); key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode)); for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) { uint32_t packed = 0; diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 221b43cc5..08c041226 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -156,6 +156,8 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo { ShaderStageRuntime stage; }; +enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne }; + struct ShaderPixelInputInfo { uint32_t interpolator_settings[32] = {0}; uint32_t input_num = 0; @@ -179,8 +181,8 @@ struct ShaderPixelInputInfo { bool ps_sample_mask_export_enable = false; bool ps_sample_shading = false; bool dual_source_blending = false; - // Export logical alpha through MRT1 for blending after channel swizzling. - bool alpha_blend_source_remap = false; + // Export logical alpha or per-channel source factors through MRT1 after channel swizzling. + ShaderAlphaBlendSource alpha_blend_source = ShaderAlphaBlendSource::None; bool ps_early_z = false; bool ps_execute_on_noop = false; ShaderStageRuntime stage; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 000896c1e..2bf6003c1 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -15272,7 +15272,8 @@ public: PipelineCache::GraphicsPrograms{{vertex_shader}, pixel_shader}); }; auto &filled = pipeline(true, 2, 2); - const auto draw = [&](const PipelineCache::Pipeline &selected, uint32_t vertex_count = 3) { + const auto draw = [&](const PipelineCache::Pipeline &selected, uint32_t vertex_count = 3, + std::array clear_value = {}) { RenderExecutorTestAccess::BindRenderTarget(executor, color.image_id); if (depth.image_id) { RenderExecutorTestAccess::BindRenderTarget(executor, depth.image_id); @@ -15299,6 +15300,7 @@ public: RenderExecutorTestAccess::CommitBindings( executor, command, selected, bindings.vertex[0], *bindings.pixel); rendering.color_attachments[0].is_clear = true; + rendering.color_attachments[0].clear_value = clear_value; command.BeginRendering(rendering); auto cmd = command.Handle(); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, selected.pipeline); @@ -15642,6 +15644,68 @@ public: wave_ids[0] != wave_ids[1] && wave_keys[0] != wave_keys[1], "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). + static const auto blend_pixel = [] { + std::vector code; + constexpr std::array source{0.8f, 0.6f, 0.4f, 0.5f}; + for (u32 component = 0; component < source.size(); component++) { + AppendVMovLiteral(&code, component, std::bit_cast(source[component])); + } + code.push_back(EncodeExp0(0, 0xf)); + code.push_back(EncodeExp1(0, 1, 2, 3)); + AppendEnd(&code); + return code; + }(); + native_pixel_regs.ps_regs.data_addr = reinterpret_cast(blend_pixel.data()); + ShaderMapUserData(native_pixel_regs.ps_regs.data_addr, + {.type = Prospero::ShaderBinaryType::kPs, + .code_size_bytes = static_cast(blend_pixel.size() * sizeof(u32))}); + const auto saved_blend = registers.GetBlendControl(0); + const auto saved_target_info = registers.GetRenderTarget(0).info; + const auto saved_output_mode = registers.GetShaderRegisters().target_output_mode[0]; + registers.SetTargetOutputMode(0, 4); + auto blend_target_info = saved_target_info; + blend_target_info.blend_bypass = false; + registers.SetColorInfo(0, blend_target_info); + export_mapping[0] = color.export_mapping = Prospero::ColorMappingAbgr; + HW::BlendControl alpha_blend{}; + alpha_blend.enable = true; + alpha_blend.color_srcblend = static_cast(Prospero::BlendFactor::kSrcAlpha); + alpha_blend.alpha_srcblend = static_cast(Prospero::BlendFactor::kOne); + alpha_blend.color_destblend = alpha_blend.alpha_destblend = + static_cast(Prospero::BlendFactor::kOneMinusSrcAlpha); + for (const bool separate : {false, true}) { + alpha_blend.separate_alpha_blend = separate; + registers.SetBlendControl(0, alpha_blend); + const auto programs = context.GetPipelineCache().GetGraphicsPrograms( + native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(), + registers, user_config, export_mapping, true, native_vertex_info, pixel); + const auto mode = separate ? ShaderAlphaBlendSource::SourceAlphaOne + : ShaderAlphaBlendSource::SourceAlpha; + Require(name, "logical alpha blend program", pixel.alpha_blend_source == mode && + pixel.dual_source_blending, + "the separate alpha equation did not reach the compiled pixel program"); + vertex_shader = programs.vertex[0]; + pixel_shader = programs.pixel; + vertex = native_vertex_info[0]; + constexpr std::array destination{0.3f, 0.3f, 0.1f, 0.2f}; + draw(pipeline(true, 2, 2), 3, std::bit_cast>(destination)); + const auto blend_pixels = read_color(); + const std::array expected{separate ? 0.65f : 0.4f, 0.35f, 0.35f, 0.5f}; + for (size_t component = 0; component < blend_pixels.size(); component++) { + Require(name, "logical alpha blend readback", + std::abs(std::bit_cast(blend_pixels[component]) - + expected[component % 4]) < 0.00001f, + "reversed blending changed logical RGB or the separate alpha equation"); + } + } + native_pixel_regs.ps_regs.data_addr = pixel_address; + export_mapping[0] = color.export_mapping = {}; + registers.SetBlendControl(0, saved_blend); + registers.SetColorInfo(0, saved_target_info); + registers.SetTargetOutputMode(0, saved_output_mode); + // Captured Playroom strips contain the fullscreen triangle followed by an // out-of-bounds fetch exporting (0,0,0,0). Additive color reveals any extra triangle. static constexpr std::array, 4> fourth_positions{{ diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index b4aea2fdf..b959c1091 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -12851,6 +12851,28 @@ void TestBlendMappingClassification() { blend.color_srcblend = static_cast(Factor::kDstAlpha); Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported, "destination alpha incorrectly used the physical alpha channel"); + + blend.separate_alpha_blend = true; + blend.color_srcblend = static_cast(Factor::kSrcAlpha); + blend.alpha_srcblend = static_cast(Factor::kOne); + blend.color_destblend = blend.alpha_destblend = + static_cast(Factor::kOneMinusSrcAlpha); + Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlphaOne, + "separate alpha accumulation did not preserve its unit source factor"); + const auto exact = blend; + for (uint32_t changed = 0; changed < 6; changed++) { + blend = exact; + 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 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; + } + Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported, + "separate alpha lowering accepted a different blend equation"); + } } void TestLogicalAlphaBlendExport() { @@ -12860,58 +12882,81 @@ void TestLogicalAlphaBlendExport() { const auto ordinary_key = MakeStageStaticKey(pixel); pixel.dual_source_blending = true; const auto guest_key = MakeStageStaticKey(pixel); - pixel.alpha_blend_source_remap = true; + pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlpha; const auto remapped_key = MakeStageStaticKey(pixel); + pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaOne; + const auto separate_key = MakeStageStaticKey(pixel); Check(ordinary_key != guest_key && guest_key != remapped_key && - ordinary_key != remapped_key, - "ordinary, guest dual-source, and logical-alpha shaders share a cache key"); + ordinary_key != remapped_key && separate_key != ordinary_key && + separate_key != guest_key && separate_key != remapped_key, + "ordinary, guest dual-source, and logical-alpha modes share a cache key"); auto options = MakeCompileOptions(ShaderType::Pixel); options.input_info.pixel = &pixel; - for (const bool compressed : {false, true}) { - const uint32_t shader[] = { - // RGBA = (1000, 2, 3, 0.25), packed into two half pairs when compressed. - EncodeVop1(0x01, 0, 255), compressed ? 0x400063d0u : 0x447a0000u, - EncodeVop1(0x01, 1, 255), compressed ? 0x34004200u : 0x40000000u, - EncodeVop1(0x01, 2, 255), 0x40400000u, - EncodeVop1(0x01, 3, 255), 0x3e800000u, - EncodeExp0(0, 0xf, false, compressed), EncodeExp1(0, 1, 2, 3), - EncodeExp0(1, 0xf, false), EncodeExp1(0, 0, 0, 0), - EncodeExp0(2, 0xf, true, false, true), EncodeExp1(0, 0, 0, 0), - EncodeSopp(0x01), - }; - const auto result = RecompileForTest(shader, options); - CheckSpirvBinaryValidates(result.spirv); - const auto source = DisassembleSpirvBinary(result.spirv); - Check(source.find("OpDecorate %out_mrt_1 Location 0") != std::string::npos && - source.find("OpDecorate %out_mrt_1 Index 1") != std::string::npos && - CountSourceOccurrences(source, "OpStore %out_mrt_1 ") == 1 && - source.find("out_mrt_2") == std::string::npos, - "inactive MRT exports overwrote the logical-alpha output"); - Check(SpirvInstructionOpcodeCount(result.spirv, 252u) != 0, - "ignoring inactive MRT stores discarded their valid-mask export"); - uint32_t alpha_input = 0; - uint32_t color_input = 0; - for (size_t i = 5; i < result.spirv.size(); i += result.spirv[i] >> 16u) { - if ((result.spirv[i] & 0xffffu) != 79u || (result.spirv[i] >> 16u) != 9u) { - continue; - } - const auto selectors = std::span(result.spirv).subspan(i + 5, 4); - if (std::ranges::equal(selectors, std::array{3u, 3u, 3u, 3u})) { - alpha_input = result.spirv[i + 3]; - } else if (std::ranges::equal(selectors, std::array{3u, 2u, 1u, 0u})) { - color_input = result.spirv[i + 3]; + for (const auto mode : {ShaderAlphaBlendSource::SourceAlpha, + ShaderAlphaBlendSource::SourceAlphaOne}) { + pixel.alpha_blend_source = mode; + for (const bool compressed : {false, true}) { + const uint32_t shader[] = { + // RGBA = (1000, 2, 3, 0.25), packed into two half pairs when compressed. + EncodeVop1(0x01, 0, 255), compressed ? 0x400063d0u : 0x447a0000u, + EncodeVop1(0x01, 1, 255), compressed ? 0x34004200u : 0x40000000u, + EncodeVop1(0x01, 2, 255), 0x40400000u, + EncodeVop1(0x01, 3, 255), 0x3e800000u, + EncodeExp0(0, 0xf, false, compressed), EncodeExp1(0, 1, 2, 3), + EncodeExp0(1, 0xf, false), EncodeExp1(0, 0, 0, 0), + EncodeExp0(2, 0xf, true, false, true), EncodeExp1(0, 0, 0, 0), + EncodeSopp(0x01), + }; + const auto result = RecompileForTest(shader, options); + CheckSpirvBinaryValidates(result.spirv); + const auto source = DisassembleSpirvBinary(result.spirv); + Check(source.find("OpDecorate %out_mrt_1 Location 0") != std::string::npos && + source.find("OpDecorate %out_mrt_1 Index 1") != std::string::npos && + CountSourceOccurrences(source, "OpStore %out_mrt_1 ") == 1 && + source.find("out_mrt_2") == std::string::npos, + "inactive MRT exports overwrote the logical-alpha output"); + Check(SpirvInstructionOpcodeCount(result.spirv, 252u) != 0, + "ignoring inactive MRT stores discarded their valid-mask export"); + uint32_t alpha_input = 0; + uint32_t alpha_broadcast = 0; + uint32_t color_input = 0; + uint32_t one = 0; + bool unit_alpha_factor = false; + for (size_t i = 5; i < result.spirv.size(); i += result.spirv[i] >> 16u) { + const auto opcode = result.spirv[i] & 0xffffu; + if (opcode == 43u && (result.spirv[i] >> 16u) == 4u && + result.spirv[i + 3] == 0x3f800000u) { + one = result.spirv[i + 2]; + } + if (opcode == 82u && (result.spirv[i] >> 16u) == 6u && + result.spirv[i + 3] == one && result.spirv[i + 4] == alpha_broadcast && + result.spirv[i + 5] == 0u) { + unit_alpha_factor = true; + } + if ((result.spirv[i] & 0xffffu) != 79u || (result.spirv[i] >> 16u) != 9u) { + continue; + } + const auto selectors = std::span(result.spirv).subspan(i + 5, 4); + if (std::ranges::equal(selectors, std::array{3u, 3u, 3u, 3u})) { + alpha_input = result.spirv[i + 3]; + alpha_broadcast = result.spirv[i + 2]; + } else if (std::ranges::equal(selectors, std::array{3u, 2u, 1u, 0u})) { + color_input = result.spirv[i + 3]; + } } + Check(alpha_input != 0 && alpha_input == color_input, + "blend source did not broadcast logical alpha before the physical export swizzle"); + Check(unit_alpha_factor == (mode == ShaderAlphaBlendSource::SourceAlphaOne), + "secondary ABGR output must be (1, Sa, Sa, Sa) only for separate alpha accumulation"); } - Check(alpha_input != 0 && alpha_input == color_input, - "blend source did not broadcast logical alpha before the physical export swizzle"); } const uint32_t guest_shader[] = { EncodeExp0(0, 0xf, false), EncodeExp1(0, 1, 2, 3), EncodeExp0(1, 0xf), EncodeExp1(4, 5, 6, 7), EncodeSopp(0x01), }; - pixel.alpha_blend_source_remap = false; + pixel.alpha_blend_source = ShaderAlphaBlendSource::None; pixel.target_output_mode[1] = pixel.target_output_mode[0]; pixel.target_export_mapping = {}; const auto guest = RecompileForTest(guest_shader, options);