bobframes 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bobframes/__init__.py +3 -0
- bobframes/_version.py +1 -0
- bobframes/catalog.py +154 -0
- bobframes/cli.py +266 -0
- bobframes/derive_post_merge.py +365 -0
- bobframes/derives/__init__.py +0 -0
- bobframes/derives/pass_class_breakdown.py +102 -0
- bobframes/derives/texture_usage.py +121 -0
- bobframes/discovery.py +132 -0
- bobframes/global_entities.py +99 -0
- bobframes/html/__init__.py +0 -0
- bobframes/html/template.py +1056 -0
- bobframes/lint.py +114 -0
- bobframes/manifest.py +127 -0
- bobframes/parquetize.py +282 -0
- bobframes/parsers/__init__.py +0 -0
- bobframes/parsers/derive_program_transitions.py +73 -0
- bobframes/parsers/parse_init_state.py +675 -0
- bobframes/paths.py +111 -0
- bobframes/probes/__init__.py +0 -0
- bobframes/probes/whatif.py +165 -0
- bobframes/qrd_harness.py +119 -0
- bobframes/query_examples.py +222 -0
- bobframes/rdcmd.py +72 -0
- bobframes/replay/__init__.py +26 -0
- bobframes/replay/replay_main.py +2305 -0
- bobframes/reports/__init__.py +0 -0
- bobframes/reports/_dashboard.py +425 -0
- bobframes/reports/ab.py +88 -0
- bobframes/reports/base.py +114 -0
- bobframes/reports/cache.py +147 -0
- bobframes/reports/chrome.py +1306 -0
- bobframes/reports/cli.py +99 -0
- bobframes/reports/delta.py +167 -0
- bobframes/reports/discovery.py +118 -0
- bobframes/reports/draws_by_class.py +165 -0
- bobframes/reports/formatters.py +122 -0
- bobframes/reports/instancing_opportunities.py +276 -0
- bobframes/reports/orchestrator.py +59 -0
- bobframes/reports/overdraw.py +293 -0
- bobframes/reports/pass_gpu.py +190 -0
- bobframes/reports/shader_hotlist.py +240 -0
- bobframes/reports/trend_table.py +444 -0
- bobframes/resource_labels.py +162 -0
- bobframes/run.py +480 -0
- bobframes/schemas.py +426 -0
- bobframes/stable_keys.py +83 -0
- bobframes/tests/__init__.py +0 -0
- bobframes/tests/_render_util.py +84 -0
- bobframes/tests/data/golden/_reports/draws_by_class.html +323 -0
- bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/index.html +1560 -0
- bobframes/tests/data/golden/_reports/index.html +264 -0
- bobframes/tests/data/golden/_reports/instancing_opportunities.html +266 -0
- bobframes/tests/data/golden/_reports/overdraw.html +275 -0
- bobframes/tests/data/golden/_reports/pass_gpu.html +277 -0
- bobframes/tests/data/golden/_reports/shader_hotlist.html +265 -0
- bobframes/tests/data/golden/_reports/trend_table.html +390 -0
- bobframes/tests/data/golden/index.html +1175 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/_manifest.json +51 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/buffers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/clears.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/counters_per_event.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/descriptor_access.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/dispatches.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draw_bindings.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draws.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/fbos.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/frame_totals.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/ibo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/indirect_args.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/passes.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/pixel_history.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/post_vs_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/program_transitions.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/programs.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/render_targets.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/resource_creation.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/rt_event_timeline.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/samplers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/shaders.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/state_change_events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/texture_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/textures.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vbo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vertex_inputs.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/_manifest.json +51 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/buffers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/clears.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/counters_per_event.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/descriptor_access.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/dispatches.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draw_bindings.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draws.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/fbos.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/frame_totals.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/ibo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/indirect_args.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/passes.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/pixel_history.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/post_vs_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/program_transitions.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/programs.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/render_targets.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/resource_creation.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/rt_event_timeline.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/samplers.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/shaders.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/state_change_events.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/texture_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/textures.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vbo_samples.parquet +0 -0
- bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vertex_inputs.parquet +0 -0
- bobframes/tests/make_synthetic.py +171 -0
- bobframes/tests/smoke.py +199 -0
- bobframes/tests/test_determinism.py +19 -0
- bobframes/tests/test_discovery.py +97 -0
- bobframes/tests/test_hardening.py +142 -0
- bobframes/tests/test_parity.py +22 -0
- bobframes/tests/test_perf.py +18 -0
- bobframes/tests/test_replay_drift.py +115 -0
- bobframes/tests/test_schemas.py +26 -0
- bobframes/tests/test_schemas_unit.py +55 -0
- bobframes/tests/test_stable_keys.py +61 -0
- bobframes-0.1.0.dist-info/METADATA +144 -0
- bobframes-0.1.0.dist-info/RECORD +130 -0
- bobframes-0.1.0.dist-info/WHEEL +4 -0
- bobframes-0.1.0.dist-info/entry_points.txt +2 -0
- bobframes-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
"""Stream a .zip.xml file and emit partial CSVs of init-state data.
|
|
2
|
+
|
|
3
|
+
Heavyweight text extraction that doesn't need replay state:
|
|
4
|
+
- glShaderSource source dumps (one .glsl file per shader id)
|
|
5
|
+
- shaders.csv partial rows (id, type, src_len, src_hash, complexity counts)
|
|
6
|
+
- programs.csv partial rows (id, linked flag, attached_shader_ids)
|
|
7
|
+
- textures.csv partial rows (id, format, w, h, mip_levels, sample_count)
|
|
8
|
+
- samplers.csv partial rows (id, filter/wrap/aniso parameters)
|
|
9
|
+
- buffers.csv partial rows (id, allocated_size_bytes, usage_hint)
|
|
10
|
+
- fbos.csv partial rows (id, attachments)
|
|
11
|
+
- resource_creation.csv rows for every glGen*/glCreate* chunk
|
|
12
|
+
- labels.json: {resource_id (int): label_string}
|
|
13
|
+
- chunk_index_init_max: max chunkIndex covered (so replay knows where the init region ends)
|
|
14
|
+
|
|
15
|
+
Replay-side (replay_main.py) fills the entity tables further at merge time
|
|
16
|
+
(used_by_draw_count, attached_to_fbo_ids from frame usage, etc.).
|
|
17
|
+
|
|
18
|
+
`created_at_event` is set to -1 ("init state") for everything emitted here
|
|
19
|
+
since these chunks are pre-action-tree. A merge-time pass overlays the real
|
|
20
|
+
event_id when one is associated.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import csv
|
|
26
|
+
import hashlib
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
from collections.abc import Iterator
|
|
33
|
+
|
|
34
|
+
# --- Streaming chunk reader --------------------------------------------------
|
|
35
|
+
|
|
36
|
+
_RE_CHUNK_START = re.compile(
|
|
37
|
+
r'<chunk\s+id="(\d+)"\s+chunkIndex="(\d+)"\s+name="([^"]+)"'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def iter_chunks(xml_path: str) -> Iterator[tuple[int, int, str, str]]:
|
|
42
|
+
"""Yield (chunk_id, chunk_index, chunk_name, body) for every <chunk>.
|
|
43
|
+
|
|
44
|
+
Streams line by line; constant memory regardless of file size.
|
|
45
|
+
body excludes the opening <chunk ...> tag and the closing </chunk> tag.
|
|
46
|
+
"""
|
|
47
|
+
in_chunk = False
|
|
48
|
+
cid = cidx = 0
|
|
49
|
+
cname = ''
|
|
50
|
+
buf: list[str] = []
|
|
51
|
+
with open(xml_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
52
|
+
for line in f:
|
|
53
|
+
if not in_chunk:
|
|
54
|
+
m = _RE_CHUNK_START.search(line)
|
|
55
|
+
if m:
|
|
56
|
+
cid = int(m.group(1))
|
|
57
|
+
cidx = int(m.group(2))
|
|
58
|
+
cname = m.group(3)
|
|
59
|
+
in_chunk = True
|
|
60
|
+
buf = []
|
|
61
|
+
if '</chunk>' in line:
|
|
62
|
+
body = ''.join(buf)
|
|
63
|
+
yield cid, cidx, cname, body
|
|
64
|
+
in_chunk = False
|
|
65
|
+
buf = []
|
|
66
|
+
else:
|
|
67
|
+
buf.append(line)
|
|
68
|
+
if '</chunk>' in line:
|
|
69
|
+
body = ''.join(buf)
|
|
70
|
+
yield cid, cidx, cname, body
|
|
71
|
+
in_chunk = False
|
|
72
|
+
buf = []
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# --- Body extractors ---------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
_RE_RESID = re.compile(
|
|
78
|
+
r'<ResourceId\s+name="([^"]+)"[^>]*>(\d+)</ResourceId>'
|
|
79
|
+
)
|
|
80
|
+
_RE_INT = re.compile(
|
|
81
|
+
r'<int\s+name="([^"]+)"[^>]*>(-?\d+)</int>'
|
|
82
|
+
)
|
|
83
|
+
_RE_UINT = re.compile(
|
|
84
|
+
r'<u?int(?:64_t)?\s+name="([^"]+)"[^>]*>(\d+)</u?int(?:64_t)?>'
|
|
85
|
+
)
|
|
86
|
+
_RE_ENUM_WITH_STRING = re.compile(
|
|
87
|
+
r'<enum\s+name="([^"]+)"[^>]*string="([^"]+)"[^>]*>(\d+)</enum>'
|
|
88
|
+
)
|
|
89
|
+
_RE_STRING_PLAIN = re.compile(
|
|
90
|
+
r'<string\s+name="([^"]+)"[^>]*>([\s\S]*?)</string>'
|
|
91
|
+
)
|
|
92
|
+
_RE_SHADER_SRC = re.compile(
|
|
93
|
+
r'<array\s+name="sources"[^>]*>\s*<string[^>]*>([\s\S]*?)</string>'
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _CI(dict):
|
|
98
|
+
"""Case-insensitive dict for chunk arg lookups."""
|
|
99
|
+
def get(self, key, default=None):
|
|
100
|
+
v = super().get(key, _MISS)
|
|
101
|
+
if v is not _MISS:
|
|
102
|
+
return v
|
|
103
|
+
kl = key.lower()
|
|
104
|
+
for k, v in self.items():
|
|
105
|
+
if k.lower() == kl:
|
|
106
|
+
return v
|
|
107
|
+
return default
|
|
108
|
+
|
|
109
|
+
def __contains__(self, key):
|
|
110
|
+
return self.get(key, _MISS) is not _MISS
|
|
111
|
+
|
|
112
|
+
_MISS = object()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _resids(body: str) -> _CI:
|
|
116
|
+
d = _CI()
|
|
117
|
+
for m in _RE_RESID.finditer(body):
|
|
118
|
+
d[m.group(1)] = int(m.group(2))
|
|
119
|
+
return d
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _ints(body: str) -> _CI:
|
|
123
|
+
out = _CI()
|
|
124
|
+
for m in _RE_INT.finditer(body):
|
|
125
|
+
out[m.group(1)] = int(m.group(2))
|
|
126
|
+
for m in _RE_UINT.finditer(body):
|
|
127
|
+
if m.group(1) not in out:
|
|
128
|
+
out[m.group(1)] = int(m.group(2))
|
|
129
|
+
return out
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _enums(body: str) -> _CI:
|
|
133
|
+
out = _CI()
|
|
134
|
+
for m in _RE_ENUM_WITH_STRING.finditer(body):
|
|
135
|
+
out[m.group(1)] = (int(m.group(3)), m.group(2))
|
|
136
|
+
return out
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _strings(body: str) -> _CI:
|
|
140
|
+
out = _CI()
|
|
141
|
+
for m in _RE_STRING_PLAIN.finditer(body):
|
|
142
|
+
out[m.group(1)] = m.group(2)
|
|
143
|
+
return out
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _first_resid(body: str) -> int:
|
|
147
|
+
"""Return the FIRST non-zero ResourceId in body, or 0 if none."""
|
|
148
|
+
for m in _RE_RESID.finditer(body):
|
|
149
|
+
v = int(m.group(2))
|
|
150
|
+
if v:
|
|
151
|
+
return v
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# --- Shader source complexity heuristics (counts, not interpretation) --------
|
|
156
|
+
|
|
157
|
+
_RE_TEX_SAMPLE = re.compile(r'\btexture(?:\w*)\(')
|
|
158
|
+
_RE_BRANCH = re.compile(r'\b(if|else)\b')
|
|
159
|
+
_RE_LOOP = re.compile(r'\b(for|while)\b')
|
|
160
|
+
_RE_DISCARD = re.compile(r'\bdiscard\b')
|
|
161
|
+
_RE_DFDX = re.compile(r'\b(dFdx|dFdy|fwidth)\b')
|
|
162
|
+
_RE_MAT4_CTOR = re.compile(r'\bmat4\s*\(')
|
|
163
|
+
_RE_VARYING = re.compile(r'\b(in|out|varying)\s+(?:highp|mediump|lowp)?\s*\w+\s+\w+\s*;')
|
|
164
|
+
_RE_MEDIUMP = re.compile(r'\bmediump\b')
|
|
165
|
+
_RE_HIGHP = re.compile(r'\bhighp\b')
|
|
166
|
+
_RE_LOWP = re.compile(r'\blowp\b')
|
|
167
|
+
_RE_FB_FETCH = re.compile(r'\b(GL_EXT_shader_framebuffer_fetch|inout\s+\w+\s+gl_LastFragData)\b')
|
|
168
|
+
_RE_CUBEMAP = re.compile(r'\bsamplerCube\b')
|
|
169
|
+
_RE_TEX_GATHER = re.compile(r'\btextureGather\w*\(')
|
|
170
|
+
_RE_TEX_GRAD = re.compile(r'\btextureGrad\w*\(')
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def shader_complexity(source: str) -> dict[str, int]:
|
|
174
|
+
return {
|
|
175
|
+
'total_texture_samples': len(_RE_TEX_SAMPLE.findall(source)),
|
|
176
|
+
'total_branches': len(_RE_BRANCH.findall(source)),
|
|
177
|
+
'total_loops': len(_RE_LOOP.findall(source)),
|
|
178
|
+
'total_discards': len(_RE_DISCARD.findall(source)),
|
|
179
|
+
'total_dfdx_dfdy': len(_RE_DFDX.findall(source)),
|
|
180
|
+
'total_mat4_constructors': len(_RE_MAT4_CTOR.findall(source)),
|
|
181
|
+
'total_varyings': len(_RE_VARYING.findall(source)),
|
|
182
|
+
'mediump_decls': len(_RE_MEDIUMP.findall(source)),
|
|
183
|
+
'highp_decls': len(_RE_HIGHP.findall(source)),
|
|
184
|
+
'lowp_decls': len(_RE_LOWP.findall(source)),
|
|
185
|
+
'fb_fetch': 1 if _RE_FB_FETCH.search(source) else 0,
|
|
186
|
+
'uses_cubemap': 1 if _RE_CUBEMAP.search(source) else 0,
|
|
187
|
+
'uses_texture_gather': 1 if _RE_TEX_GATHER.search(source) else 0,
|
|
188
|
+
'uses_texture_grad': 1 if _RE_TEX_GRAD.search(source) else 0,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# --- Shader type decode ------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
_GL_SHADER_TYPE = {
|
|
195
|
+
35633: 'vertex',
|
|
196
|
+
35632: 'fragment',
|
|
197
|
+
36313: 'geometry',
|
|
198
|
+
36488: 'tess_control',
|
|
199
|
+
36487: 'tess_evaluation',
|
|
200
|
+
37305: 'compute',
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# --- Resource kind mapping ---------------------------------------------------
|
|
205
|
+
|
|
206
|
+
_RESOURCE_GEN_CHUNKS = {
|
|
207
|
+
'glGenBuffers': 'buffer',
|
|
208
|
+
'glCreateBuffers': 'buffer',
|
|
209
|
+
'glGenTextures': 'texture',
|
|
210
|
+
'glCreateTextures': 'texture',
|
|
211
|
+
'glGenFramebuffers': 'framebuffer',
|
|
212
|
+
'glCreateFramebuffers': 'framebuffer',
|
|
213
|
+
'glGenSamplers': 'sampler',
|
|
214
|
+
'glCreateSamplers': 'sampler',
|
|
215
|
+
'glGenRenderbuffers': 'renderbuffer',
|
|
216
|
+
'glGenVertexArrays': 'vao',
|
|
217
|
+
'glCreateVertexArrays': 'vao',
|
|
218
|
+
'glGenQueries': 'query',
|
|
219
|
+
'glFenceSync': 'sync',
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# --- Parser ------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
class _Acc:
|
|
226
|
+
"""Accumulator across chunks during one parse pass."""
|
|
227
|
+
|
|
228
|
+
def __init__(self) -> None:
|
|
229
|
+
self.resource_creation: list[dict] = []
|
|
230
|
+
self.shaders: dict[int, dict] = {} # shader_id → row dict
|
|
231
|
+
self.programs: dict[int, dict] = {} # program_id → row
|
|
232
|
+
self.textures: dict[int, dict] = {} # texture_id → row
|
|
233
|
+
self.buffers: dict[int, dict] = {} # buffer_id → row
|
|
234
|
+
self.samplers: dict[int, dict] = {} # sampler_id → row
|
|
235
|
+
self.fbos: dict[int, dict] = {} # fbo_id → row
|
|
236
|
+
self.labels: dict[int, str] = {} # resource_id → label
|
|
237
|
+
self.max_chunk_index: int = -1
|
|
238
|
+
self.shader_sources: dict[int, str] = {} # shader_id → full source
|
|
239
|
+
|
|
240
|
+
def _ensure_shader(self, sid: int) -> dict:
|
|
241
|
+
return self.shaders.setdefault(sid, {
|
|
242
|
+
'shader_id': sid,
|
|
243
|
+
'shader_type': '',
|
|
244
|
+
'src_len': 0,
|
|
245
|
+
'src_hash': '',
|
|
246
|
+
'linked_program_ids': '',
|
|
247
|
+
'used_by_draw_count': 0,
|
|
248
|
+
'total_texture_samples': 0, 'total_branches': 0, 'total_loops': 0,
|
|
249
|
+
'total_discards': 0, 'total_dfdx_dfdy': 0,
|
|
250
|
+
'total_mat4_constructors': 0, 'total_varyings': 0,
|
|
251
|
+
'mediump_decls': 0, 'highp_decls': 0, 'lowp_decls': 0,
|
|
252
|
+
'fb_fetch': 0, 'uses_cubemap': 0, 'uses_texture_gather': 0,
|
|
253
|
+
'uses_texture_grad': 0,
|
|
254
|
+
'src_file_path': '',
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
def _ensure_program(self, pid: int) -> dict:
|
|
258
|
+
return self.programs.setdefault(pid, {
|
|
259
|
+
'program_id': pid, 'linked': 0,
|
|
260
|
+
'num_attached_shaders': 0, 'attached_shader_ids': '',
|
|
261
|
+
'vs_shader_id': 0, 'fs_shader_id': 0, 'cs_shader_id': 0,
|
|
262
|
+
'gs_shader_id': 0, 'tcs_shader_id': 0, 'tes_shader_id': 0,
|
|
263
|
+
'num_active_uniforms': 0, 'num_active_uniform_blocks': 0,
|
|
264
|
+
'num_active_attributes': 0, 'used_by_draw_count': 0, 'label': '',
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
def _ensure_texture(self, tid: int) -> dict:
|
|
268
|
+
return self.textures.setdefault(tid, {
|
|
269
|
+
'tex_id': tid, 'format': '', 'width': 0, 'height': 0, 'depth': 0,
|
|
270
|
+
'mip_levels': 0, 'sample_count': 0, 'kind': '',
|
|
271
|
+
'est_bytes': 0, 'is_rt': 0, 'is_swap_chain': 0, 'label': '',
|
|
272
|
+
'created_at_event': -1, 'num_bind_events': 0, 'num_sample_events': 0,
|
|
273
|
+
'sampled_by_shader_ids': '', 'attached_to_fbo_ids': '',
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
def _ensure_buffer(self, bid: int) -> dict:
|
|
277
|
+
return self.buffers.setdefault(bid, {
|
|
278
|
+
'buffer_id': bid, 'allocated_size_bytes': 0, 'usage_hint': '',
|
|
279
|
+
'target_history': '',
|
|
280
|
+
'first_alloc_event': -1, 'last_alloc_event': -1,
|
|
281
|
+
'first_bind_event': -1, 'last_bind_event': -1,
|
|
282
|
+
'num_glBufferData': 0, 'num_glBufferSubData': 0,
|
|
283
|
+
'num_glBindBuffer': 0, 'num_glBindBufferBase': 0, 'num_glBindBufferRange': 0,
|
|
284
|
+
'used_by_draws': 0,
|
|
285
|
+
'used_as_vbo': 0, 'used_as_ibo': 0, 'used_as_ubo': 0,
|
|
286
|
+
'used_as_ssbo': 0, 'used_as_indirect': 0,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
def _ensure_sampler(self, sid: int) -> dict:
|
|
290
|
+
return self.samplers.setdefault(sid, {
|
|
291
|
+
'sampler_id': sid, 'min_filter': '', 'mag_filter': '',
|
|
292
|
+
'wrap_s': '', 'wrap_t': '', 'wrap_r': '',
|
|
293
|
+
'mip_min_lod': -1000.0, 'mip_max_lod': 1000.0, 'mip_lod_bias': 0.0,
|
|
294
|
+
'max_anisotropy': 1, 'compare_mode': '', 'compare_func': '',
|
|
295
|
+
'border_color_r': 0.0, 'border_color_g': 0.0,
|
|
296
|
+
'border_color_b': 0.0, 'border_color_a': 0.0,
|
|
297
|
+
'created_at_event': -1, 'bound_to_draw_count': 0, 'label': '',
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
def _ensure_fbo(self, fid: int) -> dict:
|
|
301
|
+
return self.fbos.setdefault(fid, {
|
|
302
|
+
'fbo_id': fid, 'attachment_point': '', 'kind': '',
|
|
303
|
+
'resource_id': 0, 'format': '',
|
|
304
|
+
'width': 0, 'height': 0, 'sample_count': 0,
|
|
305
|
+
'mip_level': 0, 'layer': 0, 'created_at_event': -1,
|
|
306
|
+
'bound_at_events': '',
|
|
307
|
+
'num_clears': 0, 'num_writes': 0, 'num_reads': 0, 'label': '',
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _handle_create_shader(acc: _Acc, body: str, cidx: int) -> None:
|
|
312
|
+
rids = _resids(body)
|
|
313
|
+
enums = _enums(body)
|
|
314
|
+
sid = rids.get('real') or rids.get('shader') or 0
|
|
315
|
+
if not sid:
|
|
316
|
+
return
|
|
317
|
+
type_enum = enums.get('type')
|
|
318
|
+
kind_full = type_enum[1] if type_enum else ''
|
|
319
|
+
type_int = type_enum[0] if type_enum else 0
|
|
320
|
+
short = _GL_SHADER_TYPE.get(type_int, '')
|
|
321
|
+
row = acc._ensure_shader(sid)
|
|
322
|
+
row['shader_type'] = short or kind_full
|
|
323
|
+
acc.resource_creation.append({
|
|
324
|
+
'resource_id': sid, 'resource_kind': 'shader',
|
|
325
|
+
'created_at_event': -1, 'creation_chunk': 'glCreateShader',
|
|
326
|
+
'declared_label': '',
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _handle_shader_source(acc: _Acc, body: str, cidx: int) -> None:
|
|
331
|
+
rids = _resids(body)
|
|
332
|
+
sid = rids.get('shader') or rids.get('shaderId') or 0
|
|
333
|
+
if not sid:
|
|
334
|
+
return
|
|
335
|
+
m = _RE_SHADER_SRC.search(body)
|
|
336
|
+
source = m.group(1) if m else ''
|
|
337
|
+
if not source:
|
|
338
|
+
return
|
|
339
|
+
acc.shader_sources[sid] = source
|
|
340
|
+
row = acc._ensure_shader(sid)
|
|
341
|
+
row['src_len'] = len(source)
|
|
342
|
+
row['src_hash'] = hashlib.sha256(source.encode('utf-8')).hexdigest()[:16]
|
|
343
|
+
row.update(shader_complexity(source))
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _handle_create_program(acc: _Acc, body: str, cidx: int) -> None:
|
|
347
|
+
rids = _resids(body)
|
|
348
|
+
pid = rids.get('real') or rids.get('program') or 0
|
|
349
|
+
if not pid:
|
|
350
|
+
return
|
|
351
|
+
acc._ensure_program(pid)
|
|
352
|
+
acc.resource_creation.append({
|
|
353
|
+
'resource_id': pid, 'resource_kind': 'program',
|
|
354
|
+
'created_at_event': -1, 'creation_chunk': 'glCreateProgram',
|
|
355
|
+
'declared_label': '',
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _handle_attach_shader(acc: _Acc, body: str, cidx: int) -> None:
|
|
360
|
+
rids = _resids(body)
|
|
361
|
+
pid = rids.get('program') or 0
|
|
362
|
+
sid = rids.get('shader') or 0
|
|
363
|
+
if not pid or not sid:
|
|
364
|
+
return
|
|
365
|
+
prog = acc._ensure_program(pid)
|
|
366
|
+
existing = [int(x) for x in prog['attached_shader_ids'].split(';') if x]
|
|
367
|
+
if sid not in existing:
|
|
368
|
+
existing.append(sid)
|
|
369
|
+
prog['attached_shader_ids'] = ';'.join(str(x) for x in existing)
|
|
370
|
+
prog['num_attached_shaders'] = len(existing)
|
|
371
|
+
sh = acc._ensure_shader(sid)
|
|
372
|
+
sh_type = sh.get('shader_type', '')
|
|
373
|
+
slot_map = {
|
|
374
|
+
'vertex': 'vs_shader_id', 'fragment': 'fs_shader_id',
|
|
375
|
+
'compute': 'cs_shader_id', 'geometry': 'gs_shader_id',
|
|
376
|
+
'tess_control': 'tcs_shader_id', 'tess_evaluation': 'tes_shader_id',
|
|
377
|
+
}
|
|
378
|
+
if sh_type in slot_map:
|
|
379
|
+
prog[slot_map[sh_type]] = sid
|
|
380
|
+
linked = [int(x) for x in sh['linked_program_ids'].split(';') if x]
|
|
381
|
+
if pid not in linked:
|
|
382
|
+
linked.append(pid)
|
|
383
|
+
sh['linked_program_ids'] = ';'.join(str(x) for x in linked)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _handle_link_program(acc: _Acc, body: str, cidx: int) -> None:
|
|
387
|
+
rids = _resids(body)
|
|
388
|
+
pid = rids.get('program') or 0
|
|
389
|
+
if pid:
|
|
390
|
+
acc._ensure_program(pid)['linked'] = 1
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _handle_gen_resources(acc: _Acc, body: str, cidx: int, chunk_name: str) -> None:
|
|
394
|
+
kind = _RESOURCE_GEN_CHUNKS[chunk_name]
|
|
395
|
+
for m in _RE_RESID.finditer(body):
|
|
396
|
+
rid = int(m.group(2))
|
|
397
|
+
if rid == 0:
|
|
398
|
+
continue
|
|
399
|
+
acc.resource_creation.append({
|
|
400
|
+
'resource_id': rid, 'resource_kind': kind,
|
|
401
|
+
'created_at_event': -1, 'creation_chunk': chunk_name,
|
|
402
|
+
'declared_label': '',
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _handle_tex_storage(acc: _Acc, body: str, cidx: int, chunk_name: str) -> None:
|
|
407
|
+
rids = _resids(body)
|
|
408
|
+
tid = rids.get('textureId') or rids.get('texture') or rids.get('target') or 0
|
|
409
|
+
if not tid:
|
|
410
|
+
return
|
|
411
|
+
ints = _ints(body)
|
|
412
|
+
enums = _enums(body)
|
|
413
|
+
row = acc._ensure_texture(tid)
|
|
414
|
+
fmt = enums.get('internalformat')
|
|
415
|
+
if fmt:
|
|
416
|
+
row['format'] = fmt[1]
|
|
417
|
+
row['width'] = ints.get('width', row['width'])
|
|
418
|
+
row['height'] = ints.get('height', row['height'])
|
|
419
|
+
row['depth'] = ints.get('depth', row['depth'])
|
|
420
|
+
row['mip_levels'] = ints.get('levels', row['mip_levels']) or 1
|
|
421
|
+
row['sample_count'] = ints.get('samples', row['sample_count']) or 1
|
|
422
|
+
target = enums.get('target')
|
|
423
|
+
if target:
|
|
424
|
+
row['kind'] = target[1].replace('GL_TEXTURE_', 'tex_').lower()
|
|
425
|
+
if chunk_name.startswith('glTexStorage'):
|
|
426
|
+
row['kind'] = row['kind'] or 'tex_2d'
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _handle_buffer_data(acc: _Acc, body: str, cidx: int) -> None:
|
|
430
|
+
rids = _resids(body)
|
|
431
|
+
bid = rids.get('bufferId') or rids.get('buffer') or 0
|
|
432
|
+
if not bid:
|
|
433
|
+
return
|
|
434
|
+
row = acc._ensure_buffer(bid)
|
|
435
|
+
ints = _ints(body)
|
|
436
|
+
enums = _enums(body)
|
|
437
|
+
sz = ints.get('bytesize') or ints.get('size') or 0
|
|
438
|
+
if sz > row['allocated_size_bytes']:
|
|
439
|
+
row['allocated_size_bytes'] = sz
|
|
440
|
+
usage = enums.get('usage')
|
|
441
|
+
if usage:
|
|
442
|
+
row['usage_hint'] = usage[1]
|
|
443
|
+
target = enums.get('target')
|
|
444
|
+
if target:
|
|
445
|
+
th = [t for t in row['target_history'].split(';') if t]
|
|
446
|
+
if target[1] not in th:
|
|
447
|
+
th.append(target[1])
|
|
448
|
+
row['target_history'] = ';'.join(th)
|
|
449
|
+
row['num_glBufferData'] += 1
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _handle_sampler_parameter(acc: _Acc, body: str, cidx: int, chunk_name: str) -> None:
|
|
453
|
+
rids = _resids(body)
|
|
454
|
+
sid = rids.get('sampler') or 0
|
|
455
|
+
if not sid:
|
|
456
|
+
return
|
|
457
|
+
row = acc._ensure_sampler(sid)
|
|
458
|
+
enums = _enums(body)
|
|
459
|
+
pname = enums.get('pname')
|
|
460
|
+
if not pname:
|
|
461
|
+
return
|
|
462
|
+
pn = pname[1]
|
|
463
|
+
param_enum = enums.get('param')
|
|
464
|
+
ints = _ints(body)
|
|
465
|
+
pval_int = ints.get('param')
|
|
466
|
+
pval_float = None
|
|
467
|
+
if 'param' in body and 'float' in body:
|
|
468
|
+
m = re.search(r'<float\s+name="param"[^>]*>(-?\d*\.?\d+(?:[eE][+-]?\d+)?)</float>', body)
|
|
469
|
+
if m:
|
|
470
|
+
pval_float = float(m.group(1))
|
|
471
|
+
pstr = param_enum[1] if param_enum else (str(pval_int) if pval_int is not None else '')
|
|
472
|
+
if pn == 'GL_TEXTURE_MIN_FILTER': row['min_filter'] = pstr
|
|
473
|
+
elif pn == 'GL_TEXTURE_MAG_FILTER': row['mag_filter'] = pstr
|
|
474
|
+
elif pn == 'GL_TEXTURE_WRAP_S': row['wrap_s'] = pstr
|
|
475
|
+
elif pn == 'GL_TEXTURE_WRAP_T': row['wrap_t'] = pstr
|
|
476
|
+
elif pn == 'GL_TEXTURE_WRAP_R': row['wrap_r'] = pstr
|
|
477
|
+
elif pn == 'GL_TEXTURE_MIN_LOD': row['mip_min_lod'] = pval_float if pval_float is not None else (pval_int or 0)
|
|
478
|
+
elif pn == 'GL_TEXTURE_MAX_LOD': row['mip_max_lod'] = pval_float if pval_float is not None else (pval_int or 0)
|
|
479
|
+
elif pn == 'GL_TEXTURE_LOD_BIAS': row['mip_lod_bias'] = pval_float if pval_float is not None else (pval_int or 0)
|
|
480
|
+
elif pn == 'GL_TEXTURE_MAX_ANISOTROPY' or pn == 'GL_TEXTURE_MAX_ANISOTROPY_EXT':
|
|
481
|
+
row['max_anisotropy'] = int(pval_float if pval_float is not None else (pval_int or 1))
|
|
482
|
+
elif pn == 'GL_TEXTURE_COMPARE_MODE': row['compare_mode'] = pstr
|
|
483
|
+
elif pn == 'GL_TEXTURE_COMPARE_FUNC': row['compare_func'] = pstr
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _handle_label(acc: _Acc, body: str) -> None:
|
|
487
|
+
rids = _resids(body)
|
|
488
|
+
rid = rids.get('Resource') or rids.get('resource') or 0
|
|
489
|
+
if not rid:
|
|
490
|
+
return
|
|
491
|
+
strs = _strings(body)
|
|
492
|
+
label = strs.get('Label') or strs.get('label') or ''
|
|
493
|
+
if label:
|
|
494
|
+
acc.labels[rid] = label
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# --- Driver ------------------------------------------------------------------
|
|
498
|
+
|
|
499
|
+
_CHUNK_HANDLERS = {
|
|
500
|
+
'glCreateShader': _handle_create_shader,
|
|
501
|
+
'glShaderSource': _handle_shader_source,
|
|
502
|
+
'glCreateProgram': _handle_create_program,
|
|
503
|
+
'glAttachShader': _handle_attach_shader,
|
|
504
|
+
'glLinkProgram': _handle_link_program,
|
|
505
|
+
'glBufferData': _handle_buffer_data,
|
|
506
|
+
'glBufferStorageEXT': _handle_buffer_data,
|
|
507
|
+
'glLabelObjectEXT': lambda acc, body, cidx: _handle_label(acc, body),
|
|
508
|
+
'glObjectLabel': lambda acc, body, cidx: _handle_label(acc, body),
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def parse(xml_path: str) -> _Acc:
|
|
513
|
+
acc = _Acc()
|
|
514
|
+
for cid, cidx, cname, body in iter_chunks(xml_path):
|
|
515
|
+
if cidx > acc.max_chunk_index:
|
|
516
|
+
acc.max_chunk_index = cidx
|
|
517
|
+
if cname in _RESOURCE_GEN_CHUNKS:
|
|
518
|
+
_handle_gen_resources(acc, body, cidx, cname)
|
|
519
|
+
elif cname.startswith('glTexStorage') or cname.startswith('glTexImage'):
|
|
520
|
+
_handle_tex_storage(acc, body, cidx, cname)
|
|
521
|
+
elif cname.startswith('glSamplerParameter'):
|
|
522
|
+
_handle_sampler_parameter(acc, body, cidx, cname)
|
|
523
|
+
elif cname in _CHUNK_HANDLERS:
|
|
524
|
+
_CHUNK_HANDLERS[cname](acc, body, cidx)
|
|
525
|
+
# apply labels to entity rows
|
|
526
|
+
for rid, label in acc.labels.items():
|
|
527
|
+
if rid in acc.textures:
|
|
528
|
+
acc.textures[rid]['label'] = label
|
|
529
|
+
if rid in acc.buffers:
|
|
530
|
+
acc.buffers[rid]['target_history'] = acc.buffers[rid]['target_history'] # noop
|
|
531
|
+
if rid in acc.programs:
|
|
532
|
+
acc.programs[rid]['label'] = label
|
|
533
|
+
if rid in acc.samplers:
|
|
534
|
+
acc.samplers[rid]['label'] = label
|
|
535
|
+
if rid in acc.fbos:
|
|
536
|
+
acc.fbos[rid]['label'] = label
|
|
537
|
+
return acc
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# --- CSV writers (stage layout) ---------------------------------------------
|
|
541
|
+
|
|
542
|
+
def _open_csv(path: str, fieldnames: list[str]):
|
|
543
|
+
f = open(path, 'w', encoding='utf-8', newline='')
|
|
544
|
+
w = csv.DictWriter(f, fieldnames=fieldnames)
|
|
545
|
+
w.writeheader()
|
|
546
|
+
return f, w
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _id_cols(ctx: dict) -> dict:
|
|
550
|
+
return {
|
|
551
|
+
'area': ctx['area'], 'drop_date': ctx['drop_date'],
|
|
552
|
+
'drop_label': ctx['drop_label'], 'capture': ctx['capture'],
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def write_outputs(acc: _Acc, ctx: dict, capture_stage: str) -> dict[str, int]:
|
|
557
|
+
"""Write CSVs + shader source files under capture_stage. Returns row counts."""
|
|
558
|
+
os.makedirs(capture_stage, exist_ok=True)
|
|
559
|
+
shader_src_dir = os.path.join(capture_stage, 'shader_src')
|
|
560
|
+
os.makedirs(shader_src_dir, exist_ok=True)
|
|
561
|
+
|
|
562
|
+
rc_path = os.path.join(capture_stage, 'resource_creation.csv')
|
|
563
|
+
sh_path = os.path.join(capture_stage, 'shaders.csv')
|
|
564
|
+
pr_path = os.path.join(capture_stage, 'programs.csv')
|
|
565
|
+
tx_path = os.path.join(capture_stage, 'textures.csv')
|
|
566
|
+
bf_path = os.path.join(capture_stage, 'buffers.csv')
|
|
567
|
+
sm_path = os.path.join(capture_stage, 'samplers.csv')
|
|
568
|
+
fb_path = os.path.join(capture_stage, 'fbos.csv')
|
|
569
|
+
|
|
570
|
+
counts: dict[str, int] = {}
|
|
571
|
+
|
|
572
|
+
# write shader source files first so we can put paths in shaders.csv
|
|
573
|
+
src_path_by_id: dict[int, str] = {}
|
|
574
|
+
for sid, source in acc.shader_sources.items():
|
|
575
|
+
rel = os.path.join('shader_src', f'{sid}.glsl')
|
|
576
|
+
full = os.path.join(capture_stage, rel)
|
|
577
|
+
with open(full, 'w', encoding='utf-8') as f:
|
|
578
|
+
f.write(source)
|
|
579
|
+
src_path_by_id[sid] = rel
|
|
580
|
+
|
|
581
|
+
id_cols = _id_cols(ctx)
|
|
582
|
+
|
|
583
|
+
from .. import schemas
|
|
584
|
+
RESOURCE_CREATION_FIELDS = list(schemas.RESOURCE_CREATION_COLS)
|
|
585
|
+
SHADERS_FIELDS = list(schemas.SHADERS_COLS)
|
|
586
|
+
PROGRAMS_FIELDS = list(schemas.PROGRAMS_COLS)
|
|
587
|
+
TEXTURES_FIELDS = list(schemas.TEXTURES_COLS)
|
|
588
|
+
BUFFERS_FIELDS = list(schemas.BUFFERS_COLS)
|
|
589
|
+
SAMPLERS_FIELDS = list(schemas.SAMPLERS_COLS)
|
|
590
|
+
FBOS_FIELDS = list(schemas.FBOS_COLS)
|
|
591
|
+
|
|
592
|
+
# resource_creation
|
|
593
|
+
f, w = _open_csv(rc_path, RESOURCE_CREATION_FIELDS)
|
|
594
|
+
n = 0
|
|
595
|
+
for r in acc.resource_creation:
|
|
596
|
+
row = {**id_cols, **r}
|
|
597
|
+
row['declared_label'] = acc.labels.get(r['resource_id'], '')
|
|
598
|
+
w.writerow(row); n += 1
|
|
599
|
+
f.close(); counts['resource_creation'] = n
|
|
600
|
+
|
|
601
|
+
# shaders
|
|
602
|
+
f, w = _open_csv(sh_path, SHADERS_FIELDS)
|
|
603
|
+
n = 0
|
|
604
|
+
for sid, r in sorted(acc.shaders.items()):
|
|
605
|
+
r = dict(r)
|
|
606
|
+
r['src_file_path'] = src_path_by_id.get(sid, '')
|
|
607
|
+
w.writerow({**id_cols, 'stable_key': '', **r}); n += 1
|
|
608
|
+
f.close(); counts['shaders'] = n
|
|
609
|
+
|
|
610
|
+
# programs
|
|
611
|
+
f, w = _open_csv(pr_path, PROGRAMS_FIELDS)
|
|
612
|
+
n = 0
|
|
613
|
+
for pid, r in sorted(acc.programs.items()):
|
|
614
|
+
r = dict(r)
|
|
615
|
+
r['label'] = acc.labels.get(pid, '')
|
|
616
|
+
w.writerow({**id_cols, 'stable_key': '', **r}); n += 1
|
|
617
|
+
f.close(); counts['programs'] = n
|
|
618
|
+
|
|
619
|
+
# textures
|
|
620
|
+
f, w = _open_csv(tx_path, TEXTURES_FIELDS)
|
|
621
|
+
n = 0
|
|
622
|
+
for tid, r in sorted(acc.textures.items()):
|
|
623
|
+
r = dict(r)
|
|
624
|
+
r['label'] = acc.labels.get(tid, '')
|
|
625
|
+
w.writerow({**id_cols, 'stable_key': '', **r}); n += 1
|
|
626
|
+
f.close(); counts['textures'] = n
|
|
627
|
+
|
|
628
|
+
# buffers
|
|
629
|
+
f, w = _open_csv(bf_path, BUFFERS_FIELDS)
|
|
630
|
+
n = 0
|
|
631
|
+
for bid, r in sorted(acc.buffers.items()):
|
|
632
|
+
w.writerow({**id_cols, 'stable_key': '', **r}); n += 1
|
|
633
|
+
f.close(); counts['buffers'] = n
|
|
634
|
+
|
|
635
|
+
# samplers
|
|
636
|
+
f, w = _open_csv(sm_path, SAMPLERS_FIELDS)
|
|
637
|
+
n = 0
|
|
638
|
+
for sid, r in sorted(acc.samplers.items()):
|
|
639
|
+
r = dict(r)
|
|
640
|
+
r['label'] = acc.labels.get(sid, '')
|
|
641
|
+
w.writerow({**id_cols, 'stable_key': '', **r}); n += 1
|
|
642
|
+
f.close(); counts['samplers'] = n
|
|
643
|
+
|
|
644
|
+
# fbos (only ones we accumulated; mostly populated by replay_main)
|
|
645
|
+
f, w = _open_csv(fb_path, FBOS_FIELDS)
|
|
646
|
+
n = 0
|
|
647
|
+
for fid, r in sorted(acc.fbos.items()):
|
|
648
|
+
w.writerow({**id_cols, 'stable_key': '', **r}); n += 1
|
|
649
|
+
f.close(); counts['fbos'] = n
|
|
650
|
+
|
|
651
|
+
# labels.json as a sidecar so replay_main can pick up labels for entities
|
|
652
|
+
# it discovers (e.g. RTs not seen as raw textures at parse time).
|
|
653
|
+
with open(os.path.join(capture_stage, 'labels.json'), 'w', encoding='utf-8') as f:
|
|
654
|
+
json.dump({str(k): v for k, v in acc.labels.items()}, f)
|
|
655
|
+
|
|
656
|
+
return counts
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def main(argv: list[str]) -> int:
|
|
660
|
+
if len(argv) < 6:
|
|
661
|
+
print('usage: parse_init_state.py <zip_xml_path> <capture_stage> <area> <drop_date> <drop_label> <capture>',
|
|
662
|
+
file=sys.stderr)
|
|
663
|
+
return 2
|
|
664
|
+
xml_path, capture_stage, area, drop_date, drop_label, capture = argv[:6]
|
|
665
|
+
ctx = {'area': area, 'drop_date': drop_date, 'drop_label': drop_label, 'capture': capture}
|
|
666
|
+
t0 = time.monotonic()
|
|
667
|
+
acc = parse(xml_path)
|
|
668
|
+
counts = write_outputs(acc, ctx, capture_stage)
|
|
669
|
+
elapsed = time.monotonic() - t0
|
|
670
|
+
print(f'parse_init_state: {os.path.basename(xml_path)} -> {sum(counts.values())} rows in {elapsed:.1f}s; per-table {counts}')
|
|
671
|
+
return 0
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
if __name__ == '__main__':
|
|
675
|
+
sys.exit(main(sys.argv[1:]))
|