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.
Files changed (130) hide show
  1. bobframes/__init__.py +3 -0
  2. bobframes/_version.py +1 -0
  3. bobframes/catalog.py +154 -0
  4. bobframes/cli.py +266 -0
  5. bobframes/derive_post_merge.py +365 -0
  6. bobframes/derives/__init__.py +0 -0
  7. bobframes/derives/pass_class_breakdown.py +102 -0
  8. bobframes/derives/texture_usage.py +121 -0
  9. bobframes/discovery.py +132 -0
  10. bobframes/global_entities.py +99 -0
  11. bobframes/html/__init__.py +0 -0
  12. bobframes/html/template.py +1056 -0
  13. bobframes/lint.py +114 -0
  14. bobframes/manifest.py +127 -0
  15. bobframes/parquetize.py +282 -0
  16. bobframes/parsers/__init__.py +0 -0
  17. bobframes/parsers/derive_program_transitions.py +73 -0
  18. bobframes/parsers/parse_init_state.py +675 -0
  19. bobframes/paths.py +111 -0
  20. bobframes/probes/__init__.py +0 -0
  21. bobframes/probes/whatif.py +165 -0
  22. bobframes/qrd_harness.py +119 -0
  23. bobframes/query_examples.py +222 -0
  24. bobframes/rdcmd.py +72 -0
  25. bobframes/replay/__init__.py +26 -0
  26. bobframes/replay/replay_main.py +2305 -0
  27. bobframes/reports/__init__.py +0 -0
  28. bobframes/reports/_dashboard.py +425 -0
  29. bobframes/reports/ab.py +88 -0
  30. bobframes/reports/base.py +114 -0
  31. bobframes/reports/cache.py +147 -0
  32. bobframes/reports/chrome.py +1306 -0
  33. bobframes/reports/cli.py +99 -0
  34. bobframes/reports/delta.py +167 -0
  35. bobframes/reports/discovery.py +118 -0
  36. bobframes/reports/draws_by_class.py +165 -0
  37. bobframes/reports/formatters.py +122 -0
  38. bobframes/reports/instancing_opportunities.py +276 -0
  39. bobframes/reports/orchestrator.py +59 -0
  40. bobframes/reports/overdraw.py +293 -0
  41. bobframes/reports/pass_gpu.py +190 -0
  42. bobframes/reports/shader_hotlist.py +240 -0
  43. bobframes/reports/trend_table.py +444 -0
  44. bobframes/resource_labels.py +162 -0
  45. bobframes/run.py +480 -0
  46. bobframes/schemas.py +426 -0
  47. bobframes/stable_keys.py +83 -0
  48. bobframes/tests/__init__.py +0 -0
  49. bobframes/tests/_render_util.py +84 -0
  50. bobframes/tests/data/golden/_reports/draws_by_class.html +323 -0
  51. bobframes/tests/data/golden/_reports/drill/District 01/2026-05-28_r110600/index.html +1560 -0
  52. bobframes/tests/data/golden/_reports/index.html +264 -0
  53. bobframes/tests/data/golden/_reports/instancing_opportunities.html +266 -0
  54. bobframes/tests/data/golden/_reports/overdraw.html +275 -0
  55. bobframes/tests/data/golden/_reports/pass_gpu.html +277 -0
  56. bobframes/tests/data/golden/_reports/shader_hotlist.html +265 -0
  57. bobframes/tests/data/golden/_reports/trend_table.html +390 -0
  58. bobframes/tests/data/golden/index.html +1175 -0
  59. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/_manifest.json +51 -0
  60. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/buffers.parquet +0 -0
  61. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/clears.parquet +0 -0
  62. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/counters_per_event.parquet +0 -0
  63. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/descriptor_access.parquet +0 -0
  64. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/dispatches.parquet +0 -0
  65. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draw_bindings.parquet +0 -0
  66. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/draws.parquet +0 -0
  67. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/events.parquet +0 -0
  68. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/fbos.parquet +0 -0
  69. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/frame_totals.parquet +0 -0
  70. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/ibo_samples.parquet +0 -0
  71. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/indirect_args.parquet +0 -0
  72. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/passes.parquet +0 -0
  73. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/pixel_history.parquet +0 -0
  74. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/post_vs_samples.parquet +0 -0
  75. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/program_transitions.parquet +0 -0
  76. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/programs.parquet +0 -0
  77. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/render_targets.parquet +0 -0
  78. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/resource_creation.parquet +0 -0
  79. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/rt_event_timeline.parquet +0 -0
  80. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/samplers.parquet +0 -0
  81. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/shaders.parquet +0 -0
  82. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/state_change_events.parquet +0 -0
  83. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/texture_samples.parquet +0 -0
  84. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/textures.parquet +0 -0
  85. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vbo_samples.parquet +0 -0
  86. bobframes/tests/data/synthetic/_data/District 01/2026-05-27_r110565/vertex_inputs.parquet +0 -0
  87. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/_manifest.json +51 -0
  88. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/buffers.parquet +0 -0
  89. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/clears.parquet +0 -0
  90. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/counters_per_event.parquet +0 -0
  91. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/descriptor_access.parquet +0 -0
  92. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/dispatches.parquet +0 -0
  93. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draw_bindings.parquet +0 -0
  94. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/draws.parquet +0 -0
  95. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/events.parquet +0 -0
  96. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/fbos.parquet +0 -0
  97. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/frame_totals.parquet +0 -0
  98. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/ibo_samples.parquet +0 -0
  99. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/indirect_args.parquet +0 -0
  100. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/passes.parquet +0 -0
  101. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/pixel_history.parquet +0 -0
  102. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/post_vs_samples.parquet +0 -0
  103. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/program_transitions.parquet +0 -0
  104. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/programs.parquet +0 -0
  105. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/render_targets.parquet +0 -0
  106. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/resource_creation.parquet +0 -0
  107. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/rt_event_timeline.parquet +0 -0
  108. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/samplers.parquet +0 -0
  109. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/shaders.parquet +0 -0
  110. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/state_change_events.parquet +0 -0
  111. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/texture_samples.parquet +0 -0
  112. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/textures.parquet +0 -0
  113. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vbo_samples.parquet +0 -0
  114. bobframes/tests/data/synthetic/_data/District 01/2026-05-28_r110600/vertex_inputs.parquet +0 -0
  115. bobframes/tests/make_synthetic.py +171 -0
  116. bobframes/tests/smoke.py +199 -0
  117. bobframes/tests/test_determinism.py +19 -0
  118. bobframes/tests/test_discovery.py +97 -0
  119. bobframes/tests/test_hardening.py +142 -0
  120. bobframes/tests/test_parity.py +22 -0
  121. bobframes/tests/test_perf.py +18 -0
  122. bobframes/tests/test_replay_drift.py +115 -0
  123. bobframes/tests/test_schemas.py +26 -0
  124. bobframes/tests/test_schemas_unit.py +55 -0
  125. bobframes/tests/test_stable_keys.py +61 -0
  126. bobframes-0.1.0.dist-info/METADATA +144 -0
  127. bobframes-0.1.0.dist-info/RECORD +130 -0
  128. bobframes-0.1.0.dist-info/WHEEL +4 -0
  129. bobframes-0.1.0.dist-info/entry_points.txt +2 -0
  130. bobframes-0.1.0.dist-info/licenses/LICENSE +21 -0
bobframes/schemas.py ADDED
@@ -0,0 +1,426 @@
1
+ """Single source of truth for table schemas.
2
+
3
+ Every fat table's column tuple lives here. Writers and readers import these
4
+ constants. SCHEMA_VERSION bumps on any column add/remove/rename.
5
+ """
6
+
7
+ SCHEMA_VERSION = 3
8
+
9
+ ID_COLS = ('area', 'drop_date', 'drop_label', 'capture')
10
+
11
+
12
+ # --- Entity tables (carry stable_key after ID_COLS) ---
13
+
14
+ SHADERS_COLS = ID_COLS + (
15
+ 'stable_key',
16
+ 'shader_id', 'shader_type', 'src_len', 'src_hash',
17
+ 'linked_program_ids', 'used_by_draw_count',
18
+ 'total_texture_samples', 'total_branches', 'total_loops', 'total_discards',
19
+ 'total_dfdx_dfdy', 'total_mat4_constructors', 'total_varyings',
20
+ 'mediump_decls', 'highp_decls', 'lowp_decls',
21
+ 'fb_fetch', 'uses_cubemap', 'uses_texture_gather', 'uses_texture_grad',
22
+ 'src_file_path',
23
+ 'complexity_score',
24
+ )
25
+
26
+ TEXTURES_COLS = ID_COLS + (
27
+ 'stable_key',
28
+ 'tex_id', 'format', 'width', 'height', 'depth', 'mip_levels', 'sample_count', 'kind',
29
+ 'est_bytes', 'is_rt', 'is_swap_chain', 'label',
30
+ 'created_at_event', 'num_bind_events', 'num_sample_events',
31
+ 'sampled_by_shader_ids', 'attached_to_fbo_ids',
32
+ )
33
+
34
+ RENDER_TARGETS_COLS = ID_COLS + (
35
+ 'stable_key',
36
+ 'rt_id', 'format', 'width', 'height', 'depth', 'mip_levels', 'sample_count',
37
+ 'is_color', 'is_depth', 'is_stencil', 'is_swap_chain_target',
38
+ 'first_write_event', 'last_write_event', 'first_read_event', 'last_read_event',
39
+ 'num_write_events', 'num_read_events',
40
+ 'attached_to_fbo_ids', 'sampled_by_shader_ids',
41
+ 'min_value_r', 'min_value_g', 'min_value_b', 'min_value_a',
42
+ 'max_value_r', 'max_value_g', 'max_value_b', 'max_value_a',
43
+ )
44
+
45
+ BUFFERS_COLS = ID_COLS + (
46
+ 'stable_key',
47
+ 'buffer_id', 'allocated_size_bytes', 'usage_hint', 'target_history',
48
+ 'first_alloc_event', 'last_alloc_event', 'first_bind_event', 'last_bind_event',
49
+ 'num_glBufferData', 'num_glBufferSubData', 'num_glBindBuffer',
50
+ 'num_glBindBufferBase', 'num_glBindBufferRange',
51
+ 'used_by_draws', 'used_as_vbo', 'used_as_ibo', 'used_as_ubo',
52
+ 'used_as_ssbo', 'used_as_indirect',
53
+ )
54
+
55
+ PROGRAMS_COLS = ID_COLS + (
56
+ 'stable_key',
57
+ 'program_id', 'linked', 'num_attached_shaders', 'attached_shader_ids',
58
+ 'vs_shader_id', 'fs_shader_id', 'cs_shader_id',
59
+ 'gs_shader_id', 'tcs_shader_id', 'tes_shader_id',
60
+ 'num_active_uniforms', 'num_active_uniform_blocks', 'num_active_attributes',
61
+ 'used_by_draw_count', 'label',
62
+ )
63
+
64
+ SAMPLERS_COLS = ID_COLS + (
65
+ 'stable_key',
66
+ 'sampler_id', 'min_filter', 'mag_filter',
67
+ 'wrap_s', 'wrap_t', 'wrap_r',
68
+ 'mip_min_lod', 'mip_max_lod', 'mip_lod_bias',
69
+ 'max_anisotropy', 'compare_mode', 'compare_func',
70
+ 'border_color_r', 'border_color_g', 'border_color_b', 'border_color_a',
71
+ 'created_at_event', 'bound_to_draw_count', 'label',
72
+ )
73
+
74
+ FBOS_COLS = ID_COLS + (
75
+ 'stable_key',
76
+ 'fbo_id', 'attachment_point', 'kind', 'resource_id', 'format',
77
+ 'width', 'height', 'sample_count',
78
+ 'mip_level', 'layer', 'created_at_event', 'bound_at_events',
79
+ 'num_clears', 'num_writes', 'num_reads', 'label',
80
+ )
81
+
82
+
83
+ # --- Non-entity tables (no stable_key) ---
84
+
85
+ DRAWS_COLS = ID_COLS + (
86
+ 'event_id', 'parent_pass_path', 'parent_pass_path_norm', 'draw_name', 'draw_class',
87
+ 'num_indices', 'num_instances', 'base_vertex', 'vertex_offset', 'index_offset',
88
+ 'topology',
89
+ 'program_id', 'vs_shader_id', 'fs_shader_id',
90
+ 'color_rt_ids', 'depth_rt_id',
91
+ 'viewport_x', 'viewport_y', 'viewport_w', 'viewport_h',
92
+ 'scissor_x', 'scissor_y', 'scissor_w', 'scissor_h',
93
+ 'cull_mode', 'front_face',
94
+ 'depth_test_enable', 'depth_write_enable', 'depth_func',
95
+ 'stencil_enable',
96
+ 'stencil_front_pass_op', 'stencil_front_fail_op', 'stencil_front_depth_fail_op',
97
+ 'stencil_back_pass_op', 'stencil_back_fail_op', 'stencil_back_depth_fail_op',
98
+ 'stencil_ref', 'stencil_read_mask', 'stencil_write_mask',
99
+ 'blend_enable',
100
+ 'blend_src_color', 'blend_dst_color', 'blend_op_color',
101
+ 'blend_src_alpha', 'blend_dst_alpha', 'blend_op_alpha',
102
+ 'color_write_mask',
103
+ 'ibo_id', 'ibo_index_type',
104
+ 'gpu_duration_s',
105
+ 'post_vs_primitives', 'post_vs_vertices',
106
+ 'mesh_hash',
107
+ 'screen_min_x', 'screen_min_y', 'screen_max_x', 'screen_max_y',
108
+ 'screen_coverage_px',
109
+ )
110
+
111
+ DRAW_BINDINGS_COLS = ID_COLS + (
112
+ 'event_id', 'slot_kind', 'slot_index', 'resource_id', 'sampler_id',
113
+ 'offset', 'size', 'stride',
114
+ )
115
+
116
+ PASSES_COLS = ID_COLS + (
117
+ 'marker_path', 'marker_path_norm', 'depth', 'first_event_id', 'last_event_id',
118
+ 'num_draws', 'num_dispatches', 'num_clears', 'num_other_actions',
119
+ 'num_primitives_pre_vs', 'num_primitives_post_vs',
120
+ 'num_vertices_pre_vs', 'num_vertices_post_vs',
121
+ 'gpu_duration_s',
122
+ 'unique_programs', 'unique_shaders', 'unique_meshes', 'unique_materials',
123
+ 'color_rt_id_first', 'depth_rt_id_first',
124
+ 'draws_by_class_opaque', 'draws_by_class_prepass', 'draws_by_class_translucent',
125
+ 'draws_by_class_decal', 'draws_by_class_shadow', 'draws_by_class_ui',
126
+ 'draws_by_class_postprocess', 'draws_by_class_additive', 'draws_by_class_other',
127
+ )
128
+
129
+ EVENTS_COLS = ID_COLS + (
130
+ 'event_id', 'parent_marker_path', 'parent_marker_path_norm', 'chunk_name', 'depth',
131
+ 'is_drawcall', 'is_dispatch', 'is_clear', 'is_copy', 'is_resolve',
132
+ 'is_marker_push', 'is_marker_pop', 'is_set_state',
133
+ 'num_indices', 'num_instances',
134
+ 'dispatch_x', 'dispatch_y', 'dispatch_z',
135
+ 'output_color_rt_id', 'output_depth_rt_id',
136
+ 'copy_source_id', 'copy_destination_id',
137
+ )
138
+
139
+ CLEARS_COLS = ID_COLS + (
140
+ 'event_id', 'parent_marker_path', 'target',
141
+ 'color_r', 'color_g', 'color_b', 'color_a',
142
+ 'depth_value', 'stencil_value',
143
+ 'buffer_mask', 'fbo_id',
144
+ )
145
+
146
+ DISPATCHES_COLS = ID_COLS + (
147
+ 'event_id', 'parent_marker_path',
148
+ 'program_id', 'cs_shader_id',
149
+ 'group_count_x', 'group_count_y', 'group_count_z',
150
+ 'work_group_size_x', 'work_group_size_y', 'work_group_size_z',
151
+ 'total_threads',
152
+ 'ssbo_bindings', 'image_bindings', 'atomic_counter_bindings',
153
+ 'gpu_duration_s',
154
+ )
155
+
156
+ STATE_CHANGE_COLS = ID_COLS + (
157
+ 'event_id', 'parent_marker_path', 'call_name',
158
+ 'target_or_cap', 'arg_id', 'arg_int', 'arg_float', 'arg_extra_json',
159
+ )
160
+
161
+ INDIRECT_ARGS_COLS = ID_COLS + (
162
+ 'event_id', 'call_name', 'indirect_buffer_id', 'offset',
163
+ 'count', 'instance_count', 'first', 'base_vertex', 'base_instance',
164
+ 'group_x', 'group_y', 'group_z', 'stride', 'draw_count',
165
+ )
166
+
167
+ VERTEX_INPUTS_COLS = ID_COLS + (
168
+ 'event_id', 'attribute_index', 'attribute_name', 'enabled',
169
+ 'component_count', 'component_type', 'normalized', 'integer',
170
+ 'stride_bytes', 'offset_bytes', 'buffer_id', 'vbo_slot',
171
+ 'divisor',
172
+ )
173
+
174
+ RESOURCE_CREATION_COLS = ID_COLS + (
175
+ 'resource_id', 'resource_kind', 'created_at_event', 'creation_chunk', 'declared_label',
176
+ )
177
+
178
+ COUNTERS_COLS = ID_COLS + (
179
+ 'event_id', 'counter_name', 'counter_unit', 'value_double', 'value_uint64',
180
+ )
181
+
182
+ DESCRIPTOR_ACCESS_COLS = ID_COLS + (
183
+ 'event_id', 'descriptor_kind', 'slot_index', 'resource_id', 'view_id',
184
+ 'byte_offset', 'byte_size', 'access_type',
185
+ )
186
+
187
+ RT_TIMELINE_COLS = ID_COLS + (
188
+ 'rt_id', 'event_id', 'usage_code', 'usage_name', 'view_id',
189
+ 'attachment_point_or_slot',
190
+ )
191
+
192
+ PROG_TRANS_COLS = ID_COLS + (
193
+ 'from_program_id', 'to_program_id', 'count',
194
+ )
195
+
196
+ FRAME_TOTALS_COLS = ID_COLS + (
197
+ 'n_events', 'n_draws', 'n_dispatches', 'n_clears',
198
+ 'total_primitives_pre_vs', 'total_vertices_pre_vs',
199
+ 'total_primitives_post_vs', 'total_vertices_post_vs',
200
+ 'total_gpu_duration_s',
201
+ 'glUseProgram_count', 'glBindBuffer_count', 'glBindTexture_count', 'glActiveTexture_count',
202
+ 'glBindFramebuffer_count', 'glBindBufferBase_count', 'glBindSampler_count',
203
+ 'glDrawElements_count', 'glDrawArrays_count', 'glDrawElementsInstanced_count',
204
+ 'glDispatchCompute_count', 'glClear_count', 'glClearBuffer_count',
205
+ 'total_vbo_bytes_uploaded', 'total_ibo_bytes_uploaded', 'total_ubo_bytes_uploaded',
206
+ 'total_texture_bytes_allocated', 'total_renderbuffer_bytes_allocated',
207
+ 'unique_programs_used', 'unique_shaders_used', 'unique_meshes_drawn',
208
+ 'unique_materials_drawn', 'unique_textures_bound',
209
+ 'fbo_switches', 'program_switches', 'texture_unit_switches',
210
+ )
211
+
212
+ PIXEL_HISTORY_COLS = ID_COLS + (
213
+ 'rt_id', 'sample_x', 'sample_y', 'mod_index',
214
+ 'event_id', 'primitive_id',
215
+ 'passed', 'backface_culled', 'depth_test_failed', 'stencil_test_failed',
216
+ 'scissor_clipped', 'shader_discarded', 'sample_masked',
217
+ 'depth_clipped', 'view_clipped',
218
+ 'shader_out_r', 'shader_out_g', 'shader_out_b', 'shader_out_a',
219
+ 'pre_mod_r', 'pre_mod_g', 'pre_mod_b', 'pre_mod_a',
220
+ 'post_mod_r', 'post_mod_g', 'post_mod_b', 'post_mod_a',
221
+ )
222
+
223
+ VBO_SAMPLES_COLS = ID_COLS + (
224
+ 'buffer_id', 'vertex_index', 'byte_offset',
225
+ 'raw_hex', 'as_f32_0', 'as_f32_1', 'as_f32_2', 'as_f32_3',
226
+ )
227
+
228
+ IBO_SAMPLES_COLS = ID_COLS + (
229
+ 'buffer_id', 'index_position', 'index_value', 'index_type',
230
+ )
231
+
232
+ POST_VS_SAMPLES_COLS = ID_COLS + (
233
+ 'event_id', 'vertex_index',
234
+ 'position_x', 'position_y', 'position_z', 'position_w',
235
+ 'clipped',
236
+ )
237
+
238
+ TEXTURE_SAMPLES_COLS = ID_COLS + (
239
+ 'tex_id', 'row_index', 'col_index',
240
+ 'raw_hex', 'as_unorm8_r', 'as_unorm8_g', 'as_unorm8_b', 'as_unorm8_a',
241
+ )
242
+
243
+ PASS_CLASS_BREAKDOWN_COLS = ID_COLS + (
244
+ 'marker_path_norm', 'draw_class',
245
+ 'n_draws', 'n_dispatches',
246
+ 'sum_pre_vs_vertices', 'sum_gpu_duration_s',
247
+ )
248
+
249
+ TEXTURE_USAGE_COLS = ID_COLS + (
250
+ 'tex_id', 'stable_key', 'label', 'format',
251
+ 'n_unique_events_sampled', 'n_descriptor_accesses',
252
+ 'first_event_id', 'last_event_id',
253
+ )
254
+
255
+
256
+ # Master table registry. Map file stem to (column tuple, size class, carries stable_key).
257
+ TABLES = {
258
+ 'draws': (DRAWS_COLS, 'large', False),
259
+ 'draw_bindings': (DRAW_BINDINGS_COLS, 'large', False),
260
+ 'passes': (PASSES_COLS, 'small', False),
261
+ 'shaders': (SHADERS_COLS, 'small', True),
262
+ 'textures': (TEXTURES_COLS, 'small', True),
263
+ 'render_targets': (RENDER_TARGETS_COLS, 'small', True),
264
+ 'rt_event_timeline': (RT_TIMELINE_COLS, 'large', False),
265
+ 'buffers': (BUFFERS_COLS, 'small', True),
266
+ 'programs': (PROGRAMS_COLS, 'small', True),
267
+ 'samplers': (SAMPLERS_COLS, 'small', True),
268
+ 'fbos': (FBOS_COLS, 'small', True),
269
+ 'events': (EVENTS_COLS, 'large', False),
270
+ 'clears': (CLEARS_COLS, 'small', False),
271
+ 'dispatches': (DISPATCHES_COLS, 'small', False),
272
+ 'state_change_events': (STATE_CHANGE_COLS, 'large', False),
273
+ 'indirect_args': (INDIRECT_ARGS_COLS, 'small', False),
274
+ 'vertex_inputs': (VERTEX_INPUTS_COLS, 'large', False),
275
+ 'resource_creation': (RESOURCE_CREATION_COLS, 'small', False),
276
+ 'counters_per_event': (COUNTERS_COLS, 'large', False),
277
+ 'descriptor_access': (DESCRIPTOR_ACCESS_COLS, 'large', False),
278
+ 'program_transitions': (PROG_TRANS_COLS, 'small', False),
279
+ 'frame_totals': (FRAME_TOTALS_COLS, 'small', False),
280
+ 'pixel_history': (PIXEL_HISTORY_COLS, 'small', False),
281
+ 'vbo_samples': (VBO_SAMPLES_COLS, 'small', False),
282
+ 'ibo_samples': (IBO_SAMPLES_COLS, 'small', False),
283
+ 'post_vs_samples': (POST_VS_SAMPLES_COLS, 'large', False),
284
+ 'texture_samples': (TEXTURE_SAMPLES_COLS, 'small', False),
285
+ 'pass_class_breakdown': (PASS_CLASS_BREAKDOWN_COLS, 'small', False),
286
+ 'texture_usage': (TEXTURE_USAGE_COLS, 'small', False),
287
+ }
288
+
289
+
290
+ # --- dtype inference for parquetize ---
291
+
292
+ _INT_NAMES = {
293
+ 'depth', 'width', 'height', 'mip_levels', 'sample_count',
294
+ 'mip_level', 'layer', 'src_len',
295
+ 'first_write_event', 'last_write_event', 'first_read_event', 'last_read_event',
296
+ 'num_write_events', 'num_read_events',
297
+ 'first_alloc_event', 'last_alloc_event', 'first_bind_event', 'last_bind_event',
298
+ 'created_at_event', 'used_by_draw_count', 'used_by_draws', 'bound_to_draw_count',
299
+ 'allocated_size_bytes', 'est_bytes',
300
+ 'num_attached_shaders', 'num_active_uniforms', 'num_active_uniform_blocks',
301
+ 'num_active_attributes',
302
+ 'event_id', 'first_event_id', 'last_event_id', 'parent_event_id',
303
+ 'num_draws', 'num_dispatches', 'num_clears', 'num_other_actions',
304
+ 'num_primitives_pre_vs', 'num_primitives_post_vs',
305
+ 'num_vertices_pre_vs', 'num_vertices_post_vs',
306
+ 'unique_programs', 'unique_shaders', 'unique_meshes', 'unique_materials',
307
+ 'unique_textures_bound', 'unique_programs_used', 'unique_shaders_used',
308
+ 'unique_meshes_drawn', 'unique_materials_drawn',
309
+ 'fbo_switches', 'program_switches', 'texture_unit_switches',
310
+ 'num_indices', 'num_instances', 'base_vertex', 'vertex_offset', 'index_offset',
311
+ 'viewport_x', 'viewport_y', 'viewport_w', 'viewport_h',
312
+ 'scissor_x', 'scissor_y', 'scissor_w', 'scissor_h',
313
+ 'stencil_ref', 'stencil_read_mask', 'stencil_write_mask',
314
+ 'color_write_mask',
315
+ 'post_vs_primitives', 'post_vs_vertices',
316
+ 'slot_index', 'attribute_index', 'component_count',
317
+ 'stride_bytes', 'offset_bytes', 'divisor',
318
+ 'count', 'instance_count', 'first', 'base_instance',
319
+ 'group_x', 'group_y', 'group_z', 'stride', 'draw_count',
320
+ 'group_count_x', 'group_count_y', 'group_count_z',
321
+ 'work_group_size_x', 'work_group_size_y', 'work_group_size_z',
322
+ 'total_threads',
323
+ 'stencil_value', 'buffer_mask',
324
+ 'mod_index', 'primitive_id', 'sample_x', 'sample_y',
325
+ 'usage_code', 'byte_offset', 'byte_size',
326
+ 'from_program_id', 'to_program_id',
327
+ 'n_events', 'n_draws', 'n_dispatches', 'n_clears',
328
+ 'total_primitives_pre_vs', 'total_vertices_pre_vs',
329
+ 'total_primitives_post_vs', 'total_vertices_post_vs',
330
+ 'value_uint64',
331
+ 'vertex_index', 'index_position', 'index_value',
332
+ 'row_index', 'col_index',
333
+ 'as_unorm8_r', 'as_unorm8_g', 'as_unorm8_b', 'as_unorm8_a',
334
+ 'total_texture_samples', 'total_branches', 'total_loops', 'total_discards',
335
+ 'total_dfdx_dfdy', 'total_mat4_constructors', 'total_varyings',
336
+ 'mediump_decls', 'highp_decls', 'lowp_decls',
337
+ 'num_bind_events', 'num_sample_events',
338
+ 'num_glBufferData', 'num_glBufferSubData', 'num_glBindBuffer',
339
+ 'num_glBindBufferBase', 'num_glBindBufferRange',
340
+ 'glUseProgram_count', 'glBindBuffer_count', 'glBindTexture_count',
341
+ 'glActiveTexture_count', 'glBindFramebuffer_count', 'glBindBufferBase_count',
342
+ 'glBindSampler_count', 'glDrawElements_count', 'glDrawArrays_count',
343
+ 'glDrawElementsInstanced_count', 'glDispatchCompute_count',
344
+ 'glClear_count', 'glClearBuffer_count',
345
+ 'total_vbo_bytes_uploaded', 'total_ibo_bytes_uploaded', 'total_ubo_bytes_uploaded',
346
+ 'total_texture_bytes_allocated', 'total_renderbuffer_bytes_allocated',
347
+ 'num_writes', 'num_reads',
348
+ 'arg_int', 'arg_id',
349
+ 'max_anisotropy',
350
+ 'mip_min_lod', 'mip_max_lod', # actually float; overridden below
351
+ # resource IDs (per-capture ints; large values OK in int64)
352
+ 'shader_id', 'program_id', 'vs_shader_id', 'fs_shader_id', 'cs_shader_id',
353
+ 'gs_shader_id', 'tcs_shader_id', 'tes_shader_id',
354
+ 'tex_id', 'rt_id', 'buffer_id', 'sampler_id', 'fbo_id',
355
+ 'depth_rt_id', 'ibo_id',
356
+ 'resource_id', 'view_id',
357
+ 'indirect_buffer_id', 'copy_source_id', 'copy_destination_id',
358
+ 'output_color_rt_id', 'output_depth_rt_id',
359
+ 'created_at_event',
360
+ 'est_bytes',
361
+ 'screen_coverage_px',
362
+ 'n_unique_events_sampled', 'n_descriptor_accesses',
363
+ 'first_event_id', 'last_event_id',
364
+ 'sum_pre_vs_vertices',
365
+ 'unique_meshes_drawn', 'unique_materials_drawn',
366
+ 'fbo_switches', 'program_switches', 'texture_unit_switches',
367
+ }
368
+
369
+ _FLOAT_NAMES = {
370
+ 'gpu_duration_s', 'total_gpu_duration_s',
371
+ 'depth_value',
372
+ 'mip_lod_bias', 'mip_min_lod', 'mip_max_lod',
373
+ 'min_value_r', 'min_value_g', 'min_value_b', 'min_value_a',
374
+ 'max_value_r', 'max_value_g', 'max_value_b', 'max_value_a',
375
+ 'border_color_r', 'border_color_g', 'border_color_b', 'border_color_a',
376
+ 'color_r', 'color_g', 'color_b', 'color_a',
377
+ 'shader_out_r', 'shader_out_g', 'shader_out_b', 'shader_out_a',
378
+ 'pre_mod_r', 'pre_mod_g', 'pre_mod_b', 'pre_mod_a',
379
+ 'post_mod_r', 'post_mod_g', 'post_mod_b', 'post_mod_a',
380
+ 'position_x', 'position_y', 'position_z', 'position_w',
381
+ 'as_f32_0', 'as_f32_1', 'as_f32_2', 'as_f32_3',
382
+ 'value_double',
383
+ 'arg_float',
384
+ 'complexity_score',
385
+ 'screen_min_x', 'screen_min_y', 'screen_max_x', 'screen_max_y',
386
+ 'sum_gpu_duration_s',
387
+ }
388
+
389
+ _BOOL_NAMES = {
390
+ 'is_rt', 'is_color', 'is_depth', 'is_stencil',
391
+ 'is_swap_chain', 'is_swap_chain_target',
392
+ 'is_drawcall', 'is_dispatch', 'is_clear', 'is_copy', 'is_resolve',
393
+ 'is_marker_push', 'is_marker_pop', 'is_set_state',
394
+ 'depth_test_enable', 'depth_write_enable', 'stencil_enable', 'blend_enable',
395
+ 'enabled', 'normalized', 'integer', 'linked',
396
+ 'used_as_vbo', 'used_as_ibo', 'used_as_ubo', 'used_as_ssbo', 'used_as_indirect',
397
+ 'fb_fetch', 'uses_cubemap', 'uses_texture_gather', 'uses_texture_grad',
398
+ 'passed', 'backface_culled', 'depth_test_failed', 'stencil_test_failed',
399
+ 'scissor_clipped', 'shader_discarded', 'sample_masked',
400
+ 'depth_clipped', 'view_clipped',
401
+ 'clipped',
402
+ }
403
+
404
+
405
+ def infer_dtype(col_name: str) -> str:
406
+ """Return one of: 'int', 'float', 'bool', 'str'."""
407
+ if col_name in _BOOL_NAMES:
408
+ return 'bool'
409
+ if col_name in _FLOAT_NAMES:
410
+ return 'float'
411
+ if col_name in _INT_NAMES:
412
+ return 'int'
413
+ return 'str'
414
+
415
+
416
+ def expected_columns(table_stem: str) -> tuple:
417
+ """Lookup column tuple by table stem. Raises KeyError if unknown."""
418
+ return TABLES[table_stem][0]
419
+
420
+
421
+ def is_entity_table(table_stem: str) -> bool:
422
+ return TABLES[table_stem][2]
423
+
424
+
425
+ def size_class(table_stem: str) -> str:
426
+ return TABLES[table_stem][1]
@@ -0,0 +1,83 @@
1
+ """Stable cross-capture entity keys.
2
+
3
+ Raw resource IDs are per-capture replay state; they cannot be joined across
4
+ captures or drops. Each entity table carries `stable_key` computed from
5
+ content/shape so reports can identify "the same shader" across drops.
6
+
7
+ When inputs are unknown/null, stable_key is the empty string. Reports filter
8
+ `WHERE stable_key != ''` for cross-drop joins.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import re
15
+
16
+ # Version byte prepended to every stable-key hash input. Bump when a key-derivation
17
+ # rule changes so old and new keys are provably distinct (rebuild with `ingest --force`).
18
+ KEY_VERSION = 1
19
+
20
+ _LINE_COMMENT = re.compile(r'//[^\n]*')
21
+ _BLOCK_COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL)
22
+ _TRAILING_WS = re.compile(r'[ \t]+$', re.MULTILINE)
23
+ _BLANK_RUNS = re.compile(r'\n{3,}')
24
+
25
+
26
+ def normalize_glsl(source: str) -> str:
27
+ """Strip comments + trailing whitespace + collapse blank-line runs."""
28
+ if not source:
29
+ return ''
30
+ s = _BLOCK_COMMENT.sub('', source)
31
+ s = _LINE_COMMENT.sub('', s)
32
+ s = _TRAILING_WS.sub('', s)
33
+ s = _BLANK_RUNS.sub('\n\n', s)
34
+ return s.strip()
35
+
36
+
37
+ def _sha(payload: str) -> str:
38
+ return hashlib.sha256(bytes([KEY_VERSION]) + payload.encode('utf-8')).hexdigest()
39
+
40
+
41
+ def shader_key(normalized_source: str) -> str:
42
+ if not normalized_source:
43
+ return ''
44
+ return _sha(normalized_source)
45
+
46
+
47
+ def program_key(attached_shader_keys: list[str]) -> str:
48
+ keys = [k for k in attached_shader_keys if k]
49
+ if not keys:
50
+ return ''
51
+ return _sha('|'.join(sorted(keys)))
52
+
53
+
54
+ def texture_key(label, fmt, width, height, depth, mip_levels, sample_count) -> str:
55
+ if fmt is None or width is None or height is None:
56
+ return ''
57
+ payload = f"{label or ''}|{fmt}|{width}|{height}|{depth or 0}|{mip_levels or 1}|{sample_count or 1}"
58
+ return _sha(payload)
59
+
60
+
61
+ def sampler_key(
62
+ min_filter, mag_filter,
63
+ wrap_s, wrap_t, wrap_r,
64
+ max_anisotropy, compare_mode, compare_func,
65
+ ) -> str:
66
+ if min_filter is None or mag_filter is None:
67
+ return ''
68
+ payload = f"{min_filter}|{mag_filter}|{wrap_s}|{wrap_t}|{wrap_r}|{max_anisotropy}|{compare_mode}|{compare_func}"
69
+ return _sha(payload)
70
+
71
+
72
+ def buffer_key(usage_hint, allocated_size_bytes, first_usage_target) -> str:
73
+ if allocated_size_bytes is None or allocated_size_bytes <= 0:
74
+ return ''
75
+ payload = f"{usage_hint or ''}|{allocated_size_bytes}|{first_usage_target or ''}"
76
+ return _sha(payload)
77
+
78
+
79
+ def fbo_key(attached_keys: list[str]) -> str:
80
+ keys = [k for k in attached_keys if k]
81
+ if not keys:
82
+ return ''
83
+ return _sha('|'.join(sorted(keys)))
File without changes
@@ -0,0 +1,84 @@
1
+ """Shared helper: render the synthetic fixture into a throwaway root.
2
+
3
+ `render-only` discovers drops via `discovery.find_drops`, which scans RAW input dirs
4
+ (`<root>/<area>/<drop>/*.rdc`) and skips any drop with no `.rdc`. The committed fixture is
5
+ `_data`-only (ADR-8), so we fabricate empty `.rdc` stubs in the temp root at render time.
6
+ The stubs are never read by render-only (it reads `_data/`) and never committed.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import re
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+
17
+ HERE = os.path.dirname(__file__)
18
+
19
+ # The only render-time nondeterminism is the catalog build timestamp on the footer line
20
+ # `built <strong>...</strong>` (mixed UTC/local per H-28; c03 will unify). Mask it on both sides.
21
+ # (The `Math.random()` table id in chrome.py is client-side JS text — emitted identically.)
22
+ _TS_RE = re.compile(r"(built <strong>)[^<]*(</strong>)")
23
+
24
+
25
+ def normalize(html: str) -> str:
26
+ return _TS_RE.sub(r"\1<TS>\2", html)
27
+ SYNTHETIC_DATA = os.path.join(HERE, "data", "synthetic", "_data")
28
+ GOLDEN_DIR = os.path.join(HERE, "data", "golden")
29
+
30
+
31
+ def setup_root(dest: str, data_src: str = SYNTHETIC_DATA) -> str:
32
+ """Lay down <dest>/_data (copy of the fixture) + raw .rdc stubs so discovery sees the drops."""
33
+ if os.path.exists(dest):
34
+ shutil.rmtree(dest)
35
+ os.makedirs(dest)
36
+ shutil.copytree(data_src, os.path.join(dest, "_data"))
37
+ data = os.path.join(dest, "_data")
38
+ for area in os.listdir(data):
39
+ area_dir = os.path.join(data, area)
40
+ if area.startswith("_") or not os.path.isdir(area_dir):
41
+ continue
42
+ for drop in os.listdir(area_dir):
43
+ mf = os.path.join(area_dir, drop, "_manifest.json")
44
+ if not os.path.isfile(mf):
45
+ continue
46
+ with open(mf, encoding="utf-8") as f:
47
+ caps = json.load(f).get("captures") or ["1"]
48
+ raw = os.path.join(dest, area, drop)
49
+ os.makedirs(raw, exist_ok=True)
50
+ for c in caps:
51
+ open(os.path.join(raw, f"{c}.rdc"), "w", encoding="utf-8").close()
52
+ return dest
53
+
54
+
55
+ def render(dest: str) -> str:
56
+ """Run `bobframes --render-only --root <dest>`; raise on nonzero with captured output."""
57
+ r = subprocess.run(
58
+ [sys.executable, "-m", "bobframes.run", "--render-only", "--root", dest],
59
+ capture_output=True, text=True,
60
+ )
61
+ if r.returncode != 0:
62
+ raise RuntimeError(f"render failed ({r.returncode}):\nSTDOUT:\n{r.stdout}\nSTDERR:\n{r.stderr}")
63
+ return dest
64
+
65
+
66
+ def render_fresh(dest: str, data_src: str = SYNTHETIC_DATA) -> str:
67
+ setup_root(dest, data_src)
68
+ return render(dest)
69
+
70
+
71
+ def rendered_html_files(root: str) -> list[str]:
72
+ """Relative paths of every emitted .html under <root> (root index + _reports), sorted.
73
+ Excludes the parquet cache dir."""
74
+ out = []
75
+ for dirpath, _dirs, files in os.walk(root):
76
+ if os.sep + "_cache" in dirpath:
77
+ continue
78
+ for fn in files:
79
+ if fn.endswith(".html"):
80
+ rel = os.path.relpath(os.path.join(dirpath, fn), root).replace("\\", "/")
81
+ if rel.startswith("_data/"):
82
+ continue
83
+ out.append(rel)
84
+ return sorted(out)