From b5fd35f95145ecc8f88a179229ed69b390eb76be Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Thu, 22 Apr 2021 20:57:56 -0700 Subject: Refactor OpcodeDecoding and FIFO analyzer to use callbacks --- Source/Core/VideoCommon/OpcodeDecoding.h | 222 ++++++++++++++++++++++++++++++- 1 file changed, 221 insertions(+), 1 deletion(-) (limited to 'Source/Core/VideoCommon/OpcodeDecoding.h') diff --git a/Source/Core/VideoCommon/OpcodeDecoding.h b/Source/Core/VideoCommon/OpcodeDecoding.h index df1059f221..2d1632efc3 100644 --- a/Source/Core/VideoCommon/OpcodeDecoding.h +++ b/Source/Core/VideoCommon/OpcodeDecoding.h @@ -3,9 +3,17 @@ #pragma once +#include + +#include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/EnumFormatter.h" +#include "Common/Inline.h" +#include "Common/Swap.h" +#include "VideoCommon/CPMemory.h" +#include "VideoCommon/VertexLoaderBase.h" +struct CPState; class DataReader; namespace OpcodeDecoder @@ -55,8 +63,220 @@ enum class Primitive : u8 void Init(); +// Interface for the Run and RunCommand functions below. +// The functions themselves are templates so that the compiler generates separate versions for each +// callback (with the callback functions inlined), so the callback doesn't actually need to be +// publicly inherited. +// Compilers don't generate warnings for failed inlining with virtual functions, so this define +// allows disabling the use of virtual functions to generate those warnings. However, this means +// that missing functions will generate errors on their use in RunCommand, instead of in the +// subclass, which can be confusing. +#define OPCODE_CALLBACK_USE_INHERITANCE + +#ifdef OPCODE_CALLBACK_USE_INHERITANCE +#define OPCODE_CALLBACK(sig) DOLPHIN_FORCE_INLINE sig override +#define OPCODE_CALLBACK_NOINLINE(sig) sig override +#else +#define OPCODE_CALLBACK(sig) DOLPHIN_FORCE_INLINE sig +#define OPCODE_CALLBACK_NOINLINE(sig) sig +#endif +class Callback +{ +#ifdef OPCODE_CALLBACK_USE_INHERITANCE +public: + virtual ~Callback() = default; + + // Called on any XF command. + virtual void OnXF(u16 address, u8 count, const u8* data) = 0; + // Called on any CP command. + // Subclasses should update the CP state with GetCPState().LoadCPReg(command, value) so that + // primitive commands decode properly. + virtual void OnCP(u8 command, u32 value) = 0; + // Called on any BP command. + virtual void OnBP(u8 command, u32 value) = 0; + // Called on any indexed XF load command. + virtual void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size) = 0; + // Called on any primitive command. + virtual void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat, u32 vertex_size, + u16 num_vertices, const u8* vertex_data) = 0; + // Called on a display list. + virtual void OnDisplayList(u32 address, u32 size) = 0; + // Called on any NOP commands (which are all merged into a single call). + virtual void OnNop(u32 count) = 0; + // Called on an unknown opcode, or an opcode that is known but not implemented. + // data[0] is opcode. + virtual void OnUnknown(u8 opcode, const u8* data) = 0; + + // Called on ANY command. The first byte of data is the opcode. Size will be at least 1. + // This function is called after one of the above functions is called. + virtual void OnCommand(const u8* data, u32 size) = 0; + + // Get the current CP state. Needed for vertex decoding; will also be mutated for CP commands. + virtual CPState& GetCPState() = 0; +#endif +}; + +namespace detail +{ +// Main logic; split so that the main RunCommand can call OnCommand with the returned size. +template >> +static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback) +{ + if (available < 1) + return 0; + + const Opcode cmd = static_cast(data[0]); + + switch (cmd) + { + case Opcode::GX_NOP: + { + u32 count = 1; + while (count < available && static_cast(data[count]) == Opcode::GX_NOP) + count++; + callback.OnNop(count); + return count; + } + + case Opcode::GX_LOAD_CP_REG: + { + if (available < 6) + return 0; + + const u8 cmd2 = data[1]; + const u32 value = Common::swap32(&data[2]); + + callback.OnCP(cmd2, value); + + return 6; + } + + case Opcode::GX_LOAD_XF_REG: + { + if (available < 5) + return 0; + + const u32 cmd2 = Common::swap32(&data[1]); + const u16 base_address = cmd2 & 0xffff; + + const u16 stream_size_temp = cmd2 >> 16; + ASSERT(stream_size_temp < 16); + const u8 stream_size = (stream_size_temp & 0xf) + 1; + + if (available < u32(5 + stream_size * 4)) + return 0; + + callback.OnXF(base_address, stream_size, &data[5]); + + return 5 + stream_size * 4; + } + + case Opcode::GX_LOAD_INDX_A: // Used for position matrices + case Opcode::GX_LOAD_INDX_B: // Used for normal matrices + case Opcode::GX_LOAD_INDX_C: // Used for postmatrices + case Opcode::GX_LOAD_INDX_D: // Used for lights + { + if (available < 5) + return 0; + + const u32 value = Common::swap32(&data[1]); + + const u32 index = value >> 16; + const u16 address = value & 0xFFF; // TODO: check mask + const u8 size = ((value >> 12) & 0xF) + 1; + + // Map the command byte to its ref array. + // GX_LOAD_INDX_A (32 = 8*4) . CPArray::XF_A (4+8 = 12) + // GX_LOAD_INDX_B (40 = 8*5) . CPArray::XF_B (5+8 = 13) + // GX_LOAD_INDX_C (48 = 8*6) . CPArray::XF_C (6+8 = 14) + // GX_LOAD_INDX_D (56 = 8*7) . CPArray::XF_D (7+8 = 15) + const auto ref_array = static_cast((static_cast(cmd) / 8) + 8); + + callback.OnIndexedLoad(ref_array, index, address, size); + return 5; + } + + case Opcode::GX_CMD_CALL_DL: + { + if (available < 9) + return 0; + + const u32 address = Common::swap32(&data[1]); + const u32 size = Common::swap32(&data[5]); + + callback.OnDisplayList(address, size); + return 9; + } + + case Opcode::GX_LOAD_BP_REG: + { + if (available < 5) + return 0; + + const u8 cmd2 = data[1]; + const u32 value = Common::swap24(&data[2]); + + callback.OnBP(cmd2, value); + + return 5; + } + + default: + if (cmd >= Opcode::GX_PRIMITIVE_START && cmd <= Opcode::GX_PRIMITIVE_END) + { + if (available < 3) + return 0; + + const u8 cmdbyte = static_cast(cmd); + const OpcodeDecoder::Primitive primitive = static_cast( + (cmdbyte & OpcodeDecoder::GX_PRIMITIVE_MASK) >> OpcodeDecoder::GX_PRIMITIVE_SHIFT); + const u8 vat = cmdbyte & OpcodeDecoder::GX_VAT_MASK; + + const u32 vertex_size = VertexLoaderBase::GetVertexSize(callback.GetCPState().vtx_desc, + callback.GetCPState().vtx_attr[vat]); + const u16 num_vertices = Common::swap16(&data[1]); + + if (available < 3 + num_vertices * vertex_size) + return 0; + + callback.OnPrimitiveCommand(primitive, vat, vertex_size, num_vertices, &data[3]); + + return 3 + num_vertices * vertex_size; + } + } + + callback.OnUnknown(static_cast(cmd), data); + return 1; +} +} // namespace detail + +template >> +DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback) +{ + const u32 size = detail::RunCommand(data, available, callback); + if (size > 0) + { + callback.OnCommand(data, size); + } + return size; +} + +template >> +DOLPHIN_FORCE_INLINE u32 Run(const u8* data, u32 available, T& callback) +{ + u32 size = 0; + while (size < available) + { + const u32 command_size = RunCommand(&data[size], available - size, callback); + if (command_size == 0) + break; + size += command_size; + } + return size; +} + template -u8* Run(DataReader src, u32* cycles, bool in_display_list); +u8* RunFifo(DataReader src, u32* cycles); } // namespace OpcodeDecoder -- cgit v1.2.3