diff --git a/Source/Core/Common/EnumFormatter.h b/Source/Core/Common/EnumFormatter.h index 1ab6bbeadd..26cc910438 100644 --- a/Source/Core/Common/EnumFormatter.h +++ b/Source/Core/Common/EnumFormatter.h @@ -55,9 +55,9 @@ public: constexpr auto parse(fmt::format_parse_context& ctx) { auto it = ctx.begin(), end = ctx.end(); - // 'u' for user display, 's' for shader generation - if (it != end && (*it == 'u' || *it == 's')) - formatting_for_shader = (*it++ == 's'); + // 'u' for user display, 's' for shader generation, 'n' for name only + if (it != end && (*it == 'u' || *it == 's' || *it == 'n')) + format_type = *it++; return it; } @@ -68,19 +68,24 @@ public: const auto value_u = static_cast>(value_s); // Always unsigned const bool has_name = m_names.InBounds(e) && m_names[e] != nullptr; - if (!formatting_for_shader) + switch (format_type) { + default: + case 'u': if (has_name) return fmt::format_to(ctx.out(), "{} ({})", m_names[e], value_s); else return fmt::format_to(ctx.out(), "Invalid ({})", value_s); - } - else - { + case 's': if (has_name) return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value_u, m_names[e]); else return fmt::format_to(ctx.out(), "{:#x}u /* Invalid */", value_u); + case 'n': + if (has_name) + return fmt::format_to(ctx.out(), "{}", m_names[e]); + else + return fmt::format_to(ctx.out(), "Invalid ({})", value_s); } } @@ -92,5 +97,5 @@ protected: private: const array_type m_names; - bool formatting_for_shader = false; + char format_type = 'u'; }; diff --git a/Source/Core/VideoCommon/BPMemory.h b/Source/Core/VideoCommon/BPMemory.h index 97dc5993bd..f5009875e7 100644 --- a/Source/Core/VideoCommon/BPMemory.h +++ b/Source/Core/VideoCommon/BPMemory.h @@ -258,7 +258,7 @@ enum class TevBias : u32 { Zero = 0, AddHalf = 1, - Subhalf = 2, + SubHalf = 2, Compare = 3 }; template <> @@ -491,6 +491,94 @@ struct fmt::formatter template auto format(const TevStageCombiner::ColorCombiner& cc, FormatContext& ctx) { + auto out = ctx.out(); + if (cc.bias != TevBias::Compare) + { + // Generate an equation view, simplifying out addition of zero and multiplication by 1 + // dest = (d (OP) ((1 - c)*a + c*b) + bias) * scale + // or equivalently and more readably when the terms are not constants: + // dest = (d (OP) lerp(a, b, c) + bias) * scale + // Note that lerping is more complex than the first form shows; see PixelShaderGen's + // WriteTevRegular for more details. + + static constexpr Common::EnumMap alt_names = { + "prev.rgb", "prev.aaa", "c0.rgb", "c0.aaa", "c1.rgb", "c1.aaa", "c2.rgb", "c2.aaa", + "tex.rgb", "tex.aaa", "ras.rgb", "ras.aaa", "1", ".5", "konst.rgb", "0", + }; + + const bool has_d = cc.d != TevColorArg::Zero; + // If c is one, (1 - c) is zero, so (1-c)*a is zero + const bool has_ac = cc.a != TevColorArg::Zero && cc.c != TevColorArg::One; + // If either b or c is zero, b*c is zero + const bool has_bc = cc.b != TevColorArg::Zero && cc.c != TevColorArg::Zero; + const bool has_bias = cc.bias != TevBias::Zero; // != Compare is already known + const bool has_scale = cc.scale != TevScale::Scale1; + + const char op = (cc.op == TevOp::Sub ? '-' : '+'); + + if (cc.dest == TevOutput::Prev) + out = format_to(out, "dest.rgb = "); + else + out = format_to(out, "{:n}.rgb = ", cc.dest); + + if (has_scale) + out = format_to(out, "("); + if (has_d) + out = format_to(out, "{}", alt_names[cc.d]); + if (has_ac || has_bc) + { + if (has_d) + out = format_to(out, " {} ", op); + else if (cc.op == TevOp::Sub) + out = format_to(out, "{}", op); + if (has_ac && has_bc) + { + if (cc.c == TevColorArg::Half) + { + // has_a and has_b imply that c is not Zero or One, and Half is the only remaining + // numeric constant. This results in an average. + out = format_to(out, "({} + {})/2", alt_names[cc.a], alt_names[cc.b]); + } + else + { + out = format_to(out, "lerp({}, {}, {})", alt_names[cc.a], alt_names[cc.b], + alt_names[cc.c]); + } + } + else if (has_ac) + { + if (cc.c == TevColorArg::Zero) + out = format_to(out, "{}", alt_names[cc.a]); + else if (cc.c == TevColorArg::Half) // 1 - .5 is .5 + out = format_to(out, ".5*{}", alt_names[cc.a]); + else + out = format_to(out, "(1 - {})*{}", alt_names[cc.c], alt_names[cc.a]); + } + else // has_bc + { + if (cc.c == TevColorArg::One) + out = format_to(out, "{}", alt_names[cc.b]); + else + out = format_to(out, "{}*{}", alt_names[cc.c], alt_names[cc.b]); + } + } + if (has_bias) + { + if (has_ac || has_bc || has_d) + out = format_to(out, cc.bias == TevBias::AddHalf ? " + .5" : " - .5"); + else + out = format_to(out, cc.bias == TevBias::AddHalf ? ".5" : "-.5"); + } + else + { + // If nothing has been written so far, add a zero + if (!(has_ac || has_bc || has_d)) + out = format_to(out, "0"); + } + if (has_scale) + out = format_to(out, ") * {:n}", cc.scale); + out = format_to(out, "\n\n"); + } return format_to(ctx.out(), "a: {}\n" "b: {}\n" @@ -512,7 +600,80 @@ struct fmt::formatter template auto format(const TevStageCombiner::AlphaCombiner& ac, FormatContext& ctx) { - return format_to(ctx.out(), + auto out = ctx.out(); + if (ac.bias != TevBias::Compare) + { + // Generate an equation view, simplifying out addition of zero and multiplication by 1 + // dest = (d (OP) ((1 - c)*a + c*b) + bias) * scale + // or equivalently and more readably when the terms are not constants: + // dest = (d (OP) lerp(a, b, c) + bias) * scale + // Note that lerping is more complex than the first form shows; see PixelShaderGen's + // WriteTevRegular for more details. + + // We don't need an alt_names map here, unlike the color combiner, as the only special term is + // Zero, and we we filter that out below. However, we do need to append ".a" to all + // parameters, to make it explicit that these are operations on the alpha term instead of the + // 4-element vector. We also need to use the :n specifier so that the numeric ID isn't shown. + + const bool has_d = ac.d != TevAlphaArg::Zero; + // There is no c value for alpha that results in (1 - c) always being zero + const bool has_ac = ac.a != TevAlphaArg::Zero; + // If either b or c is zero, b*c is zero + const bool has_bc = ac.b != TevAlphaArg::Zero && ac.c != TevAlphaArg::Zero; + const bool has_bias = ac.bias != TevBias::Zero; // != Compare is already known + const bool has_scale = ac.scale != TevScale::Scale1; + + const char op = (ac.op == TevOp::Sub ? '-' : '+'); + + if (ac.dest == TevOutput::Prev) + out = format_to(out, "dest.a = "); + else + out = format_to(out, "{:n}.a = ", ac.dest); + + if (has_scale) + out = format_to(out, "("); + if (has_d) + out = format_to(out, "{:n}.a", ac.d); + if (has_ac || has_bc) + { + if (has_d) + out = format_to(out, " {} ", op); + else if (ac.op == TevOp::Sub) + out = format_to(out, "{}", op); + if (has_ac && has_bc) + { + out = format_to(out, "lerp({:n}.a, {:n}.a, {:n}.a)", ac.a, ac.b, ac.c); + } + else if (has_ac) + { + if (ac.c == TevAlphaArg::Zero) + out = format_to(out, "{:n}.a", ac.a); + else + out = format_to(out, "(1 - {:n}.a)*{:n}.a", ac.c, ac.a); + } + else // has_bc + { + out = format_to(out, "{:n}.a*{:n}.a", ac.c, ac.b); + } + } + if (has_bias) + { + if (has_ac || has_bc || has_d) + out = format_to(out, ac.bias == TevBias::AddHalf ? " + .5" : " - .5"); + else + out = format_to(out, ac.bias == TevBias::AddHalf ? ".5" : "-.5"); + } + else + { + // If nothing has been written so far, add a zero + if (!(has_ac || has_bc || has_d)) + out = format_to(out, "0"); + } + if (has_scale) + out = format_to(out, ") * {:n}", ac.scale); + out = format_to(out, "\n\n"); + } + return format_to(out, "a: {}\n" "b: {}\n" "c: {}\n" diff --git a/Source/UnitTests/Common/EnumFormatterTest.cpp b/Source/UnitTests/Common/EnumFormatterTest.cpp index 55e03152ef..793328b0dd 100644 --- a/Source/UnitTests/Common/EnumFormatterTest.cpp +++ b/Source/UnitTests/Common/EnumFormatterTest.cpp @@ -46,6 +46,12 @@ TEST(EnumUtil, Enum1) EXPECT_EQ(fmt::format("{:s}", Enum1::C), "0x2u /* C */"); EXPECT_EQ(fmt::format("{:s}", static_cast(3)), "0x3u /* Invalid */"); EXPECT_EQ(fmt::format("{:s}", static_cast(4)), "0x4u /* Invalid */"); + + EXPECT_EQ(fmt::format("{:n}", Enum1::A), "A"); + EXPECT_EQ(fmt::format("{:n}", Enum1::B), "B"); + EXPECT_EQ(fmt::format("{:n}", Enum1::C), "C"); + EXPECT_EQ(fmt::format("{:n}", static_cast(3)), "Invalid (3)"); + EXPECT_EQ(fmt::format("{:n}", static_cast(4)), "Invalid (4)"); } TEST(EnumUtil, Enum2) @@ -63,4 +69,11 @@ TEST(EnumUtil, Enum2) EXPECT_EQ(fmt::format("{:s}", Enum2::F), "0x3u /* F */"); EXPECT_EQ(fmt::format("{:s}", static_cast(4)), "0x4u /* Invalid */"); EXPECT_EQ(fmt::format("{:s}", static_cast(-1)), "0xffffffffu /* Invalid */"); + + EXPECT_EQ(fmt::format("{:n}", Enum2::D), "D"); + EXPECT_EQ(fmt::format("{:n}", Enum2::E), "E"); + EXPECT_EQ(fmt::format("{:n}", static_cast(2)), "Invalid (2)"); + EXPECT_EQ(fmt::format("{:n}", Enum2::F), "F"); + EXPECT_EQ(fmt::format("{:n}", static_cast(4)), "Invalid (4)"); + EXPECT_EQ(fmt::format("{:n}", static_cast(-1)), "Invalid (-1)"); }