-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoptimization_passes.py
More file actions
375 lines (328 loc) · 13.2 KB
/
Copy pathoptimization_passes.py
File metadata and controls
375 lines (328 loc) · 13.2 KB
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
from rom_database import RomDatabase, CommandIR
from typing import List
def eliminate_degenerate_triangles(commands: List[CommandIR]) -> List[CommandIR]:
"""Remove triangles where at least two indices are the same."""
new_cmds = []
for cmd in commands:
if cmd.name == "gsSP1Triangle":
params = cmd.params[0].params
if "indices" in params:
v0, v1, v2 = params["indices"]
else:
v0 = params.get("v0", 0)
v1 = params.get("v1", 0)
v2 = params.get("v2", 0)
if v0 == v1 or v1 == v2 or v0 == v2:
continue
elif cmd.name == "gsSP2Triangles":
params = cmd.params[0].params
if "indices" in params:
idx = params["indices"]
v00, v01, v02, v10, v11, v12 = idx[0], idx[1], idx[2], idx[3], idx[4], idx[5]
else:
v00 = params.get("v00", 0)
v01 = params.get("v01", 0)
v02 = params.get("v02", 0)
v10 = params.get("v10", 0)
v11 = params.get("v11", 0)
v12 = params.get("v12", 0)
deg1 = v00 == v01 or v01 == v02 or v00 == v02
deg2 = v10 == v11 or v11 == v12 or v10 == v12
if deg1 and deg2:
continue
if deg1:
# Convert to 1 triangle (the second one)
if "indices" in params:
params["indices"] = [v10, v11, v12]
else:
params["v0"] = v10
params["v1"] = v11
params["v2"] = v12
params.pop("v00", None)
params.pop("v01", None)
params.pop("v02", None)
params.pop("v10", None)
params.pop("v11", None)
params.pop("v12", None)
params["flag"] = params.get("flag1", 0)
cmd.name = "gsSP1Triangle"
elif deg2:
# Convert to 1 triangle (the first one)
if "indices" in params:
params["indices"] = [v00, v01, v02]
else:
params["v0"] = v00
params["v1"] = v01
params["v2"] = v02
params.pop("v00", None)
params.pop("v01", None)
params.pop("v02", None)
params.pop("v10", None)
params.pop("v11", None)
params.pop("v12", None)
params["flag"] = params.get("flag0", 0)
cmd.name = "gsSP1Triangle"
new_cmds.append(cmd)
return new_cmds
def batch_tri2(commands: List[CommandIR]) -> List[CommandIR]:
"""Convert pairs of consecutive gsSP1Triangle into gsSP2Triangles."""
new_cmds = []
i = 0
while i < len(commands):
cmd = commands[i]
if cmd.name == "gsSP1Triangle" and i + 1 < len(commands):
next_cmd = commands[i + 1]
if next_cmd.name == "gsSP1Triangle":
# Combine them
p1 = cmd.params[0].params
p2 = next_cmd.params[0].params
if "indices" in p1:
v00, v01, v02 = p1["indices"]
flag0 = 0
else:
v00 = p1.get("v0", 0)
v01 = p1.get("v1", 0)
v02 = p1.get("v2", 0)
flag0 = p1.get("flag", 0)
if "indices" in p2:
v10, v11, v12 = p2["indices"]
flag1 = 0
else:
v10 = p2.get("v0", 0)
v11 = p2.get("v1", 0)
v12 = p2.get("v2", 0)
flag1 = p2.get("flag", 0)
from display_list import GfxCommand
batch_gfx_cmd = GfxCommand(
w0=cmd.params[0].w0,
w1=next_cmd.params[0].w1,
params={
"v00": v00,
"v01": v01,
"v02": v02,
"flag0": flag0,
"v10": v10,
"v11": v11,
"v12": v12,
"flag1": flag1,
},
)
batch_ir = CommandIR(
opcode=0xB1, # G_TRI2
params=[batch_gfx_cmd],
address=cmd.address,
name="gsSP2Triangles",
)
new_cmds.append(batch_ir)
i += 2
continue
new_cmds.append(cmd)
i += 1
return new_cmds
def eliminate_redundant_rdp_state(commands: List[CommandIR]) -> List[CommandIR]:
"""Remove redundant gsDP and gsSP state changes."""
new_cmds = []
# State tracking
last_combine_w0 = None
last_combine_w1 = None
last_env_color = None
last_prim_color = None
last_fog_color = None
last_blend_color = None
last_render_mode = None
last_other_mode_l = None
last_other_mode_h = None
last_geometry_mode = None
last_texture_w0 = None
last_texture_w1 = None
last_fog_factor = None
# We reset state tracking on jump/branch list or end dl to be safe
# Also reset on Matrix and MoveMem as they can have complex side effects
reset_opcodes = {0x06, 0xDE, 0xB8, 0xDF, 0x01, 0xDA, 0x03, 0xDC}
for cmd in commands:
if cmd.opcode in reset_opcodes:
last_combine_w0 = last_combine_w1 = None
last_env_color = last_prim_color = last_fog_color = last_blend_color = None
last_render_mode = last_other_mode_l = last_other_mode_h = None
last_geometry_mode = last_texture_w0 = last_texture_w1 = None
last_fog_factor = None
new_cmds.append(cmd)
continue
w0 = cmd.params[0].w0
w1 = cmd.params[0].w1
# Combine Mode
if cmd.name == "gsDPSetCombineMode" or cmd.opcode == 0xFC:
if w0 == last_combine_w0 and w1 == last_combine_w1:
continue
last_combine_w0, last_combine_w1 = w0, w1
# Colors
elif cmd.name == "gsDPSetEnvColor":
if w1 == last_env_color:
continue
last_env_color = w1
elif cmd.name == "gsDPSetPrimColor":
val = (w0 & 0xFFFF, w1)
if val == last_prim_color:
continue
last_prim_color = val
elif cmd.name == "gsDPSetFogColor":
if w1 == last_fog_color:
continue
last_fog_color = w1
elif cmd.name == "gsDPSetBlendColor":
if w1 == last_blend_color:
continue
last_blend_color = w1
# Geometry Mode (Additive/Subtractive)
elif cmd.name in ("gsSPSetGeometryMode", "gsSPClearGeometryMode", "gsSPGeometryMode"):
# If we don't know the current mode, we can't optimize easily yet.
# But we can at least avoid exact repeats.
val = (cmd.name, w1)
if val == last_geometry_mode:
continue
last_geometry_mode = val
# Texture State
elif cmd.name == "gsSPTexture" or cmd.opcode in (0xBB, 0xD7):
if w0 == last_texture_w0 and w1 == last_texture_w1:
continue
last_texture_w0, last_texture_w1 = w0, w1
# Other Modes (L/H)
elif cmd.name in ("gsSPSetOtherMode", "gsDPSetOtherMode") or cmd.opcode in (
0xB9,
0xBA,
0xE2,
0xE3,
):
# Check L or H based on opcode or shift
is_l = cmd.opcode in (0xB9, 0xE2)
if not is_l and cmd.name == "gsSPSetOtherMode":
params = cmd.params[0].params
is_l = params.get("cmd") == "G_SETOTHERMODE_L" or params.get("shift", 0) < 0x20
if is_l:
if w1 == last_other_mode_l:
continue
last_other_mode_l = w1
else:
if w1 == last_other_mode_h:
continue
last_other_mode_h = w1
# Fog Factor / MoveWord
elif cmd.name in ("gsSPFogFactor", "gsSPFogPosition") or (
cmd.opcode in (0xBC, 0xDB) and (w0 & 0xFF) == 0x08
):
if w1 == last_fog_factor:
continue
last_fog_factor = w1
new_cmds.append(cmd)
return new_cmds
def eliminate_redundant_vertices(commands: List[CommandIR]) -> List[CommandIR]:
"""Remove gsSPVertex commands that are overwritten before they are used."""
new_cmds = []
needed_vertices = set()
# Iterate backwards to track vertex usage
for cmd in reversed(commands):
keep = True
if cmd.name == "gsSPVertex":
params = cmd.params[0].params
v0 = params.get("v0", 0)
count = params.get("count", 0)
# Check if any vertex in this range is currently needed
v_range = set(range(v0, v0 + count))
if not (v_range & needed_vertices):
# None of these vertices are needed yet (they are overwritten or unused)
keep = False
else:
# Some are needed. Satisfy them and remove from needed set.
needed_vertices -= v_range
elif cmd.name in ("gsSP1Triangle", "gsSP1Quadrangle", "gsSPLine3D"):
params = cmd.params[0].params
if "indices" in params:
for v in params["indices"]:
needed_vertices.add(v)
else:
for k in ("v0", "v1", "v2", "v3"):
if k in params:
needed_vertices.add(params[k])
elif cmd.name == "gsSP2Triangles":
params = cmd.params[0].params
if "indices" in params:
for v in params["indices"]:
needed_vertices.add(v)
else:
for k in ("v00", "v01", "v02", "v10", "v11", "v12"):
if k in params:
needed_vertices.add(params[k])
elif cmd.name == "gsSPModifyVertex":
params = cmd.params[0].params
needed_vertices.add(params.get("vtx", 0))
elif cmd.name == "gsSPCullDisplayList":
params = cmd.params[0].params
v0 = params.get("v0", 0)
vn = params.get("vn", 0)
for v in range(v0, vn + 1):
needed_vertices.add(v)
elif cmd.name == "gsSPBranchLessZ":
params = cmd.params[0].params
needed_vertices.add(params.get("vtx", 0))
elif cmd.opcode == 0x06 or cmd.opcode == 0xDE: # gsSPDisplayList / gsSPBranchList
# We must assume the child DL uses all vertices
# This is a safe assumption because we don't do cross-DL analysis here
for v in range(32):
needed_vertices.add(v)
if keep:
new_cmds.append(cmd)
return list(reversed(new_cmds))
def insert_cull_dl(commands: List[CommandIR]) -> List[CommandIR]:
"""Insert gsSPCullDisplayList for the entire vertex range if a DL uses vertices."""
# Simplified version: insert one cull at the start if we load vertices
first_vtx = 999
last_vtx = -1
has_tris = False
for cmd in commands:
if cmd.name == "gsSP1Triangle":
params = cmd.params[0].params
if "indices" in params:
idx = params["indices"]
else:
idx = [params.get("v0", 0), params.get("v1", 0), params.get("v2", 0)]
first_vtx = min(first_vtx, *idx)
last_vtx = max(last_vtx, *idx)
has_tris = True
elif cmd.name == "gsSP2Triangles":
params = cmd.params[0].params
if "indices" in params:
idx = params["indices"]
else:
idx = [
params.get("v00", 0),
params.get("v01", 0),
params.get("v02", 0),
params.get("v10", 0),
params.get("v11", 0),
params.get("v12", 0),
]
first_vtx = min(first_vtx, *idx)
last_vtx = max(last_vtx, *idx)
has_tris = True
if has_tris and first_vtx <= last_vtx:
from display_list import GfxCommand
cull_gfx_cmd = GfxCommand(0xBE, 0, {"v0": first_vtx, "vn": last_vtx}, False)
cull_ir = CommandIR(
opcode=0xBE, # G_CULLDL
params=[cull_gfx_cmd],
address=commands[0].address if commands else 0,
name="gsSPCullDisplayList",
)
return [cull_ir] + commands
return commands
def run_model_optimization_passes(db: RomDatabase):
"""Run all optimization passes on Display Lists in the database."""
for dl_rec in db.display_lists.values():
if not dl_rec.commands:
continue
# Order matters
dl_rec.commands = eliminate_degenerate_triangles(dl_rec.commands)
dl_rec.commands = eliminate_redundant_rdp_state(dl_rec.commands)
dl_rec.commands = eliminate_redundant_vertices(dl_rec.commands)
dl_rec.commands = batch_tri2(dl_rec.commands)
dl_rec.commands = insert_cull_dl(dl_rec.commands)