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,190 @@
|
|
|
1
|
+
"""Per-area pass GPU breakdown. Stacked bars colored by draw_class.
|
|
2
|
+
|
|
3
|
+
Aggregation key: (area, marker_path_norm). Captures + classes summed.
|
|
4
|
+
Bar widths normalized within each area (share-of-area-total).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
import pyarrow.parquet as papq
|
|
14
|
+
|
|
15
|
+
from . import base
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _aggregate(drops: list, ok_caps: set) -> dict:
|
|
19
|
+
"""Return {area: {marker: {drop_key: {'gpu': sum, 'draws': sum,
|
|
20
|
+
'verts': sum,
|
|
21
|
+
'class_gpu': {cls: sum}}}}}."""
|
|
22
|
+
out: dict = defaultdict(lambda: defaultdict(lambda: defaultdict(
|
|
23
|
+
lambda: {'gpu': 0.0, 'draws': 0, 'verts': 0,
|
|
24
|
+
'class_gpu': defaultdict(float)})))
|
|
25
|
+
for d in drops:
|
|
26
|
+
drop_key = d.key
|
|
27
|
+
for r in d.rows:
|
|
28
|
+
p = os.path.join(r.drop_dir, 'pass_class_breakdown.parquet')
|
|
29
|
+
if not os.path.exists(p):
|
|
30
|
+
continue
|
|
31
|
+
try:
|
|
32
|
+
t = papq.read_table(p)
|
|
33
|
+
except Exception:
|
|
34
|
+
continue
|
|
35
|
+
cols = {c: t.column(c).to_pylist() for c in t.column_names}
|
|
36
|
+
for i in range(t.num_rows):
|
|
37
|
+
cap = cols['capture'][i]
|
|
38
|
+
key = (r.area, r.drop_date, r.drop_label, cap)
|
|
39
|
+
if ok_caps and key not in ok_caps:
|
|
40
|
+
continue
|
|
41
|
+
marker = cols['marker_path_norm'][i] or ''
|
|
42
|
+
cls = cols['draw_class'][i] or 'other'
|
|
43
|
+
gpu = cols['sum_gpu_duration_s'][i] or 0.0
|
|
44
|
+
ndr = cols['n_draws'][i] or 0
|
|
45
|
+
verts = cols['sum_pre_vs_vertices'][i] or 0
|
|
46
|
+
bucket = out[r.area][marker][drop_key]
|
|
47
|
+
bucket['gpu'] += gpu
|
|
48
|
+
bucket['draws'] += ndr
|
|
49
|
+
bucket['verts'] += verts
|
|
50
|
+
bucket['class_gpu'][cls] += gpu
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _drop_dir_for_area(drops: list, drop_key: str, area: str) -> str:
|
|
55
|
+
for d in drops:
|
|
56
|
+
if d.key != drop_key:
|
|
57
|
+
continue
|
|
58
|
+
for r in d.rows:
|
|
59
|
+
if r.area == area:
|
|
60
|
+
return r.drop_dir
|
|
61
|
+
return ''
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build(root: str, *, drops: list | None = None, ab=None) -> str:
|
|
65
|
+
if drops is None:
|
|
66
|
+
drops = base.discover_drops(root)
|
|
67
|
+
out_path = base.output_path(root, 'pass_gpu', ab)
|
|
68
|
+
out_dir = os.path.dirname(out_path)
|
|
69
|
+
|
|
70
|
+
ok_caps = base.ok_capture_set(root)
|
|
71
|
+
data = _aggregate(drops, ok_caps)
|
|
72
|
+
drop_keys = [d.key for d in drops]
|
|
73
|
+
|
|
74
|
+
parts = [base.page_open('pass gpu', hdr_offset_px=120)]
|
|
75
|
+
parts.append(base.header(
|
|
76
|
+
'pass gpu',
|
|
77
|
+
drops=len(drops),
|
|
78
|
+
captures=sum(d.n_captures for d in drops),
|
|
79
|
+
build_ts=base.now_iso(),
|
|
80
|
+
crumb_depth=base.crumb_depth(ab),
|
|
81
|
+
))
|
|
82
|
+
parts.append(base.ab_strip(ab))
|
|
83
|
+
parts.append(base.ab_picker_for(root, 'pass_gpu', ab=ab))
|
|
84
|
+
|
|
85
|
+
# Summary bar: top pass globally + area count
|
|
86
|
+
if data:
|
|
87
|
+
global_top = None
|
|
88
|
+
global_top_gpu = 0.0
|
|
89
|
+
for area, markers in data.items():
|
|
90
|
+
for marker, drop_buckets in markers.items():
|
|
91
|
+
mg = max((b['gpu'] for b in drop_buckets.values()), default=0.0)
|
|
92
|
+
if mg > global_top_gpu:
|
|
93
|
+
global_top_gpu = mg
|
|
94
|
+
global_top = (area, base.pass_suffix(marker) or marker)
|
|
95
|
+
if global_top is not None:
|
|
96
|
+
area, marker_short = global_top
|
|
97
|
+
parts.append(base.summary_bar(
|
|
98
|
+
'top pass',
|
|
99
|
+
f'{area} / {marker_short}',
|
|
100
|
+
sub=f'across {len(data)} areas',
|
|
101
|
+
link_href=f'#{base.h(area)}',
|
|
102
|
+
link_text='area',
|
|
103
|
+
tone='neutral',
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
parts.append(base.legend())
|
|
107
|
+
|
|
108
|
+
if not data:
|
|
109
|
+
parts.append('<p class="note">no pass_class_breakdown data found</p>')
|
|
110
|
+
else:
|
|
111
|
+
for area in sorted(data.keys()):
|
|
112
|
+
markers = data[area]
|
|
113
|
+
ranked = sorted(
|
|
114
|
+
markers.items(),
|
|
115
|
+
key=lambda kv: max((b['gpu'] for b in kv[1].values()), default=0.0),
|
|
116
|
+
reverse=True,
|
|
117
|
+
)[:20]
|
|
118
|
+
area_total = sum(
|
|
119
|
+
max((b['gpu'] for b in m.values()), default=0.0)
|
|
120
|
+
for _, m in ranked
|
|
121
|
+
) or 1.0
|
|
122
|
+
|
|
123
|
+
area_body = []
|
|
124
|
+
for marker, drop_buckets in ranked:
|
|
125
|
+
max_gpu = max((b['gpu'] for b in drop_buckets.values()), default=0.0)
|
|
126
|
+
latest_bucket = drop_buckets.get(drop_keys[-1], {})
|
|
127
|
+
latest_class_gpu = dict(latest_bucket.get('class_gpu', {}))
|
|
128
|
+
latest_gpu = latest_bucket.get('gpu', 0.0)
|
|
129
|
+
latest_draws = latest_bucket.get('draws', 0)
|
|
130
|
+
latest_verts = latest_bucket.get('verts', 0)
|
|
131
|
+
|
|
132
|
+
bar_total = latest_gpu if latest_gpu > 0 else max_gpu
|
|
133
|
+
bar_weights = latest_class_gpu
|
|
134
|
+
if bar_total <= 0:
|
|
135
|
+
bar_weights = {'other': 1.0}
|
|
136
|
+
bar_total = 1.0
|
|
137
|
+
|
|
138
|
+
rep_drop = drop_keys[-1] if latest_gpu > 0 else next(
|
|
139
|
+
(k for k in drop_keys if drop_buckets.get(k, {}).get('gpu', 0) > 0),
|
|
140
|
+
drop_keys[-1] if drop_keys else ''
|
|
141
|
+
)
|
|
142
|
+
drop_dir = _drop_dir_for_area(drops, rep_drop, area)
|
|
143
|
+
link = base.rel_path_to_drop_index(out_dir, drop_dir, 'passes') if drop_dir else '#'
|
|
144
|
+
|
|
145
|
+
pct_share = (max_gpu / area_total) * 100.0 if area_total > 0 else 0.0
|
|
146
|
+
|
|
147
|
+
area_body.append('<div class="bar-row">')
|
|
148
|
+
short = base.pass_short(marker)
|
|
149
|
+
if len(short) > 60:
|
|
150
|
+
short = base.trunc_left(short, 60)
|
|
151
|
+
area_body.append(
|
|
152
|
+
f'<span class="key" title="{base.h(marker)}">'
|
|
153
|
+
f'<a href="{base.h(link)}" data-link-kind="drill">'
|
|
154
|
+
f'{base.safe_chrome_text(short)}</a></span>'
|
|
155
|
+
)
|
|
156
|
+
bar_html = base.class_segments_bar(bar_weights, bar_total)
|
|
157
|
+
area_body.append(f'<div style="width: {pct_share:.2f}%;">{bar_html}</div>')
|
|
158
|
+
area_body.append(f'<span class="total">{pct_share:.1f}%</span>')
|
|
159
|
+
area_body.append('</div>')
|
|
160
|
+
|
|
161
|
+
if len(drop_keys) >= 2:
|
|
162
|
+
cells = ['<div class="bar-row">',
|
|
163
|
+
'<span class="key" style="color: var(--text-2)">drops</span>',
|
|
164
|
+
'<div style="display:flex;gap:var(--sp-3);font:var(--fs-small) ui-monospace,monospace;align-items:center;color:var(--text-2)">']
|
|
165
|
+
prev_gpu = None
|
|
166
|
+
for i, k in enumerate(drop_keys):
|
|
167
|
+
b = drop_buckets.get(k, {})
|
|
168
|
+
g = b.get('gpu', 0.0)
|
|
169
|
+
cells.append(f'<span>{base.h(k)}: {base.fmt_float(g, 3)}</span>')
|
|
170
|
+
if i > 0:
|
|
171
|
+
cells.append(base.delta_pill(
|
|
172
|
+
g, prev_gpu,
|
|
173
|
+
lower_is_better=True, fmt='{:+,.3f}'))
|
|
174
|
+
prev_gpu = g
|
|
175
|
+
cells.append('</div>')
|
|
176
|
+
cells.append(f'<span class="total" style="color:var(--text-3)">'
|
|
177
|
+
f'd={base.fmt_int(latest_draws)} v={base.fmt_int(latest_verts)}</span>')
|
|
178
|
+
cells.append('</div>')
|
|
179
|
+
area_body.append(''.join(cells))
|
|
180
|
+
|
|
181
|
+
parts.append(f'<h2 id="{base.h(area)}">{base.h(area)}</h2>')
|
|
182
|
+
parts.append(''.join(area_body))
|
|
183
|
+
|
|
184
|
+
parts.append(base.page_close())
|
|
185
|
+
|
|
186
|
+
return base.write_report(out_path, parts)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == '__main__':
|
|
190
|
+
sys.exit(base.run_report(build, module_name='pass_gpu'))
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Top fragment shaders by complexity * uses.
|
|
2
|
+
|
|
3
|
+
Reads _reports/_cache/shader_summary_per_drop.parquet when present; falls
|
|
4
|
+
back to live scan of **/shaders.parquet.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from collections import Counter, defaultdict
|
|
12
|
+
|
|
13
|
+
import pyarrow.parquet as papq
|
|
14
|
+
|
|
15
|
+
from . import base
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_SHADER_COLS = [
|
|
19
|
+
'area', 'drop_date', 'drop_label', 'capture', 'shader_id', 'stable_key',
|
|
20
|
+
'shader_type', 'src_len', 'complexity_score', 'total_branches',
|
|
21
|
+
'total_loops', 'total_discards', 'total_dfdx_dfdy',
|
|
22
|
+
'total_texture_samples', 'used_by_draw_count', 'src_file_path',
|
|
23
|
+
'fb_fetch', 'uses_cubemap',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _iter_shaders(root: str, drops: list):
|
|
28
|
+
cache = base.cache_path(root, 'shader_summary')
|
|
29
|
+
if os.path.exists(cache):
|
|
30
|
+
try:
|
|
31
|
+
t = papq.read_table(cache)
|
|
32
|
+
cols = {c: t.column(c).to_pylist() for c in t.column_names}
|
|
33
|
+
wanted = {(d.date, d.label) for d in drops}
|
|
34
|
+
for i in range(t.num_rows):
|
|
35
|
+
if (cols['drop_date'][i], cols['drop_label'][i]) not in wanted:
|
|
36
|
+
continue
|
|
37
|
+
yield {c: cols[c][i] for c in cols}
|
|
38
|
+
return
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
for d in drops:
|
|
42
|
+
for r in d.rows:
|
|
43
|
+
p = os.path.join(r.drop_dir, 'shaders.parquet')
|
|
44
|
+
if not os.path.exists(p):
|
|
45
|
+
continue
|
|
46
|
+
schema_cols = set(papq.read_schema(p).names)
|
|
47
|
+
want = [c for c in _SHADER_COLS if c in schema_cols]
|
|
48
|
+
try:
|
|
49
|
+
t = papq.read_table(p, columns=want)
|
|
50
|
+
except Exception:
|
|
51
|
+
continue
|
|
52
|
+
cols = {c: t.column(c).to_pylist() for c in t.column_names}
|
|
53
|
+
for i in range(t.num_rows):
|
|
54
|
+
row = {c: cols[c][i] for c in cols}
|
|
55
|
+
row['drop_date'] = r.drop_date
|
|
56
|
+
row['drop_label'] = r.drop_label
|
|
57
|
+
yield row
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _drop_dir_first(drops: list, drop_date, drop_label) -> str:
|
|
61
|
+
for d in drops:
|
|
62
|
+
if d.date == drop_date and d.label == drop_label and d.rows:
|
|
63
|
+
return d.rows[0].drop_dir
|
|
64
|
+
return ''
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build(root: str, *, drops: list | None = None, ab=None,
|
|
68
|
+
stage: str = 'fragment') -> str:
|
|
69
|
+
if drops is None:
|
|
70
|
+
drops = base.discover_drops(root)
|
|
71
|
+
out_path = base.output_path(root, 'shader_hotlist', ab)
|
|
72
|
+
out_dir = os.path.dirname(out_path)
|
|
73
|
+
|
|
74
|
+
drop_keys = [d.key for d in drops]
|
|
75
|
+
|
|
76
|
+
per_key: dict = defaultdict(lambda: {
|
|
77
|
+
'uses_by_drop': Counter(),
|
|
78
|
+
'complexity': 0.0,
|
|
79
|
+
'branches': 0,
|
|
80
|
+
'loops': 0,
|
|
81
|
+
'discards': 0,
|
|
82
|
+
'dfdx_dfdy': 0,
|
|
83
|
+
'tex_samples': 0,
|
|
84
|
+
'src_len': 0,
|
|
85
|
+
'shader_type': '',
|
|
86
|
+
'rep_drop_date': None,
|
|
87
|
+
'rep_drop_label': None,
|
|
88
|
+
'rep_shader_id': 0,
|
|
89
|
+
'rep_src_path': '',
|
|
90
|
+
'rep_capture': '',
|
|
91
|
+
'fb_fetch': False,
|
|
92
|
+
'uses_cubemap': False,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
for row in _iter_shaders(root, drops):
|
|
96
|
+
stype = row.get('shader_type') or ''
|
|
97
|
+
if stage and stype != stage:
|
|
98
|
+
continue
|
|
99
|
+
sk = row.get('stable_key') or ''
|
|
100
|
+
if not sk:
|
|
101
|
+
continue
|
|
102
|
+
drop_key = f"{row['drop_date']}_{row['drop_label']}"
|
|
103
|
+
p = per_key[sk]
|
|
104
|
+
p['uses_by_drop'][drop_key] += int(row.get('used_by_draw_count') or 0)
|
|
105
|
+
p['complexity'] = max(p['complexity'], float(row.get('complexity_score') or 0))
|
|
106
|
+
p['branches'] = max(p['branches'], int(row.get('total_branches') or 0))
|
|
107
|
+
p['loops'] = max(p['loops'], int(row.get('total_loops') or 0))
|
|
108
|
+
p['discards'] = max(p['discards'], int(row.get('total_discards') or 0))
|
|
109
|
+
p['dfdx_dfdy'] = max(p['dfdx_dfdy'], int(row.get('total_dfdx_dfdy') or 0))
|
|
110
|
+
p['tex_samples'] = max(p['tex_samples'], int(row.get('total_texture_samples') or 0))
|
|
111
|
+
p['src_len'] = max(p['src_len'], int(row.get('src_len') or 0))
|
|
112
|
+
p['shader_type'] = stype
|
|
113
|
+
if row.get('fb_fetch'):
|
|
114
|
+
p['fb_fetch'] = True
|
|
115
|
+
if row.get('uses_cubemap'):
|
|
116
|
+
p['uses_cubemap'] = True
|
|
117
|
+
if p['rep_shader_id'] == 0 and row.get('shader_id'):
|
|
118
|
+
p['rep_drop_date'] = row['drop_date']
|
|
119
|
+
p['rep_drop_label'] = row['drop_label']
|
|
120
|
+
p['rep_shader_id'] = row.get('shader_id') or 0
|
|
121
|
+
p['rep_src_path'] = row.get('src_file_path') or ''
|
|
122
|
+
p['rep_capture'] = row.get('capture') or ''
|
|
123
|
+
|
|
124
|
+
ranked = []
|
|
125
|
+
for sk, p in per_key.items():
|
|
126
|
+
total_uses = sum(p['uses_by_drop'].values())
|
|
127
|
+
cost = p['complexity'] * total_uses
|
|
128
|
+
ranked.append((sk, p, total_uses, cost))
|
|
129
|
+
ranked.sort(key=lambda x: x[3], reverse=True)
|
|
130
|
+
ranked = ranked[:50]
|
|
131
|
+
|
|
132
|
+
max_cost = max((c for _, _, _, c in ranked), default=0.0)
|
|
133
|
+
|
|
134
|
+
parts = [base.page_open(f'shader hotlist ({stage})', hdr_offset_px=120)]
|
|
135
|
+
parts.append(base.header(
|
|
136
|
+
f'shader hotlist ({stage})',
|
|
137
|
+
drops=len(drops),
|
|
138
|
+
captures=sum(d.n_captures for d in drops),
|
|
139
|
+
build_ts=base.now_iso(),
|
|
140
|
+
crumb_depth=base.crumb_depth(ab),
|
|
141
|
+
))
|
|
142
|
+
parts.append(base.ab_strip(ab))
|
|
143
|
+
parts.append(base.ab_picker_for(root, 'shader_hotlist', ab=ab))
|
|
144
|
+
|
|
145
|
+
# Summary bar: top shader by cost proxy
|
|
146
|
+
if ranked:
|
|
147
|
+
sk, p, total_uses, cost = ranked[0]
|
|
148
|
+
label_top = f'{p["shader_type"][:4]}-cplx-{int(p["complexity"])}'
|
|
149
|
+
parts.append(base.summary_bar(
|
|
150
|
+
'top shader',
|
|
151
|
+
f'{label_top}',
|
|
152
|
+
sub=f'{base.fmt_int(total_uses)} uses across drops',
|
|
153
|
+
link_href='#shaders',
|
|
154
|
+
link_text='table',
|
|
155
|
+
tone='neutral',
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
sec = []
|
|
159
|
+
sec.append(f'<h2 id="shaders">top {stage} shaders by complexity * uses</h2>')
|
|
160
|
+
sec.append('<div class="table-wrap"><rdc-sortable-table data-default-sort="cost proxy" data-default-dir="desc">')
|
|
161
|
+
sec.append('<table class="report"><thead><tr>')
|
|
162
|
+
sec.append('<th>shader</th>')
|
|
163
|
+
sec.append('<th class="num">complexity</th>')
|
|
164
|
+
sec.append('<th class="num">uses total</th>')
|
|
165
|
+
single = len(drop_keys) == 1
|
|
166
|
+
for i, k in enumerate(drop_keys):
|
|
167
|
+
head = 'uses' if single else f'uses@{base.h(k)}'
|
|
168
|
+
sec.append(f'<th class="num">{head}</th>')
|
|
169
|
+
if i > 0:
|
|
170
|
+
latest = ' delta-latest' if i == len(drop_keys) - 1 else ''
|
|
171
|
+
sec.append(f'<th class="num{latest}">delta</th>')
|
|
172
|
+
if len(drop_keys) >= 3:
|
|
173
|
+
sec.append('<th class="num">trend</th>')
|
|
174
|
+
sec.extend([
|
|
175
|
+
'<th class="num">cost proxy</th>',
|
|
176
|
+
'<th class="num">branches</th>',
|
|
177
|
+
'<th class="num">loops</th>',
|
|
178
|
+
'<th class="num">tex samples</th>',
|
|
179
|
+
'<th class="num">src bytes</th>',
|
|
180
|
+
'<th>flags</th>',
|
|
181
|
+
'<th>src</th>',
|
|
182
|
+
'</tr></thead><tbody>',
|
|
183
|
+
])
|
|
184
|
+
|
|
185
|
+
for rank_i, (sk, p, total_uses, cost) in enumerate(ranked, 1):
|
|
186
|
+
sec.append('<tr>')
|
|
187
|
+
rp = base.rank_pill(rank_i) if rank_i <= 3 else ''
|
|
188
|
+
shader_label = f'{p["shader_type"][:4]}-cplx-{int(p["complexity"])}'
|
|
189
|
+
drop_dir = _drop_dir_first(drops, p['rep_drop_date'], p['rep_drop_label'])
|
|
190
|
+
src_link = base.rel_path_to_drop_file(out_dir, drop_dir, p['rep_src_path'])
|
|
191
|
+
if src_link:
|
|
192
|
+
sec.append(f'<td>{rp}<a href="{base.h(src_link)}" data-link-kind="inline" target="_blank" rel="noopener">{base.h(shader_label)}{base.icon("link-out")}</a></td>')
|
|
193
|
+
else:
|
|
194
|
+
sec.append(f'<td>{rp}{base.h(shader_label)}</td>')
|
|
195
|
+
sec.append(f'<td class="num">{base.fmt_float(p["complexity"], 2)}</td>')
|
|
196
|
+
sec.append(f'<td class="num">{base.fmt_int(total_uses)}</td>')
|
|
197
|
+
prev = None
|
|
198
|
+
series = []
|
|
199
|
+
for i, k in enumerate(drop_keys):
|
|
200
|
+
v = p['uses_by_drop'].get(k, 0)
|
|
201
|
+
series.append(v)
|
|
202
|
+
sec.append(f'<td class="num">{base.fmt_int(v)}</td>')
|
|
203
|
+
if i > 0:
|
|
204
|
+
sec.append(base.delta_cell(v, prev,
|
|
205
|
+
lower_is_better=None, fmt='{:+,.0f}',
|
|
206
|
+
regression_threshold_pct=None))
|
|
207
|
+
prev = v
|
|
208
|
+
if len(drop_keys) >= 3:
|
|
209
|
+
sec.append(f'<td class="num">{base.sparkline_svg(series)}</td>')
|
|
210
|
+
|
|
211
|
+
bar = base.inline_bar(cost, max_cost) if max_cost > 0 else ''
|
|
212
|
+
sec.append(f'<td class="num">{base.fmt_float(cost, 1)}{bar}</td>')
|
|
213
|
+
sec.append(f'<td class="num">{base.fmt_int(p["branches"])}</td>')
|
|
214
|
+
sec.append(f'<td class="num">{base.fmt_int(p["loops"])}</td>')
|
|
215
|
+
sec.append(f'<td class="num">{base.fmt_int(p["tex_samples"])}</td>')
|
|
216
|
+
sec.append(f'<td class="num">{base.fmt_int(p["src_len"])}</td>')
|
|
217
|
+
|
|
218
|
+
flags = []
|
|
219
|
+
if p['fb_fetch']:
|
|
220
|
+
flags.append('fb_fetch')
|
|
221
|
+
if p['uses_cubemap']:
|
|
222
|
+
flags.append('cubemap')
|
|
223
|
+
sec.append(f'<td>{base.h(",".join(flags))}</td>')
|
|
224
|
+
|
|
225
|
+
if src_link:
|
|
226
|
+
sec.append(f'<td><a href="{base.h(src_link)}" data-link-kind="inline" target="_blank" rel="noopener">{base.h(p["rep_src_path"])}{base.icon("file")}</a></td>')
|
|
227
|
+
else:
|
|
228
|
+
sec.append('<td></td>')
|
|
229
|
+
|
|
230
|
+
sec.append('</tr>')
|
|
231
|
+
sec.append('</tbody></table></rdc-sortable-table></div>')
|
|
232
|
+
parts.append(''.join(sec))
|
|
233
|
+
|
|
234
|
+
parts.append(base.page_close())
|
|
235
|
+
|
|
236
|
+
return base.write_report(out_path, parts)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == '__main__':
|
|
240
|
+
sys.exit(base.run_report(build, module_name='shader_hotlist'))
|