1 // Copyright 2008 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 // DL facts:
6 //  Ikaruga uses (nearly) NO display lists!
7 //  Zelda WW uses TONS of display lists
8 //  Zelda TP uses almost 100% display lists except menus (we like this!)
9 //  Super Mario Galaxy has nearly all geometry and more than half of the state in DLs (great!)
10 
11 // Note that it IS NOT GENERALLY POSSIBLE to precompile display lists! You can compile them as they
12 // are while interpreting them, and hope that the vertex format doesn't change, though, if you do
13 // it right when they are called. The reason is that the vertex format affects the sizes of the
14 // vertices.
15 
16 #include "VideoCommon/OpcodeDecoding.h"
17 
18 #include "Common/CommonTypes.h"
19 #include "Common/Logging/Log.h"
20 #include "Core/FifoPlayer/FifoRecorder.h"
21 #include "Core/HW/Memmap.h"
22 #include "VideoCommon/BPMemory.h"
23 #include "VideoCommon/CPMemory.h"
24 #include "VideoCommon/CommandProcessor.h"
25 #include "VideoCommon/DataReader.h"
26 #include "VideoCommon/Fifo.h"
27 #include "VideoCommon/Statistics.h"
28 #include "VideoCommon/VertexLoaderManager.h"
29 #include "VideoCommon/XFMemory.h"
30 
31 namespace OpcodeDecoder
32 {
33 namespace
34 {
35 bool s_is_fifo_error_seen = false;
36 
InterpretDisplayList(u32 address,u32 size)37 u32 InterpretDisplayList(u32 address, u32 size)
38 {
39   u8* start_address;
40 
41   if (Fifo::UseDeterministicGPUThread())
42     start_address = static_cast<u8*>(Fifo::PopFifoAuxBuffer(size));
43   else
44     start_address = Memory::GetPointer(address);
45 
46   u32 cycles = 0;
47 
48   // Avoid the crash if Memory::GetPointer failed ..
49   if (start_address != nullptr)
50   {
51     // temporarily swap dl and non-dl (small "hack" for the stats)
52     g_stats.SwapDL();
53 
54     Run(DataReader(start_address, start_address + size), &cycles, true);
55     INCSTAT(g_stats.this_frame.num_dlists_called);
56 
57     // un-swap
58     g_stats.SwapDL();
59   }
60 
61   return cycles;
62 }
63 
InterpretDisplayListPreprocess(u32 address,u32 size)64 void InterpretDisplayListPreprocess(u32 address, u32 size)
65 {
66   u8* const start_address = Memory::GetPointer(address);
67 
68   Fifo::PushFifoAuxBuffer(start_address, size);
69 
70   if (start_address == nullptr)
71     return;
72 
73   Run<true>(DataReader(start_address, start_address + size), nullptr, true);
74 }
75 }  // Anonymous namespace
76 
77 bool g_record_fifo_data = false;
78 
Init()79 void Init()
80 {
81   s_is_fifo_error_seen = false;
82 }
83 
84 template <bool is_preprocess>
Run(DataReader src,u32 * cycles,bool in_display_list)85 u8* Run(DataReader src, u32* cycles, bool in_display_list)
86 {
87   u32 total_cycles = 0;
88   u8* opcode_start = nullptr;
89 
90   const auto finish_up = [cycles, &opcode_start, &total_cycles] {
91     if (cycles != nullptr)
92     {
93       *cycles = total_cycles;
94     }
95     return opcode_start;
96   };
97 
98   while (true)
99   {
100     opcode_start = src.GetPointer();
101 
102     if (!src.size())
103       return finish_up();
104 
105     const u8 cmd_byte = src.Read<u8>();
106     switch (cmd_byte)
107     {
108     case GX_NOP:
109       total_cycles += 6;  // Hm, this means that we scan over nop streams pretty slowly...
110       break;
111 
112     case GX_UNKNOWN_RESET:
113       total_cycles += 6;  // Datel software uses this command
114       DEBUG_LOG(VIDEO, "GX Reset?: %08x", cmd_byte);
115       break;
116 
117     case GX_LOAD_CP_REG:
118     {
119       if (src.size() < 1 + 4)
120         return finish_up();
121 
122       total_cycles += 12;
123 
124       const u8 sub_cmd = src.Read<u8>();
125       const u32 value = src.Read<u32>();
126       LoadCPReg(sub_cmd, value, is_preprocess);
127       if constexpr (!is_preprocess)
128         INCSTAT(g_stats.this_frame.num_cp_loads);
129     }
130     break;
131 
132     case GX_LOAD_XF_REG:
133     {
134       if (src.size() < 4)
135         return finish_up();
136 
137       const u32 cmd2 = src.Read<u32>();
138       const u32 transfer_size = ((cmd2 >> 16) & 15) + 1;
139       if (src.size() < transfer_size * sizeof(u32))
140         return finish_up();
141 
142       total_cycles += 18 + 6 * transfer_size;
143 
144       if constexpr (!is_preprocess)
145       {
146         const u32 xf_address = cmd2 & 0xFFFF;
147         LoadXFReg(transfer_size, xf_address, src);
148 
149         INCSTAT(g_stats.this_frame.num_xf_loads);
150       }
151       src.Skip<u32>(transfer_size);
152     }
153     break;
154 
155     case GX_LOAD_INDX_A:  // Used for position matrices
156     case GX_LOAD_INDX_B:  // Used for normal matrices
157     case GX_LOAD_INDX_C:  // Used for postmatrices
158     case GX_LOAD_INDX_D:  // Used for lights
159     {
160       if (src.size() < 4)
161         return finish_up();
162 
163       total_cycles += 6;
164 
165       // Map the command byte to its ref array.
166       // GX_LOAD_INDX_A (32) -> 0xC
167       // GX_LOAD_INDX_B (40) -> 0xD
168       // GX_LOAD_INDX_C (48) -> 0xE
169       // GX_LOAD_INDX_D (56) -> 0xF
170       const int ref_array = (cmd_byte / 8) + 8;
171 
172       if constexpr (is_preprocess)
173         PreprocessIndexedXF(src.Read<u32>(), ref_array);
174       else
175         LoadIndexedXF(src.Read<u32>(), ref_array);
176     }
177     break;
178 
179     case GX_CMD_CALL_DL:
180     {
181       if (src.size() < 8)
182         return finish_up();
183 
184       const u32 address = src.Read<u32>();
185       const u32 count = src.Read<u32>();
186 
187       if (in_display_list)
188       {
189         total_cycles += 6;
190         INFO_LOG(VIDEO, "recursive display list detected");
191       }
192       else
193       {
194         if constexpr (is_preprocess)
195           InterpretDisplayListPreprocess(address, count);
196         else
197           total_cycles += 6 + InterpretDisplayList(address, count);
198       }
199     }
200     break;
201 
202     case GX_CMD_UNKNOWN_METRICS:  // zelda 4 swords calls it and checks the metrics registers after
203                                   // that
204       total_cycles += 6;
205       DEBUG_LOG(VIDEO, "GX 0x44: %08x", cmd_byte);
206       break;
207 
208     case GX_CMD_INVL_VC:  // Invalidate Vertex Cache
209       total_cycles += 6;
210       DEBUG_LOG(VIDEO, "Invalidate (vertex cache?)");
211       break;
212 
213     case GX_LOAD_BP_REG:
214       // In skipped_frame case: We have to let BP writes through because they set
215       // tokens and stuff.  TODO: Call a much simplified LoadBPReg instead.
216       {
217         if (src.size() < 4)
218           return finish_up();
219 
220         total_cycles += 12;
221 
222         const u32 bp_cmd = src.Read<u32>();
223         if constexpr (is_preprocess)
224         {
225           LoadBPRegPreprocess(bp_cmd);
226         }
227         else
228         {
229           LoadBPReg(bp_cmd);
230           INCSTAT(g_stats.this_frame.num_bp_loads);
231         }
232       }
233       break;
234 
235     // draw primitives
236     default:
237       if ((cmd_byte & 0xC0) == 0x80)
238       {
239         // load vertices
240         if (src.size() < 2)
241           return finish_up();
242 
243         const u16 num_vertices = src.Read<u16>();
244         const int bytes = VertexLoaderManager::RunVertices(
245             cmd_byte & GX_VAT_MASK,  // Vertex loader index (0 - 7)
246             (cmd_byte & GX_PRIMITIVE_MASK) >> GX_PRIMITIVE_SHIFT, num_vertices, src, is_preprocess);
247 
248         if (bytes < 0)
249           return finish_up();
250 
251         src.Skip(bytes);
252 
253         // 4 GPU ticks per vertex, 3 CPU ticks per GPU tick
254         total_cycles += num_vertices * 4 * 3 + 6;
255       }
256       else
257       {
258         if (!s_is_fifo_error_seen)
259           CommandProcessor::HandleUnknownOpcode(cmd_byte, opcode_start, is_preprocess);
260         ERROR_LOG(VIDEO, "FIFO: Unknown Opcode(0x%02x @ %p, preprocessing = %s)", cmd_byte,
261                   opcode_start, is_preprocess ? "yes" : "no");
262         s_is_fifo_error_seen = true;
263         total_cycles += 1;
264       }
265       break;
266     }
267 
268     // Display lists get added directly into the FIFO stream
269     if constexpr (!is_preprocess)
270     {
271       if (g_record_fifo_data && cmd_byte != GX_CMD_CALL_DL)
272       {
273         const u8* const opcode_end = src.GetPointer();
274         FifoRecorder::GetInstance().WriteGPCommand(opcode_start, u32(opcode_end - opcode_start));
275       }
276     }
277   }
278 }
279 
280 template u8* Run<true>(DataReader src, u32* cycles, bool in_display_list);
281 template u8* Run<false>(DataReader src, u32* cycles, bool in_display_list);
282 
283 }  // namespace OpcodeDecoder
284