From 9014861858295489cf597322801b37dad9aaf2ce Mon Sep 17 00:00:00 2001 From: ameerj Date: Wed, 18 Nov 2020 20:08:51 -0500 Subject: [PATCH 1/4] vulkan_renderer: Alpha Test Culling Implementation Used by various textures in many titles, e.g. SSBU menu. --- .../renderer_vulkan/fixed_pipeline_state.cpp | 5 ++ .../renderer_vulkan/fixed_pipeline_state.h | 8 +++ .../renderer_vulkan/vk_pipeline_cache.cpp | 8 +++ .../renderer_vulkan/vk_shader_decompiler.cpp | 54 ++++++++++++++++++- .../renderer_vulkan/vk_shader_decompiler.h | 3 ++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index da5c550eae..1b9611c595 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -60,6 +60,11 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0); topology.Assign(regs.draw.topology); + alpha_raw = 0; + alpha_test_enabled.Assign(regs.alpha_test_enabled); + alpha_test_func.Assign(PackComparisonOp(regs.alpha_test_func)); + std::memcpy(&alpha_test_ref, ®s.alpha_test_ref, sizeof(u32)); // TODO: C++20 std::bit_cast + std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 2c18eeaaeb..9a45ec6b7b 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -187,6 +187,14 @@ struct FixedPipelineState { BitField<23, 1, u32> rasterize_enable; BitField<24, 4, Maxwell::PrimitiveTopology> topology; }; + + u32 alpha_test_ref; /// < Alpha test reference + union { + u32 alpha_raw; + BitField<0, 3, u32> alpha_test_func; + BitField<3, 1, u32> alpha_test_enabled; + }; + u32 point_size; std::array binding_divisors; std::array attributes; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index dedc9c466f..9ccf5d0117 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -344,6 +344,14 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { } specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one; + // Alpha test + if (fixed_state.alpha_test_enabled == 1) { + specialization.alpha_test_enabled = true; + specialization.alpha_test_func = static_cast(fixed_state.alpha_test_func); + // memcpy from u32 to float TODO: C++20 std::bit_cast + std::memcpy(&specialization.alpha_test_ref, &fixed_state.alpha_test_ref, sizeof(float)); + } + SPIRVProgram program; std::vector bindings; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index a20452b87e..356d2ab7a5 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -2075,6 +2075,55 @@ private: return {}; } + void AlphaTest(const Id& pointer) { + const Id true_label = OpLabel(); + const Id skip_label = OpLabel(); + Id condition; + switch (specialization.alpha_test_func) { + case VK_COMPARE_OP_NEVER: + condition = Constant(t_float, false); // Never true + break; + case VK_COMPARE_OP_LESS: + condition = OpFOrdLessThan(t_bool, Constant(t_float, specialization.alpha_test_ref), + OpLoad(t_float, pointer)); + break; + case VK_COMPARE_OP_EQUAL: + condition = OpFOrdEqual(t_bool, Constant(t_float, specialization.alpha_test_ref), + OpLoad(t_float, pointer)); + break; + case VK_COMPARE_OP_LESS_OR_EQUAL: + condition = OpFOrdLessThanEqual( + t_bool, Constant(t_float, specialization.alpha_test_ref), OpLoad(t_float, pointer)); + break; + case VK_COMPARE_OP_GREATER: + // Note: requires "Equal" to properly work for ssbu. perhaps a precision issue + condition = OpFOrdGreaterThanEqual( + t_bool, Constant(t_float, specialization.alpha_test_ref), OpLoad(t_float, pointer)); + break; + case VK_COMPARE_OP_NOT_EQUAL: + // Note: not accurate when tested against a unit test + // TODO: confirm if used by games + condition = OpFOrdNotEqual(t_bool, Constant(t_float, specialization.alpha_test_ref), + OpLoad(t_float, pointer)); + break; + case VK_COMPARE_OP_GREATER_OR_EQUAL: + condition = OpFOrdGreaterThanEqual( + t_bool, Constant(t_float, specialization.alpha_test_ref), OpLoad(t_float, pointer)); + break; + case VK_COMPARE_OP_ALWAYS: + condition = Constant(t_bool, true); // Always true + break; + default: + LOG_WARNING(Render_Vulkan, "Unimplemented alpha test function"); + condition = Constant(t_bool, true); // Always true + break; + } + OpBranchConditional(condition, true_label, skip_label); + AddLabel(true_label); + OpKill(); + AddLabel(skip_label); + } + void PreExit() { if (stage == ShaderType::Vertex && specialization.ndc_minus_one_to_one) { const u32 position_index = out_indices.position.value(); @@ -2097,8 +2146,6 @@ private: UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); - // TODO(Rodrigo): Alpha testing - // Write the color outputs using the data in the shader registers, disabled // rendertargets/components are skipped in the register assignment. u32 current_reg = 0; @@ -2110,6 +2157,9 @@ private: } const Id pointer = AccessElement(t_out_float, frag_colors[rt], component); OpStore(pointer, SafeGetRegister(current_reg)); + if (specialization.alpha_test_enabled && component == 3) { + AlphaTest(pointer); + } ++current_reg; } } diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h index 2b0e903967..ddbcb0b417 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h @@ -95,6 +95,9 @@ struct Specialization final { std::bitset enabled_attributes; std::array attribute_types{}; bool ndc_minus_one_to_one{}; + bool alpha_test_enabled{}; + float alpha_test_ref{}; + u8 alpha_test_func{}; }; // Old gcc versions don't consider this trivially copyable. // static_assert(std::is_trivially_copyable_v); From 1dbf71ceb3b84691101228a2981cafed477b27e9 Mon Sep 17 00:00:00 2001 From: ameerj Date: Thu, 19 Nov 2020 02:25:37 -0500 Subject: [PATCH 2/4] Address PR feedback from Rein --- .../renderer_vulkan/fixed_pipeline_state.cpp | 5 +- .../renderer_vulkan/fixed_pipeline_state.h | 3 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 10 ++-- .../renderer_vulkan/vk_shader_decompiler.cpp | 50 ++++++++----------- .../renderer_vulkan/vk_shader_decompiler.h | 3 +- 5 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 1b9611c595..1928283001 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -61,8 +61,9 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta topology.Assign(regs.draw.topology); alpha_raw = 0; - alpha_test_enabled.Assign(regs.alpha_test_enabled); - alpha_test_func.Assign(PackComparisonOp(regs.alpha_test_func)); + const auto test_func = + regs.alpha_test_enabled == 1 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; + alpha_test_func.Assign(PackComparisonOp(test_func)); std::memcpy(&alpha_test_ref, ®s.alpha_test_ref, sizeof(u32)); // TODO: C++20 std::bit_cast std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 9a45ec6b7b..42480e8d0a 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -188,11 +188,10 @@ struct FixedPipelineState { BitField<24, 4, Maxwell::PrimitiveTopology> topology; }; - u32 alpha_test_ref; /// < Alpha test reference + u32 alpha_test_ref; ///< Alpha test reference value union { u32 alpha_raw; BitField<0, 3, u32> alpha_test_func; - BitField<3, 1, u32> alpha_test_enabled; }; u32 point_size; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 9ccf5d0117..a66a841fbc 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -345,12 +345,10 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one; // Alpha test - if (fixed_state.alpha_test_enabled == 1) { - specialization.alpha_test_enabled = true; - specialization.alpha_test_func = static_cast(fixed_state.alpha_test_func); - // memcpy from u32 to float TODO: C++20 std::bit_cast - std::memcpy(&specialization.alpha_test_ref, &fixed_state.alpha_test_ref, sizeof(float)); - } + specialization.alpha_test_func = + FixedPipelineState::UnpackComparisonOp(fixed_state.alpha_test_func.Value()); + // memcpy from u32 to float TODO: C++20 std::bit_cast + std::memcpy(&specialization.alpha_test_ref, &fixed_state.alpha_test_ref, sizeof(float)); SPIRVProgram program; std::vector bindings; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index 356d2ab7a5..81550bc96a 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -2075,48 +2075,42 @@ private: return {}; } - void AlphaTest(const Id& pointer) { + void AlphaTest(Id pointer) { const Id true_label = OpLabel(); const Id skip_label = OpLabel(); + const Id alpha_reference = Constant(t_float, specialization.alpha_test_ref); + const Id alpha_value = OpLoad(t_float, pointer); Id condition; + using Compare = Maxwell::ComparisonOp; switch (specialization.alpha_test_func) { - case VK_COMPARE_OP_NEVER: - condition = Constant(t_float, false); // Never true + case Compare::NeverOld: + condition = v_false; // Never true break; - case VK_COMPARE_OP_LESS: - condition = OpFOrdLessThan(t_bool, Constant(t_float, specialization.alpha_test_ref), - OpLoad(t_float, pointer)); + case Compare::LessOld: + condition = OpFOrdLessThan(t_bool, alpha_reference, alpha_value); break; - case VK_COMPARE_OP_EQUAL: - condition = OpFOrdEqual(t_bool, Constant(t_float, specialization.alpha_test_ref), - OpLoad(t_float, pointer)); + case Compare::EqualOld: + condition = OpFOrdEqual(t_bool, alpha_reference, alpha_value); break; - case VK_COMPARE_OP_LESS_OR_EQUAL: - condition = OpFOrdLessThanEqual( - t_bool, Constant(t_float, specialization.alpha_test_ref), OpLoad(t_float, pointer)); + case Compare::LessEqualOld: + condition = OpFOrdLessThanEqual(t_bool, alpha_reference, alpha_value); break; - case VK_COMPARE_OP_GREATER: + case Compare::GreaterOld: // Note: requires "Equal" to properly work for ssbu. perhaps a precision issue - condition = OpFOrdGreaterThanEqual( - t_bool, Constant(t_float, specialization.alpha_test_ref), OpLoad(t_float, pointer)); + condition = OpFOrdGreaterThanEqual(t_bool, alpha_reference, alpha_value); break; - case VK_COMPARE_OP_NOT_EQUAL: + case Compare::NotEqualOld: // Note: not accurate when tested against a unit test // TODO: confirm if used by games - condition = OpFOrdNotEqual(t_bool, Constant(t_float, specialization.alpha_test_ref), - OpLoad(t_float, pointer)); + condition = OpFOrdNotEqual(t_bool, alpha_reference, alpha_value); break; - case VK_COMPARE_OP_GREATER_OR_EQUAL: - condition = OpFOrdGreaterThanEqual( - t_bool, Constant(t_float, specialization.alpha_test_ref), OpLoad(t_float, pointer)); - break; - case VK_COMPARE_OP_ALWAYS: - condition = Constant(t_bool, true); // Always true + case Compare::GreaterEqualOld: + condition = OpFOrdGreaterThanEqual(t_bool, alpha_reference, alpha_value); break; + case Compare::AlwaysOld: + return; default: - LOG_WARNING(Render_Vulkan, "Unimplemented alpha test function"); - condition = Constant(t_bool, true); // Always true - break; + UNREACHABLE(); } OpBranchConditional(condition, true_label, skip_label); AddLabel(true_label); @@ -2157,7 +2151,7 @@ private: } const Id pointer = AccessElement(t_out_float, frag_colors[rt], component); OpStore(pointer, SafeGetRegister(current_reg)); - if (specialization.alpha_test_enabled && component == 3) { + if (rt == 0 && component == 3) { AlphaTest(pointer); } ++current_reg; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h index ddbcb0b417..cd3d0a4154 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h @@ -95,9 +95,8 @@ struct Specialization final { std::bitset enabled_attributes; std::array attribute_types{}; bool ndc_minus_one_to_one{}; - bool alpha_test_enabled{}; float alpha_test_ref{}; - u8 alpha_test_func{}; + Maxwell::ComparisonOp alpha_test_func{}; }; // Old gcc versions don't consider this trivially copyable. // static_assert(std::is_trivially_copyable_v); From e87670ee48c896ba029a11ad590234e00260f875 Mon Sep 17 00:00:00 2001 From: ameerj Date: Wed, 25 Nov 2020 00:33:20 -0500 Subject: [PATCH 3/4] Refactor MaxwellToSpirvComparison. Use Common::BitCast Co-Authored-By: Rodrigo Locatti --- .../renderer_vulkan/fixed_pipeline_state.cpp | 5 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 4 +- .../renderer_vulkan/vk_shader_decompiler.cpp | 56 ++++++++++--------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 1928283001..fffae528ea 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -8,6 +8,7 @@ #include +#include "common/bit_cast.h" #include "common/cityhash.h" #include "common/common_types.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" @@ -64,9 +65,9 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta const auto test_func = regs.alpha_test_enabled == 1 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; alpha_test_func.Assign(PackComparisonOp(test_func)); - std::memcpy(&alpha_test_ref, ®s.alpha_test_ref, sizeof(u32)); // TODO: C++20 std::bit_cast + alpha_test_ref = Common::BitCast(regs.alpha_test_ref); - std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast + point_size = Common::BitCast(regs.point_size); for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { binding_divisors[index] = diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index a66a841fbc..f9efe526df 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -7,6 +7,7 @@ #include #include +#include "common/bit_cast.h" #include "common/microprofile.h" #include "core/core.h" #include "core/memory.h" @@ -347,8 +348,7 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { // Alpha test specialization.alpha_test_func = FixedPipelineState::UnpackComparisonOp(fixed_state.alpha_test_func.Value()); - // memcpy from u32 to float TODO: C++20 std::bit_cast - std::memcpy(&specialization.alpha_test_ref, &fixed_state.alpha_test_ref, sizeof(float)); + specialization.alpha_test_ref = Common::BitCast(fixed_state.alpha_test_ref); SPIRVProgram program; std::vector bindings; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index 81550bc96a..d6685cd122 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -2075,47 +2075,49 @@ private: return {}; } - void AlphaTest(Id pointer) { - const Id true_label = OpLabel(); - const Id skip_label = OpLabel(); - const Id alpha_reference = Constant(t_float, specialization.alpha_test_ref); - const Id alpha_value = OpLoad(t_float, pointer); - Id condition; + Id MaxwellToSpirvComparison(Maxwell::ComparisonOp compare_op, Id operand_1, Id operand_2) { using Compare = Maxwell::ComparisonOp; - switch (specialization.alpha_test_func) { + switch (compare_op) { case Compare::NeverOld: - condition = v_false; // Never true - break; + return v_false; // Never let the test pass case Compare::LessOld: - condition = OpFOrdLessThan(t_bool, alpha_reference, alpha_value); - break; + return OpFOrdLessThan(t_bool, operand_1, operand_2); case Compare::EqualOld: - condition = OpFOrdEqual(t_bool, alpha_reference, alpha_value); - break; + // Note: not accurate when tested against a unit test + // TODO: confirm if used by games + return OpFOrdEqual(t_bool, operand_1, operand_2); case Compare::LessEqualOld: - condition = OpFOrdLessThanEqual(t_bool, alpha_reference, alpha_value); - break; + return OpFOrdLessThanEqual(t_bool, operand_1, operand_2); case Compare::GreaterOld: - // Note: requires "Equal" to properly work for ssbu. perhaps a precision issue - condition = OpFOrdGreaterThanEqual(t_bool, alpha_reference, alpha_value); - break; + return OpFOrdGreaterThan(t_bool, operand_1, operand_2); case Compare::NotEqualOld: // Note: not accurate when tested against a unit test // TODO: confirm if used by games - condition = OpFOrdNotEqual(t_bool, alpha_reference, alpha_value); - break; + return OpFOrdNotEqual(t_bool, operand_1, operand_2); case Compare::GreaterEqualOld: - condition = OpFOrdGreaterThanEqual(t_bool, alpha_reference, alpha_value); - break; - case Compare::AlwaysOld: - return; + return OpFOrdGreaterThanEqual(t_bool, operand_1, operand_2); default: UNREACHABLE(); } - OpBranchConditional(condition, true_label, skip_label); - AddLabel(true_label); + } + + void AlphaTest(Id pointer) { + if (specialization.alpha_test_func == Maxwell::ComparisonOp::AlwaysOld) { + return; + } + + const Id true_label = OpLabel(); + const Id discard_label = OpLabel(); + const Id alpha_reference = Constant(t_float, specialization.alpha_test_ref); + const Id alpha_value = OpLoad(t_float, pointer); + + const Id condition = + MaxwellToSpirvComparison(specialization.alpha_test_func, alpha_value, alpha_reference); + + OpBranchConditional(condition, true_label, discard_label); + AddLabel(discard_label); OpKill(); - AddLabel(skip_label); + AddLabel(true_label); } void PreExit() { From d52ee6d0a7e5e588e57603d7a62604ba6f58db83 Mon Sep 17 00:00:00 2001 From: ameerj Date: Wed, 25 Nov 2020 14:46:08 -0500 Subject: [PATCH 4/4] cleanup unneeded comments and newlines --- src/video_core/renderer_vulkan/vk_shader_decompiler.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index d6685cd122..1c52f40bbe 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -2083,16 +2083,12 @@ private: case Compare::LessOld: return OpFOrdLessThan(t_bool, operand_1, operand_2); case Compare::EqualOld: - // Note: not accurate when tested against a unit test - // TODO: confirm if used by games return OpFOrdEqual(t_bool, operand_1, operand_2); case Compare::LessEqualOld: return OpFOrdLessThanEqual(t_bool, operand_1, operand_2); case Compare::GreaterOld: return OpFOrdGreaterThan(t_bool, operand_1, operand_2); case Compare::NotEqualOld: - // Note: not accurate when tested against a unit test - // TODO: confirm if used by games return OpFOrdNotEqual(t_bool, operand_1, operand_2); case Compare::GreaterEqualOld: return OpFOrdGreaterThanEqual(t_bool, operand_1, operand_2); @@ -2105,12 +2101,10 @@ private: if (specialization.alpha_test_func == Maxwell::ComparisonOp::AlwaysOld) { return; } - const Id true_label = OpLabel(); const Id discard_label = OpLabel(); const Id alpha_reference = Constant(t_float, specialization.alpha_test_ref); const Id alpha_value = OpLoad(t_float, pointer); - const Id condition = MaxwellToSpirvComparison(specialization.alpha_test_func, alpha_value, alpha_reference);