From 27cab6477e7e72771d0661418d71cce3c2721723 Mon Sep 17 00:00:00 2001
From: Tony Wasserka <NeoBrainX@gmail.com>
Date: Fri, 15 Aug 2014 16:33:17 +0200
Subject: [PATCH] Pica/Rasterizer: Add initial implementation of texture
 combiners.

---
 src/video_core/pica.h         | 105 ++++++++++++++++++++++++++++-
 src/video_core/rasterizer.cpp | 122 +++++++++++++++++++++++++++++++++-
 2 files changed, 225 insertions(+), 2 deletions(-)

diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index f288615b8..7bd4388b5 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <array>
 #include <cstddef>
 #include <initializer_list>
 #include <map>
@@ -133,7 +134,97 @@ struct Regs {
     INSERT_PADDING_WORDS(0x8);
     BitField<0, 4, TextureFormat> texture0_format;
 
-    INSERT_PADDING_WORDS(0x81);
+    INSERT_PADDING_WORDS(0x31);
+
+    // 0xc0-0xff: Texture Combiner (akin to glTexEnv)
+    struct TevStageConfig {
+        enum class Source : u32 {
+            PrimaryColor           = 0x0,
+            Texture0               = 0x3,
+            Texture1               = 0x4,
+            Texture2               = 0x5,
+            Texture3               = 0x6,
+            // 0x7-0xc = primary color??
+            Constant               = 0xe,
+            Previous               = 0xf,
+        };
+
+        enum class ColorModifier : u32 {
+            SourceColor         = 0,
+            OneMinusSourceColor = 1,
+            SourceAlpha         = 2,
+            OneMinusSourceAlpha = 3,
+
+            // Other values seem to be non-standard extensions
+        };
+
+        enum class AlphaModifier : u32 {
+            SourceAlpha         = 0,
+            OneMinusSourceAlpha = 1,
+
+            // Other values seem to be non-standard extensions
+        };
+
+        enum class Operation : u32 {
+            Replace         = 0,
+            Modulate        = 1,
+            Add             = 2,
+            AddSigned       = 3,
+            Lerp            = 4,
+            Subtract        = 5,
+        };
+
+        union {
+            BitField< 0, 4, Source> color_source1;
+            BitField< 4, 4, Source> color_source2;
+            BitField< 8, 4, Source> color_source3;
+            BitField<16, 4, Source> alpha_source1;
+            BitField<20, 4, Source> alpha_source2;
+            BitField<24, 4, Source> alpha_source3;
+        };
+
+        union {
+            BitField< 0, 4, ColorModifier> color_modifier1;
+            BitField< 4, 4, ColorModifier> color_modifier2;
+            BitField< 8, 4, ColorModifier> color_modifier3;
+            BitField<12, 3, AlphaModifier> alpha_modifier1;
+            BitField<16, 3, AlphaModifier> alpha_modifier2;
+            BitField<20, 3, AlphaModifier> alpha_modifier3;
+        };
+
+        union {
+            BitField< 0, 4, Operation> color_op;
+            BitField<16, 4, Operation> alpha_op;
+        };
+
+        union {
+            BitField< 0, 8, u32> const_r;
+            BitField< 8, 8, u32> const_g;
+            BitField<16, 8, u32> const_b;
+            BitField<24, 8, u32> const_a;
+        };
+
+        INSERT_PADDING_WORDS(0x1);
+    };
+
+    TevStageConfig tev_stage0;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage1;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage2;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage3;
+    INSERT_PADDING_WORDS(0x13);
+    TevStageConfig tev_stage4;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage5;
+    INSERT_PADDING_WORDS(0x13);
+
+    const std::array<Regs::TevStageConfig,6> GetTevStages() const {
+        return { tev_stage0, tev_stage1,
+                 tev_stage2, tev_stage3,
+                 tev_stage4, tev_stage5 };
+    };
 
     struct {
         enum ColorFormat : u32 {
@@ -444,6 +535,12 @@ struct Regs {
         ADD_FIELD(viewport_corner);
         ADD_FIELD(texture0);
         ADD_FIELD(texture0_format);
+        ADD_FIELD(tev_stage0);
+        ADD_FIELD(tev_stage1);
+        ADD_FIELD(tev_stage2);
+        ADD_FIELD(tev_stage3);
+        ADD_FIELD(tev_stage4);
+        ADD_FIELD(tev_stage5);
         ADD_FIELD(framebuffer);
         ADD_FIELD(vertex_attributes);
         ADD_FIELD(index_array);
@@ -503,6 +600,12 @@ ASSERT_REG_POSITION(vs_output_attributes[1], 0x51);
 ASSERT_REG_POSITION(viewport_corner, 0x68);
 ASSERT_REG_POSITION(texture0, 0x81);
 ASSERT_REG_POSITION(texture0_format, 0x8e);
+ASSERT_REG_POSITION(tev_stage0, 0xc0);
+ASSERT_REG_POSITION(tev_stage1, 0xc8);
+ASSERT_REG_POSITION(tev_stage2, 0xd0);
+ASSERT_REG_POSITION(tev_stage3, 0xd8);
+ASSERT_REG_POSITION(tev_stage4, 0xf0);
+ASSERT_REG_POSITION(tev_stage5, 0xf8);
 ASSERT_REG_POSITION(framebuffer, 0x110);
 ASSERT_REG_POSITION(vertex_attributes, 0x200);
 ASSERT_REG_POSITION(index_array, 0x227);
diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp
index f418518a1..5a4155c84 100644
--- a/src/video_core/rasterizer.cpp
+++ b/src/video_core/rasterizer.cpp
@@ -165,12 +165,132 @@ void ProcessTriangle(const VertexShader::OutputVertex& v0,
                 (u8)(GetInterpolatedAttribute(v0.color.a(), v1.color.a(), v2.color.a()).ToFloat32() * 255)
             };
 
+            // Texture environment - consists of 6 stages of color and alpha combining.
+            //
+            // Color combiners take three input color values from some source (e.g. interpolated
+            // vertex color, texture color, previous stage, etc), perform some very simple
+            // operations on each of them (e.g. inversion) and then calculate the output color
+            // with some basic arithmetic. Alpha combiners can be configured separately but work
+            // analogously.
+            Math::Vec4<u8> combiner_output;
+            for (auto tev_stage : registers.GetTevStages()) {
+                using Source = Regs::TevStageConfig::Source;
+                using ColorModifier = Regs::TevStageConfig::ColorModifier;
+                using AlphaModifier = Regs::TevStageConfig::AlphaModifier;
+                using Operation = Regs::TevStageConfig::Operation;
+
+                auto GetColorSource = [&](Source source) -> Math::Vec3<u8> {
+                    switch (source) {
+                    case Source::PrimaryColor:
+                        return primary_color.rgb();
+
+                    case Source::Constant:
+                        return {tev_stage.const_r, tev_stage.const_g, tev_stage.const_b};
+
+                    case Source::Previous:
+                        return combiner_output.rgb();
+
+                    default:
+                        ERROR_LOG(GPU, "Unknown color combiner source %d\n", (int)source);
+                        return {};
+                    }
+                };
+
+                auto GetAlphaSource = [&](Source source) -> u8 {
+                    switch (source) {
+                    case Source::PrimaryColor:
+                        return primary_color.a();
+
+                    case Source::Constant:
+                        return tev_stage.const_a;
+
+                    case Source::Previous:
+                        return combiner_output.a();
+
+                    default:
+                        ERROR_LOG(GPU, "Unknown alpha combiner source %d\n", (int)source);
+                        return 0;
+                    }
+                };
+
+                auto GetColorModifier = [](ColorModifier factor, const Math::Vec3<u8>& values) -> Math::Vec3<u8> {
+                    switch (factor)
+                    {
+                    case ColorModifier::SourceColor:
+                        return values;
+                    default:
+                        ERROR_LOG(GPU, "Unknown color factor %d\n", (int)factor);
+                        return {};
+                    }
+                };
+
+                auto GetAlphaModifier = [](AlphaModifier factor, u8 value) -> u8 {
+                    switch (factor) {
+                    case AlphaModifier::SourceAlpha:
+                        return value;
+                    default:
+                        ERROR_LOG(GPU, "Unknown color factor %d\n", (int)factor);
+                        return 0;
+                    }
+                };
+
+                auto ColorCombine = [](Operation op, const Math::Vec3<u8> input[3]) -> Math::Vec3<u8> {
+                    switch (op) {
+                    case Operation::Replace:
+                        return input[0];
+
+                    case Operation::Modulate:
+                        return ((input[0] * input[1]) / 255).Cast<u8>();
+
+                    default:
+                        ERROR_LOG(GPU, "Unknown color combiner operation %d\n", (int)op);
+                        return {};
+                    }
+                };
+
+                auto AlphaCombine = [](Operation op, const std::array<u8,3>& input) -> u8 {
+                    switch (op) {
+                    case Operation::Replace:
+                        return input[0];
+
+                    case Operation::Modulate:
+                        return input[0] * input[1] / 255;
+
+                    default:
+                        ERROR_LOG(GPU, "Unknown alpha combiner operation %d\n", (int)op);
+                        return 0;
+                    }
+                };
+
+                // color combiner
+                // NOTE: Not sure if the alpha combiner might use the color output of the previous
+                //       stage as input. Hence, we currently don't directly write the result to
+                //       combiner_output.rgb(), but instead store it in a temporary variable until
+                //       alpha combining has been done.
+                Math::Vec3<u8> color_result[3] = {
+                    GetColorModifier(tev_stage.color_modifier1, GetColorSource(tev_stage.color_source1)),
+                    GetColorModifier(tev_stage.color_modifier2, GetColorSource(tev_stage.color_source2)),
+                    GetColorModifier(tev_stage.color_modifier3, GetColorSource(tev_stage.color_source3))
+                };
+                auto color_output = ColorCombine(tev_stage.color_op, color_result);
+
+                // alpha combiner
+                std::array<u8,3> alpha_result = {
+                    GetAlphaModifier(tev_stage.alpha_modifier1, GetAlphaSource(tev_stage.alpha_source1)),
+                    GetAlphaModifier(tev_stage.alpha_modifier2, GetAlphaSource(tev_stage.alpha_source2)),
+                    GetAlphaModifier(tev_stage.alpha_modifier3, GetAlphaSource(tev_stage.alpha_source3))
+                };
+                auto alpha_output = AlphaCombine(tev_stage.alpha_op, alpha_result);
+
+                combiner_output = Math::MakeVec(color_output, alpha_output);
+            }
+
             u16 z = (u16)(((float)v0.screenpos[2].ToFloat32() * w0 +
                            (float)v1.screenpos[2].ToFloat32() * w1 +
                            (float)v2.screenpos[2].ToFloat32() * w2) * 65535.f / wsum); // TODO: Shouldn't need to multiply by 65536?
             SetDepth(x >> 4, y >> 4, z);
 
-            DrawPixel(x >> 4, y >> 4, primary_color);
+            DrawPixel(x >> 4, y >> 4, combiner_output);
         }
     }
 }