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(&current, frame);
1327     retained.retain(&current) catch @panic("gui retained baseline failed");
1328     recordRetainedFrame(&current, frame);
1329     _ = retained.diff(
1330         &current,
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(&current) 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(&current, frame);
1342         const damage = retained.diff(
1343             &current,
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(&current) 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(&current, false);
1374     retained.retain(&current) catch @panic("gui retained scale baseline failed");
1375     appendRetainedScale(&current, true);
1376     _ = retained.diff(
1377         &current,
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             &current,
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 }