From d8d54c5ccfe8a64854f4a60dba35e0d6e910ff91 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Fri, 17 Oct 2025 16:07:35 +1000 Subject: [PATCH] shader_recompiler: Fix alpha-to-coverage fragment output interface The Vulkan spec requires fragment shaders to declare an output covering Location 0, Component 3 (alpha) when alpha-to-coverage is enabled. This change: - Tracks alpha_to_coverage_enabled through RuntimeInfo from pipeline state - Forces declaration of frag_color[0] with full RGBA when enabled - Initializes alpha to 1.0 in shader epilogue if not explicitly written Signed-off-by: Zephyron --- .../backend/spirv/emit_spirv_special.cpp | 25 +++++++++++++++++++ .../backend/spirv/spirv_emit_context.cpp | 4 ++- src/shader_recompiler/runtime_info.h | 2 ++ src/shader_recompiler/shader_info.h | 2 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 2 ++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index f60da758e..39f9bd8e8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "shader_recompiler/backend/spirv/emit_spirv.h" @@ -61,6 +62,29 @@ Id ComparisonFunction(EmitContext& ctx, CompareFunction comparison, Id operand_1 throw InvalidArgument("Comparison function {}", comparison); } +void InitializeAlphaToCoverage(EmitContext& ctx) { + if (!ctx.runtime_info.alpha_to_coverage_enabled || !Sirit::ValidId(ctx.frag_color[0])) { + return; + } + + // Load the current color value + const Id current_color{ctx.OpLoad(ctx.F32[4], ctx.frag_color[0])}; + + // Extract RGB components + const Id r{ctx.OpCompositeExtract(ctx.F32[1], current_color, 0u)}; + const Id g{ctx.OpCompositeExtract(ctx.F32[1], current_color, 1u)}; + const Id b{ctx.OpCompositeExtract(ctx.F32[1], current_color, 2u)}; + + // Set alpha to 1.0 for alpha-to-coverage + const Id alpha{ctx.Const(1.0f)}; + + // Reconstruct the color with alpha = 1.0 + const Id new_color{ctx.OpCompositeConstruct(ctx.F32[4], r, g, b, alpha)}; + + // Store the updated color + ctx.OpStore(ctx.frag_color[0], new_color); +} + void AlphaTest(EmitContext& ctx) { if (!ctx.runtime_info.alpha_test_func) { return; @@ -121,6 +145,7 @@ void EmitEpilogue(EmitContext& ctx) { ConvertDepthMode(ctx); } if (ctx.stage == Stage::Fragment) { + InitializeAlphaToCoverage(ctx); AlphaTest(ctx); } } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 46752c2fe..13f6a97d2 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -1664,7 +1665,8 @@ 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) { + if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors && + !(index == 0 && runtime_info.alpha_to_coverage_enabled)) { continue; } frag_color[index] = DefineOutput(*this, F32[4], std::nullopt); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index bc764e7af..8b8c5ac36 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -93,6 +94,7 @@ struct RuntimeInfo { std::optional fixed_state_point_size; std::optional alpha_test_func; float alpha_test_reference{}; + bool alpha_to_coverage_enabled{}; /// Static Y negate value bool y_negate{}; diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index 0bd971a6e..858e3eb84 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -317,6 +318,7 @@ struct Info { bool uses_rescaling_uniform{}; bool uses_cbuf_indirect{}; bool uses_render_area{}; + bool alpha_to_coverage_enabled{}; IR::Type used_constant_buffer_types{}; IR::Type used_storage_buffer_types{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 13ca2b699..07b9b2371 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -227,6 +228,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program info.alpha_test_func = MaxwellToCompareFunction( key.state.UnpackComparisonOp(key.state.alpha_test_func.Value())); info.alpha_test_reference = Common::BitCast(key.state.alpha_test_ref); + info.alpha_to_coverage_enabled = key.state.alpha_to_coverage_enabled != 0; break; default: break;