Parcourir la source

Merge pull request #822 from bunnei/pica-improvements

Pica improvements
bunnei il y a 11 ans
Parent
commit
8852fc6a87

+ 38 - 32
src/video_core/command_processor.cpp

@@ -56,7 +56,17 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
         // Trigger IRQ
         case PICA_REG_INDEX(trigger_irq):
             GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::P3D);
-            return;
+            break;
+
+        case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[0], 0x23c):
+        case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[1], 0x23d):
+        {
+            unsigned index = id - PICA_REG_INDEX(command_buffer.trigger[0]);
+            u32* head_ptr = (u32*)Memory::GetPhysicalPointer(regs.command_buffer.GetPhysicalAddress(index));
+            g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = head_ptr;
+            g_state.cmd_list.length = regs.command_buffer.GetSize(index) / sizeof(u32);
+            break;
+        }
 
         // It seems like these trigger vertex rendering
         case PICA_REG_INDEX(trigger_draw):
@@ -363,38 +373,34 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
         g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast<void*>(&id));
 }
 
-static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) {
-    const CommandHeader& header = *(const CommandHeader*)(&first_command_word[1]);
-
-    u32* read_pointer = (u32*)first_command_word;
-
-    const u32 write_mask = ((header.parameter_mask & 0x1) ? (0xFFu <<  0) : 0u) |
-                           ((header.parameter_mask & 0x2) ? (0xFFu <<  8) : 0u) |
-                           ((header.parameter_mask & 0x4) ? (0xFFu << 16) : 0u) |
-                           ((header.parameter_mask & 0x8) ? (0xFFu << 24) : 0u);
-
-    WritePicaReg(header.cmd_id, *read_pointer, write_mask);
-    read_pointer += 2;
-
-    for (unsigned int i = 1; i < 1+header.extra_data_length; ++i) {
-        u32 cmd = header.cmd_id + ((header.group_commands) ? i : 0);
-        WritePicaReg(cmd, *read_pointer, write_mask);
-        ++read_pointer;
-    }
-
-    // align read pointer to 8 bytes
-    if ((first_command_word - read_pointer) % 2)
-        ++read_pointer;
-
-    return read_pointer - first_command_word;
-}
-
 void ProcessCommandList(const u32* list, u32 size) {
-    u32* read_pointer = (u32*)list;
-    u32 list_length = size / sizeof(u32);
-
-    while (read_pointer < list + list_length) {
-        read_pointer += ExecuteCommandBlock(read_pointer);
+    g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = list;
+    g_state.cmd_list.length = size / sizeof(u32);
+
+    while (g_state.cmd_list.current_ptr < g_state.cmd_list.head_ptr + g_state.cmd_list.length) {
+        // Expand a 4-bit mask to 4-byte mask, e.g. 0b0101 -> 0x00FF00FF
+        static const u32 expand_bits_to_bytes[] = {
+            0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff,
+            0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff,
+            0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff,
+            0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff
+        };
+
+        // Align read pointer to 8 bytes
+        if ((g_state.cmd_list.head_ptr - g_state.cmd_list.current_ptr) % 2 != 0)
+            ++g_state.cmd_list.current_ptr;
+
+        u32 value = *g_state.cmd_list.current_ptr++;
+        const CommandHeader header = { *g_state.cmd_list.current_ptr++ };
+        const u32 write_mask = expand_bits_to_bytes[header.parameter_mask];
+        u32 cmd = header.cmd_id;
+
+        WritePicaReg(cmd, value, write_mask);
+
+        for (unsigned i = 0; i < header.extra_data_length; ++i) {
+            u32 cmd = header.cmd_id + (header.group_commands ? i + 1 : 0);
+            WritePicaReg(cmd, *g_state.cmd_list.current_ptr++, write_mask);
+         }
     }
 }
 

+ 60 - 18
src/video_core/pica.h

@@ -162,6 +162,25 @@ struct Regs {
         ETC1A4       = 13,  // compressed
     };
 
+    enum class LogicOp : u32 {
+        Clear        =  0,
+        And          =  1,
+        AndReverse   =  2,
+        Copy         =  3,
+        Set          =  4,
+        CopyInverted =  5,
+        NoOp         =  6,
+        Invert       =  7,
+        Nand         =  8,
+        Or           =  9,
+        Nor          = 10,
+        Xor          = 11,
+        Equiv        = 12,
+        AndInverted  = 13,
+        OrReverse    = 14,
+        OrInverted   = 15,
+    };
+
     static unsigned NibblesPerPixel(TextureFormat format) {
         switch (format) {
         case TextureFormat::RGBA8:
@@ -221,6 +240,7 @@ struct Regs {
         enum class Source : u32 {
             PrimaryColor           = 0x0,
             PrimaryFragmentColor   = 0x1,
+            SecondaryFragmentColor = 0x2,
 
             Texture0               = 0x3,
             Texture1               = 0x4,
@@ -413,12 +433,8 @@ struct Regs {
         } alpha_blending;
 
         union {
-            enum Op {
-                Set = 4,
-            };
-
-            BitField<0, 4, Op> op;
-        } logic_op;
+            BitField<0, 4, LogicOp> logic_op;
+        };
 
         union {
             BitField< 0, 8, u32> r;
@@ -708,7 +724,33 @@ struct Regs {
         u32 set_value[3];
     } vs_default_attributes_setup;
 
-    INSERT_PADDING_WORDS(0x28);
+    INSERT_PADDING_WORDS(0x2);
+
+    struct {
+        // There are two channels that can be used to configure the next command buffer, which
+        // can be then executed by writing to the "trigger" registers. There are two reasons why a
+        // game might use this feature:
+        //  1) With this, an arbitrary number of additional command buffers may be executed in
+        //     sequence without requiring any intervention of the CPU after the initial one is
+        //     kicked off.
+        //  2) Games can configure these registers to provide a command list subroutine mechanism.
+
+        BitField< 0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer
+        BitField< 0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer
+        u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to
+
+        unsigned GetSize(unsigned index) const {
+            ASSERT(index < 2);
+            return 8 * size[index];
+        }
+
+        PAddr GetPhysicalAddress(unsigned index) const {
+            ASSERT(index < 2);
+            return (PAddr)(8 * addr[index]);
+        }
+    } command_buffer;
+
+    INSERT_PADDING_WORDS(0x20);
 
     enum class TriangleTopology : u32 {
         List        = 0,
@@ -861,6 +903,7 @@ struct Regs {
         ADD_FIELD(trigger_draw);
         ADD_FIELD(trigger_draw_indexed);
         ADD_FIELD(vs_default_attributes_setup);
+        ADD_FIELD(command_buffer);
         ADD_FIELD(triangle_topology);
         ADD_FIELD(vs_bool_uniforms);
         ADD_FIELD(vs_int_uniforms);
@@ -938,6 +981,7 @@ ASSERT_REG_POSITION(num_vertices, 0x228);
 ASSERT_REG_POSITION(trigger_draw, 0x22e);
 ASSERT_REG_POSITION(trigger_draw_indexed, 0x22f);
 ASSERT_REG_POSITION(vs_default_attributes_setup, 0x232);
+ASSERT_REG_POSITION(command_buffer, 0x238);
 ASSERT_REG_POSITION(triangle_topology, 0x25e);
 ASSERT_REG_POSITION(vs_bool_uniforms, 0x2b0);
 ASSERT_REG_POSITION(vs_int_uniforms, 0x2b1);
@@ -1053,21 +1097,12 @@ private:
     float value;
 };
 
-union CommandHeader {
-    CommandHeader(u32 h) : hex(h) {}
-
-    u32 hex;
-
-    BitField< 0, 16, u32> cmd_id;
-    BitField<16,  4, u32> parameter_mask;
-    BitField<20, 11, u32> extra_data_length;
-    BitField<31,  1, u32> group_commands;
-};
-
 /// Struct used to describe current Pica state
 struct State {
+    /// Pica registers
     Regs regs;
 
+    /// Vertex shader memory
     struct {
         struct {
             Math::Vec4<float24> f[96];
@@ -1080,6 +1115,13 @@ struct State {
         std::array<u32, 1024> program_code;
         std::array<u32, 1024> swizzle_data;
     } vs;
+
+    /// Current Pica command list
+    struct {
+        const u32* head_ptr;
+        const u32* current_ptr;
+        u32 length;
+    } cmd_list;
 };
 
 /// Initialize Pica state

+ 71 - 5
src/video_core/rasterizer.cpp

@@ -402,11 +402,16 @@ static void ProcessTriangleInternal(const VertexShader::OutputVertex& v0,
 
                 auto GetSource = [&](Source source) -> Math::Vec4<u8> {
                     switch (source) {
-                    // TODO: What's the difference between these two?
                     case Source::PrimaryColor:
+
+                    // HACK: Until we implement fragment lighting, use primary_color
                     case Source::PrimaryFragmentColor:
                         return primary_color;
 
+                    // HACK: Until we implement fragment lighting, use zero
+                    case Source::SecondaryFragmentColor:
+                        return {0, 0, 0, 0};
+
                     case Source::Texture0:
                         return texture_color[0];
 
@@ -570,6 +575,13 @@ static void ProcessTriangleInternal(const VertexShader::OutputVertex& v0,
                     case Operation::Add:
                         return std::min(255, input[0] + input[1]);
 
+                    case Operation::AddSigned:
+                    {
+                        // TODO(bunnei): Verify that the color conversion from (float) 0.5f to (byte) 128 is correct
+                        auto result = static_cast<int>(input[0]) + static_cast<int>(input[1]) - 128;
+                        return static_cast<u8>(MathUtil::Clamp<int>(result, 0, 255));
+                    }
+
                     case Operation::Lerp:
                         return (input[0] * input[2] + input[1] * (255 - input[2])) / 255;
 
@@ -808,10 +820,9 @@ static void ProcessTriangleInternal(const VertexShader::OutputVertex& v0,
                     }
                 };
 
-                using BlendEquation = Regs::BlendEquation;
                 static auto EvaluateBlendEquation = [](const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor,
                                                        const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor,
-                                                       BlendEquation equation) {
+                                                       Regs::BlendEquation equation) {
                     Math::Vec4<int> result;
 
                     auto src_result = (src  *  srcfactor).Cast<int>();
@@ -866,8 +877,63 @@ static void ProcessTriangleInternal(const VertexShader::OutputVertex& v0,
                 blend_output     = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_rgb);
                 blend_output.a() = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_a).a();
             } else {
-                LOG_CRITICAL(HW_GPU, "logic op: %x", output_merger.logic_op);
-                UNIMPLEMENTED();
+                static auto LogicOp = [](u8 src, u8 dest, Regs::LogicOp op) -> u8 {
+                    switch (op) {
+                    case Regs::LogicOp::Clear:
+                        return 0;
+
+                    case Regs::LogicOp::And:
+                        return src & dest;
+
+                    case Regs::LogicOp::AndReverse:
+                        return src & ~dest;
+
+                    case Regs::LogicOp::Copy:
+                        return src;
+
+                    case Regs::LogicOp::Set:
+                        return 255;
+
+                    case Regs::LogicOp::CopyInverted:
+                        return ~src;
+
+                    case Regs::LogicOp::NoOp:
+                        return dest;
+
+                    case Regs::LogicOp::Invert:
+                        return ~dest;
+
+                    case Regs::LogicOp::Nand:
+                        return ~(src & dest);
+
+                    case Regs::LogicOp::Or:
+                        return src | dest;
+
+                    case Regs::LogicOp::Nor:
+                        return ~(src | dest);
+
+                    case Regs::LogicOp::Xor:
+                        return src ^ dest;
+
+                    case Regs::LogicOp::Equiv:
+                        return ~(src ^ dest);
+
+                    case Regs::LogicOp::AndInverted:
+                        return ~src & dest;
+
+                    case Regs::LogicOp::OrReverse:
+                        return src | ~dest;
+
+                    case Regs::LogicOp::OrInverted:
+                        return ~src | dest;
+                    }
+                };
+
+                blend_output = Math::MakeVec(
+                    LogicOp(combiner_output.r(), dest.r(), output_merger.logic_op),
+                    LogicOp(combiner_output.g(), dest.g(), output_merger.logic_op),
+                    LogicOp(combiner_output.b(), dest.b(), output_merger.logic_op),
+                    LogicOp(combiner_output.a(), dest.a(), output_merger.logic_op));
             }
 
             const Math::Vec4<u8> result = {

+ 10 - 0
src/video_core/renderer_opengl/gl_rasterizer.cpp

@@ -135,6 +135,7 @@ void RasterizerOpenGL::Reset() {
     SyncBlendFuncs();
     SyncBlendColor();
     SyncAlphaTest();
+    SyncLogicOp();
     SyncStencilTest();
     SyncDepthTest();
 
@@ -249,6 +250,11 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
         SyncDepthTest();
         break;
 
+    // Logic op
+    case PICA_REG_INDEX(output_merger.logic_op):
+        SyncLogicOp();
+        break;
+
     // TEV stage 0
     case PICA_REG_INDEX(tev_stage0.color_source1):
         SyncTevSources(0, regs.tev_stage0);
@@ -633,6 +639,10 @@ void RasterizerOpenGL::SyncAlphaTest() {
     glUniform1f(uniform_alphatest_ref, regs.output_merger.alpha_test.ref / 255.0f);
 }
 
+void RasterizerOpenGL::SyncLogicOp() {
+    state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.output_merger.logic_op);
+}
+
 void RasterizerOpenGL::SyncStencilTest() {
     // TODO: Implement stencil test, mask, and op
 }

+ 3 - 0
src/video_core/renderer_opengl/gl_rasterizer.h

@@ -125,6 +125,9 @@ private:
     /// Syncs the alpha test states to match the PICA register
     void SyncAlphaTest();
 
+    /// Syncs the logic op states to match the PICA register
+    void SyncLogicOp();
+
     /// Syncs the stencil test states to match the PICA register
     void SyncStencilTest();
 

+ 14 - 10
src/video_core/renderer_opengl/gl_shaders.h

@@ -69,15 +69,16 @@ const char g_fragment_shader_hw[] = R"(
 #define NUM_VTX_ATTR 7
 #define NUM_TEV_STAGES 6
 
-#define SOURCE_PRIMARYCOLOR         0x0
-#define SOURCE_PRIMARYFRAGMENTCOLOR 0x1
-#define SOURCE_TEXTURE0             0x3
-#define SOURCE_TEXTURE1             0x4
-#define SOURCE_TEXTURE2             0x5
-#define SOURCE_TEXTURE3             0x6
-#define SOURCE_PREVIOUSBUFFER       0xd
-#define SOURCE_CONSTANT             0xe
-#define SOURCE_PREVIOUS             0xf
+#define SOURCE_PRIMARYCOLOR           0x0
+#define SOURCE_PRIMARYFRAGMENTCOLOR   0x1
+#define SOURCE_SECONDARYFRAGMENTCOLOR 0x2
+#define SOURCE_TEXTURE0               0x3
+#define SOURCE_TEXTURE1               0x4
+#define SOURCE_TEXTURE2               0x5
+#define SOURCE_TEXTURE3               0x6
+#define SOURCE_PREVIOUSBUFFER         0xd
+#define SOURCE_CONSTANT               0xe
+#define SOURCE_PREVIOUS               0xf
 
 #define COLORMODIFIER_SOURCECOLOR         0x0
 #define COLORMODIFIER_ONEMINUSSOURCECOLOR 0x1
@@ -151,8 +152,11 @@ vec4 GetSource(int source) {
     if (source == SOURCE_PRIMARYCOLOR) {
         return o[2];
     } else if (source == SOURCE_PRIMARYFRAGMENTCOLOR) {
-        // HACK: Uses color value, but should really use fragment lighting output
+        // HACK: Until we implement fragment lighting, use primary_color
         return o[2];
+    } else if (source == SOURCE_SECONDARYFRAGMENTCOLOR) {
+        // HACK: Until we implement fragment lighting, use zero
+        return vec4(0.0, 0.0, 0.0, 0.0);
     } else if (source == SOURCE_TEXTURE0) {
         return texture(tex[0], o[3].xy);
     } else if (source == SOURCE_TEXTURE1) {

+ 11 - 0
src/video_core/renderer_opengl/gl_state.cpp

@@ -32,6 +32,8 @@ OpenGLState::OpenGLState() {
     blend.color.blue = 0.0f;
     blend.color.alpha = 0.0f;
 
+    logic_op = GL_COPY;
+
     for (auto& texture_unit : texture_units) {
         texture_unit.enabled_2d = false;
         texture_unit.texture_2d = 0;
@@ -99,8 +101,13 @@ void OpenGLState::Apply() {
     if (blend.enabled != cur_state.blend.enabled) {
         if (blend.enabled) {
             glEnable(GL_BLEND);
+
+            cur_state.logic_op = GL_COPY;
+            glLogicOp(cur_state.logic_op);
+            glDisable(GL_COLOR_LOGIC_OP);
         } else {
             glDisable(GL_BLEND);
+            glEnable(GL_COLOR_LOGIC_OP);
         }
     }
 
@@ -118,6 +125,10 @@ void OpenGLState::Apply() {
         glBlendFuncSeparate(blend.src_rgb_func, blend.dst_rgb_func, blend.src_a_func, blend.dst_a_func);
     }
 
+    if (logic_op != cur_state.logic_op) {
+        glLogicOp(logic_op);
+    }
+
     // Textures
     for (unsigned texture_index = 0; texture_index < ARRAY_SIZE(texture_units); ++texture_index) {
         if (texture_units[texture_index].enabled_2d != cur_state.texture_units[texture_index].enabled_2d) {

+ 2 - 0
src/video_core/renderer_opengl/gl_state.h

@@ -42,6 +42,8 @@ public:
         } color; // GL_BLEND_COLOR
     } blend;
 
+    GLenum logic_op; // GL_LOGIC_OP_MODE
+
     // 3 texture units - one for each that is used in PICA fragment shader emulation
     struct {
         bool enabled_2d; // GL_TEXTURE_2D

+ 31 - 0
src/video_core/renderer_opengl/pica_to_gl.h

@@ -71,6 +71,37 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) {
     return blend_func_table[(unsigned)factor];
 }
 
+inline GLenum LogicOp(Pica::Regs::LogicOp op) {
+    static const GLenum logic_op_table[] = {
+        GL_CLEAR,           // Clear
+        GL_AND,             // And
+        GL_AND_REVERSE,     // AndReverse
+        GL_COPY,            // Copy
+        GL_SET,             // Set
+        GL_COPY_INVERTED,   // CopyInverted
+        GL_NOOP,            // NoOp
+        GL_INVERT,          // Invert
+        GL_NAND,            // Nand
+        GL_OR,              // Or
+        GL_NOR,             // Nor
+        GL_XOR,             // Xor
+        GL_EQUIV,           // Equiv
+        GL_AND_INVERTED,    // AndInverted
+        GL_OR_REVERSE,      // OrReverse
+        GL_OR_INVERTED,     // OrInverted
+    };
+
+    // Range check table for input
+    if ((unsigned)op >= ARRAY_SIZE(logic_op_table)) {
+        LOG_CRITICAL(Render_OpenGL, "Unknown logic op %d", op);
+        UNREACHABLE();
+
+        return GL_COPY;
+    }
+
+    return logic_op_table[(unsigned)op];
+}
+
 inline GLenum CompareFunc(Pica::Regs::CompareFunc func) {
     static const GLenum compare_func_table[] = {
         GL_NEVER,    // CompareFunc::Never

+ 22 - 7
src/video_core/vertex_shader.cpp

@@ -119,17 +119,13 @@ static void ProcessShaderCode(VertexShaderState& state) {
         switch (instr.opcode.Value().GetInfo().type) {
         case OpCode::Type::Arithmetic:
         {
-            bool is_inverted = 0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed);
-            // TODO: We don't really support this properly: For instance, the address register
-            //       offset needs to be applied to SRC2 instead, etc.
-            //       For now, we just abort in this situation.
-            ASSERT_MSG(!is_inverted, "Bad condition...");
+            const bool is_inverted = (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed));
 
             const int address_offset = (instr.common.address_register_index == 0)
                                        ? 0 : state.address_registers[instr.common.address_register_index - 1];
 
-            const float24* src1_ = LookupSourceRegister(instr.common.GetSrc1(is_inverted) + address_offset);
-            const float24* src2_ = LookupSourceRegister(instr.common.GetSrc2(is_inverted));
+            const float24* src1_ = LookupSourceRegister(instr.common.GetSrc1(is_inverted) + (!is_inverted * address_offset));
+            const float24* src2_ = LookupSourceRegister(instr.common.GetSrc2(is_inverted) + ( is_inverted * address_offset));
 
             const bool negate_src1 = ((bool)swizzle.negate_src1 != false);
             const bool negate_src2 = ((bool)swizzle.negate_src2 != false);
@@ -208,6 +204,15 @@ static void ProcessShaderCode(VertexShaderState& state) {
                 }
                 break;
 
+            case OpCode::Id::MIN:
+                for (int i = 0; i < 4; ++i) {
+                    if (!swizzle.DestComponentEnabled(i))
+                        continue;
+
+                    dest[i] = std::min(src1[i], src2[i]);
+                }
+                break;
+
             case OpCode::Id::DP3:
             case OpCode::Id::DP4:
             {
@@ -279,6 +284,16 @@ static void ProcessShaderCode(VertexShaderState& state) {
                 break;
             }
 
+            case OpCode::Id::SLT:
+            case OpCode::Id::SLTI:
+                for (int i = 0; i < 4; ++i) {
+                    if (!swizzle.DestComponentEnabled(i))
+                        continue;
+
+                    dest[i] = (src1[i] < src2[i]) ? float24::FromFloat32(1.0f) : float24::FromFloat32(0.0f);
+                }
+                break;
+
             case OpCode::Id::CMP:
                 for (int i = 0; i < 2; ++i) {
                     // TODO: Can you restrict to one compare via dest masking?