summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon/OpcodeDecoding.h
blob: bf9b94d218086a9dee49fe7a2faba71eecaa277a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <concepts>

#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
{
// Global flag to signal if FifoRecorder is active.
extern bool g_record_fifo_data;

enum class Opcode
{
  GX_NOP = 0x00,

  GX_LOAD_BP_REG = 0x61,
  GX_LOAD_CP_REG = 0x08,
  GX_LOAD_XF_REG = 0x10,
  GX_LOAD_INDX_A = 0x20,
  GX_LOAD_INDX_B = 0x28,
  GX_LOAD_INDX_C = 0x30,
  GX_LOAD_INDX_D = 0x38,

  GX_CMD_CALL_DL = 0x40,
  GX_CMD_UNKNOWN_METRICS = 0x44,
  GX_CMD_INVL_VC = 0x48,

  GX_PRIMITIVE_START = 0x80,
  GX_PRIMITIVE_END = 0xbf,
};

constexpr u8 GX_PRIMITIVE_MASK = 0x78;
constexpr u32 GX_PRIMITIVE_SHIFT = 3;
constexpr u8 GX_VAT_MASK = 0x07;

// These values are the values extracted using GX_PRIMITIVE_MASK
// and GX_PRIMITIVE_SHIFT.
// GX_DRAW_QUADS_2 behaves the same way as GX_DRAW_QUADS.
enum class Primitive : u8
{
  GX_DRAW_QUADS = 0x0,           // 0x80
  GX_DRAW_QUADS_2 = 0x1,         // 0x88
  GX_DRAW_TRIANGLES = 0x2,       // 0x90
  GX_DRAW_TRIANGLE_STRIP = 0x3,  // 0x98
  GX_DRAW_TRIANGLE_FAN = 0x4,    // 0xA0
  GX_DRAW_LINES = 0x5,           // 0xA8
  GX_DRAW_LINE_STRIP = 0x6,      // 0xB0
  GX_DRAW_POINTS = 0x7           // 0xB8
};

// 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;

  virtual u32 GetVertexSize(u8 vat)
  {
    const CPState& cpmem = GetCPState();
    return VertexLoaderBase::GetVertexSize(cpmem.vtx_desc, cpmem.vtx_attr[vat]);
  }
#endif
};

namespace detail
{
// Main logic; split so that the main RunCommand can call OnCommand with the returned size.
static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available,
                                           std::derived_from<Callback> auto& callback)
{
  if (available < 1)
    return 0;

  const Opcode cmd = static_cast<Opcode>(data[0]);

  switch (cmd)
  {
  case Opcode::GX_NOP:
  {
    u32 count = 1;
    while (count < available && static_cast<Opcode>(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_MSG(VIDEO, stream_size_temp < 16, "cmd2 = 0x{:08X}", cmd2);
    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<CPArray>((static_cast<u8>(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]);

    // Force 32-byte alignment for both the address and the size.
    callback.OnDisplayList(address & ~31, size & ~31);
    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<u8>(cmd);
      const OpcodeDecoder::Primitive primitive = static_cast<OpcodeDecoder::Primitive>(
          (cmdbyte & OpcodeDecoder::GX_PRIMITIVE_MASK) >> OpcodeDecoder::GX_PRIMITIVE_SHIFT);
      const u8 vat = cmdbyte & OpcodeDecoder::GX_VAT_MASK;

      const u32 vertex_size = callback.GetVertexSize(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<u8>(cmd), data);
  return 1;
}
}  // namespace detail

DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available,
                                    std::derived_from<Callback> auto& callback)
{
  const u32 size = detail::RunCommand(data, available, callback);
  if (size > 0)
  {
    callback.OnCommand(data, size);
  }
  return size;
}

DOLPHIN_FORCE_INLINE u32 Run(const u8* data, u32 available,
                             std::derived_from<Callback> auto& 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 <bool is_preprocess = false>
u8* RunFifo(DataReader src, u32* cycles);

}  // namespace OpcodeDecoder

template <>
struct fmt::formatter<OpcodeDecoder::Primitive>
    : EnumFormatter<OpcodeDecoder::Primitive::GX_DRAW_POINTS>
{
  static constexpr array_type names = {
      "GX_DRAW_QUADS",        "GX_DRAW_QUADS_2 (nonstandard)",
      "GX_DRAW_TRIANGLES",    "GX_DRAW_TRIANGLE_STRIP",
      "GX_DRAW_TRIANGLE_FAN", "GX_DRAW_LINES",
      "GX_DRAW_LINE_STRIP",   "GX_DRAW_POINTS",
  };
  constexpr formatter() : EnumFormatter(names) {}
};