lib/gui/src/profiling/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const gpu = @import("gpu");
3 const choir_abi = @import("choir_abi");
4 const accy = @import("accy");
5 const evidence = @import("accy_device_evidence");
6 const bench = @import("bench");
7 const filigree = @import("filigree");
8 const gui = @import("gui");
9 const sys = @import("sys");
10 const windowing = @import("windowing");
11
12 const Allocator = std.mem.Allocator;
13 const Color = gui.model.UiColor;
14 const Command = gui.paint.Command;
15
16 const clear: Color = .{ .r = 16, .g = 18, .b = 22, .a = 255 };
17 const cpu_spec: Spec = .{ .width = 320, .height = 180, .row_count = 18, .repetitions = 3 };
18 const accy_cpu_spec: Spec = .{ .width = 48, .height = 48, .row_count = 4, .repetitions = 1 };
19 const frame_cpu_spec: Spec = .{ .width = 320, .height = 180, .row_count = 18, .repetitions = 3 };
20 const frame_accy_cpu_spec: Spec = .{ .width = 48, .height = 48, .row_count = 4, .repetitions = 1 };
21 const phase_spec: Spec = .{ .width = 48, .height = 48, .row_count = 4, .repetitions = 1 };
22 const retained_spec: Spec = .{ .width = 960, .height = 640, .row_count = 80, .repetitions = 1 };
23 const strip_spec: Spec = .{ .width = 320, .height = 180, .row_count = 18, .repetitions = 1 };
24 const strip_benchmark_name = "gui paint phase strip reification";
25 const vulkan_evidence_spec: Spec = .{ .width = 960, .height = 640, .row_count = 80, .repetitions = 3 };
26 const vulkan_evidence_benchmark_name = "gui paint phase Accy live Vulkan launch range";
27 const vulkan_surface_benchmark_name = "gui paint phase Accy live Vulkan surface frame";
28 const vulkan_surface_steady_benchmark_name = "gui paint phase Accy live Vulkan surface steady";
29 const vulkan_surface_queued_benchmark_name = "gui paint phase Accy live Vulkan surface queued";
30 const vulkan_surface_prepared_benchmark_name = "gui paint phase Accy live Vulkan surface prepared";
31 const vulkan_surface_timing_repetitions: usize = 12;
32 const binning_repetitions: usize = 4;
33 const paint_storage_epoch_repetitions: usize = 4;
34 const image_processor_launch_repetitions: usize = 1024;
35 const workspace_repetitions: usize = 64;
36 const retained_repetitions: usize = 256;
37 const retained_scale_fragments: usize = 10_000;
38 const retained_scale_repetitions: usize = 16;
39 const span_benchmark_pixels: usize = 4096;
40 const span_benchmark_repetitions: usize = 32;
41 const caret_benchmark_queries: usize = 1024;
42 const caret_benchmark_line_bytes: usize = 1024;
43 const caret_geometry_benchmark_name = "gui text caret cached-run x1024";
44 const caret_prefix_benchmark_name = "gui text caret prefix reshape x1024";
45 const multiline_caret_benchmark_name = "gui text multiline caret round trip x1024";
46 const widget_text_hit_benchmark_name = "gui widget visible text hit round trip x1024";
47 const styled_text_benchmark_name = "gui text styled transcript row command emission x64";
48 const mixed_metric_text_benchmark_name = "gui text mixed metric transcript row command emission x64";
49 const fallback_text_benchmark_name = "gui text fallback primary and mixed beta shaping x1024";
50 const styled_text_benchmark_repetitions: usize = 64;
51 const fallback_text_benchmark_repetitions: usize = 1024;
52 const gradient_frame_benchmark_name = "gui paint linear gradient 1920x1080 CPU frame";
53 const gradient_frame_width: u32 = 1920;
54 const gradient_frame_height: u32 = 1080;
55 const gradient_frame_pixel_count: usize = @as(usize, gradient_frame_width) * gradient_frame_height;
56 const profiling_text_atlas_cache_limits = gui.paint.TextAtlasCacheLimits{
57 .measure_entries = 512,
58 .measure_payload_bytes = 64 * 1024,
59 .shape_entries = 1024,
60 .shape_payload_bytes = 512 * 1024,
61 };
62
63 var strip_fixture_commands: []const Command = &.{};
64 var caret_benchmark_workload: ?*CaretBenchmarkWorkload = null;
65 var styled_text_benchmark_workload: ?*StyledTextBenchmarkWorkload = null;
66 var mixed_metric_text_benchmark_workload: ?*MixedMetricTextBenchmarkWorkload = null;
67 var fallback_text_benchmark_workload: ?*FallbackTextBenchmarkWorkload = null;
68 var gradient_frame_pixels: [gradient_frame_pixel_count]u32 = undefined;
69
70 const SpanBlend = enum {
71 scalar,
72 selected,
73 };
74
75 fn PackedSpanBenchmark(comptime count: usize, comptime blend: SpanBlend) type {
76 if (count == 0 or count > span_benchmark_pixels) {
77 @compileError("packed span benchmark count is outside static storage");
78 }
79 return struct {
80 var initialized = false;
81 var generation: u8 = 0;
82 var pixels: [span_benchmark_pixels]u32 = undefined;
83
84 fn run(_: Allocator) void {
85 if (!initialized) {
86 for (&pixels, 0..) |*value, index| {
87 value.* = @as(u32, @truncate(index *% 0x9e37_79b9)) ^
88 0xa5c3_71e9;
89 }
90 initialized = true;
91 }
92 generation +%= 1;
93 const color = Color{
94 .r = generation,
95 .g = generation *% 73 +% 5,
96 .b = generation *% 151 +% 7,
97 .a = 113,
98 };
99 const store = gui.paint.cpu.pixel.WordStore{ .words = &pixels };
100 const phase = bench.phaseAt(
101 "gui.paint.cpu_packed.translucent_span",
102 @src(),
103 );
104 defer phase.end();
105 for (0..span_benchmark_repetitions) |_| {
106 switch (blend) {
107 .scalar => gui.paint.cpu.pixel.blendSpanScalar(
108 gui.paint.cpu.pixel.WordStore,
109 store,
110 0,
111 count,
112 color,
113 ),
114 .selected => gui.paint.cpu.pixel.blendSpan(
115 gui.paint.cpu.pixel.WordStore,
116 store,
117 0,
118 count,
119 color,
120 ),
121 }
122 }
123 const checksum = checksumAllPixels(pixels[0..count]);
124 std.mem.doNotOptimizeAway(checksum);
125 bench.coz.progressNamed("gui.paint.cpu_packed.translucent_span.complete");
126 }
127 };
128 }
129
130 const ScalarSpan7 = PackedSpanBenchmark(7, .scalar);
131 const SelectedSpan7 = PackedSpanBenchmark(7, .selected);
132 const ScalarSpan320 = PackedSpanBenchmark(320, .scalar);
133 const SelectedSpan320 = PackedSpanBenchmark(320, .selected);
134 const ScalarSpan4096 = PackedSpanBenchmark(4096, .scalar);
135 const SelectedSpan4096 = PackedSpanBenchmark(4096, .selected);
136
137 const multiline_caret_content =
138 "ALPHA BETA GAMMA DELTA EPSILON ZETA\n" ++
139 "POINTER CARET SELECTION GEOMETRY\n" ++
140 "\n" ++
141 "BROWSER EDITOR GAME INTERACTION\n" ++
142 "HARD LINES AND SOFT WRAPS AGREE\n" ++
143 "VISIBLE WINDOWS MAP TO BYTES\n" ++
144 "CLAMP POINTS ABOVE AND BELOW\n" ++
145 "FINAL TRAILING ROW";
146 const multiline_caret_box = gui.layout.Size{ .width = 192, .height = 192 };
147
148 fn multilineCaretText() gui.model.UiText {
149 return .{
150 .content = multiline_caret_content,
151 .point_size = 16,
152 .line_height = 1.2,
153 .wrap_width = 176,
154 };
155 }
156
157 const CaretBenchmarkWorkload = struct {
158 scratch: gui.paint.text.AtlasScratch,
159 geometry_atlas: gui.paint.text.Atlas,
160 prefix_atlas: gui.paint.text.Atlas,
161 line: [caret_benchmark_line_bytes]u8,
162
163 fn init(allocator: Allocator) !CaretBenchmarkWorkload {
164 var scratch = try gui.paint.text.AtlasScratch.init(allocator, .{ .bytes = 1024 * 1024 });
165 errdefer scratch.deinit(allocator);
166 const geometry_bytes = try filigree.fixtures.createWithOutlines(allocator);
167 var geometry_atlas = try gui.paint.text.Atlas.initFromOwnedBytes(
168 allocator,
169 &scratch,
170 geometry_bytes,
171 20,
172 profiling_text_atlas_cache_limits,
173 .{ .max_glyphs = caret_benchmark_line_bytes, .max_ligature_carets = caret_benchmark_line_bytes },
174 );
175 errdefer geometry_atlas.deinit();
176 const prefix_bytes = try filigree.fixtures.createWithOutlines(allocator);
177 var prefix_atlas = try gui.paint.text.Atlas.initFromOwnedBytes(
178 allocator,
179 &scratch,
180 prefix_bytes,
181 20,
182 profiling_text_atlas_cache_limits,
183 .{ .max_glyphs = caret_benchmark_line_bytes, .max_ligature_carets = caret_benchmark_line_bytes },
184 );
185 errdefer prefix_atlas.deinit();
186 const line = @as([caret_benchmark_line_bytes]u8, @splat('A'));
187 _ = try geometry_atlas.caretLine(&line);
188 _ = try prefix_atlas.shape(&line);
189 const entries = [_]gui.paint.text.AtlasSet.Entry{.{
190 .face = 0,
191 .image_index = 0,
192 .atlas = &geometry_atlas,
193 }};
194 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
195 const multiline_text = multilineCaretText();
196 for (0..multiline_text.content.len + 1) |byte_offset| {
197 const caret = try gui.paint.text.textCaretGeometry(
198 &atlases,
199 multiline_text,
200 multiline_caret_box,
201 byte_offset,
202 .downstream,
203 );
204 _ = try gui.paint.text.textHitTestPoint(
205 &atlases,
206 multiline_text,
207 multiline_caret_box,
208 .{
209 .x = caret.x,
210 .y = caret.y + caret.height / 2,
211 },
212 );
213 }
214 return .{
215 .scratch = scratch,
216 .geometry_atlas = geometry_atlas,
217 .prefix_atlas = prefix_atlas,
218 .line = line,
219 };
220 }
221
222 fn deinit(self: *CaretBenchmarkWorkload, allocator: Allocator) void {
223 self.prefix_atlas.deinit();
224 self.geometry_atlas.deinit();
225 self.scratch.deinit(allocator);
226 self.* = undefined;
227 }
228 };
229
230 fn caretGeometryQueries(_: Allocator) void {
231 const workload = caret_benchmark_workload orelse @panic("gui text caret benchmark workload missing");
232 const line = workload.geometry_atlas.caretLine(&workload.line) catch @panic("gui text caret line preparation failed");
233 const phase = bench.phaseAt("gui.paint.text.caret_cached_run", @src());
234 defer phase.end();
235 var checksum: f32 = 0;
236 for (0..caret_benchmark_queries) |query| {
237 const byte_offset = query % caret_benchmark_line_bytes;
238 checksum += line.advanceForByteOffset(byte_offset);
239 }
240 std.mem.doNotOptimizeAway(checksum);
241 bench.coz.progressNamed("gui.paint.text.caret_cached_run.complete");
242 }
243
244 fn caretPrefixQueries(_: Allocator) void {
245 const workload = caret_benchmark_workload orelse @panic("gui text caret benchmark workload missing");
246 const phase = bench.phaseAt("gui.paint.text.caret_prefix_reshape", @src());
247 defer phase.end();
248 var checksum: f32 = 0;
249 for (0..caret_benchmark_queries) |query| {
250 const byte_offset = query % caret_benchmark_line_bytes;
251 if (byte_offset == 0) continue;
252 const run = workload.prefix_atlas.shape(workload.line[0..byte_offset]) catch @panic("gui text caret prefix shaping failed");
253 checksum += @as(f32, @floatFromInt(run.total_x_advance)) / 64;
254 }
255 std.mem.doNotOptimizeAway(checksum);
256 bench.coz.progressNamed("gui.paint.text.caret_prefix_reshape.complete");
257 }
258
259 fn multilineCaretQueries(_: Allocator) void {
260 const workload = caret_benchmark_workload orelse @panic("gui text caret benchmark workload missing");
261 const entries = [_]gui.paint.text.AtlasSet.Entry{.{
262 .face = 0,
263 .image_index = 0,
264 .atlas = &workload.geometry_atlas,
265 }};
266 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
267 const text_value = multilineCaretText();
268 const phase = bench.phaseAt("gui.paint.text.multiline_caret_round_trip", @src());
269 defer phase.end();
270 var checksum: f32 = 0;
271 for (0..caret_benchmark_queries) |query| {
272 const byte_offset = (query * 37) % (text_value.content.len + 1);
273 const affinity: gui.paint.text.TextCaretAffinity = if (query % 2 == 0)
274 .downstream
275 else
276 .upstream;
277 const caret = gui.paint.text.textCaretGeometry(
278 &atlases,
279 text_value,
280 multiline_caret_box,
281 byte_offset,
282 affinity,
283 ) catch @panic("gui multiline caret geometry query failed");
284 const hit = gui.paint.text.textHitTestPoint(
285 &atlases,
286 text_value,
287 multiline_caret_box,
288 .{
289 .x = caret.x,
290 .y = caret.y + caret.height / 2,
291 },
292 ) catch @panic("gui multiline caret hit query failed");
293 checksum += caret.x + caret.y + @as(f32, @floatFromInt(hit.byte_offset));
294 }
295 std.mem.doNotOptimizeAway(checksum);
296 bench.coz.progressNamed("gui.paint.text.multiline_caret_round_trip.complete");
297 }
298
299 fn widgetTextHitQueries(_: Allocator) void {
300 const workload = caret_benchmark_workload orelse @panic("gui text caret benchmark workload missing");
301 const entries = [_]gui.paint.text.AtlasSet.Entry{.{
302 .face = 0,
303 .image_index = 0,
304 .atlas = &workload.geometry_atlas,
305 }};
306 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
307 const text_value = multilineCaretText();
308 const widget = gui.model.WidgetFrame{
309 .root_id = 1,
310 .widget_id = 2,
311 .kind = .label,
312 .rect = .{ .x = 24, .y = -17, .width = 192, .height = 192 },
313 .visible_rect = .{ .x = 32, .y = 0, .width = 168, .height = 148 },
314 .paint = .{},
315 .scroll = .{},
316 .constraints = .{},
317 .content_size = multiline_caret_box,
318 .focusable = false,
319 .has_text = true,
320 .text = text_value,
321 };
322 const phase = bench.phaseAt("gui.paint.text.widget_visible_hit_round_trip", @src());
323 defer phase.end();
324 var checksum: f32 = 0;
325 for (0..caret_benchmark_queries) |query| {
326 const byte_offset = (query * 37) % (text_value.content.len + 1);
327 const affinity: gui.paint.text.TextCaretAffinity = if (query % 2 == 0)
328 .downstream
329 else
330 .upstream;
331 const caret = gui.paint.text.textCaretGeometry(
332 &atlases,
333 text_value,
334 multiline_caret_box,
335 byte_offset,
336 affinity,
337 ) catch @panic("gui widget caret geometry query failed");
338 const hit = (gui.paint.text.textHitTestWidgetPointClamped(
339 &atlases,
340 widget,
341 .{
342 .x = widget.rect.x + caret.x,
343 .y = widget.rect.y + caret.y + caret.height / 2,
344 },
345 ) catch @panic("gui widget visible text hit query failed")) orelse
346 @panic("gui widget visible text hit query unavailable");
347 checksum += caret.x + caret.y + @as(f32, @floatFromInt(hit.byte_offset));
348 }
349 std.mem.doNotOptimizeAway(checksum);
350 bench.coz.progressNamed("gui.paint.text.widget_visible_hit_round_trip.complete");
351 }
352
353 const styled_text_content = "INFO CODE LINK OLD TEXT WRAPS HERE";
354 const styled_text_runs = [_]gui.model.UiTextRun{
355 .{
356 .byte_start = 0,
357 .byte_end = 4,
358 .foreground = .{ .r = 121, .g = 178, .b = 255, .a = 255 },
359 },
360 .{
361 .byte_start = 5,
362 .byte_end = 9,
363 .foreground = .{ .r = 219, .g = 232, .b = 255, .a = 255 },
364 .background = .{ .r = 43, .g = 57, .b = 83, .a = 255 },
365 },
366 .{
367 .byte_start = 10,
368 .byte_end = 14,
369 .foreground = .{ .r = 111, .g = 199, .b = 255, .a = 255 },
370 .underline = true,
371 },
372 .{
373 .byte_start = 15,
374 .byte_end = 18,
375 .foreground = .{ .r = 144, .g = 151, .b = 166, .a = 255 },
376 .strikethrough = true,
377 },
378 .{
379 .byte_start = 24,
380 .byte_end = 34,
381 .foreground = .{ .r = 139, .g = 213, .b = 154, .a = 255 },
382 },
383 };
384 const styled_text_glyph_rows = [_]u8{ 0x7e, 0x42, 0x5a, 0x5a, 0x42, 0x42, 0x7e, 0x00 };
385 const styled_text_space_rows = @as([styled_text_glyph_rows.len]u8, @splat(0));
386 const mixed_metric_text_styles = [_]gui.model.UiTextStyle{
387 .{ .font_asset_id = 1, .point_size = 20 },
388 .{ .font_asset_id = 2, .point_size = 16 },
389 };
390 const mixed_metric_text_runs = [_]gui.model.UiTextRun{
391 .{
392 .byte_start = 0,
393 .byte_end = 4,
394 .style_slot = 1,
395 .foreground = .{ .r = 121, .g = 178, .b = 255, .a = 255 },
396 },
397 .{
398 .byte_start = 5,
399 .byte_end = 9,
400 .style_slot = 2,
401 .foreground = .{ .r = 219, .g = 232, .b = 255, .a = 255 },
402 .background = .{ .r = 43, .g = 57, .b = 83, .a = 255 },
403 },
404 .{
405 .byte_start = 10,
406 .byte_end = 14,
407 .foreground = .{ .r = 111, .g = 199, .b = 255, .a = 255 },
408 .underline = true,
409 },
410 .{
411 .byte_start = 15,
412 .byte_end = 18,
413 .foreground = .{ .r = 144, .g = 151, .b = 166, .a = 255 },
414 .strikethrough = true,
415 },
416 .{
417 .byte_start = 24,
418 .byte_end = 34,
419 .foreground = .{ .r = 139, .g = 213, .b = 154, .a = 255 },
420 },
421 };
422
423 const StyledTextBenchmarkWorkload = struct {
424 atlas: gui.paint.text.Atlas,
425 frame_workspace: gui.frame.Workspace,
426 frame: gui.model.UiFrame,
427 commands: gui.paint.CommandBuffer,
428
429 fn init(allocator: Allocator) !StyledTextBenchmarkWorkload {
430 var glyphs: [95]gui.paint.text.BitmapGlyph = undefined;
431 for (&glyphs, 0..) |*glyph, index| {
432 const codepoint: u21 = @intCast(index + 32);
433 glyph.* = .{
434 .codepoint = codepoint,
435 .rows = if (codepoint == ' ') &styled_text_space_rows else &styled_text_glyph_rows,
436 };
437 }
438 var atlas = try gui.paint.text.Atlas.initFromBitmapGlyphs(
439 allocator,
440 &glyphs,
441 .{ .width = 8, .height = 8, .stride = 1 },
442 16,
443 profiling_text_atlas_cache_limits,
444 );
445 errdefer atlas.deinit();
446 var frame_workspace = gui.frame.Workspace.init(allocator);
447 errdefer frame_workspace.deinit();
448 const children = [_]gui.model.UiNode{.{
449 .widget_id = 2,
450 .kind = .text_input,
451 .text = .{
452 .content = styled_text_content,
453 .runs = &styled_text_runs,
454 .point_size = 16,
455 .line_height = 1.25,
456 .wrap_width = 144,
457 },
458 .text_selection = .{
459 .cursor_visible = true,
460 .cursor_byte_offset = 23,
461 .selection_active = true,
462 .selection_anchor_byte_offset = 10,
463 .selection_focus_byte_offset = 14,
464 },
465 .paint = .{
466 .foreground = .{ .r = 224, .g = 227, .b = 234, .a = 255 },
467 .background = .{ .r = 24, .g = 28, .b = 37, .a = 255 },
468 },
469 .size = .{ .width = 144, .height = 80 },
470 }};
471 const surface = gui.model.UiSurfaceTree{
472 .available_size = .{ .width = 144, .height = 80 },
473 .root = .{ .widget_id = 1, .children = &children },
474 };
475 const frame = try frame_workspace.buildSurface(&surface, .{});
476 var commands = gui.paint.CommandBuffer.init(allocator);
477 errdefer commands.deinit();
478 const entries = [_]gui.paint.text.AtlasSet.Entry{.{
479 .face = 0,
480 .image_index = 0,
481 .atlas = &atlas,
482 }};
483 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
484 try gui.paint.text.appendFrameCommands(&commands, frame, &atlases, 1, 0);
485 commands.reset();
486 return .{
487 .atlas = atlas,
488 .frame_workspace = frame_workspace,
489 .frame = frame,
490 .commands = commands,
491 };
492 }
493
494 fn deinit(self: *StyledTextBenchmarkWorkload) void {
495 self.commands.deinit();
496 self.frame_workspace.deinit();
497 self.atlas.deinit();
498 self.* = undefined;
499 }
500 };
501
502 fn recordStyledTextCommands(_: Allocator) void {
503 const workload = styled_text_benchmark_workload orelse
504 @panic("gui styled text benchmark workload missing");
505 const entries = [_]gui.paint.text.AtlasSet.Entry{.{
506 .face = 0,
507 .image_index = 0,
508 .atlas = &workload.atlas,
509 }};
510 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
511 var checksum: usize = 0;
512 const phase = bench.phaseAt("gui.paint.text.styled_transcript_row", @src());
513 defer phase.end();
514 for (0..styled_text_benchmark_repetitions) |_| {
515 workload.commands.reset();
516 gui.paint.text.appendFrameCommands(
517 &workload.commands,
518 workload.frame,
519 &atlases,
520 1,
521 0,
522 ) catch @panic("gui styled text command emission failed");
523 checksum +%= workload.commands.items().len;
524 bench.coz.progressNamed("gui.paint.text.styled_transcript_row.frame");
525 }
526 std.mem.doNotOptimizeAway(checksum);
527 }
528
529 const MixedMetricTextBenchmarkWorkload = struct {
530 base_atlas: gui.paint.text.Atlas,
531 code_atlas: gui.paint.text.Atlas,
532 frame_workspace: gui.frame.Workspace,
533 frame: gui.model.UiFrame,
534 commands: gui.paint.CommandBuffer,
535
536 fn init(allocator: Allocator) !MixedMetricTextBenchmarkWorkload {
537 var glyphs: [95]gui.paint.text.BitmapGlyph = undefined;
538 for (&glyphs, 0..) |*glyph, index| {
539 const codepoint: u21 = @intCast(index + 32);
540 glyph.* = .{
541 .codepoint = codepoint,
542 .rows = if (codepoint == ' ') &styled_text_space_rows else &styled_text_glyph_rows,
543 };
544 }
545 var base_atlas = try gui.paint.text.Atlas.initFromBitmapGlyphs(
546 allocator,
547 &glyphs,
548 .{ .width = 8, .height = 8, .stride = 1 },
549 16,
550 profiling_text_atlas_cache_limits,
551 );
552 errdefer base_atlas.deinit();
553 var code_atlas = try gui.paint.text.Atlas.initFromBitmapGlyphs(
554 allocator,
555 &glyphs,
556 .{ .width = 8, .height = 8, .stride = 1 },
557 16,
558 profiling_text_atlas_cache_limits,
559 );
560 errdefer code_atlas.deinit();
561 const entries = [_]gui.paint.text.AtlasSet.Entry{
562 .{ .face = 1, .image_index = 0, .atlas = &base_atlas },
563 .{ .face = 2, .image_index = 1, .atlas = &code_atlas },
564 };
565 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
566 var frame_workspace = gui.frame.Workspace.init(allocator);
567 errdefer frame_workspace.deinit();
568 const children = [_]gui.model.UiNode{.{
569 .widget_id = 2,
570 .kind = .text_input,
571 .text = .{
572 .content = styled_text_content,
573 .runs = &mixed_metric_text_runs,
574 .styles = &mixed_metric_text_styles,
575 .font_asset_id = 1,
576 .point_size = 16,
577 .line_height = 1.25,
578 .wrap_width = 144,
579 },
580 .text_selection = .{
581 .cursor_visible = true,
582 .cursor_byte_offset = 23,
583 .selection_active = true,
584 .selection_anchor_byte_offset = 5,
585 .selection_focus_byte_offset = 14,
586 },
587 .paint = .{
588 .foreground = .{ .r = 224, .g = 227, .b = 234, .a = 255 },
589 .background = .{ .r = 24, .g = 28, .b = 37, .a = 255 },
590 },
591 .size = .{ .width = 144, .height = 80 },
592 }};
593 const surface = gui.model.UiSurfaceTree{
594 .available_size = .{ .width = 144, .height = 80 },
595 .root = .{ .widget_id = 1, .children = &children },
596 };
597 const frame = try frame_workspace.buildSurface(&surface, .{
598 .resolvers = gui.paint.text.frameResolvers(&atlases),
599 });
600 var commands = gui.paint.CommandBuffer.init(allocator);
601 errdefer commands.deinit();
602 try gui.paint.text.appendFrameCommands(&commands, frame, &atlases, 1, 0);
603 commands.reset();
604 return .{
605 .base_atlas = base_atlas,
606 .code_atlas = code_atlas,
607 .frame_workspace = frame_workspace,
608 .frame = frame,
609 .commands = commands,
610 };
611 }
612
613 fn deinit(self: *MixedMetricTextBenchmarkWorkload) void {
614 self.commands.deinit();
615 self.frame_workspace.deinit();
616 self.code_atlas.deinit();
617 self.base_atlas.deinit();
618 self.* = undefined;
619 }
620 };
621
622 fn recordMixedMetricTextCommands(_: Allocator) void {
623 const workload = mixed_metric_text_benchmark_workload orelse
624 @panic("gui mixed metric text benchmark workload missing");
625 const entries = [_]gui.paint.text.AtlasSet.Entry{
626 .{ .face = 1, .image_index = 0, .atlas = &workload.base_atlas },
627 .{ .face = 2, .image_index = 1, .atlas = &workload.code_atlas },
628 };
629 const atlases = gui.paint.text.AtlasSet{ .entries = &entries };
630 var checksum: usize = 0;
631 const phase = bench.phaseAt("gui.paint.text.mixed_metric_transcript_row", @src());
632 defer phase.end();
633 for (0..styled_text_benchmark_repetitions) |_| {
634 workload.commands.reset();
635 gui.paint.text.appendFrameCommands(
636 &workload.commands,
637 workload.frame,
638 &atlases,
639 1,
640 0,
641 ) catch @panic("gui mixed metric text command emission failed");
642 checksum +%= workload.commands.items().len;
643 bench.coz.progressNamed("gui.paint.text.mixed_metric_transcript_row.frame");
644 }
645 std.mem.doNotOptimizeAway(checksum);
646 }
647
648 const FallbackTextBenchmarkWorkload = struct {
649 allocator: Allocator,
650 primary_bytes: []u8,
651 fallback_bytes: []u8,
652 primary_font: filigree.Font,
653 fallback_font: filigree.Font,
654 fallback: gui.paint.TextFallback,
655
656 const primary_content = "const alpha = value + 1;";
657 const mixed_content = "const \u{3b2}eta = alpha + 1;";
658
659 fn init(allocator: Allocator) !FallbackTextBenchmarkWorkload {
660 const primary_bytes = try filigree.fixtures.createWithOutlines(allocator);
661 errdefer allocator.free(primary_bytes);
662 const fallback_bytes = try filigree.fixtures.createFallbackWithOutlines(allocator);
663 errdefer allocator.free(fallback_bytes);
664 var primary_font = filigree.Font.initFromBytes(primary_bytes.ptr, primary_bytes.len) orelse return error.InvalidFont;
665 errdefer primary_font.deinit();
666 var fallback_font = filigree.Font.initFromBytes(fallback_bytes.ptr, fallback_bytes.len) orelse return error.InvalidFont;
667 errdefer fallback_font.deinit();
668 var fallback = try gui.paint.TextFallback.init(allocator, .{
669 .max_source_units = 128,
670 .cache_entries = 4,
671 .cache_payload_bytes = 4096,
672 });
673 errdefer fallback.deinit();
674 _ = try fallback.segments(&primary_font, &fallback_font, primary_content);
675 _ = try fallback.segments(&primary_font, &fallback_font, mixed_content);
676 return .{
677 .allocator = allocator,
678 .primary_bytes = primary_bytes,
679 .fallback_bytes = fallback_bytes,
680 .primary_font = primary_font,
681 .fallback_font = fallback_font,
682 .fallback = fallback,
683 };
684 }
685
686 fn deinit(self: *FallbackTextBenchmarkWorkload) void {
687 self.fallback.deinit();
688 self.fallback_font.deinit();
689 self.primary_font.deinit();
690 self.allocator.free(self.fallback_bytes);
691 self.allocator.free(self.primary_bytes);
692 self.* = undefined;
693 }
694 };
695
696 fn shapeFallbackText(_: Allocator) void {
697 const workload = fallback_text_benchmark_workload orelse
698 @panic("gui fallback text benchmark workload missing");
699 var checksum: usize = 0;
700 const phase = bench.phaseAt("gui.paint.text.fallback_primary_mixed_beta", @src());
701 defer phase.end();
702 for (0..fallback_text_benchmark_repetitions) |_| {
703 const primary = workload.fallback.segments(
704 &workload.primary_font,
705 &workload.fallback_font,
706 FallbackTextBenchmarkWorkload.primary_content,
707 ) catch @panic("gui primary text fallback lookup failed");
708 const mixed = workload.fallback.segments(
709 &workload.primary_font,
710 &workload.fallback_font,
711 FallbackTextBenchmarkWorkload.mixed_content,
712 ) catch @panic("gui mixed beta text fallback lookup failed");
713 checksum +%= primary.len + mixed.len;
714 }
715 std.mem.doNotOptimizeAway(checksum);
716 bench.coz.progressNamed("gui.paint.text.fallback_primary_mixed_beta.complete");
717 }
718
719 const Spec = struct {
720 width: u32,
721 height: u32,
722 row_count: usize,
723 repetitions: usize,
724
725 fn commandCount(self: Spec) usize {
726 return 2 + self.row_count * 3;
727 }
728
729 fn pixelCount(self: Spec) usize {
730 return @as(usize, self.width) * @as(usize, self.height);
731 }
732 };
733
734 fn benchmarkAllocator() Allocator {
735 return sys.allocator.benchmarkAllocator();
736 }
737
738 fn benchMinTimeNs(allocator: Allocator, default: u64) !u64 {
739 const value = try sys.env.getOwned(allocator, "BENCH_MIN_TIME_NS");
740 defer if (value) |text| allocator.free(text);
741 return if (value) |text| try std.fmt.parseUnsigned(u64, text, 10) else default;
742 }
743
744 fn benchVulkanDeviceIndex(allocator: Allocator) !i32 {
745 const value = try sys.env.getOwned(allocator, "BENCH_VULKAN_DEVICE");
746 defer if (value) |text| allocator.free(text);
747 return if (value) |text| try std.fmt.parseInt(i32, text, 10) else 0;
748 }
749
750 fn cpuTimestampNs() u64 {
751 const now = sys.time.nanoTimestamp();
752 return @intCast(@max(now, @as(i128, 0)));
753 }
754
755 fn vulkanEvidenceCapture() evidence.Capture {
756 return .{
757 .tool = .profile_tests,
758 .id = "gui-paint-accy-vulkan",
759 .trace_schema = evidence.schema,
760 };
761 }
762
763 const CpuRangeOptions = struct {
764 capture: evidence.Capture,
765 launch: evidence.LaunchIdentity,
766 probe_id: ?[]const u8 = null,
767 range_name: []const u8,
768 range_kind: evidence.RangeKind = .annotation,
769 start_ns: u64,
770 end_ns: u64,
771 };
772
773 const HostPhaseMax = struct {
774 name: []const u8,
775 ns: u64,
776 };
777
778 const SurfacePhaseStats = struct {
779 count: usize,
780 total_ns: u64,
781 min_ns: u64,
782 median_ns: u64,
783 p75_ns: u64,
784 p95_ns: u64,
785 max_ns: u64,
786 };
787
788 const SurfacePhaseSamples = struct {
789 values: [vulkan_surface_timing_repetitions]u64 = @as([vulkan_surface_timing_repetitions]u64, @splat(0)),
790 count: usize = 0,
791 total_ns: u64 = 0,
792 max_ns: u64 = 0,
793
794 fn record(self: *SurfacePhaseSamples, duration: u64) void {
795 std.debug.assert(self.count < self.values.len);
796 self.values[self.count] = duration;
797 self.count += 1;
798 self.total_ns += duration;
799 self.max_ns = if (self.count == 1) duration else @max(self.max_ns, duration);
800 }
801
802 fn stats(self: SurfacePhaseSamples) SurfacePhaseStats {
803 std.debug.assert(self.count > 0);
804 var sorted: [vulkan_surface_timing_repetitions]u64 = undefined;
805 @memcpy(sorted[0..self.count], self.values[0..self.count]);
806 std.mem.sort(u64, sorted[0..self.count], {}, std.sort.asc(u64));
807 return .{
808 .count = self.count,
809 .total_ns = self.total_ns,
810 .min_ns = sorted[0],
811 .median_ns = medianNs(sorted[0..self.count]),
812 .p75_ns = percentileNs(sorted[0..self.count], 75, 100),
813 .p95_ns = percentileNs(sorted[0..self.count], 95, 100),
814 .max_ns = self.max_ns,
815 };
816 }
817 };
818
819 const VulkanEvidencePhases = struct {
820 prepare_ns: u64 = 0,
821 warm_launch_ns: u64 = 0,
822 warm_readback_ns: u64 = 0,
823 measured_launch_cpu_total_ns: u64 = 0,
824 measured_launch_cpu_max_ns: u64 = 0,
825 measured_readback_total_ns: u64 = 0,
826 measured_readback_max_ns: u64 = 0,
827 evaluation_ns: u64 = 0,
828
829 fn recordPrepare(self: *VulkanEvidencePhases, start_ns: u64, end_ns: u64) void {
830 self.prepare_ns = durationNs(start_ns, end_ns);
831 }
832
833 fn recordWarmLaunch(self: *VulkanEvidencePhases, start_ns: u64, end_ns: u64) void {
834 self.warm_launch_ns = durationNs(start_ns, end_ns);
835 }
836
837 fn recordWarmReadback(self: *VulkanEvidencePhases, start_ns: u64, end_ns: u64) void {
838 self.warm_readback_ns = durationNs(start_ns, end_ns);
839 }
840
841 fn recordMeasuredLaunch(self: *VulkanEvidencePhases, start_ns: u64, end_ns: u64) void {
842 const duration = durationNs(start_ns, end_ns);
843 self.measured_launch_cpu_total_ns += duration;
844 self.measured_launch_cpu_max_ns = @max(self.measured_launch_cpu_max_ns, duration);
845 }
846
847 fn recordMeasuredReadback(self: *VulkanEvidencePhases, start_ns: u64, end_ns: u64) void {
848 const duration = durationNs(start_ns, end_ns);
849 self.measured_readback_total_ns += duration;
850 self.measured_readback_max_ns = @max(self.measured_readback_max_ns, duration);
851 }
852
853 fn recordEvaluation(self: *VulkanEvidencePhases, start_ns: u64, end_ns: u64) void {
854 self.evaluation_ns = durationNs(start_ns, end_ns);
855 }
856
857 fn maxHostPhase(self: VulkanEvidencePhases) HostPhaseMax {
858 var result: HostPhaseMax = .{ .name = "prepare", .ns = self.prepare_ns };
859 result = maxPhase(result, "warm_launch", self.warm_launch_ns);
860 result = maxPhase(result, "warm_readback", self.warm_readback_ns);
861 result = maxPhase(result, "measured_launch_cpu_total", self.measured_launch_cpu_total_ns);
862 result = maxPhase(result, "measured_readback_total", self.measured_readback_total_ns);
863 return result;
864 }
865 };
866
867 const VulkanSurfaceEvidencePhases = struct {
868 prepare_ns: u64 = 0,
869 warm_launch_ns: u64 = 0,
870 warm_surface_ns: u64 = 0,
871 measured_launch_cpu_total_ns: u64 = 0,
872 measured_launch_cpu_max_ns: u64 = 0,
873 measured_surface_total_ns: u64 = 0,
874 measured_surface_max_ns: u64 = 0,
875 evaluation_ns: u64 = 0,
876
877 fn recordPrepare(self: *VulkanSurfaceEvidencePhases, start_ns: u64, end_ns: u64) void {
878 self.prepare_ns = durationNs(start_ns, end_ns);
879 }
880
881 fn recordWarmLaunch(self: *VulkanSurfaceEvidencePhases, start_ns: u64, end_ns: u64) void {
882 self.warm_launch_ns = durationNs(start_ns, end_ns);
883 }
884
885 fn recordWarmSurface(self: *VulkanSurfaceEvidencePhases, start_ns: u64, end_ns: u64) void {
886 self.warm_surface_ns = durationNs(start_ns, end_ns);
887 }
888
889 fn recordMeasuredLaunch(self: *VulkanSurfaceEvidencePhases, start_ns: u64, end_ns: u64) void {
890 const duration = durationNs(start_ns, end_ns);
891 self.measured_launch_cpu_total_ns += duration;
892 self.measured_launch_cpu_max_ns = @max(self.measured_launch_cpu_max_ns, duration);
893 }
894
895 fn recordMeasuredSurface(self: *VulkanSurfaceEvidencePhases, start_ns: u64, end_ns: u64) void {
896 const duration = durationNs(start_ns, end_ns);
897 self.measured_surface_total_ns += duration;
898 self.measured_surface_max_ns = @max(self.measured_surface_max_ns, duration);
899 }
900
901 fn recordEvaluation(self: *VulkanSurfaceEvidencePhases, start_ns: u64, end_ns: u64) void {
902 self.evaluation_ns = durationNs(start_ns, end_ns);
903 }
904
905 fn maxHostPhase(self: VulkanSurfaceEvidencePhases) HostPhaseMax {
906 var result: HostPhaseMax = .{ .name = "prepare", .ns = self.prepare_ns };
907 result = maxPhase(result, "warm_launch", self.warm_launch_ns);
908 result = maxPhase(result, "warm_surface", self.warm_surface_ns);
909 result = maxPhase(result, "measured_launch_cpu_total", self.measured_launch_cpu_total_ns);
910 result = maxPhase(result, "measured_surface_total", self.measured_surface_total_ns);
911 return result;
912 }
913 };
914
915 const VulkanSurfaceSteadyPhases = struct {
916 prepare_ns: u64 = 0,
917 warm_frame_ns: u64 = 0,
918 measured_frames: SurfacePhaseSamples = .{},
919 measured_prepares: SurfacePhaseSamples = .{},
920 measured_launches: SurfacePhaseSamples = .{},
921 measured_surfaces: SurfacePhaseSamples = .{},
922 evaluation_ns: u64 = 0,
923
924 fn recordPrepare(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
925 self.prepare_ns = durationNs(start_ns, end_ns);
926 }
927
928 fn recordWarmFrame(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
929 self.warm_frame_ns = durationNs(start_ns, end_ns);
930 }
931
932 fn recordMeasuredFrame(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
933 self.measured_frames.record(durationNs(start_ns, end_ns));
934 }
935
936 fn recordMeasuredPrepare(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
937 self.measured_prepares.record(durationNs(start_ns, end_ns));
938 }
939
940 fn recordMeasuredLaunch(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
941 self.measured_launches.record(durationNs(start_ns, end_ns));
942 }
943
944 fn recordMeasuredSurface(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
945 self.measured_surfaces.record(durationNs(start_ns, end_ns));
946 }
947
948 fn recordEvaluation(self: *VulkanSurfaceSteadyPhases, start_ns: u64, end_ns: u64) void {
949 self.evaluation_ns = durationNs(start_ns, end_ns);
950 }
951
952 fn maxHostPhase(self: VulkanSurfaceSteadyPhases) HostPhaseMax {
953 var result: HostPhaseMax = .{ .name = "prepare", .ns = self.prepare_ns };
954 result = maxPhase(result, "warm_frame", self.warm_frame_ns);
955 result = maxPhase(result, "measured_prepare_total", self.measured_prepares.total_ns);
956 result = maxPhase(result, "measured_launch_cpu_total", self.measured_launches.total_ns);
957 result = maxPhase(result, "measured_surface_total", self.measured_surfaces.total_ns);
958 return result;
959 }
960 };
961
962 fn durationNs(start_ns: u64, end_ns: u64) u64 {
963 return end_ns - start_ns;
964 }
965
966 fn medianNs(sorted: []const u64) u64 {
967 std.debug.assert(sorted.len > 0);
968 const middle = sorted.len / 2;
969 if (sorted.len % 2 == 1) return sorted[middle];
970 return sorted[middle - 1] + (sorted[middle] - sorted[middle - 1]) / 2;
971 }
972
973 fn percentileNs(sorted: []const u64, numerator: usize, denominator: usize) u64 {
974 std.debug.assert(sorted.len > 0);
975 std.debug.assert(numerator <= denominator);
976 const index = @min((sorted.len * numerator + denominator - 1) / denominator - 1, sorted.len - 1);
977 return sorted[index];
978 }
979
980 fn maxPhase(current: HostPhaseMax, name: []const u8, ns: u64) HostPhaseMax {
981 return if (ns > current.ns) .{ .name = name, .ns = ns } else current;
982 }
983
984 test "bench: surface phase samples report latency distribution" {
985 var samples = SurfacePhaseSamples{};
986 samples.record(40);
987 samples.record(10);
988 samples.record(30);
989 samples.record(20);
990 const stats = samples.stats();
991 try std.testing.expectEqual(@as(usize, 4), stats.count);
992 try std.testing.expectEqual(@as(u64, 100), stats.total_ns);
993 try std.testing.expectEqual(@as(u64, 10), stats.min_ns);
994 try std.testing.expectEqual(@as(u64, 25), stats.median_ns);
995 try std.testing.expectEqual(@as(u64, 30), stats.p75_ns);
996 try std.testing.expectEqual(@as(u64, 40), stats.p95_ns);
997 try std.testing.expectEqual(@as(u64, 40), stats.max_ns);
998 }
999
1000 fn writeCpuRangeRecord(writer: *std.Io.Writer, options: CpuRangeOptions) !void {
1001 const record = evidence.RangeRecord{
1002 .capture = options.capture,
1003 .launch = options.launch,
1004 .probe_id = options.probe_id,
1005 .range = .{
1006 .name = options.range_name,
1007 .kind = options.range_kind,
1008 .cpu_start_ns = options.start_ns,
1009 .cpu_end_ns = options.end_ns,
1010 },
1011 };
1012 try record.validate();
1013 try evidence.writeRecord(writer, .{ .range = record });
1014 }
1015
1016 fn addCpuRange(record: *evidence.RangeRecord, start_ns: u64, end_ns: u64) !void {
1017 record.range.cpu_start_ns = start_ns;
1018 record.range.cpu_end_ns = end_ns;
1019 try record.validate();
1020 }
1021
1022 fn launchRangeRecordFromTiming(options: evidence.LaunchRangeOptions, timing: gui.paint.PreparedLaunchTiming) !evidence.RangeRecord {
1023 const end_ns = std.math.add(u64, options.device_start_ns, timing.elapsed_ns) catch return error.InvalidRange;
1024 const record = evidence.RangeRecord{
1025 .capture = options.capture,
1026 .launch = options.launch,
1027 .probe_id = options.probe_id,
1028 .range = .{
1029 .name = options.range_name,
1030 .kind = options.range_kind,
1031 .device_start_ns = options.device_start_ns,
1032 .device_end_ns = end_ns,
1033 .stream_id = options.stream_id orelse timing.stream_id,
1034 .context_id = options.context_id,
1035 },
1036 };
1037 try record.validate();
1038 return record;
1039 }
1040
1041 fn renderCpuPacked(sample_allocator: Allocator) void {
1042 var workload = Workload.init(sample_allocator, cpu_spec) catch @panic("gui paint benchmark workload allocation failed");
1043 defer workload.deinit(sample_allocator);
1044
1045 var checksum: u32 = 0;
1046 const phase = bench.phaseAt("gui.paint.cpu_packed", @src());
1047 defer phase.end();
1048 var index: usize = 0;
1049 while (index < workload.spec.repetitions) : (index += 1) {
1050 gui.paint.renderCommandsPacked(workload.commands, .{
1051 .width = workload.spec.width,
1052 .height = workload.spec.height,
1053 .pixels = workload.pixels,
1054 }, clear) catch @panic("gui paint CPU benchmark render failed");
1055 checksum +%= checksumPixels(workload.pixels);
1056 bench.coz.progressNamed("gui.paint.cpu_packed.frame");
1057 }
1058 std.mem.doNotOptimizeAway(checksum);
1059 }
1060
1061 fn renderLinearGradientFullHdCpuPacked(_: Allocator) void {
1062 const commands = [_]Command{.{
1063 .kind = .linear_gradient,
1064 .rect = .{ .width = @floatFromInt(gradient_frame_width), .height = @floatFromInt(gradient_frame_height) },
1065 .clip = .{ .width = @floatFromInt(gradient_frame_width), .height = @floatFromInt(gradient_frame_height) },
1066 .color = .{ .r = 18, .g = 28, .b = 52, .a = 255 },
1067 .color_end = .{ .r = 92, .g = 64, .b = 148, .a = 255 },
1068 .gradient_end = .{ .x = @floatFromInt(gradient_frame_width), .y = @floatFromInt(gradient_frame_height) },
1069 }};
1070 const phase = bench.phaseAt("gui.paint.linear_gradient.full_hd", @src());
1071 defer phase.end();
1072 gui.paint.renderCommandsPacked(commands[0..], .{
1073 .width = gradient_frame_width,
1074 .height = gradient_frame_height,
1075 .pixels = gradient_frame_pixels[0..],
1076 }, clear) catch @panic("gui paint linear gradient full-HD render failed");
1077 const checksum = checksumPixels(gradient_frame_pixels[0..]);
1078 std.mem.doNotOptimizeAway(checksum);
1079 bench.coz.progressNamed("gui.paint.linear_gradient.full_hd.frame");
1080 }
1081
1082 fn renderCompositorCpuPacked(sample_allocator: Allocator) void {
1083 var workload = Workload.init(sample_allocator, cpu_spec) catch @panic("gui paint compositor benchmark workload allocation failed");
1084 defer workload.deinit(sample_allocator);
1085 var compositor = gui.paint.Compositor.initCpu();
1086 defer compositor.deinit();
1087
1088 var checksum: u32 = 0;
1089 const phase = bench.phaseAt("gui.paint.compositor.cpu_packed", @src());
1090 defer phase.end();
1091 var index: usize = 0;
1092 while (index < workload.spec.repetitions) : (index += 1) {
1093 compositor.renderCommandsPacked(workload.commands, .{
1094 .width = workload.spec.width,
1095 .height = workload.spec.height,
1096 .pixels = workload.pixels,
1097 }, clear) catch @panic("gui paint compositor CPU benchmark render failed");
1098 checksum +%= checksumPixels(workload.pixels);
1099 bench.coz.progressNamed("gui.paint.compositor.cpu_packed.frame");
1100 }
1101 std.mem.doNotOptimizeAway(checksum);
1102 }
1103
1104 fn renderAccyCpuPacked(sample_allocator: Allocator) void {
1105 var workload = Workload.init(sample_allocator, accy_cpu_spec) catch @panic("gui paint benchmark workload allocation failed");
1106 defer workload.deinit(sample_allocator);
1107 var encoded = gui.paint.accy.packCommandsAlloc(sample_allocator, workload.commands) catch @panic("gui paint Accy benchmark command packing failed");
1108 defer encoded.deinit(sample_allocator);
1109 var encoded_images = gui.paint.accy.packImagesAlloc(sample_allocator, .{}) catch @panic("gui paint Accy benchmark image packing failed");
1110 defer encoded_images.deinit(sample_allocator);
1111 var bins = gui.paint.accy.binCommandsAlloc(sample_allocator, workload.commands, workload.spec.width, workload.spec.height, gui.paint.Region.full(workload.spec.width, workload.spec.height)) catch @panic("gui paint Accy benchmark binning failed");
1112 defer bins.deinit(sample_allocator);
1113 var graph = gui.paint.accy.buildGraph(sample_allocator, gui.paint.accy.default_threads) catch @panic("gui paint Accy benchmark graph build failed");
1114 defer graph.deinit();
1115
1116 var checksum: u32 = 0;
1117 const phase = bench.phaseAt("gui.paint.accy_cpu_packed", @src());
1118 defer phase.end();
1119 var index: usize = 0;
1120 while (index < workload.spec.repetitions) : (index += 1) {
1121 gui.paint.accy.runPackedCommandsCpu(
1122 sample_allocator,
1123 &graph,
1124 encoded,
1125 encoded_images,
1126 bins,
1127 0,
1128 workload.commands.len,
1129 workload.spec.width,
1130 workload.spec.height,
1131 workload.pixels,
1132 clear,
1133 ) catch @panic("gui paint Accy benchmark render failed");
1134 checksum +%= checksumPixels(workload.pixels);
1135 bench.coz.progressNamed("gui.paint.accy_cpu_packed.frame");
1136 }
1137 std.mem.doNotOptimizeAway(checksum);
1138 }
1139
1140 fn renderCpuFramePacked(sample_allocator: Allocator) void {
1141 var workload = FrameWorkload.init(sample_allocator, frame_cpu_spec) catch @panic("gui paint frame benchmark workload allocation failed");
1142 defer workload.deinit(sample_allocator);
1143
1144 var checksum: u32 = 0;
1145 const phase = bench.phaseAt("gui.paint.frame.cpu_packed", @src());
1146 defer phase.end();
1147 var index: usize = 0;
1148 while (index < workload.spec.repetitions) : (index += 1) {
1149 gui.paint.renderCommandsPacked(workload.commands, .{
1150 .width = workload.spec.width,
1151 .height = workload.spec.height,
1152 .pixels = workload.pixels,
1153 }, clear) catch @panic("gui paint frame CPU benchmark render failed");
1154 checksum +%= checksumPixels(workload.pixels);
1155 bench.coz.progressNamed("gui.paint.frame.cpu_packed.frame");
1156 }
1157 std.mem.doNotOptimizeAway(checksum);
1158 }
1159
1160 fn renderCompositorCpuFramePacked(sample_allocator: Allocator) void {
1161 var workload = FrameWorkload.init(sample_allocator, frame_cpu_spec) catch @panic("gui paint compositor frame benchmark workload allocation failed");
1162 defer workload.deinit(sample_allocator);
1163 var compositor = gui.paint.Compositor.initCpu();
1164 defer compositor.deinit();
1165
1166 var checksum: u32 = 0;
1167 const phase = bench.phaseAt("gui.paint.frame.compositor.cpu_packed", @src());
1168 defer phase.end();
1169 var index: usize = 0;
1170 while (index < workload.spec.repetitions) : (index += 1) {
1171 compositor.renderCommandsPacked(workload.commands, .{
1172 .width = workload.spec.width,
1173 .height = workload.spec.height,
1174 .pixels = workload.pixels,
1175 }, clear) catch @panic("gui paint compositor frame CPU benchmark render failed");
1176 checksum +%= checksumPixels(workload.pixels);
1177 bench.coz.progressNamed("gui.paint.frame.compositor.cpu_packed.frame");
1178 }
1179 std.mem.doNotOptimizeAway(checksum);
1180 }
1181
1182 fn renderAccyCpuFramePacked(sample_allocator: Allocator) void {
1183 var workload = FrameWorkload.init(sample_allocator, frame_accy_cpu_spec) catch @panic("gui paint frame benchmark workload allocation failed");
1184 defer workload.deinit(sample_allocator);
1185 var encoded = gui.paint.accy.packCommandsAlloc(sample_allocator, workload.commands) catch @panic("gui paint frame Accy benchmark command packing failed");
1186 defer encoded.deinit(sample_allocator);
1187 var encoded_images = gui.paint.accy.packImagesAlloc(sample_allocator, .{}) catch @panic("gui paint frame Accy benchmark image packing failed");
1188 defer encoded_images.deinit(sample_allocator);
1189 var bins = gui.paint.accy.binCommandsAlloc(sample_allocator, workload.commands, workload.spec.width, workload.spec.height, gui.paint.Region.full(workload.spec.width, workload.spec.height)) catch @panic("gui paint frame Accy benchmark binning failed");
1190 defer bins.deinit(sample_allocator);
1191 var graph = gui.paint.accy.buildGraph(sample_allocator, gui.paint.accy.default_threads) catch @panic("gui paint frame Accy benchmark graph build failed");
1192 defer graph.deinit();
1193
1194 var checksum: u32 = 0;
1195 const phase = bench.phaseAt("gui.paint.frame.accy_cpu_packed", @src());
1196 defer phase.end();
1197 var index: usize = 0;
1198 while (index < workload.spec.repetitions) : (index += 1) {
1199 gui.paint.accy.runPackedCommandsCpu(
1200 sample_allocator,
1201 &graph,
1202 encoded,
1203 encoded_images,
1204 bins,
1205 0,
1206 workload.commands.len,
1207 workload.spec.width,
1208 workload.spec.height,
1209 workload.pixels,
1210 clear,
1211 ) catch @panic("gui paint frame Accy benchmark render failed");
1212 checksum +%= checksumPixels(workload.pixels);
1213 bench.coz.progressNamed("gui.paint.frame.accy_cpu_packed.frame");
1214 }
1215 std.mem.doNotOptimizeAway(checksum);
1216 }
1217
1218 fn recordFrameCommands(sample_allocator: Allocator) void {
1219 var surface = frameSurfaceAlloc(sample_allocator, phase_spec) catch @panic("gui paint frame command record surface failed");
1220 defer surface.deinit();
1221 var frame_workspace = gui.frame.Workspace.init(sample_allocator);
1222 defer frame_workspace.deinit();
1223 const frame = frame_workspace.buildSurface(&surface.surface, .{}) catch @panic("gui paint frame command record frame failed");
1224 var commands = gui.paint.CommandBuffer.init(sample_allocator);
1225 defer commands.deinit();
1226
1227 var checksum: usize = 0;
1228 const phase = bench.phaseAt("gui.paint.phase.record_frame_commands", @src());
1229 defer phase.end();
1230 var index: usize = 0;
1231 while (index < phase_spec.repetitions) : (index += 1) {
1232 commands.reset();
1233 commands.appendFrame(frame, 1, phase_spec.width, phase_spec.height, .{}) catch @panic("gui paint frame command record failed");
1234 checksum +%= commands.items().len;
1235 bench.coz.progressNamed("gui.paint.phase.record_frame_commands.frame");
1236 }
1237 std.mem.doNotOptimizeAway(checksum);
1238 }
1239
1240 fn buildSurfaceFrames(sample_allocator: Allocator) void {
1241 var surface = frameSurfaceAlloc(sample_allocator, phase_spec) catch @panic("gui frame workspace surface failed");
1242 defer surface.deinit();
1243 var workspace = gui.frame.Workspace.init(sample_allocator);
1244 defer workspace.deinit();
1245
1246 var checksum: usize = 0;
1247 const phase = bench.phaseAt("gui.frame.workspace.build", @src());
1248 defer phase.end();
1249 for (0..workspace_repetitions) |_| {
1250 const frame = workspace.buildSurface(&surface.surface, .{}) catch @panic("gui frame workspace build failed");
1251 checksum +%= frame.widgets.len;
1252 bench.coz.progressNamed("gui.frame.workspace.build.frame");
1253 }
1254 std.mem.doNotOptimizeAway(checksum);
1255 }
1256
1257 fn recordCommandBuffers(sample_allocator: Allocator) void {
1258 var surface = frameSurfaceAlloc(sample_allocator, phase_spec) catch @panic("gui command buffer surface failed");
1259 defer surface.deinit();
1260 var frame_workspace = gui.frame.Workspace.init(sample_allocator);
1261 defer frame_workspace.deinit();
1262 const frame = frame_workspace.buildSurface(&surface.surface, .{}) catch @panic("gui command buffer frame failed");
1263 var commands = gui.paint.CommandBuffer.init(sample_allocator);
1264 defer commands.deinit();
1265
1266 var checksum: usize = 0;
1267 const phase = bench.phaseAt("gui.paint.command.buffer", @src());
1268 defer phase.end();
1269 for (0..workspace_repetitions) |_| {
1270 commands.reset();
1271 commands.appendFrame(frame, 1, phase_spec.width, phase_spec.height, .{}) catch @panic("gui command buffer record failed");
1272 checksum +%= commands.items().len;
1273 bench.coz.progressNamed("gui.paint.command.buffer.frame");
1274 }
1275 std.mem.doNotOptimizeAway(checksum);
1276 }
1277
1278 fn recordRetainedSizedFrames(sample_allocator: Allocator) void {
1279 var surface = frameSurfaceAlloc(sample_allocator, retained_spec) catch
1280 @panic("gui retained recording surface failed");
1281 defer surface.deinit();
1282 var workspace = gui.frame.Workspace.init(sample_allocator);
1283 defer workspace.deinit();
1284 const frame = workspace.buildSurface(&surface.surface, .{}) catch
1285 @panic("gui retained recording frame failed");
1286 var commands = gui.paint.CommandBuffer.init(sample_allocator);
1287 defer commands.deinit();
1288 commands.appendFrame(
1289 frame,
1290 1,
1291 retained_spec.width,
1292 retained_spec.height,
1293 .{},
1294 ) catch @panic("gui retained recording warmup failed");
1295
1296 var checksum: usize = 0;
1297 const phase = bench.phaseAt("gui.paint.retained.record", @src());
1298 defer phase.end();
1299 for (0..retained_repetitions) |_| {
1300 commands.reset();
1301 commands.appendFrame(
1302 frame,
1303 1,
1304 retained_spec.width,
1305 retained_spec.height,
1306 .{},
1307 ) catch @panic("gui retained recording failed");
1308 checksum +%= commands.items().len + commands.fragmentItems().len;
1309 bench.coz.progressNamed("gui.paint.retained.record.frame");
1310 }
1311 std.mem.doNotOptimizeAway(checksum);
1312 }
1313
1314 fn diffRetainedFrames(sample_allocator: Allocator) void {
1315 var surface = frameSurfaceAlloc(sample_allocator, retained_spec) catch
1316 @panic("gui retained diff surface failed");
1317 defer surface.deinit();
1318 var workspace = gui.frame.Workspace.init(sample_allocator);
1319 defer workspace.deinit();
1320 const frame = workspace.buildSurface(&surface.surface, .{}) catch
1321 @panic("gui retained diff frame failed");
1322 var retained = gui.paint.RetainedCommands.init(sample_allocator);
1323 defer retained.deinit();
1324 var current = gui.paint.CommandBuffer.init(sample_allocator);
1325 defer current.deinit();
1326 recordRetainedFrame(¤t, frame);
1327 retained.retain(¤t) catch @panic("gui retained baseline failed");
1328 recordRetainedFrame(¤t, frame);
1329 _ = retained.diff(
1330 ¤t,
1331 &.{gui.paint.Region.full(retained_spec.width, retained_spec.height)},
1332 retained_spec.width,
1333 retained_spec.height,
1334 ) catch @panic("gui retained diff warmup failed");
1335 retained.retain(¤t) catch @panic("gui retained warmup failed");
1336
1337 var checksum: usize = 0;
1338 const phase = bench.phaseAt("gui.paint.retained.diff", @src());
1339 defer phase.end();
1340 for (0..retained_repetitions) |_| {
1341 recordRetainedFrame(¤t, frame);
1342 const damage = retained.diff(
1343 ¤t,
1344 &.{gui.paint.Region.full(retained_spec.width, retained_spec.height)},
1345 retained_spec.width,
1346 retained_spec.height,
1347 ) catch @panic("gui retained diff failed");
1348 checksum +%= if (damage == .semantic) 1 else damage.narrowed.pixelCount();
1349 retained.retain(¤t) catch @panic("gui retained commit failed");
1350 bench.coz.progressNamed("gui.paint.retained.diff.frame");
1351 }
1352 std.mem.doNotOptimizeAway(checksum);
1353 }
1354
1355 fn recordRetainedFrame(
1356 commands: *gui.paint.CommandBuffer,
1357 frame: gui.model.UiFrame,
1358 ) void {
1359 commands.appendFrame(
1360 frame,
1361 1,
1362 retained_spec.width,
1363 retained_spec.height,
1364 .{},
1365 ) catch @panic("gui retained frame recording failed");
1366 }
1367
1368 fn diffRetainedScale(sample_allocator: Allocator) void {
1369 var retained = gui.paint.RetainedCommands.init(sample_allocator);
1370 defer retained.deinit();
1371 var current = gui.paint.CommandBuffer.init(sample_allocator);
1372 defer current.deinit();
1373 appendRetainedScale(¤t, false);
1374 retained.retain(¤t) catch @panic("gui retained scale baseline failed");
1375 appendRetainedScale(¤t, true);
1376 _ = retained.diff(
1377 ¤t,
1378 &.{gui.paint.Region.full(4096, 64)},
1379 4096,
1380 64,
1381 ) catch @panic("gui retained scale warmup failed");
1382
1383 var checksum: usize = 0;
1384 const phase = bench.phaseAt("gui.paint.retained.diff_scale", @src());
1385 defer phase.end();
1386 for (0..retained_scale_repetitions) |_| {
1387 const damage = retained.diff(
1388 ¤t,
1389 &.{gui.paint.Region.full(4096, 64)},
1390 4096,
1391 64,
1392 ) catch @panic("gui retained scale diff failed");
1393 checksum +%= if (damage == .semantic) 1 else damage.narrowed.pixelCount();
1394 bench.coz.progressNamed("gui.paint.retained.diff_scale.frame");
1395 }
1396 std.mem.doNotOptimizeAway(checksum);
1397 }
1398
1399 fn appendRetainedScale(
1400 commands: *gui.paint.CommandBuffer,
1401 changed: bool,
1402 ) void {
1403 commands.ensureCapacity(retained_scale_fragments) catch
1404 @panic("gui retained scale command capacity failed");
1405 commands.ensureFragmentCapacity(retained_scale_fragments) catch
1406 @panic("gui retained scale fragment capacity failed");
1407 for (0..retained_scale_fragments) |index| {
1408 const start = commands.items().len;
1409 const selected = changed and index == retained_scale_fragments / 2;
1410 commands.append(.{
1411 .kind = .fill,
1412 .rect = .{
1413 .x = @floatFromInt(index % 4096),
1414 .y = @floatFromInt(index / 4096),
1415 .width = 1,
1416 .height = 1,
1417 },
1418 .clip = .{ .width = 4096, .height = 64 },
1419 .color = .{ .r = if (selected) 255 else 32, .a = 255 },
1420 }) catch @panic("gui retained scale command failed");
1421 commands.commitFragment(.{
1422 .root_id = 1,
1423 .element_id = @intCast(index + 1),
1424 .namespace = gui.paint.command.fragment_namespace_widget,
1425 .part = gui.paint.command.fragment_part_chrome,
1426 }, start) catch @panic("gui retained scale fragment failed");
1427 }
1428 }
1429
1430 fn packAccyCommands(sample_allocator: Allocator) void {
1431 var workload = FrameWorkload.init(sample_allocator, phase_spec) catch @panic("gui paint Accy command pack workload failed");
1432 defer workload.deinit(sample_allocator);
1433 const floats = sample_allocator.alloc(f32, workload.commands.len * gui.paint.accy.float_lanes) catch @panic("gui paint Accy command pack floats failed");
1434 defer sample_allocator.free(floats);
1435 const words = sample_allocator.alloc(u32, workload.commands.len * gui.paint.accy.word_lanes) catch @panic("gui paint Accy command pack words failed");
1436 defer sample_allocator.free(words);
1437
1438 var checksum: u32 = 0;
1439 const phase = bench.phaseAt("gui.paint.phase.accy_pack_commands", @src());
1440 defer phase.end();
1441 var index: usize = 0;
1442 while (index < phase_spec.repetitions) : (index += 1) {
1443 gui.paint.accy.packCommands(workload.commands, floats, words);
1444 checksum +%= words[0];
1445 bench.coz.progressNamed("gui.paint.phase.accy_pack_commands.frame");
1446 }
1447 std.mem.doNotOptimizeAway(checksum);
1448 }
1449
1450 fn binAccyCommands(sample_allocator: Allocator) void {
1451 var workload = FrameWorkload.init(sample_allocator, phase_spec) catch @panic("gui paint Accy command bin workload failed");
1452 defer workload.deinit(sample_allocator);
1453
1454 var checksum: usize = 0;
1455 const phase = bench.phaseAt("gui.paint.phase.accy_bin_commands", @src());
1456 defer phase.end();
1457 var index: usize = 0;
1458 while (index < binning_repetitions) : (index += 1) {
1459 var bins = gui.paint.accy.binCommandsAlloc(sample_allocator, workload.commands, workload.spec.width, workload.spec.height, gui.paint.Region.full(workload.spec.width, workload.spec.height)) catch @panic("gui paint Accy command binning failed");
1460 checksum +%= bins.pairCount();
1461 bins.deinit(sample_allocator);
1462 bench.coz.progressNamed("gui.paint.phase.accy_bin_commands.frame");
1463 }
1464 std.mem.doNotOptimizeAway(checksum);
1465 }
1466
1467 fn binAccyCommandsScratch(sample_allocator: Allocator) void {
1468 var workload = FrameWorkload.init(sample_allocator, phase_spec) catch @panic("gui paint Accy scratch command bin workload failed");
1469 defer workload.deinit(sample_allocator);
1470 const region = gui.paint.Region.full(workload.spec.width, workload.spec.height);
1471 const shape = gui.paint.accy.binShape(workload.spec.width, workload.spec.height, region);
1472 const pair_count = gui.paint.accy.binPairCount(workload.commands, workload.spec.width, workload.spec.height, region) catch @panic("gui paint Accy scratch pair survey failed");
1473 const ranges = sample_allocator.alloc(u32, gui.paint.accy.tileRangeValueCount(workload.commands.len)) catch @panic("gui paint Accy scratch ranges failed");
1474 defer sample_allocator.free(ranges);
1475 const offsets = sample_allocator.alloc(u32, shape.tile_count + 1) catch @panic("gui paint Accy scratch offsets failed");
1476 defer sample_allocator.free(offsets);
1477 const indices = sample_allocator.alloc(u32, pair_count) catch @panic("gui paint Accy scratch indices failed");
1478 defer sample_allocator.free(indices);
1479 const cursors = sample_allocator.alloc(u32, shape.tile_count) catch @panic("gui paint Accy scratch cursors failed");
1480 defer sample_allocator.free(cursors);
1481 var scratch = gui.paint.accy.BinScratch{
1482 .ranges = ranges,
1483 .offsets = offsets,
1484 .indices = indices,
1485 .cursors = cursors,
1486 };
1487 _ = scratch.binCommands(workload.commands, workload.spec.width, workload.spec.height, region) catch @panic("gui paint Accy scratch warmup binning failed");
1488
1489 var checksum: usize = 0;
1490 const phase = bench.phaseAt("gui.paint.phase.accy_bin_commands_scratch", @src());
1491 defer phase.end();
1492 var index: usize = 0;
1493 while (index < binning_repetitions) : (index += 1) {
1494 const bins = scratch.binCommands(workload.commands, workload.spec.width, workload.spec.height, region) catch @panic("gui paint Accy scratch command binning failed");
1495 checksum +%= bins.pairCount();
1496 bench.coz.progressNamed("gui.paint.phase.accy_bin_commands_scratch.frame");
1497 }
1498 std.mem.doNotOptimizeAway(checksum);
1499 }
1500
1501 fn executorRecordingStorageEpoch(sample_allocator: Allocator) void {
1502 var workload = FrameWorkload.init(sample_allocator, phase_spec) catch @panic("gui paint Executor storage workload failed");
1503 defer workload.deinit(sample_allocator);
1504 var state = gpu.recording.BackendState{
1505 .allocator = sample_allocator,
1506 .kind = .vulkan,
1507 .format = .vulkan_spirv,
1508 };
1509 var executor = gui.paint.Executor.init(sample_allocator, state.handle(), .{
1510 .artifact_format = .vulkan_spirv,
1511 }) catch |err| std.debug.panic("gui paint Executor storage initialization failed: {s}", .{@errorName(err)});
1512 defer executor.deinit();
1513
1514 const phase = bench.phaseAt("gui.paint.executor.recording_storage_epoch", @src());
1515 defer phase.end();
1516 var checksum: usize = 0;
1517 var index: usize = 0;
1518 while (index < paint_storage_epoch_repetitions) : (index += 1) {
1519 const extent: u32 = @intCast(phase_spec.width - paint_storage_epoch_repetitions + index + 1);
1520 const prepared = (executor.prepareCommandsPackedLaunch(
1521 workload.commands,
1522 extent,
1523 extent,
1524 clear,
1525 .{},
1526 gui.paint.Region.full(extent, extent),
1527 ) catch |err| std.debug.panic("gui paint Executor replacement storage epoch failed: {s}", .{@errorName(err)})) orelse @panic("gui paint Executor replacement storage epoch missing");
1528 checksum +%= prepared.pixel_count;
1529 bench.coz.progressNamed("gui.paint.executor.recording_storage_epoch.lifecycle");
1530 }
1531 std.mem.doNotOptimizeAway(checksum);
1532 }
1533
1534 fn imageProcessorRecordingSteady(sample_allocator: Allocator) void {
1535 const width: u32 = 16;
1536 const height: u32 = 16;
1537 const pixel_count: usize = width * height;
1538 const src = sample_allocator.alloc(u32, pixel_count) catch @panic("gui paint image source allocation failed");
1539 defer sample_allocator.free(src);
1540 const dst = sample_allocator.alloc(u32, pixel_count) catch @panic("gui paint image destination allocation failed");
1541 defer sample_allocator.free(dst);
1542 for (src, 0..) |*pixel, index| pixel.* = @truncate(index *% 2654435761);
1543 var state = gpu.recording.BackendState{
1544 .allocator = sample_allocator,
1545 .kind = .vulkan,
1546 .format = .vulkan_spirv,
1547 };
1548 var processor = gui.paint.ImageProcessor.init(sample_allocator, state.handle(), .{
1549 .artifact_format = .vulkan_spirv,
1550 }) catch |err| std.debug.panic("gui paint image Processor initialization failed: {s}", .{@errorName(err)});
1551 defer processor.deinit();
1552
1553 const phase = bench.phaseAt("gui.paint.image_processor.recording_steady", @src());
1554 defer phase.end();
1555 var index: usize = 0;
1556 while (index < image_processor_launch_repetitions) : (index += 1) {
1557 processor.resizeBilinear(dst, src, width, height, width, height) catch @panic("gui paint image Processor steady resize failed");
1558 bench.coz.progressNamed("gui.paint.image_processor.recording_steady.launch");
1559 }
1560 std.mem.doNotOptimizeAway(state.launch_count);
1561 }
1562
1563 fn reifyStripCommands(sample_allocator: Allocator) void {
1564 const commands = strip_fixture_commands;
1565 if (commands.len == 0) @panic("gui paint strip reification fixture missing");
1566
1567 var checksum: usize = 0;
1568 const phase = bench.phaseAt("gui.paint.phase.strip_reification", @src());
1569 defer phase.end();
1570 var index: usize = 0;
1571 while (index < strip_spec.repetitions) : (index += 1) {
1572 var records = gui.paint.reifyStripsAlloc(sample_allocator, commands, strip_spec.width, strip_spec.height, gui.paint.Region.full(strip_spec.width, strip_spec.height), .{ .height = gui.paint.accy.tile_size }) catch @panic("gui paint strip reification failed");
1573 checksum +%= records.count;
1574 if (records.count > 0) {
1575 const last = records.at(records.count - 1);
1576 checksum +%= last.command_index;
1577 checksum +%= last.order;
1578 checksum +%= last.strip;
1579 checksum +%= last.x0;
1580 checksum +%= last.y0;
1581 checksum +%= last.x1;
1582 checksum +%= last.y1;
1583 }
1584 records.deinit(sample_allocator);
1585 bench.coz.progressNamed("gui.paint.phase.strip_reification.frame");
1586 }
1587 std.mem.doNotOptimizeAway(checksum);
1588 }
1589
1590 fn uploadAccyRecordingBackend(sample_allocator: Allocator) void {
1591 const fixture = AccyPhaseFixture.create(sample_allocator, phase_spec) catch |err| std.debug.panic("gui paint Accy upload fixture failed: {s}", .{@errorName(err)});
1592 defer fixture.destroy();
1593
1594 const phase = bench.phaseAt("gui.paint.phase.accy_recording_upload", @src());
1595 defer phase.end();
1596 var index: usize = 0;
1597 while (index < phase_spec.repetitions) : (index += 1) {
1598 fixture.uploadInputs() catch @panic("gui paint Accy upload failed");
1599 bench.coz.progressNamed("gui.paint.phase.accy_recording_upload.frame");
1600 }
1601 std.mem.doNotOptimizeAway(fixture.floats.id);
1602 }
1603
1604 fn launchAccyRecordingBackend(sample_allocator: Allocator) void {
1605 const fixture = AccyPhaseFixture.create(sample_allocator, phase_spec) catch |err| std.debug.panic("gui paint Accy launch fixture failed: {s}", .{@errorName(err)});
1606 defer fixture.destroy();
1607 fixture.uploadInputs() catch @panic("gui paint Accy launch input upload failed");
1608
1609 const phase = bench.phaseAt("gui.paint.phase.accy_recording_launch", @src());
1610 defer phase.end();
1611 var index: usize = 0;
1612 while (index < phase_spec.repetitions) : (index += 1) {
1613 fixture.launch() catch @panic("gui paint Accy launch failed");
1614 bench.coz.progressNamed("gui.paint.phase.accy_recording_launch.frame");
1615 }
1616 fixture.readbackOutput() catch @panic("gui paint Accy launch readback failed");
1617 std.mem.doNotOptimizeAway(checksumPixels(fixture.readback));
1618 }
1619
1620 fn evidenceAccyRecordingBackend(sample_allocator: Allocator) void {
1621 const fixture = AccyPhaseFixture.create(sample_allocator, phase_spec) catch |err| std.debug.panic("gui paint Accy evidence fixture failed: {s}", .{@errorName(err)});
1622 defer fixture.destroy();
1623 fixture.uploadInputs() catch @panic("gui paint Accy evidence input upload failed");
1624 fixture.state.event_elapsed_ns = 23_000;
1625
1626 var json = std.Io.Writer.Allocating.init(sample_allocator);
1627 defer json.deinit();
1628
1629 const phase = bench.phaseAt("gui.paint.phase.accy_recording_device_evidence", @src());
1630 defer phase.end();
1631 var index: usize = 0;
1632 while (index < phase_spec.repetitions) : (index += 1) {
1633 const record = fixture.launchEvidence() catch @panic("gui paint Accy evidence launch failed");
1634 evidence.writeRecord(&json.writer, .{ .range = record }) catch @panic("gui paint Accy evidence write failed");
1635 bench.coz.progressNamed("gui.paint.phase.accy_recording_device_evidence.frame");
1636 }
1637
1638 const summary = evidence.summarizeJsonl(sample_allocator, json.written()) catch @panic("gui paint Accy evidence summary failed");
1639 if (summary.range_records != phase_spec.repetitions) @panic("gui paint Accy evidence range count mismatch");
1640 if (summary.range_records_with_device_time != phase_spec.repetitions) @panic("gui paint Accy evidence missing device ranges");
1641 const expected_device_ns = @as(u64, 23_000) * @as(u64, @intCast(phase_spec.repetitions));
1642 if (summary.device_range_total_ns != expected_device_ns) @panic("gui paint Accy evidence device duration mismatch");
1643 std.mem.doNotOptimizeAway(summary.device_range_total_ns);
1644 }
1645
1646 fn evidenceAccyVulkanBackend(sample_allocator: Allocator) void {
1647 var fixture = VulkanEvidenceFixture.create(sample_allocator) catch |err| switch (err) {
1648 error.RuntimeUnavailable, error.UnsupportedOperation => {
1649 bench.stdout("gui paint Vulkan launch evidence skipped: {s}\n", .{@errorName(err)});
1650 return;
1651 },
1652 else => std.debug.panic("gui paint Vulkan evidence fixture failed: {s}", .{@errorName(err)}),
1653 };
1654 defer fixture.destroy();
1655
1656 const capture = vulkanEvidenceCapture();
1657 var host_phases = VulkanEvidencePhases{};
1658 const evaluation_start_ns = cpuTimestampNs();
1659 const prepare_start_ns = cpuTimestampNs();
1660 var prepared = fixture.prepareLaunch() catch |err| std.debug.panic("gui paint Vulkan evidence launch preparation failed: {s}", .{@errorName(err)});
1661 const prepare_end_ns = cpuTimestampNs();
1662 host_phases.recordPrepare(prepare_start_ns, prepare_end_ns);
1663 const launch = fixture.launchIdentity(&prepared) catch |err| std.debug.panic("gui paint Vulkan launch identity failed: {s}", .{@errorName(err)});
1664 var json = std.Io.Writer.Allocating.init(sample_allocator);
1665 defer json.deinit();
1666 writeCpuRangeRecord(&json.writer, .{
1667 .capture = capture,
1668 .launch = launch,
1669 .probe_id = "gui-paint-accy-vulkan:prepare-launch",
1670 .range_name = "gui.paint.phase.accy_vulkan_prepare_launch",
1671 .range_kind = .annotation,
1672 .start_ns = prepare_start_ns,
1673 .end_ns = prepare_end_ns,
1674 }) catch @panic("gui paint Vulkan evidence prepare range write failed");
1675
1676 const warm_launch_start_ns = cpuTimestampNs();
1677 fixture.submitLaunch(&prepared) catch |err| std.debug.panic("gui paint Vulkan evidence warm launch failed: {s}", .{@errorName(err)});
1678 const warm_launch_end_ns = cpuTimestampNs();
1679 host_phases.recordWarmLaunch(warm_launch_start_ns, warm_launch_end_ns);
1680 writeCpuRangeRecord(&json.writer, .{
1681 .capture = capture,
1682 .launch = launch,
1683 .probe_id = "gui-paint-accy-vulkan:warm-launch",
1684 .range_name = "gui.paint.phase.accy_vulkan_warm_launch",
1685 .range_kind = .kernel,
1686 .start_ns = warm_launch_start_ns,
1687 .end_ns = warm_launch_end_ns,
1688 }) catch @panic("gui paint Vulkan evidence warm launch range write failed");
1689
1690 const warm_readback_start_ns = cpuTimestampNs();
1691 var checksum = fixture.readbackChecksum(&prepared) catch @panic("gui paint Vulkan evidence warm readback failed");
1692 const warm_readback_end_ns = cpuTimestampNs();
1693 host_phases.recordWarmReadback(warm_readback_start_ns, warm_readback_end_ns);
1694 writeCpuRangeRecord(&json.writer, .{
1695 .capture = capture,
1696 .launch = launch,
1697 .probe_id = "gui-paint-accy-vulkan:warm-readback",
1698 .range_name = "gui.paint.phase.accy_vulkan_warm_readback",
1699 .range_kind = .memory_copy,
1700 .start_ns = warm_readback_start_ns,
1701 .end_ns = warm_readback_end_ns,
1702 }) catch @panic("gui paint Vulkan evidence warm readback range write failed");
1703
1704 const phase = bench.phaseAt("gui.paint.phase.accy_vulkan_launch_range", @src());
1705 defer phase.end();
1706 var index: usize = 0;
1707 var device_start_ns: u64 = 0;
1708 while (index < vulkan_evidence_spec.repetitions) : (index += 1) {
1709 const launch_start_ns = cpuTimestampNs();
1710 var record = fixture.launchRangeRecord(&prepared, .{
1711 .capture = capture,
1712 .launch = launch,
1713 .probe_id = "gui-paint-accy-vulkan:launch-range",
1714 .range_name = "gui.paint.phase.accy_vulkan_launch",
1715 .device_start_ns = device_start_ns,
1716 }) catch |err| switch (err) {
1717 error.UnsupportedOperation => {
1718 bench.stdout("gui paint Vulkan launch evidence skipped: {s}\n", .{@errorName(err)});
1719 return;
1720 },
1721 else => std.debug.panic("gui paint Vulkan evidence launch failed: {s}", .{@errorName(err)}),
1722 };
1723 device_start_ns = record.range.device_end_ns.?;
1724 const launch_end_ns = cpuTimestampNs();
1725 host_phases.recordMeasuredLaunch(launch_start_ns, launch_end_ns);
1726 addCpuRange(&record, launch_start_ns, launch_end_ns) catch @panic("gui paint Vulkan evidence launch CPU range failed");
1727 evidence.writeRecord(&json.writer, .{ .range = record }) catch @panic("gui paint Vulkan evidence write failed");
1728 const readback_start_ns = cpuTimestampNs();
1729 checksum +%= fixture.readbackChecksum(&prepared) catch @panic("gui paint Vulkan evidence readback failed");
1730 const readback_end_ns = cpuTimestampNs();
1731 host_phases.recordMeasuredReadback(readback_start_ns, readback_end_ns);
1732 writeCpuRangeRecord(&json.writer, .{
1733 .capture = capture,
1734 .launch = launch,
1735 .probe_id = "gui-paint-accy-vulkan:readback",
1736 .range_name = "gui.paint.phase.accy_vulkan_readback",
1737 .range_kind = .memory_copy,
1738 .start_ns = readback_start_ns,
1739 .end_ns = readback_end_ns,
1740 }) catch @panic("gui paint Vulkan evidence readback range write failed");
1741 bench.coz.progressNamed("gui.paint.phase.accy_vulkan_launch_range.frame");
1742 }
1743 const evaluation_end_ns = cpuTimestampNs();
1744 host_phases.recordEvaluation(evaluation_start_ns, evaluation_end_ns);
1745 writeCpuRangeRecord(&json.writer, .{
1746 .capture = capture,
1747 .launch = launch,
1748 .probe_id = "gui-paint-accy-vulkan:evidence-eval",
1749 .range_name = "gui.paint.phase.accy_vulkan_evidence_eval",
1750 .range_kind = .annotation,
1751 .start_ns = evaluation_start_ns,
1752 .end_ns = evaluation_end_ns,
1753 }) catch @panic("gui paint Vulkan evidence evaluation range write failed");
1754
1755 const summary = evidence.summarizeJsonl(sample_allocator, json.written()) catch @panic("gui paint Vulkan evidence summary failed");
1756 const expected_cpu_ranges: u64 = @intCast(vulkan_evidence_spec.repetitions * 2 + 4);
1757 if (summary.range_records != expected_cpu_ranges) @panic("gui paint Vulkan evidence range count mismatch");
1758 if (summary.range_records_with_cpu_time != expected_cpu_ranges) @panic("gui paint Vulkan evidence missing CPU ranges");
1759 if (summary.range_records_with_device_time != vulkan_evidence_spec.repetitions) @panic("gui paint Vulkan evidence missing device ranges");
1760 if (summary.cpu_range_total_ns == 0) @panic("gui paint Vulkan evidence missing CPU time");
1761 if (summary.device_range_total_ns == 0) @panic("gui paint Vulkan evidence missing device time");
1762 const host_phase_max = host_phases.maxHostPhase();
1763 bench.stdout(
1764 "gui paint Vulkan launch evidence: ranges={d} cpu_ranges={d} device_ranges={d} cpu_total_ns={d} cpu_max_ns={d} cpu_span_ns={d} device_total_ns={d} device_max_ns={d} device_span_ns={d} prepare_ns={d} warm_launch_ns={d} warm_readback_ns={d} measured_launch_cpu_total_ns={d} measured_launch_cpu_max_ns={d} measured_readback_total_ns={d} measured_readback_max_ns={d} eval_ns={d} host_phase_max={s} host_phase_max_ns={d} pixels={d} commands={d} command_visits={d}\n",
1765 .{
1766 summary.range_records,
1767 summary.range_records_with_cpu_time,
1768 summary.range_records_with_device_time,
1769 summary.cpu_range_total_ns,
1770 summary.cpu_range_max_ns,
1771 summary.cpu_timeline_span_ns,
1772 summary.device_range_total_ns,
1773 summary.device_range_max_ns,
1774 summary.device_timeline_span_ns,
1775 host_phases.prepare_ns,
1776 host_phases.warm_launch_ns,
1777 host_phases.warm_readback_ns,
1778 host_phases.measured_launch_cpu_total_ns,
1779 host_phases.measured_launch_cpu_max_ns,
1780 host_phases.measured_readback_total_ns,
1781 host_phases.measured_readback_max_ns,
1782 host_phases.evaluation_ns,
1783 host_phase_max.name,
1784 host_phase_max.ns,
1785 launch.kernel.element_count,
1786 fixture.workload.commands.len,
1787 fixture.command_visits,
1788 },
1789 );
1790 std.mem.doNotOptimizeAway(checksum);
1791 }
1792
1793 fn evidenceAccyVulkanSurfaceFrame(sample_allocator: Allocator) void {
1794 var fixture = VulkanEvidenceFixture.create(sample_allocator) catch |err| switch (err) {
1795 error.RuntimeUnavailable, error.UnsupportedOperation => {
1796 bench.stdout("gui paint Vulkan surface evidence skipped: {s}\n", .{@errorName(err)});
1797 return;
1798 },
1799 else => std.debug.panic("gui paint Vulkan surface fixture failed: {s}", .{@errorName(err)}),
1800 };
1801 defer fixture.destroy();
1802
1803 var live_surface = LiveVulkanSurface.create(fixture.handle(), vulkan_evidence_spec) catch |err| switch (err) {
1804 error.ConnectionFailed,
1805 error.WindowCreationFailed,
1806 error.UnsupportedPlatform,
1807 error.RuntimeUnavailable,
1808 error.SymbolMissing,
1809 error.InvalidDisplay,
1810 error.CapabilityMismatch,
1811 error.UnsupportedOperation,
1812 => {
1813 bench.stdout("gui paint Vulkan surface evidence skipped: {s}\n", .{@errorName(err)});
1814 return;
1815 },
1816 else => std.debug.panic("gui paint Vulkan surface creation failed: {s}", .{@errorName(err)}),
1817 };
1818 defer live_surface.destroy();
1819
1820 const capture = vulkanEvidenceCapture();
1821 var host_phases = VulkanSurfaceEvidencePhases{};
1822 const evaluation_start_ns = cpuTimestampNs();
1823 const prepare_start_ns = cpuTimestampNs();
1824 var prepared = fixture.prepareLaunchForSurface(live_surface.surface) catch |err| std.debug.panic("gui paint Vulkan surface launch preparation failed: {s}", .{@errorName(err)});
1825 const prepare_end_ns = cpuTimestampNs();
1826 host_phases.recordPrepare(prepare_start_ns, prepare_end_ns);
1827 const launch = fixture.launchIdentityFor(&prepared, "gui-paint-vulkan-surface-frame") catch |err| std.debug.panic("gui paint Vulkan surface launch identity failed: {s}", .{@errorName(err)});
1828 if (launch.kernel.element_count != @as(u64, @intCast(live_surface.pixelCount()))) @panic("gui paint Vulkan surface extent mismatch");
1829 var json = std.Io.Writer.Allocating.init(sample_allocator);
1830 defer json.deinit();
1831 writeCpuRangeRecord(&json.writer, .{
1832 .capture = capture,
1833 .launch = launch,
1834 .probe_id = "gui-paint-accy-vulkan-surface:prepare-launch",
1835 .range_name = "gui.paint.phase.accy_vulkan_surface_prepare_launch",
1836 .range_kind = .annotation,
1837 .start_ns = prepare_start_ns,
1838 .end_ns = prepare_end_ns,
1839 }) catch @panic("gui paint Vulkan surface prepare range write failed");
1840
1841 const warm_launch_start_ns = cpuTimestampNs();
1842 fixture.submitLaunch(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface warm launch failed: {s}", .{@errorName(err)});
1843 const warm_launch_end_ns = cpuTimestampNs();
1844 host_phases.recordWarmLaunch(warm_launch_start_ns, warm_launch_end_ns);
1845 writeCpuRangeRecord(&json.writer, .{
1846 .capture = capture,
1847 .launch = launch,
1848 .probe_id = "gui-paint-accy-vulkan-surface:warm-launch",
1849 .range_name = "gui.paint.phase.accy_vulkan_surface_warm_launch",
1850 .range_kind = .kernel,
1851 .start_ns = warm_launch_start_ns,
1852 .end_ns = warm_launch_end_ns,
1853 }) catch @panic("gui paint Vulkan surface warm launch range write failed");
1854
1855 const warm_surface_start_ns = cpuTimestampNs();
1856 const warm_frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface warm acquire failed: {s}", .{@errorName(err)});
1857 fixture.writePresentSurface(live_surface.surface, warm_frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface warm write failed: {s}", .{@errorName(err)});
1858 const warm_surface_end_ns = cpuTimestampNs();
1859 host_phases.recordWarmSurface(warm_surface_start_ns, warm_surface_end_ns);
1860 writeCpuRangeRecord(&json.writer, .{
1861 .capture = capture,
1862 .launch = launch,
1863 .probe_id = "gui-paint-accy-vulkan-surface:warm-write-present",
1864 .range_name = "gui.paint.phase.accy_vulkan_surface_warm_write_present",
1865 .range_kind = .memory_copy,
1866 .start_ns = warm_surface_start_ns,
1867 .end_ns = warm_surface_end_ns,
1868 }) catch @panic("gui paint Vulkan surface warm write range write failed");
1869
1870 const phase = bench.phaseAt("gui.paint.phase.accy_vulkan_surface_frame", @src());
1871 defer phase.end();
1872 var index: usize = 0;
1873 var device_start_ns: u64 = 0;
1874 while (index < vulkan_evidence_spec.repetitions) : (index += 1) {
1875 const launch_start_ns = cpuTimestampNs();
1876 var record = fixture.launchRangeRecord(&prepared, .{
1877 .capture = capture,
1878 .launch = launch,
1879 .probe_id = "gui-paint-accy-vulkan-surface:launch-range",
1880 .range_name = "gui.paint.phase.accy_vulkan_surface_launch",
1881 .device_start_ns = device_start_ns,
1882 }) catch |err| switch (err) {
1883 error.UnsupportedOperation => {
1884 bench.stdout("gui paint Vulkan surface evidence skipped: {s}\n", .{@errorName(err)});
1885 return;
1886 },
1887 else => std.debug.panic("gui paint Vulkan surface launch failed: {s}", .{@errorName(err)}),
1888 };
1889 device_start_ns = record.range.device_end_ns.?;
1890 const launch_end_ns = cpuTimestampNs();
1891 host_phases.recordMeasuredLaunch(launch_start_ns, launch_end_ns);
1892 addCpuRange(&record, launch_start_ns, launch_end_ns) catch @panic("gui paint Vulkan surface launch CPU range failed");
1893 evidence.writeRecord(&json.writer, .{ .range = record }) catch @panic("gui paint Vulkan surface evidence write failed");
1894 const surface_start_ns = cpuTimestampNs();
1895 const frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface acquire failed: {s}", .{@errorName(err)});
1896 fixture.writePresentSurface(live_surface.surface, frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface write failed: {s}", .{@errorName(err)});
1897 const surface_end_ns = cpuTimestampNs();
1898 host_phases.recordMeasuredSurface(surface_start_ns, surface_end_ns);
1899 writeCpuRangeRecord(&json.writer, .{
1900 .capture = capture,
1901 .launch = launch,
1902 .probe_id = "gui-paint-accy-vulkan-surface:write-present",
1903 .range_name = "gui.paint.phase.accy_vulkan_surface_write_present",
1904 .range_kind = .memory_copy,
1905 .start_ns = surface_start_ns,
1906 .end_ns = surface_end_ns,
1907 }) catch @panic("gui paint Vulkan surface write range write failed");
1908 bench.coz.progressNamed("gui.paint.phase.accy_vulkan_surface_frame.frame");
1909 }
1910 const evaluation_end_ns = cpuTimestampNs();
1911 host_phases.recordEvaluation(evaluation_start_ns, evaluation_end_ns);
1912 writeCpuRangeRecord(&json.writer, .{
1913 .capture = capture,
1914 .launch = launch,
1915 .probe_id = "gui-paint-accy-vulkan-surface:evidence-eval",
1916 .range_name = "gui.paint.phase.accy_vulkan_surface_evidence_eval",
1917 .range_kind = .annotation,
1918 .start_ns = evaluation_start_ns,
1919 .end_ns = evaluation_end_ns,
1920 }) catch @panic("gui paint Vulkan surface evaluation range write failed");
1921
1922 const summary = evidence.summarizeJsonl(sample_allocator, json.written()) catch @panic("gui paint Vulkan surface evidence summary failed");
1923 const expected_cpu_ranges: u64 = @intCast(vulkan_evidence_spec.repetitions * 2 + 4);
1924 if (summary.range_records != expected_cpu_ranges) @panic("gui paint Vulkan surface evidence range count mismatch");
1925 if (summary.range_records_with_cpu_time != expected_cpu_ranges) @panic("gui paint Vulkan surface evidence missing CPU ranges");
1926 if (summary.range_records_with_device_time != vulkan_evidence_spec.repetitions) @panic("gui paint Vulkan surface evidence missing device ranges");
1927 if (summary.cpu_range_total_ns == 0) @panic("gui paint Vulkan surface evidence missing CPU time");
1928 if (summary.device_range_total_ns == 0) @panic("gui paint Vulkan surface evidence missing device time");
1929 if (host_phases.measured_surface_total_ns == 0) @panic("gui paint Vulkan surface evidence missing surface time");
1930 const host_phase_max = host_phases.maxHostPhase();
1931 bench.stdout(
1932 "gui paint Vulkan surface evidence: ranges={d} cpu_ranges={d} device_ranges={d} cpu_total_ns={d} cpu_max_ns={d} cpu_span_ns={d} device_total_ns={d} device_max_ns={d} device_span_ns={d} prepare_ns={d} warm_launch_ns={d} warm_surface_ns={d} measured_launch_cpu_total_ns={d} measured_launch_cpu_max_ns={d} measured_surface_total_ns={d} measured_surface_max_ns={d} eval_ns={d} host_phase_max={s} host_phase_max_ns={d} pixels={d} commands={d} command_visits={d} surface_width={d} surface_height={d} host_readbacks=0\n",
1933 .{
1934 summary.range_records,
1935 summary.range_records_with_cpu_time,
1936 summary.range_records_with_device_time,
1937 summary.cpu_range_total_ns,
1938 summary.cpu_range_max_ns,
1939 summary.cpu_timeline_span_ns,
1940 summary.device_range_total_ns,
1941 summary.device_range_max_ns,
1942 summary.device_timeline_span_ns,
1943 host_phases.prepare_ns,
1944 host_phases.warm_launch_ns,
1945 host_phases.warm_surface_ns,
1946 host_phases.measured_launch_cpu_total_ns,
1947 host_phases.measured_launch_cpu_max_ns,
1948 host_phases.measured_surface_total_ns,
1949 host_phases.measured_surface_max_ns,
1950 host_phases.evaluation_ns,
1951 host_phase_max.name,
1952 host_phase_max.ns,
1953 launch.kernel.element_count,
1954 fixture.workload.commands.len,
1955 fixture.command_visits,
1956 live_surface.surface.extent.width,
1957 live_surface.surface.extent.height,
1958 },
1959 );
1960 std.mem.doNotOptimizeAway(host_phases.measured_surface_total_ns);
1961 }
1962
1963 fn steadyAccyVulkanSurfaceFrame(sample_allocator: Allocator) void {
1964 var fixture = VulkanEvidenceFixture.create(sample_allocator) catch |err| switch (err) {
1965 error.RuntimeUnavailable, error.UnsupportedOperation => {
1966 bench.stdout("gui paint Vulkan surface steady skipped: {s}\n", .{@errorName(err)});
1967 return;
1968 },
1969 else => std.debug.panic("gui paint Vulkan surface steady fixture failed: {s}", .{@errorName(err)}),
1970 };
1971 defer fixture.destroy();
1972
1973 var live_surface = LiveVulkanSurface.create(fixture.handle(), vulkan_evidence_spec) catch |err| switch (err) {
1974 error.ConnectionFailed,
1975 error.WindowCreationFailed,
1976 error.UnsupportedPlatform,
1977 error.RuntimeUnavailable,
1978 error.SymbolMissing,
1979 error.InvalidDisplay,
1980 error.CapabilityMismatch,
1981 error.UnsupportedOperation,
1982 => {
1983 bench.stdout("gui paint Vulkan surface steady skipped: {s}\n", .{@errorName(err)});
1984 return;
1985 },
1986 else => std.debug.panic("gui paint Vulkan surface steady creation failed: {s}", .{@errorName(err)}),
1987 };
1988 defer live_surface.destroy();
1989
1990 var host_phases = VulkanSurfaceSteadyPhases{};
1991 const evaluation_start_ns = cpuTimestampNs();
1992 const prepare_start_ns = cpuTimestampNs();
1993 var prepared = fixture.prepareLaunchForSurface(live_surface.surface) catch |err| std.debug.panic("gui paint Vulkan surface steady launch preparation failed: {s}", .{@errorName(err)});
1994 const prepare_end_ns = cpuTimestampNs();
1995 host_phases.recordPrepare(prepare_start_ns, prepare_end_ns);
1996 const prepared_info = fixture.preparedLaunchInfo(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface steady launch info failed: {s}", .{@errorName(err)});
1997 if (prepared_info.pixel_count != live_surface.pixelCount()) @panic("gui paint Vulkan surface steady extent mismatch");
1998
1999 const warm_start_ns = cpuTimestampNs();
2000 fixture.submitLaunch(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface steady warm launch failed: {s}", .{@errorName(err)});
2001 const warm_frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface steady warm acquire failed: {s}", .{@errorName(err)});
2002 fixture.writePresentSurface(live_surface.surface, warm_frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface steady warm write failed: {s}", .{@errorName(err)});
2003 const warm_end_ns = cpuTimestampNs();
2004 host_phases.recordWarmFrame(warm_start_ns, warm_end_ns);
2005
2006 const phase = bench.phaseAt("gui.paint.phase.accy_vulkan_surface_steady", @src());
2007 defer phase.end();
2008 var index: usize = 0;
2009 while (index < vulkan_surface_timing_repetitions) : (index += 1) {
2010 const frame_start_ns = cpuTimestampNs();
2011 const launch_start_ns = frame_start_ns;
2012 fixture.submitLaunch(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface steady launch failed: {s}", .{@errorName(err)});
2013 const launch_end_ns = cpuTimestampNs();
2014 host_phases.recordMeasuredLaunch(launch_start_ns, launch_end_ns);
2015 const surface_start_ns = launch_end_ns;
2016 const frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface steady acquire failed: {s}", .{@errorName(err)});
2017 fixture.writePresentSurface(live_surface.surface, frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface steady write failed: {s}", .{@errorName(err)});
2018 const surface_end_ns = cpuTimestampNs();
2019 host_phases.recordMeasuredSurface(surface_start_ns, surface_end_ns);
2020 host_phases.recordMeasuredFrame(frame_start_ns, surface_end_ns);
2021 bench.coz.progressNamed("gui.paint.phase.accy_vulkan_surface_steady.frame");
2022 }
2023 const evaluation_end_ns = cpuTimestampNs();
2024 host_phases.recordEvaluation(evaluation_start_ns, evaluation_end_ns);
2025 if (host_phases.measured_frames.count == 0) @panic("gui paint Vulkan surface steady missing frame time");
2026 if (host_phases.measured_launches.count == 0) @panic("gui paint Vulkan surface steady missing launch time");
2027 if (host_phases.measured_surfaces.count == 0) @panic("gui paint Vulkan surface steady missing surface time");
2028 const frame_stats = host_phases.measured_frames.stats();
2029 const launch_stats = host_phases.measured_launches.stats();
2030 const surface_stats = host_phases.measured_surfaces.stats();
2031 const host_phase_max = host_phases.maxHostPhase();
2032 bench.stdout(
2033 "gui paint Vulkan surface steady: prepare_ns={d} warm_frame_ns={d} measured_frames={d} measured_frame_total_ns={d} measured_frame_min_ns={d} measured_frame_median_ns={d} measured_frame_p75_ns={d} measured_frame_p95_ns={d} measured_frame_max_ns={d} measured_launch_cpu_total_ns={d} measured_launch_cpu_min_ns={d} measured_launch_cpu_median_ns={d} measured_launch_cpu_p75_ns={d} measured_launch_cpu_p95_ns={d} measured_launch_cpu_max_ns={d} measured_surface_total_ns={d} measured_surface_min_ns={d} measured_surface_median_ns={d} measured_surface_p75_ns={d} measured_surface_p95_ns={d} measured_surface_max_ns={d} eval_ns={d} host_phase_max={s} host_phase_max_ns={d} pixels={d} commands={d} command_visits={d} surface_width={d} surface_height={d} host_readbacks=0 evidence_device_ranges=0\n",
2034 .{
2035 host_phases.prepare_ns,
2036 host_phases.warm_frame_ns,
2037 frame_stats.count,
2038 frame_stats.total_ns,
2039 frame_stats.min_ns,
2040 frame_stats.median_ns,
2041 frame_stats.p75_ns,
2042 frame_stats.p95_ns,
2043 frame_stats.max_ns,
2044 launch_stats.total_ns,
2045 launch_stats.min_ns,
2046 launch_stats.median_ns,
2047 launch_stats.p75_ns,
2048 launch_stats.p95_ns,
2049 launch_stats.max_ns,
2050 surface_stats.total_ns,
2051 surface_stats.min_ns,
2052 surface_stats.median_ns,
2053 surface_stats.p75_ns,
2054 surface_stats.p95_ns,
2055 surface_stats.max_ns,
2056 host_phases.evaluation_ns,
2057 host_phase_max.name,
2058 host_phase_max.ns,
2059 prepared_info.pixel_count,
2060 fixture.workload.commands.len,
2061 fixture.command_visits,
2062 live_surface.surface.extent.width,
2063 live_surface.surface.extent.height,
2064 },
2065 );
2066 std.mem.doNotOptimizeAway(frame_stats.total_ns);
2067 }
2068
2069 fn queuedAccyVulkanSurfaceFrame(sample_allocator: Allocator) void {
2070 var fixture = VulkanEvidenceFixture.create(sample_allocator) catch |err| switch (err) {
2071 error.RuntimeUnavailable, error.UnsupportedOperation => {
2072 bench.stdout("gui paint Vulkan surface queued skipped: {s}\n", .{@errorName(err)});
2073 return;
2074 },
2075 else => std.debug.panic("gui paint Vulkan surface queued fixture failed: {s}", .{@errorName(err)}),
2076 };
2077 defer fixture.destroy();
2078
2079 var live_surface = LiveVulkanSurface.create(fixture.handle(), vulkan_evidence_spec) catch |err| switch (err) {
2080 error.ConnectionFailed,
2081 error.WindowCreationFailed,
2082 error.UnsupportedPlatform,
2083 error.RuntimeUnavailable,
2084 error.SymbolMissing,
2085 error.InvalidDisplay,
2086 error.CapabilityMismatch,
2087 error.UnsupportedOperation,
2088 => {
2089 bench.stdout("gui paint Vulkan surface queued skipped: {s}\n", .{@errorName(err)});
2090 return;
2091 },
2092 else => std.debug.panic("gui paint Vulkan surface queued creation failed: {s}", .{@errorName(err)}),
2093 };
2094 defer live_surface.destroy();
2095
2096 var host_phases = VulkanSurfaceSteadyPhases{};
2097 const evaluation_start_ns = cpuTimestampNs();
2098 const prepare_start_ns = cpuTimestampNs();
2099 var prepared = fixture.prepareLaunchForSurface(live_surface.surface) catch |err| std.debug.panic("gui paint Vulkan surface queued launch preparation failed: {s}", .{@errorName(err)});
2100 const prepare_end_ns = cpuTimestampNs();
2101 host_phases.recordPrepare(prepare_start_ns, prepare_end_ns);
2102 const prepared_info = fixture.preparedLaunchInfo(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface queued launch info failed: {s}", .{@errorName(err)});
2103 if (prepared_info.pixel_count != live_surface.pixelCount()) @panic("gui paint Vulkan surface queued extent mismatch");
2104
2105 const warm_start_ns = cpuTimestampNs();
2106 fixture.submitLaunchQueued(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface queued warm launch failed: {s}", .{@errorName(err)});
2107 const warm_frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface queued warm acquire failed: {s}", .{@errorName(err)});
2108 fixture.writePresentSurface(live_surface.surface, warm_frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface queued warm write failed: {s}", .{@errorName(err)});
2109 const warm_end_ns = cpuTimestampNs();
2110 host_phases.recordWarmFrame(warm_start_ns, warm_end_ns);
2111
2112 const phase = bench.phaseAt("gui.paint.phase.accy_vulkan_surface_queued", @src());
2113 defer phase.end();
2114 var index: usize = 0;
2115 while (index < vulkan_surface_timing_repetitions) : (index += 1) {
2116 const frame_start_ns = cpuTimestampNs();
2117 const launch_start_ns = frame_start_ns;
2118 fixture.submitLaunchQueued(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface queued launch failed: {s}", .{@errorName(err)});
2119 const launch_end_ns = cpuTimestampNs();
2120 host_phases.recordMeasuredLaunch(launch_start_ns, launch_end_ns);
2121 const surface_start_ns = launch_end_ns;
2122 const frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface queued acquire failed: {s}", .{@errorName(err)});
2123 fixture.writePresentSurface(live_surface.surface, frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface queued write failed: {s}", .{@errorName(err)});
2124 const surface_end_ns = cpuTimestampNs();
2125 host_phases.recordMeasuredSurface(surface_start_ns, surface_end_ns);
2126 host_phases.recordMeasuredFrame(frame_start_ns, surface_end_ns);
2127 bench.coz.progressNamed("gui.paint.phase.accy_vulkan_surface_queued.frame");
2128 }
2129 const evaluation_end_ns = cpuTimestampNs();
2130 host_phases.recordEvaluation(evaluation_start_ns, evaluation_end_ns);
2131 if (host_phases.measured_frames.count == 0) @panic("gui paint Vulkan surface queued missing frame time");
2132 if (host_phases.measured_launches.count == 0) @panic("gui paint Vulkan surface queued missing launch time");
2133 if (host_phases.measured_surfaces.count == 0) @panic("gui paint Vulkan surface queued missing surface time");
2134 const frame_stats = host_phases.measured_frames.stats();
2135 const launch_stats = host_phases.measured_launches.stats();
2136 const surface_stats = host_phases.measured_surfaces.stats();
2137 const host_phase_max = host_phases.maxHostPhase();
2138 bench.stdout(
2139 "gui paint Vulkan surface queued: prepare_ns={d} warm_frame_ns={d} measured_frames={d} measured_frame_total_ns={d} measured_frame_min_ns={d} measured_frame_median_ns={d} measured_frame_p75_ns={d} measured_frame_p95_ns={d} measured_frame_max_ns={d} measured_launch_cpu_total_ns={d} measured_launch_cpu_min_ns={d} measured_launch_cpu_median_ns={d} measured_launch_cpu_p75_ns={d} measured_launch_cpu_p95_ns={d} measured_launch_cpu_max_ns={d} measured_surface_total_ns={d} measured_surface_min_ns={d} measured_surface_median_ns={d} measured_surface_p75_ns={d} measured_surface_p95_ns={d} measured_surface_max_ns={d} eval_ns={d} host_phase_max={s} host_phase_max_ns={d} pixels={d} commands={d} command_visits={d} surface_width={d} surface_height={d} host_readbacks=0 evidence_device_ranges=0 launch_device_syncs=0\n",
2140 .{
2141 host_phases.prepare_ns,
2142 host_phases.warm_frame_ns,
2143 frame_stats.count,
2144 frame_stats.total_ns,
2145 frame_stats.min_ns,
2146 frame_stats.median_ns,
2147 frame_stats.p75_ns,
2148 frame_stats.p95_ns,
2149 frame_stats.max_ns,
2150 launch_stats.total_ns,
2151 launch_stats.min_ns,
2152 launch_stats.median_ns,
2153 launch_stats.p75_ns,
2154 launch_stats.p95_ns,
2155 launch_stats.max_ns,
2156 surface_stats.total_ns,
2157 surface_stats.min_ns,
2158 surface_stats.median_ns,
2159 surface_stats.p75_ns,
2160 surface_stats.p95_ns,
2161 surface_stats.max_ns,
2162 host_phases.evaluation_ns,
2163 host_phase_max.name,
2164 host_phase_max.ns,
2165 prepared_info.pixel_count,
2166 fixture.workload.commands.len,
2167 fixture.command_visits,
2168 live_surface.surface.extent.width,
2169 live_surface.surface.extent.height,
2170 },
2171 );
2172 std.mem.doNotOptimizeAway(frame_stats.total_ns);
2173 }
2174
2175 fn preparedAccyVulkanSurfaceFrame(sample_allocator: Allocator) void {
2176 var fixture = VulkanEvidenceFixture.create(sample_allocator) catch |err| switch (err) {
2177 error.RuntimeUnavailable, error.UnsupportedOperation => {
2178 bench.stdout("gui paint Vulkan surface prepared skipped: {s}\n", .{@errorName(err)});
2179 return;
2180 },
2181 else => std.debug.panic("gui paint Vulkan surface prepared fixture failed: {s}", .{@errorName(err)}),
2182 };
2183 defer fixture.destroy();
2184
2185 var live_surface = LiveVulkanSurface.create(fixture.handle(), vulkan_evidence_spec) catch |err| switch (err) {
2186 error.ConnectionFailed,
2187 error.WindowCreationFailed,
2188 error.UnsupportedPlatform,
2189 error.RuntimeUnavailable,
2190 error.SymbolMissing,
2191 error.InvalidDisplay,
2192 error.CapabilityMismatch,
2193 error.UnsupportedOperation,
2194 => {
2195 bench.stdout("gui paint Vulkan surface prepared skipped: {s}\n", .{@errorName(err)});
2196 return;
2197 },
2198 else => std.debug.panic("gui paint Vulkan surface prepared creation failed: {s}", .{@errorName(err)}),
2199 };
2200 defer live_surface.destroy();
2201
2202 var host_phases = VulkanSurfaceSteadyPhases{};
2203 const evaluation_start_ns = cpuTimestampNs();
2204 const warm_frame_start_ns = cpuTimestampNs();
2205 const prepare_start_ns = warm_frame_start_ns;
2206 var warm_prepared = fixture.prepareLaunchForSurface(live_surface.surface) catch |err| std.debug.panic("gui paint Vulkan surface prepared warm preparation failed: {s}", .{@errorName(err)});
2207 const prepare_end_ns = cpuTimestampNs();
2208 host_phases.recordPrepare(prepare_start_ns, prepare_end_ns);
2209 const warm_prepared_info = fixture.preparedLaunchInfo(&warm_prepared) catch |err| std.debug.panic("gui paint Vulkan surface prepared warm launch info failed: {s}", .{@errorName(err)});
2210 if (warm_prepared_info.pixel_count != live_surface.pixelCount()) @panic("gui paint Vulkan surface prepared extent mismatch");
2211 fixture.submitLaunchQueued(&warm_prepared) catch |err| std.debug.panic("gui paint Vulkan surface prepared warm launch failed: {s}", .{@errorName(err)});
2212 const warm_frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface prepared warm acquire failed: {s}", .{@errorName(err)});
2213 fixture.writePresentSurface(live_surface.surface, warm_frame, &warm_prepared) catch |err| std.debug.panic("gui paint Vulkan surface prepared warm write failed: {s}", .{@errorName(err)});
2214 const warm_frame_end_ns = cpuTimestampNs();
2215 host_phases.recordWarmFrame(warm_frame_start_ns, warm_frame_end_ns);
2216
2217 const phase = bench.phaseAt("gui.paint.phase.accy_vulkan_surface_prepared", @src());
2218 defer phase.end();
2219 var index: usize = 0;
2220 var device_csr_prepares: usize = 0;
2221 while (index < vulkan_surface_timing_repetitions) : (index += 1) {
2222 const frame_start_ns = cpuTimestampNs();
2223 const measured_prepare_start_ns = frame_start_ns;
2224 var prepared = fixture.prepareLaunchForSurface(live_surface.surface) catch |err| std.debug.panic("gui paint Vulkan surface prepared preparation failed: {s}", .{@errorName(err)});
2225 const measured_prepare_end_ns = cpuTimestampNs();
2226 const prepared_info = fixture.preparedLaunchInfo(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface prepared launch info failed: {s}", .{@errorName(err)});
2227 if (prepared_info.device_csr_prepared) device_csr_prepares += 1;
2228 host_phases.recordMeasuredPrepare(measured_prepare_start_ns, measured_prepare_end_ns);
2229 if (prepared_info.pixel_count != live_surface.pixelCount()) @panic("gui paint Vulkan surface prepared measured extent mismatch");
2230 const launch_start_ns = measured_prepare_end_ns;
2231 fixture.submitLaunchQueued(&prepared) catch |err| std.debug.panic("gui paint Vulkan surface prepared launch failed: {s}", .{@errorName(err)});
2232 const launch_end_ns = cpuTimestampNs();
2233 host_phases.recordMeasuredLaunch(launch_start_ns, launch_end_ns);
2234 const surface_start_ns = launch_end_ns;
2235 const frame = live_surface.acquireFrame() catch |err| std.debug.panic("gui paint Vulkan surface prepared acquire failed: {s}", .{@errorName(err)});
2236 fixture.writePresentSurface(live_surface.surface, frame, &prepared) catch |err| std.debug.panic("gui paint Vulkan surface prepared write failed: {s}", .{@errorName(err)});
2237 const surface_end_ns = cpuTimestampNs();
2238 host_phases.recordMeasuredSurface(surface_start_ns, surface_end_ns);
2239 host_phases.recordMeasuredFrame(frame_start_ns, surface_end_ns);
2240 bench.coz.progressNamed("gui.paint.phase.accy_vulkan_surface_prepared.frame");
2241 }
2242 const evaluation_end_ns = cpuTimestampNs();
2243 host_phases.recordEvaluation(evaluation_start_ns, evaluation_end_ns);
2244 if (host_phases.measured_frames.count == 0) @panic("gui paint Vulkan surface prepared missing frame time");
2245 if (host_phases.measured_prepares.count == 0) @panic("gui paint Vulkan surface prepared missing prepare time");
2246 if (host_phases.measured_launches.count == 0) @panic("gui paint Vulkan surface prepared missing launch time");
2247 if (host_phases.measured_surfaces.count == 0) @panic("gui paint Vulkan surface prepared missing surface time");
2248 const frame_stats = host_phases.measured_frames.stats();
2249 const prepare_stats = host_phases.measured_prepares.stats();
2250 const launch_stats = host_phases.measured_launches.stats();
2251 const surface_stats = host_phases.measured_surfaces.stats();
2252 const host_phase_max = host_phases.maxHostPhase();
2253 bench.stdout(
2254 "gui paint Vulkan surface prepared: warm_prepare_ns={d} warm_frame_ns={d} measured_frames={d} measured_frame_total_ns={d} measured_frame_min_ns={d} measured_frame_median_ns={d} measured_frame_p75_ns={d} measured_frame_p95_ns={d} measured_frame_max_ns={d} measured_prepare_total_ns={d} measured_prepare_min_ns={d} measured_prepare_median_ns={d} measured_prepare_p75_ns={d} measured_prepare_p95_ns={d} measured_prepare_max_ns={d}",
2255 .{
2256 host_phases.prepare_ns,
2257 host_phases.warm_frame_ns,
2258 frame_stats.count,
2259 frame_stats.total_ns,
2260 frame_stats.min_ns,
2261 frame_stats.median_ns,
2262 frame_stats.p75_ns,
2263 frame_stats.p95_ns,
2264 frame_stats.max_ns,
2265 prepare_stats.total_ns,
2266 prepare_stats.min_ns,
2267 prepare_stats.median_ns,
2268 prepare_stats.p75_ns,
2269 prepare_stats.p95_ns,
2270 prepare_stats.max_ns,
2271 },
2272 );
2273 bench.stdout(
2274 " measured_launch_cpu_total_ns={d} measured_launch_cpu_min_ns={d} measured_launch_cpu_median_ns={d} measured_launch_cpu_p75_ns={d} measured_launch_cpu_p95_ns={d} measured_launch_cpu_max_ns={d} measured_surface_total_ns={d} measured_surface_min_ns={d} measured_surface_median_ns={d} measured_surface_p75_ns={d} measured_surface_p95_ns={d} measured_surface_max_ns={d} eval_ns={d} host_phase_max={s} host_phase_max_ns={d}",
2275 .{
2276 launch_stats.total_ns,
2277 launch_stats.min_ns,
2278 launch_stats.median_ns,
2279 launch_stats.p75_ns,
2280 launch_stats.p95_ns,
2281 launch_stats.max_ns,
2282 surface_stats.total_ns,
2283 surface_stats.min_ns,
2284 surface_stats.median_ns,
2285 surface_stats.p75_ns,
2286 surface_stats.p95_ns,
2287 surface_stats.max_ns,
2288 host_phases.evaluation_ns,
2289 host_phase_max.name,
2290 host_phase_max.ns,
2291 },
2292 );
2293 bench.stdout(
2294 " pixels={d} commands={d} command_visits={d} surface_width={d} surface_height={d} host_readbacks=0 evidence_device_ranges=0 launch_device_syncs=0 device_csr_prepares={d}\n",
2295 .{
2296 live_surface.pixelCount(),
2297 fixture.workload.commands.len,
2298 fixture.command_visits,
2299 live_surface.surface.extent.width,
2300 live_surface.surface.extent.height,
2301 device_csr_prepares,
2302 },
2303 );
2304 std.mem.doNotOptimizeAway(frame_stats.total_ns);
2305 }
2306
2307 fn readbackAccyRecordingBackend(sample_allocator: Allocator) void {
2308 const fixture = AccyPhaseFixture.create(sample_allocator, phase_spec) catch |err| std.debug.panic("gui paint Accy readback fixture failed: {s}", .{@errorName(err)});
2309 defer fixture.destroy();
2310 fixture.uploadInputs() catch @panic("gui paint Accy readback input upload failed");
2311 fixture.launch() catch @panic("gui paint Accy readback launch failed");
2312
2313 var checksum: u32 = 0;
2314 const phase = bench.phaseAt("gui.paint.phase.accy_recording_readback", @src());
2315 defer phase.end();
2316 var index: usize = 0;
2317 while (index < phase_spec.repetitions) : (index += 1) {
2318 fixture.readbackOutput() catch @panic("gui paint Accy readback failed");
2319 checksum +%= checksumPixels(fixture.readback);
2320 bench.coz.progressNamed("gui.paint.phase.accy_recording_readback.frame");
2321 }
2322 std.mem.doNotOptimizeAway(checksum);
2323 }
2324
2325 fn writeSurfaceAccyRecordingBackend(sample_allocator: Allocator) void {
2326 const fixture = AccyPhaseFixture.create(sample_allocator, phase_spec) catch |err| std.debug.panic("gui paint Accy surface fixture failed: {s}", .{@errorName(err)});
2327 defer fixture.destroy();
2328 fixture.uploadInputs() catch @panic("gui paint Accy surface input upload failed");
2329 fixture.launch() catch @panic("gui paint Accy surface launch failed");
2330
2331 const handle = fixture.state.handle();
2332 const surface = handle.createSurface(.{
2333 .platform = .{ .headless = .{} },
2334 .extent = .{ .width = phase_spec.width, .height = phase_spec.height },
2335 .format = .rgba8_unorm,
2336 .usage = .{ .present = true, .copy_dst = true },
2337 }) catch @panic("gui paint Accy surface creation failed");
2338 defer handle.destroySurface(surface) catch @panic("gui paint Accy surface destroy failed");
2339
2340 const phase = bench.phaseAt("gui.paint.phase.accy_recording_surface_write", @src());
2341 defer phase.end();
2342 var index: usize = 0;
2343 while (index < phase_spec.repetitions) : (index += 1) {
2344 const frame = handle.acquireSurfaceFrame(.{ .surface = surface }) catch @panic("gui paint Accy surface acquire failed");
2345 const operations = [_]gpu.SurfaceFrameWriteOp{.{ .copy_buffer = fixture.pixels }};
2346 handle.writeSurfaceFrame(.{
2347 .surface = surface,
2348 .frame = frame,
2349 .operations = operations[0..],
2350 }) catch @panic("gui paint Accy surface write failed");
2351 handle.presentSurfaceFrame(.{
2352 .surface = surface,
2353 .frame = frame,
2354 }) catch @panic("gui paint Accy surface present failed");
2355 bench.coz.progressNamed("gui.paint.phase.accy_recording_surface_write.frame");
2356 }
2357 if (fixture.state.read_count != 0) @panic("gui paint Accy surface write read back unexpectedly");
2358 std.mem.doNotOptimizeAway(fixture.state.surface_write_count);
2359 }
2360
2361 const Workload = struct {
2362 spec: Spec,
2363 commands: []Command,
2364 pixels: []u32,
2365
2366 fn init(allocator: Allocator, spec: Spec) !Workload {
2367 const commands = try allocator.alloc(Command, spec.commandCount());
2368 errdefer allocator.free(commands);
2369 fillCommands(commands, spec);
2370 const pixels = try allocator.alloc(u32, spec.pixelCount());
2371 errdefer allocator.free(pixels);
2372 return .{ .spec = spec, .commands = commands, .pixels = pixels };
2373 }
2374
2375 fn deinit(self: *Workload, allocator: Allocator) void {
2376 allocator.free(self.commands);
2377 allocator.free(self.pixels);
2378 self.* = undefined;
2379 }
2380 };
2381
2382 const FrameWorkload = struct {
2383 spec: Spec,
2384 commands: []const Command,
2385 pixels: []u32,
2386
2387 fn init(allocator: Allocator, spec: Spec) !FrameWorkload {
2388 var surface = try frameSurfaceAlloc(allocator, spec);
2389 defer surface.deinit();
2390 var frame_workspace = gui.frame.Workspace.init(allocator);
2391 defer frame_workspace.deinit();
2392 const frame = try frame_workspace.buildSurface(&surface.surface, .{});
2393 var recorded = gui.paint.CommandBuffer.init(allocator);
2394 defer recorded.deinit();
2395 try recorded.appendFrame(frame, 1, spec.width, spec.height, .{});
2396 const pixels = try allocator.alloc(u32, spec.pixelCount());
2397 errdefer allocator.free(pixels);
2398 const commands = try allocator.dupe(Command, recorded.items());
2399 return .{ .spec = spec, .commands = commands, .pixels = pixels };
2400 }
2401
2402 fn deinit(self: *FrameWorkload, allocator: Allocator) void {
2403 allocator.free(@constCast(self.commands));
2404 allocator.free(self.pixels);
2405 self.* = undefined;
2406 }
2407 };
2408
2409 const AccyPhaseFixture = struct {
2410 allocator: Allocator,
2411 state: gpu.recording.BackendState,
2412 artifact: gpu.KernelArtifact,
2413 loaded: gpu.LoadedArtifact,
2414 workload: FrameWorkload,
2415 encoded: gui.paint.accy.PackedCommands,
2416 encoded_images: gui.paint.accy.PackedImages,
2417 pixels: gpu.BufferHandle,
2418 floats: gpu.BufferHandle,
2419 words: gpu.BufferHandle,
2420 image_metadata: gpu.BufferHandle,
2421 image_pixels: gpu.BufferHandle,
2422 readback: []u32,
2423
2424 fn create(allocator: Allocator, spec: Spec) !*AccyPhaseFixture {
2425 const fixture = try allocator.create(AccyPhaseFixture);
2426 errdefer allocator.destroy(fixture);
2427 fixture.* = undefined;
2428 fixture.allocator = allocator;
2429 fixture.state = .{
2430 .allocator = allocator,
2431 .kind = .vulkan,
2432 .format = .vulkan_spirv,
2433 };
2434 fixture.artifact = try gpu.KernelArtifact.init(allocator, .{
2435 .backend = .vulkan,
2436 .format = .vulkan_spirv,
2437 .entry_name = gui.paint.accy.kernel_name,
2438 .argument_count = 18,
2439 .diagnostic_id = "gui/paint/profiling/phase",
2440 });
2441 errdefer fixture.artifact.deinit();
2442 fixture.loaded = .{
2443 .id = 1,
2444 .backend = .vulkan,
2445 .format = .vulkan_spirv,
2446 };
2447 fixture.workload = try FrameWorkload.init(allocator, spec);
2448 errdefer fixture.workload.deinit(allocator);
2449 fixture.encoded = try gui.paint.accy.packCommandsAlloc(allocator, fixture.workload.commands);
2450 errdefer fixture.encoded.deinit(allocator);
2451 fixture.encoded_images = try gui.paint.accy.packImagesAlloc(allocator, .{});
2452 errdefer fixture.encoded_images.deinit(allocator);
2453 const readback = try allocator.alloc(u32, spec.pixelCount());
2454 errdefer allocator.free(readback);
2455 fixture.readback = readback;
2456 const handle = fixture.state.handle();
2457 const pixels = try phaseBuffer(handle, u32, .u32, spec.pixelCount());
2458 errdefer handle.destroyObject(pixels.id);
2459 fixture.pixels = pixels;
2460 const floats = try phaseBuffer(handle, f32, .f32, fixture.encoded.floats.len);
2461 errdefer handle.destroyObject(floats.id);
2462 fixture.floats = floats;
2463 const words = try phaseBuffer(handle, u32, .u32, fixture.encoded.words.len);
2464 errdefer handle.destroyObject(words.id);
2465 fixture.words = words;
2466 const image_metadata = try phaseBuffer(handle, u32, .u32, fixture.encoded_images.metadata.len);
2467 errdefer handle.destroyObject(image_metadata.id);
2468 fixture.image_metadata = image_metadata;
2469 const image_pixels = try phaseBuffer(handle, u32, .u32, fixture.encoded_images.pixels.len);
2470 errdefer handle.destroyObject(image_pixels.id);
2471 fixture.image_pixels = image_pixels;
2472 return fixture;
2473 }
2474
2475 fn destroy(self: *AccyPhaseFixture) void {
2476 const handle = self.state.handle();
2477 const allocator = self.allocator;
2478 handle.destroyObject(self.pixels.id);
2479 handle.destroyObject(self.floats.id);
2480 handle.destroyObject(self.words.id);
2481 handle.destroyObject(self.image_metadata.id);
2482 handle.destroyObject(self.image_pixels.id);
2483 self.allocator.free(self.readback);
2484 self.encoded_images.deinit(self.allocator);
2485 self.encoded.deinit(self.allocator);
2486 self.workload.deinit(self.allocator);
2487 self.artifact.deinit();
2488 self.* = undefined;
2489 allocator.destroy(self);
2490 }
2491
2492 fn uploadInputs(self: *AccyPhaseFixture) !void {
2493 const handle = self.state.handle();
2494 try handle.writeBuffer(.{
2495 .handle = self.floats,
2496 .bytes = std.mem.sliceAsBytes(self.encoded.floats),
2497 });
2498 try handle.writeBuffer(.{
2499 .handle = self.words,
2500 .bytes = std.mem.sliceAsBytes(self.encoded.words),
2501 });
2502 try handle.writeBuffer(.{
2503 .handle = self.image_metadata,
2504 .bytes = std.mem.sliceAsBytes(self.encoded_images.metadata),
2505 });
2506 try handle.writeBuffer(.{
2507 .handle = self.image_pixels,
2508 .bytes = std.mem.sliceAsBytes(self.encoded_images.pixels),
2509 });
2510 }
2511
2512 fn launchInputs(self: *AccyPhaseFixture) !AccyPhaseLaunch {
2513 const pixel_count = self.workload.spec.pixelCount();
2514 const pixel_count_u32 = std.math.cast(u32, pixel_count) orelse return error.DimensionsTooLarge;
2515 const command_count = std.math.cast(u32, self.workload.commands.len) orelse return error.CommandCountTooLarge;
2516 return .{
2517 .bindings = .{
2518 phaseBinding(self.pixels, .read_write),
2519 phaseBinding(self.floats, .read_only),
2520 phaseBinding(self.words, .read_only),
2521 phaseBinding(self.image_metadata, .read_only),
2522 phaseBinding(self.image_pixels, .read_only),
2523 },
2524 .scalars = .{
2525 .{ .u32 = self.workload.spec.width },
2526 .{ .u32 = self.workload.spec.height },
2527 .{ .u32 = command_count },
2528 .{ .u32 = 0 },
2529 .{ .u32 = clear.r },
2530 .{ .u32 = clear.g },
2531 .{ .u32 = clear.b },
2532 .{ .u32 = clear.a },
2533 .{ .u32 = 0 },
2534 .{ .u32 = 0 },
2535 .{ .u32 = self.workload.spec.width },
2536 .{ .u32 = pixel_count_u32 },
2537 .{ .u32 = @backingInt(gui.paint.accy.OutputFormat.rgba) },
2538 },
2539 .geometry = .{
2540 .grid = .{ gui.paint.accy.gridFor(pixel_count, gui.paint.accy.default_threads), 1, 1 },
2541 .threadgroup = .{ gui.paint.accy.default_threads, 1, 1 },
2542 },
2543 };
2544 }
2545
2546 fn launch(self: *AccyPhaseFixture) !void {
2547 const handle = self.state.handle();
2548 var inputs = try self.launchInputs();
2549 try handle.launch(inputs.request(self));
2550 try handle.synchronize(.{ .scope = .device });
2551 }
2552
2553 fn launchEvidence(self: *AccyPhaseFixture) !evidence.RangeRecord {
2554 const handle = self.state.handle();
2555 var inputs = try self.launchInputs();
2556 return evidence.launchRangeRecord(handle, inputs.request(self), .{
2557 .capture = .{
2558 .tool = .profile_tests,
2559 .id = "gui-paint-accy-recording",
2560 .trace_schema = evidence.schema,
2561 },
2562 .launch = self.launchIdentity(inputs.geometry),
2563 .probe_id = "gui-paint-accy-recording:device-range",
2564 .range_name = "gui.paint.phase.accy_recording_launch",
2565 });
2566 }
2567
2568 fn launchIdentity(self: *AccyPhaseFixture, geometry: choir_abi.LaunchGeometry) evidence.LaunchIdentity {
2569 const pixel_count = self.workload.spec.pixelCount();
2570 return .{
2571 .kernel = .{
2572 .source = .kernel_call,
2573 .kernel_id = 0,
2574 .work_item_id = 0,
2575 .entry_name = gui.paint.accy.kernel_name,
2576 .artifact_format = self.artifact.format,
2577 .artifact_payload_bytes = 0,
2578 .output_layout_fingerprint = 0,
2579 .input_layout_fingerprint = 0,
2580 .element_count = pixel_count,
2581 .op_count = pixel_count * self.workload.commands.len,
2582 },
2583 .candidate_index = 0,
2584 .candidate_count = 1,
2585 .resource_class = "gui-paint-recording",
2586 .geometry = geometry,
2587 .fixed_threadgroup = true,
2588 };
2589 }
2590
2591 fn readbackOutput(self: *AccyPhaseFixture) !void {
2592 try self.state.handle().readBuffer(.{
2593 .handle = self.pixels,
2594 .bytes = std.mem.sliceAsBytes(self.readback),
2595 });
2596 }
2597 };
2598
2599 const AccyPhaseLaunch = struct {
2600 bindings: [5]gpu.BufferBinding,
2601 scalars: [13]choir_abi.ScalarArgument,
2602 geometry: choir_abi.LaunchGeometry,
2603
2604 fn request(self: *AccyPhaseLaunch, fixture: *AccyPhaseFixture) gpu.LaunchRequest {
2605 return .{
2606 .artifact = &fixture.artifact,
2607 .loaded_artifact = fixture.loaded,
2608 .buffers = self.bindings[0..],
2609 .scalar_arguments = self.scalars[0..],
2610 .geometry = self.geometry,
2611 };
2612 }
2613 };
2614
2615 const LiveVulkanSurface = struct {
2616 handle: gpu.BackendHandle,
2617 window: windowing.Window,
2618 xlib: sys.x11.xlib.Connection,
2619 surface: gpu.SurfaceHandle,
2620
2621 fn create(handle: gpu.BackendHandle, spec: Spec) !LiveVulkanSurface {
2622 if (comptime sys.capabilities.current.os != .linux) return error.UnsupportedOperation;
2623 if (sys.env.get("DISPLAY") == null) return error.RuntimeUnavailable;
2624 var window = try windowing.Window.create(benchmarkAllocator(), .{
2625 .title = "gui-vulkan-surface-evidence",
2626 .width = spec.width,
2627 .height = spec.height,
2628 .resizable = false,
2629 .visible = false,
2630 .backend = .x11,
2631 });
2632 errdefer window.destroy();
2633 const native = window.getNativeSurface();
2634 const x11 = switch (native) {
2635 .x11 => |value| value,
2636 else => return error.UnsupportedOperation,
2637 };
2638 var xlib = try sys.x11.xlib.openTarget(x11.display);
2639 errdefer xlib.close();
2640 const surface = try handle.createSurface(.{
2641 .platform = .{ .x11 = .{
2642 .display = @intFromPtr(xlib.display),
2643 .window = x11.window,
2644 .visual_id = x11.visual,
2645 .depth = x11.depth,
2646 } },
2647 .extent = .{ .width = x11.extent.framebuffer_width, .height = x11.extent.framebuffer_height },
2648 .format = .bgra8_unorm,
2649 .color_space = .srgb,
2650 .present_mode = .fifo,
2651 .usage = .{ .copy_dst = true, .color_attachment = true, .present = true },
2652 .max_frames_in_flight = 2,
2653 });
2654 errdefer handle.destroySurface(surface) catch {};
2655 return .{
2656 .handle = handle,
2657 .window = window,
2658 .xlib = xlib,
2659 .surface = surface,
2660 };
2661 }
2662
2663 fn destroy(self: *LiveVulkanSurface) void {
2664 self.handle.destroySurface(self.surface) catch {};
2665 self.xlib.close();
2666 self.window.destroy();
2667 self.* = undefined;
2668 }
2669
2670 fn acquireFrame(self: LiveVulkanSurface) !gpu.SurfaceFrame {
2671 return self.handle.acquireSurfaceFrame(.{ .surface = self.surface });
2672 }
2673
2674 fn pixelCount(self: LiveVulkanSurface) usize {
2675 return @as(usize, self.surface.extent.width) * @as(usize, self.surface.extent.height);
2676 }
2677 };
2678
2679 const VulkanEvidenceFixture = struct {
2680 allocator: Allocator,
2681 state: gpu.vulkan.State,
2682 executor: gui.paint.Executor,
2683 workload: FrameWorkload,
2684 command_visits: usize,
2685
2686 fn create(allocator: Allocator) !*VulkanEvidenceFixture {
2687 const device_index = try benchVulkanDeviceIndex(allocator);
2688 const fixture = try allocator.create(VulkanEvidenceFixture);
2689 errdefer allocator.destroy(fixture);
2690 fixture.* = undefined;
2691 fixture.allocator = allocator;
2692 fixture.state = try gpu.vulkan.State.initDevice(allocator, device_index);
2693 errdefer fixture.state.deinit();
2694 fixture.executor = try gui.paint.Executor.init(allocator, fixture.state.handle(), .{
2695 .artifact_format = .vulkan_spirv,
2696 });
2697 errdefer fixture.executor.deinit();
2698 fixture.workload = try FrameWorkload.init(allocator, vulkan_evidence_spec);
2699 errdefer fixture.workload.deinit(allocator);
2700 const region = gui.paint.Region.full(vulkan_evidence_spec.width, vulkan_evidence_spec.height);
2701 var bins = try gui.paint.accy.binCommandsAlloc(allocator, fixture.workload.commands, vulkan_evidence_spec.width, vulkan_evidence_spec.height, region);
2702 defer bins.deinit(allocator);
2703 fixture.command_visits = gui.paint.accy.commandVisits(bins.view(), region);
2704 return fixture;
2705 }
2706
2707 fn destroy(self: *VulkanEvidenceFixture) void {
2708 const allocator = self.allocator;
2709 self.executor.deinit();
2710 self.workload.deinit(allocator);
2711 self.state.deinit();
2712 self.* = undefined;
2713 allocator.destroy(self);
2714 }
2715
2716 fn handle(self: *VulkanEvidenceFixture) gpu.BackendHandle {
2717 return self.state.handle();
2718 }
2719
2720 fn prepareLaunch(self: *VulkanEvidenceFixture) !gui.paint.PreparedPackedLaunch {
2721 return (try self.executor.prepareCommandsPackedLaunch(
2722 self.workload.commands,
2723 self.workload.spec.width,
2724 self.workload.spec.height,
2725 clear,
2726 .{},
2727 gui.paint.Region.full(self.workload.spec.width, self.workload.spec.height),
2728 )) orelse error.EmptyLaunch;
2729 }
2730
2731 fn prepareLaunchForSurface(self: *VulkanEvidenceFixture, surface: gpu.SurfaceHandle) !gui.paint.PreparedPackedLaunch {
2732 return (try self.executor.prepareCommandsSurfaceLaunch(
2733 self.workload.commands,
2734 surface,
2735 clear,
2736 .{},
2737 )) orelse error.EmptyLaunch;
2738 }
2739
2740 fn readbackChecksum(self: *VulkanEvidenceFixture, prepared: *const gui.paint.PreparedPackedLaunch) !u32 {
2741 const pixels = try self.executor.readPreparedPackedPixels(prepared);
2742 return checksumPixels(pixels);
2743 }
2744
2745 fn submitLaunch(self: *VulkanEvidenceFixture, prepared: *gui.paint.PreparedPackedLaunch) !void {
2746 try self.executor.submitPreparedLaunch(prepared);
2747 }
2748
2749 fn submitLaunchQueued(self: *VulkanEvidenceFixture, prepared: *gui.paint.PreparedPackedLaunch) !void {
2750 try self.executor.submitPreparedLaunchQueued(prepared);
2751 }
2752
2753 fn launchRangeRecord(
2754 self: *VulkanEvidenceFixture,
2755 prepared: *const gui.paint.PreparedPackedLaunch,
2756 options: evidence.LaunchRangeOptions,
2757 ) !evidence.RangeRecord {
2758 const timing = try self.executor.submitPreparedLaunchTimed(prepared);
2759 return launchRangeRecordFromTiming(options, timing);
2760 }
2761
2762 fn writePresentSurface(
2763 self: *VulkanEvidenceFixture,
2764 surface: gpu.SurfaceHandle,
2765 frame: gpu.SurfaceFrame,
2766 prepared: *const gui.paint.PreparedPackedLaunch,
2767 ) !void {
2768 try self.executor.writePreparedSurfaceFrame(frame, prepared);
2769 try self.handle().presentSurfaceFrame(.{
2770 .surface = surface,
2771 .frame = frame,
2772 });
2773 try self.handle().synchronize(.{ .scope = .device });
2774 try self.handle().destroyTexture(frame.texture);
2775 }
2776
2777 fn preparedLaunchInfo(self: *VulkanEvidenceFixture, prepared: *const gui.paint.PreparedPackedLaunch) !gui.paint.PreparedLaunchInfo {
2778 return self.executor.preparedLaunchInfo(prepared);
2779 }
2780
2781 fn launchIdentity(self: *VulkanEvidenceFixture, prepared: *const gui.paint.PreparedPackedLaunch) !evidence.LaunchIdentity {
2782 return self.launchIdentityFor(prepared, "gui-paint-vulkan-tiled");
2783 }
2784
2785 fn launchIdentityFor(
2786 self: *VulkanEvidenceFixture,
2787 prepared: *const gui.paint.PreparedPackedLaunch,
2788 resource_class: []const u8,
2789 ) !evidence.LaunchIdentity {
2790 const info = try self.preparedLaunchInfo(prepared);
2791 return .{
2792 .kernel = .{
2793 .source = .kernel_call,
2794 .kernel_id = 0,
2795 .work_item_id = 0,
2796 .entry_name = info.entry_name,
2797 .artifact_format = info.artifact_format,
2798 .artifact_payload_bytes = info.artifact_payload_bytes,
2799 .output_layout_fingerprint = 0,
2800 .input_layout_fingerprint = 0,
2801 .element_count = @intCast(info.pixel_count),
2802 .op_count = self.command_visits,
2803 },
2804 .candidate_index = 0,
2805 .candidate_count = 1,
2806 .resource_class = resource_class,
2807 .geometry = info.geometry,
2808 .fixed_threadgroup = true,
2809 };
2810 }
2811 };
2812
2813 fn phaseBuffer(handle: gpu.BackendHandle, comptime T: type, dtype: choir_abi.DType, count: usize) !gpu.BufferHandle {
2814 const byte_size = std.math.mul(usize, @max(count, 1), @sizeOf(T)) catch return error.BufferTooLarge;
2815 return handle.allocateBuffer(.{
2816 .byte_size = byte_size,
2817 .alignment = 256,
2818 .dtype = dtype,
2819 .element_count = std.math.cast(u64, @max(count, 1)) orelse return error.BufferTooLarge,
2820 });
2821 }
2822
2823 fn phaseBinding(buffer: gpu.BufferHandle, access: gpu.BufferAccess) gpu.BufferBinding {
2824 return .{
2825 .handle = buffer,
2826 .access = access,
2827 .ownership = buffer.ownership,
2828 .byte_size = buffer.byte_size,
2829 };
2830 }
2831
2832 const OwnedFrameSurface = struct {
2833 arena: std.heap.ArenaAllocator,
2834 surface: gui.model.UiSurfaceTree,
2835
2836 fn deinit(self: *OwnedFrameSurface) void {
2837 self.arena.deinit();
2838 self.* = undefined;
2839 }
2840 };
2841
2842 fn frameSurfaceAlloc(allocator: Allocator, spec: Spec) !OwnedFrameSurface {
2843 var arena = std.heap.ArenaAllocator.init(allocator);
2844 errdefer arena.deinit();
2845 const arena_allocator = arena.allocator();
2846 const rows = try arena_allocator.alloc(gui.model.UiNode, spec.row_count);
2847 const row_step = @as(f32, @floatFromInt(@max(spec.height, 1))) / @as(f32, @floatFromInt(@max(spec.row_count + 2, 1)));
2848 for (rows, 0..) |*row, index| {
2849 row.* = .{
2850 .widget_id = 100 + index,
2851 .paint = .{
2852 .background = rowColor(index),
2853 .border = .{ .r = 24, .g = 34, .b = 46, .a = 80 },
2854 .border_width = 1,
2855 .corner_roundness = @max(row_step * 0.35, 2),
2856 },
2857 .size = .{ .height = @max(row_step * 0.62, 2) },
2858 };
2859 }
2860
2861 const children = try arena_allocator.alloc(gui.model.UiNode, 3);
2862 children[0] = .{
2863 .widget_id = 2,
2864 .paint = .{
2865 .background = .{ .r = 34, .g = 44, .b = 58, .a = 235 },
2866 .border = .{ .r = 82, .g = 105, .b = 132, .a = 160 },
2867 .border_width = 1,
2868 .corner_roundness = 6,
2869 },
2870 .size = .{ .height = 18 },
2871 };
2872 children[1] = .{
2873 .widget_id = 3,
2874 .style = .{
2875 .flex_direction = .column,
2876 .gap = 2,
2877 .padding = .{ .top = 4, .left = 4, .right = 4, .bottom = 4 },
2878 .flex_grow = 1,
2879 },
2880 .paint = .{
2881 .background = .{ .r = 236, .g = 238, .b = 241, .a = 245 },
2882 .border = .{ .r = 46, .g = 58, .b = 72, .a = 80 },
2883 .border_width = 1,
2884 .corner_roundness = 8,
2885 .shadow = .{
2886 .color = .{ .r = 0, .g = 0, .b = 0, .a = 48 },
2887 .offset_y = 2,
2888 .blur_radius = 4,
2889 },
2890 },
2891 .children = rows,
2892 };
2893 children[2] = .{
2894 .widget_id = 4,
2895 .paint = .{
2896 .background = .{ .r = 44, .g = 68, .b = 62, .a = 220 },
2897 .corner_roundness = 6,
2898 },
2899 .size = .{ .height = 16 },
2900 };
2901
2902 return .{
2903 .arena = arena,
2904 .surface = .{
2905 .available_size = .{
2906 .width = @floatFromInt(spec.width),
2907 .height = @floatFromInt(spec.height),
2908 },
2909 .root = .{
2910 .widget_id = 1,
2911 .style = .{
2912 .flex_direction = .column,
2913 .gap = 4,
2914 .padding = .{ .top = 4, .left = 4, .right = 4, .bottom = 4 },
2915 },
2916 .paint = .{ .background = clear },
2917 .children = children,
2918 },
2919 },
2920 };
2921 }
2922
2923 fn fillCommands(commands: []Command, spec: Spec) void {
2924 std.debug.assert(commands.len == spec.commandCount());
2925 const width_f = @as(f32, @floatFromInt(spec.width));
2926 const height_f = @as(f32, @floatFromInt(spec.height));
2927 const panel_width = width_f - 20;
2928 const panel_height = height_f - 20;
2929 const clip = gui.layout.Rect{ .x = 14, .y = 14, .width = width_f - 28, .height = height_f - 29 };
2930 const row_step = (height_f - 36) / @as(f32, @floatFromInt(spec.row_count));
2931 const row_height = @max(row_step * 0.62, 2);
2932 const content_width = @max(width_f - 44, 8);
2933 const highlight_width = @max(content_width * 0.21, 8);
2934 var index: usize = 0;
2935 commands[index] = .{
2936 .kind = .fill,
2937 .rect = .{ .x = 0, .y = 0, .width = width_f, .height = height_f },
2938 .clip = .{ .x = 0, .y = 0, .width = width_f, .height = height_f },
2939 .color = .{ .r = 18, .g = 22, .b = 28, .a = 255 },
2940 };
2941 index += 1;
2942 commands[index] = .{
2943 .kind = .fill,
2944 .rect = .{ .x = 10, .y = 10, .width = panel_width, .height = panel_height },
2945 .clip = .{ .x = 0, .y = 0, .width = width_f, .height = height_f },
2946 .color = .{ .r = 236, .g = 238, .b = 241, .a = 245 },
2947 .radius = 12,
2948 };
2949 index += 1;
2950
2951 var row: usize = 0;
2952 while (row < spec.row_count) : (row += 1) {
2953 const y = 18 + @as(f32, @floatFromInt(row)) * row_step;
2954 commands[index] = .{
2955 .kind = .fill,
2956 .rect = .{ .x = 18, .y = y, .width = content_width, .height = row_height },
2957 .clip = clip,
2958 .color = rowColor(row),
2959 .radius = row_height * 0.5,
2960 };
2961 index += 1;
2962 commands[index] = .{
2963 .kind = .stroke,
2964 .rect = .{ .x = 18, .y = y, .width = content_width, .height = row_height },
2965 .clip = clip,
2966 .color = .{ .r = 26, .g = 32, .b = 40, .a = 90 },
2967 .radius = row_height * 0.5,
2968 .width = 1,
2969 };
2970 index += 1;
2971 commands[index] = .{
2972 .kind = .fill,
2973 .rect = .{ .x = 24 + @as(f32, @floatFromInt(row % 4)) * 8, .y = y + 0.8, .width = highlight_width, .height = @max(row_height - 1.6, 1) },
2974 .clip = clip,
2975 .color = .{ .r = 240, .g = 247, .b = 255, .a = 112 },
2976 .radius = @max((row_height - 1.6) * 0.5, 0.5),
2977 };
2978 index += 1;
2979 }
2980 std.debug.assert(index == commands.len);
2981 gui.paint.assignOrders(commands);
2982 }
2983
2984 fn rowColor(row: usize) Color {
2985 return .{
2986 .r = @intCast(48 + row % 5 * 9),
2987 .g = @intCast(82 + row % 7 * 11),
2988 .b = @intCast(116 + row % 4 * 18),
2989 .a = @intCast(168 + row % 3 * 24),
2990 };
2991 }
2992
2993 fn checksumPixels(pixels: []const u32) u32 {
2994 var checksum: u32 = 2166136261;
2995 const stride = @max(pixels.len / 64, 1);
2996 var index: usize = 0;
2997 while (index < pixels.len) : (index += stride) {
2998 checksum = (checksum ^ pixels[index]) *% 16777619;
2999 }
3000 return checksum;
3001 }
3002
3003 fn checksumAllPixels(pixels: []const u32) u32 {
3004 var checksum: u32 = 2166136261;
3005 for (pixels) |value| checksum = (checksum ^ value) *% 16777619;
3006 return checksum;
3007 }
3008
3009 fn reportStripReificationShape(commands: []const Command, filter: ?[]const u8) !void {
3010 if (!matchesFilter(strip_benchmark_name, filter)) return;
3011 const records = try gui.paint.strip.recordCount(commands, strip_spec.width, strip_spec.height, gui.paint.Region.full(strip_spec.width, strip_spec.height), .{ .height = gui.paint.accy.tile_size });
3012 bench.stdout(
3013 "gui paint strip reification shape: commands={d} records={d} strip_height={d} extent={d}x{d}\n",
3014 .{ commands.len, records, gui.paint.accy.tile_size, strip_spec.width, strip_spec.height },
3015 );
3016 }
3017
3018 fn matchesFilter(name: []const u8, filter: ?[]const u8) bool {
3019 const needle = filter orelse return true;
3020 return needle.len == 0 or std.mem.indexOf(u8, name, needle) != null;
3021 }
3022
3023 fn matchesExplicitFilter(name: []const u8, filter: ?[]const u8) bool {
3024 const needle = filter orelse return false;
3025 return needle.len != 0 and std.mem.indexOf(u8, name, needle) != null;
3026 }
3027
3028 test "bench: gui paint compositor workloads" {
3029 const allocator = benchmarkAllocator();
3030 const filter = try sys.env.getOwned(allocator, "BENCH_FILTER");
3031 defer if (filter) |value| allocator.free(value);
3032
3033 var suite = bench.Suite.init(allocator, .{
3034 .min_time_ns = try benchMinTimeNs(allocator, 1_000_000),
3035 .min_iterations = 2,
3036 .max_iterations = 12,
3037 .warmup_iterations = 1,
3038 .allocation_attribution = .sample_call,
3039 });
3040 defer suite.deinit();
3041 suite.setFilter(filter);
3042 var strip_workload: ?FrameWorkload = null;
3043 defer {
3044 strip_fixture_commands = &.{};
3045 if (strip_workload) |*workload| workload.deinit(allocator);
3046 }
3047 var caret_workload: ?CaretBenchmarkWorkload = null;
3048 defer {
3049 caret_benchmark_workload = null;
3050 if (caret_workload) |*workload| workload.deinit(allocator);
3051 }
3052 var styled_text_workload: ?StyledTextBenchmarkWorkload = null;
3053 defer {
3054 styled_text_benchmark_workload = null;
3055 if (styled_text_workload) |*workload| workload.deinit();
3056 }
3057 var mixed_metric_text_workload: ?MixedMetricTextBenchmarkWorkload = null;
3058 defer {
3059 mixed_metric_text_benchmark_workload = null;
3060 if (mixed_metric_text_workload) |*workload| workload.deinit();
3061 }
3062 var fallback_text_workload: ?FallbackTextBenchmarkWorkload = null;
3063 defer {
3064 fallback_text_benchmark_workload = null;
3065 if (fallback_text_workload) |*workload| workload.deinit();
3066 }
3067 if (matchesFilter(strip_benchmark_name, filter)) {
3068 strip_workload = try FrameWorkload.init(allocator, strip_spec);
3069 strip_fixture_commands = strip_workload.?.commands;
3070 try reportStripReificationShape(strip_fixture_commands, filter);
3071 }
3072 if (matchesFilter(caret_geometry_benchmark_name, filter) or
3073 matchesFilter(caret_prefix_benchmark_name, filter) or
3074 matchesFilter(multiline_caret_benchmark_name, filter) or
3075 matchesFilter(widget_text_hit_benchmark_name, filter))
3076 {
3077 caret_workload = try CaretBenchmarkWorkload.init(allocator);
3078 caret_benchmark_workload = &caret_workload.?;
3079 }
3080 if (matchesFilter(styled_text_benchmark_name, filter)) {
3081 styled_text_workload = try StyledTextBenchmarkWorkload.init(allocator);
3082 styled_text_benchmark_workload = &styled_text_workload.?;
3083 }
3084 if (matchesFilter(mixed_metric_text_benchmark_name, filter)) {
3085 mixed_metric_text_workload = try MixedMetricTextBenchmarkWorkload.init(allocator);
3086 mixed_metric_text_benchmark_workload = &mixed_metric_text_workload.?;
3087 }
3088 if (matchesFilter(fallback_text_benchmark_name, filter)) {
3089 fallback_text_workload = try FallbackTextBenchmarkWorkload.init(allocator);
3090 fallback_text_benchmark_workload = &fallback_text_workload.?;
3091 }
3092
3093 try suite.add("gui paint CPU packed compositor", renderCpuPacked, .{});
3094 try suite.add(gradient_frame_benchmark_name, renderLinearGradientFullHdCpuPacked, .{
3095 .min_iterations = 3,
3096 .warmup_iterations = 2,
3097 });
3098 try suite.add("gui paint scalar translucent span 7", ScalarSpan7.run, .{});
3099 try suite.add("gui paint selected translucent span 7", SelectedSpan7.run, .{});
3100 try suite.add("gui paint scalar translucent span 320", ScalarSpan320.run, .{});
3101 try suite.add("gui paint selected translucent span 320", SelectedSpan320.run, .{});
3102 try suite.add("gui paint scalar translucent span 4096", ScalarSpan4096.run, .{});
3103 try suite.add("gui paint selected translucent span 4096", SelectedSpan4096.run, .{});
3104 try suite.add("gui paint Compositor CPU packed", renderCompositorCpuPacked, .{});
3105 try suite.add("gui paint Accy CPU packed compositor", renderAccyCpuPacked, .{});
3106 try suite.add("gui paint frame CPU packed compositor", renderCpuFramePacked, .{});
3107 try suite.add("gui paint frame Compositor CPU packed", renderCompositorCpuFramePacked, .{});
3108 try suite.add("gui paint frame Accy CPU packed compositor", renderAccyCpuFramePacked, .{});
3109 try suite.add("gui paint phase frame command recording", recordFrameCommands, .{});
3110 try suite.add("gui frame workspace build x64", buildSurfaceFrames, .{});
3111 try suite.add("gui paint command buffer x64", recordCommandBuffers, .{});
3112 try suite.add("gui paint retained record x256", recordRetainedSizedFrames, .{});
3113 try suite.add("gui paint retained diff x256", diffRetainedFrames, .{});
3114 try suite.add("gui paint retained diff 10k x16", diffRetainedScale, .{});
3115 try suite.add(caret_geometry_benchmark_name, caretGeometryQueries, .{
3116 .min_iterations = 1,
3117 .max_iterations = 1,
3118 .evaluation = bench.Evaluation{ .fixed = 1 },
3119 .warmup_iterations = 0,
3120 });
3121 try suite.add(caret_prefix_benchmark_name, caretPrefixQueries, .{
3122 .min_iterations = 1,
3123 .max_iterations = 1,
3124 .evaluation = bench.Evaluation{ .fixed = 1 },
3125 .warmup_iterations = 0,
3126 });
3127 try suite.add(multiline_caret_benchmark_name, multilineCaretQueries, .{});
3128 try suite.add(widget_text_hit_benchmark_name, widgetTextHitQueries, .{
3129 .max_median_ns = 10_000_000,
3130 .max_allocations = 0,
3131 .max_frees = 0,
3132 });
3133 try suite.add(styled_text_benchmark_name, recordStyledTextCommands, .{});
3134 try suite.add(mixed_metric_text_benchmark_name, recordMixedMetricTextCommands, .{});
3135 try suite.add(fallback_text_benchmark_name, shapeFallbackText, .{
3136 .max_allocations = 0,
3137 .max_frees = 0,
3138 });
3139 try suite.add("gui paint phase Accy command packing", packAccyCommands, .{});
3140 try suite.add("gui paint phase Accy command binning", binAccyCommands, .{});
3141 try suite.add("gui paint phase Accy command binning scratch", binAccyCommandsScratch, .{});
3142 try suite.add("gui paint Executor recording storage epoch", executorRecordingStorageEpoch, .{});
3143 try suite.add("gui paint image Processor recording steady x1024", imageProcessorRecordingSteady, .{});
3144 try suite.add(strip_benchmark_name, reifyStripCommands, .{});
3145 try suite.add("gui paint phase Accy recording backend upload", uploadAccyRecordingBackend, .{});
3146 try suite.add("gui paint phase Accy recording backend launch", launchAccyRecordingBackend, .{});
3147 try suite.add("gui paint phase Accy recording backend device evidence", evidenceAccyRecordingBackend, .{});
3148 if (matchesExplicitFilter(vulkan_evidence_benchmark_name, filter)) {
3149 try suite.add(vulkan_evidence_benchmark_name, evidenceAccyVulkanBackend, .{
3150 .min_iterations = 1,
3151 .max_iterations = 1,
3152 .warmup_iterations = 0,
3153 });
3154 }
3155 if (matchesExplicitFilter(vulkan_surface_benchmark_name, filter)) {
3156 try suite.add(vulkan_surface_benchmark_name, evidenceAccyVulkanSurfaceFrame, .{
3157 .min_iterations = 1,
3158 .max_iterations = 1,
3159 .warmup_iterations = 0,
3160 });
3161 }
3162 if (matchesExplicitFilter(vulkan_surface_steady_benchmark_name, filter)) {
3163 try suite.add(vulkan_surface_steady_benchmark_name, steadyAccyVulkanSurfaceFrame, .{
3164 .min_iterations = 1,
3165 .max_iterations = 1,
3166 .warmup_iterations = 0,
3167 });
3168 }
3169 if (matchesExplicitFilter(vulkan_surface_queued_benchmark_name, filter)) {
3170 try suite.add(vulkan_surface_queued_benchmark_name, queuedAccyVulkanSurfaceFrame, .{
3171 .min_iterations = 1,
3172 .max_iterations = 1,
3173 .warmup_iterations = 0,
3174 });
3175 }
3176 if (matchesExplicitFilter(vulkan_surface_prepared_benchmark_name, filter)) {
3177 try suite.add(vulkan_surface_prepared_benchmark_name, preparedAccyVulkanSurfaceFrame, .{
3178 .min_iterations = 1,
3179 .max_iterations = 1,
3180 .warmup_iterations = 0,
3181 });
3182 }
3183 try suite.add("gui paint phase Accy recording backend readback", readbackAccyRecordingBackend, .{});
3184 try suite.add("gui paint phase Accy recording backend surface write", writeSurfaceAccyRecordingBackend, .{});
3185 try suite.run();
3186 }