From d720a7b4b4e3c2bc4f7e2c1b16e282ec91766aa8 Mon Sep 17 00:00:00 2001 From: tarako Date: Tue, 3 Mar 2026 01:54:57 +0100 Subject: [PATCH] [vulkan] Fixed dual-source blending to correctly map shader outputs. (#3637) Fixes Skyward Sword HD eye gitch and a related MoltenVK crash due to the incorrect output mapping. Verified working on mac and android. The test in vk_pipeline_cache.cpp is a bit ugly, but it didn't seem worth it to go lambda/macro just to make it look cleaner. Could change if necessary. Co-authored-by: tarako Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3637 Reviewed-by: CamilleLaVey Co-authored-by: tarako Co-committed-by: tarako --- .../backend/spirv/emit_spirv_special.cpp | 14 +++++++++ .../backend/spirv/spirv_emit_context.cpp | 15 ++++++++-- src/shader_recompiler/runtime_info.h | 3 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 30 ++++++++++++++++++- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index fe5e70a63b..8eca1fac29 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -91,6 +94,17 @@ void AlphaTest(EmitContext& ctx) { } // Anonymous namespace void EmitPrologue(EmitContext& ctx) { + if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) { + // Initialize dual-source blending outputs - prevents MoltenVK crash. + const Id zero{ctx.Const(0.0f)}; + const Id one{ctx.Const(1.0f)}; + const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)}; + for (u32 i = 0; i < 2; ++i) { + if (Sirit::ValidId(ctx.frag_color[i])) { + ctx.OpStore(ctx.frag_color[i], default_color); + } + } + } if (ctx.stage == Stage::VertexB) { const Id zero{ctx.Const(0.0f)}; const Id one{ctx.Const(1.0f)}; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index cd3394bdf0..b9a24496c9 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1670,13 +1670,22 @@ void EmitContext::DefineOutputs(const IR::Program& program) { break; case Stage::Fragment: for (u32 index = 0; index < 8; ++index) { - if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) { + const bool need_dual_source = runtime_info.dual_source_blend && index <= 1; + if (!need_dual_source && !info.stores_frag_color[index] && + !profile.need_declared_frag_colors) { continue; } const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])}; frag_color[index] = DefineOutput(*this, type, std::nullopt); - Decorate(frag_color[index], spv::Decoration::Location, index); - Name(frag_color[index], fmt::format("frag_color{}", index)); + // Correct mapping for dual-source blending + if (runtime_info.dual_source_blend && index <= 1) { + Decorate(frag_color[index], spv::Decoration::Location, 0u); + Decorate(frag_color[index], spv::Decoration::Index, index); + Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary"); + } else { + Decorate(frag_color[index], spv::Decoration::Location, index); + Name(frag_color[index], fmt::format("frag_color{}", index)); + } } if (info.stores_frag_depth) { frag_depth = DefineOutput(*this, F32[1], std::nullopt); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 613c598d0c..be10a9bb08 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -110,6 +110,9 @@ struct RuntimeInfo { /// Output types for each color attachment std::array color_output_types{}; + + /// Dual source blending + bool dual_source_blend{}; }; } // namespace Shader diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index f3dd0f90d8..77a4e8616a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -237,11 +237,38 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program } info.convert_depth_mode = gl_ndc; break; - case Shader::Stage::Fragment: + case Shader::Stage::Fragment: { info.alpha_test_func = MaxwellToCompareFunction( key.state.UnpackComparisonOp(key.state.alpha_test_func.Value())); info.alpha_test_reference = std::bit_cast(key.state.alpha_test_ref); + // Check for dual source blending + const auto& blend0 = key.state.attachments[0]; + if (blend0.enable != 0) { + using F = Maxwell::Blend::Factor; + const auto src_rgb = blend0.SourceRGBFactor(); + const auto dst_rgb = blend0.DestRGBFactor(); + const auto src_a = blend0.SourceAlphaFactor(); + const auto dst_a = blend0.DestAlphaFactor(); + info.dual_source_blend = + src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D || + src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D || + src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL || + src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL || + dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D || + dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D || + dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL || + dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL || + src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D || + src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D || + src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL || + src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL || + dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D || + dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D || + dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL || + dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL; + } + if (device.IsMoltenVK()) { for (size_t i = 0; i < 8; ++i) { const auto format = static_cast(key.state.color_formats[i]); @@ -258,6 +285,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program } } break; + } default: break; }