aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortarako <r76036296@gmail.com>2026-03-03 01:54:57 +0100
committercrueter <crueter@eden-emu.dev>2026-03-03 01:54:57 +0100
commitd720a7b4b4e3c2bc4f7e2c1b16e282ec91766aa8 (patch)
tree4c1eebb39329f97e2bf75d73768734ac20ee988c
parent464212393e296c59488171692df1d096ecfa5607 (diff)
[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 <none@none.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3637 Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Co-authored-by: tarako <r76036296@gmail.com> Co-committed-by: tarako <r76036296@gmail.com>
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_special.cpp14
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp15
-rw-r--r--src/shader_recompiler/runtime_info.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp30
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<AttributeType, 8> 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<const Shader::IR::Program> 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<float>(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<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
@@ -258,6 +285,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
}
}
break;
+ }
default:
break;
}