Skip to documentation
SLOP

tiny.gui.paint.accy

Reference tiny.gui paint accy

Defined in paint.

API (64)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callspaintaccy
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callerspaint.accybinShapeprivate sourcelib.gui.src.paint.accycountBinPairsprivate sourcelib.gui.src.paint.accyfillBinIndicesprivate sourcelib.gui.src.paint.accyfillCommandTileRangespaint.accytileRangeValueCountpaint.accy.BinScratchbinCommands
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallspaint.accyrenderCommandsCpuRegionWithImagesprivate sourcelib.gui.src.paint.accyrunPaintFrameLifecycletest sourcelib.gui.src.paint.accytest: Accy failed frame acquisition l...test sourcelib.gui.src.paint.accytest: Accy steady frames make no allo...test sourcelib.gui.src.paint.accytest: Accy surface epochs stay alloca...+3 morepaint.accybinShapeprivate sourcelib.gui.src.paint.accycountBinPairsprivate sourcelib.gui.src.paint.accyfillBinIndicesprivate sourcelib.gui.src.paint.accyfillCommandTileRangespaint.accytileRangeValueCountpaint.accybinCommandsAlloc
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallstest sourcelib.gui.src.paint.accytest: binPairCount surveys exact comm...paint.accybinShapeprivate sourcelib.gui.src.paint.accycommandTileRangepaint.accybinPairCount
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallspaint.accy.BinScratchbinCommandspaint.accybinCommandsAllocpaint.accybinPairCountpaint.accyrunPackedTileRangesCpuRegiontest sourcelib.gui.src.paint.accytest: Accy tile count graph matches h...+5 morepaint.accytilesForExtentpaint.accybinShape
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callspaint.accycreateRecordingArtifactpaint.accyrenderCommandsCpuRegionWithImagesprivate sourcelib.gui.src.paint.accyrunPaintFrameLifecycletest sourcelib.gui.src.paint.accytest: Accy failed frame acquisition l...test sourcelib.gui.src.paint.accytest: Accy steady frames make no allo...+2 morepaint.accybuildGraph
Static calls · unresolved targets: 0 · external targets: 14.
Called byCallsNo direct callspaint.accycreateTileCountRecordingArtifacttest sourcelib.gui.src.paint.accytest: Accy tile count graph matches h...test sourcelib.gui.src.paint.accytest: Accy tile count scan covers mul...test sourcelib.gui.src.paint.accytest: Accy tile count scan matches ho...test sourcelib.gui.src.paint.accytest: Accy tile index graph fills sca...test sourcelib.gui.src.paint.accytest: Accy tile sort graph orders sca...paint.accybuildTileCountGraph
Static calls · unresolved targets: 0 · external targets: 15.
Called byCallsNo direct callspaint.accycreateTileIndexRecordingArtifacttest sourcelib.gui.src.paint.accytest: Accy tile index graph fills sca...paint.accybuildTileIndexGraph
Static calls · unresolved targets: 0 · external targets: 15.
Called byCallsNo direct callspaint.accycreateTileRangeRecordingArtifacttest sourcelib.gui.src.paint.accytest: Accy tile range graph matches h...paint.accybuildTileRangeGraph
Static calls · unresolved targets: 0 · external targets: 14.
Called byCallsNo direct callspaint.accycreateTileSortRecordingArtifacttest sourcelib.gui.src.paint.accytest: Accy tile sort graph orders sca...paint.accybuildTileSortGraph
Static calls · unresolved targets: 0 · external targets: 14.
Called byCallsNo direct callstest sourcelib.gui.src.paint.accytest: commandVisits counts per-pixel ...paint.accycommandVisits
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.gui.src.paint.accytest: gradient command packing reuses...test sourcelib.gui.src.paint.accytest: packCommands encodes geometry a...private sourcelib.gui.src.paint.accypackCommandpaint.accycommandsEqualPacked
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor creates recordi...paint.accybuildGraphprivate sourcelib.gui.src.paint.accyrecordingBackendStatetiny.smggraphdeinitpaint.accycreateRecordingArtifact
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor creates recordi...paint.accybuildTileCountGraphprivate sourcelib.gui.src.paint.accyrecordingBackendStatetiny.smggraphdeinitpaint.accycreateTileCountRecordingArtifact
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor creates recordi...paint.accybuildTileIndexGraphprivate sourcelib.gui.src.paint.accyrecordingBackendStatetiny.smggraphdeinitpaint.accycreateTileIndexRecordingArtifact
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor creates recordi...paint.accybuildTileRangeGraphprivate sourcelib.gui.src.paint.accyrecordingBackendStatetiny.smggraphdeinitpaint.accycreateTileRangeRecordingArtifact
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor creates recordi...paint.accybuildTileSortGraphprivate sourcelib.gui.src.paint.accyrecordingBackendStatetiny.smggraphdeinitpaint.accycreateTileSortRecordingArtifact
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callspaint.accyrunPackedCommandsCpuRegionpaint.accyrunPackedTileCountsCpupaint.accyrunPackedTileIndicesCpupaint.accyrunPackedTileRangesCpuRegionpaint.accyrunPackedTileSortCpupaint.accygridFor
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accyimagesEqualPackedtest sourcelib.gui.src.paint.accytest: packImages encodes metadata and...private sourcelib.gui.src.paint.accyimageMetadataCountprivate sourcelib.gui.src.paint.imagepixelCountpaint.accyimageMetadataEqualPacked
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallspaint.accyimagesEqualPackedpaint.accypackImagespaint.accypackImagesAllocprivate sourcelib.gui.src.paint.imagepixelCountpaint.accyimagePixelCount
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.gui.src.paint.accytest: packImages encodes metadata and...paint.accyimageMetadataEqualPackedpaint.accyimagePixelCountprivate sourcelib.gui.src.paint.imagepixelCountpaint.accyimagesEqualPacked
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accypackCommandsAlloctest sourcelib.gui.src.paint.accytest: gradient command packing makes ...test sourcelib.gui.src.paint.accytest: gradient command packing reuses...test sourcelib.gui.src.paint.accytest: packCommands encodes geometry a...private sourcelib.gui.src.paint.accypackCommandpaint.accypackCommands
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accyrenderCommandsCpuRegionWithImagesprivate sourcelib.gui.src.paint.accyrunPaintFrameLifecycletest sourcelib.gui.src.paint.accytest: Accy failed frame acquisition l...test sourcelib.gui.src.paint.accytest: Accy steady frames make no allo...test sourcelib.gui.src.paint.accytest: Accy surface epochs stay alloca...+4 morepaint.accypackCommandspaint.accypackCommandsAlloc
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallspaint.accypackImagesAlloctest sourcelib.gui.src.paint.accytest: packImages encodes metadata and...private sourcelib.gui.src.paint.accyimageMetadataCountpaint.accyimagePixelCountprivate sourcelib.gui.src.paint.imagepixelCountpaint.accypackImages
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accyrenderCommandsCpuRegionWithImagesprivate sourcelib.gui.src.paint.accyrunPaintFrameLifecycletest sourcelib.gui.src.paint.accytest: Accy failed frame acquisition l...test sourcelib.gui.src.paint.accytest: Accy steady frames make no allo...test sourcelib.gui.src.paint.accytest: Accy surface epochs stay alloca...test sourcelib.gui.src.paint.accytest: Accy zero-size regions render w...private sourcelib.gui.src.paint.accyimageMetadataCountpaint.accyimagePixelCountpaint.accypackImagespaint.accypackImagesAlloc
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor matches recorde...paint.accyrenderCommandsCpuRegionWithImagespaint.Regionfullpaint.accyrenderCommandsCpu
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accyrenderCommandsCpupaint.accyrenderCommandsCpuWithImagestest sourcelib.gui.src.paint.accytest: Accy compositor region preserve...paint.accybinCommandsAllocpaint.accybuildGraphpaint.accypackCommandsAllocpaint.accypackImagesAllocpaint.accyrunPackedCommandsCpuRegiontiny.smggraphdeinitpaint.accyrenderCommandsCpuRegionWithImages
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy compositor matches CPU pac...paint.accyrenderCommandsCpuRegionWithImagespaint.Regionfullpaint.accyrenderCommandsCpuWithImages
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.gui.src.paint.accyrunPaintFrameLifecycletest sourcelib.gui.src.paint.accytest: Accy failed frame acquisition l...test sourcelib.gui.src.paint.accytest: Accy steady frames make no allo...test sourcelib.gui.src.paint.accytest: Accy surface epochs stay alloca...paint.accyrunPackedCommandsCpuRegionpaint.Regionfullpaint.accyrunPackedCommandsCpu
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accyrenderCommandsCpuRegionWithImagespaint.accyrunPackedCommandsCputest sourcelib.gui.src.paint.accytest: Accy zero-size regions render w...private sourcelib.gui.src.paint.accycopyPackedRegionpaint.accygridForprivate sourcelib.gui.src.paint.accyimageMetadataCountpaint.accytilesForExtentpaint.accyrunPackedCommandsCpuRegion
Static calls · unresolved targets: 0 · external targets: 8.
Called byCallspaint.accyrunPackedTileOffsetsCputest sourcelib.gui.src.paint.accytest: Accy tile count graph matches h...paint.accygridForpaint.accytileRangeValueCountpaint.accyrunPackedTileCountsCpu
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy tile index graph fills sca...paint.accygridForpaint.accytileRangeValueCountpaint.accyrunPackedTileIndicesCpu
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy tile count scan covers mul...test sourcelib.gui.src.paint.accytest: Accy tile count scan matches ho...test sourcelib.gui.src.paint.accytest: Accy tile index graph fills sca...test sourcelib.gui.src.paint.accytest: Accy tile sort graph orders sca...paint.accyrunPackedTileCountsCpuprivate sourcelib.gui.src.paint.accyscanTileOffsetCountsCpupaint.accyrunPackedTileOffsetsCpu
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callerspaint.accyrunPackedTileRangesCpuRegionpaint.Regionfullpaint.accyrunPackedTileRangesCpu
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallspaint.accyrunPackedTileRangesCputest sourcelib.gui.src.paint.accytest: Accy tile range graph matches h...paint.accybinShapepaint.accygridForpaint.accytileRangeValueCountpaint.accyrunPackedTileRangesCpuRegion
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallstest sourcelib.gui.src.paint.accytest: Accy tile sort graph orders sca...paint.accygridForpaint.accyrunPackedTileSortCpu
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callspaint.accy.BinScratchbinCommandspaint.accybinCommandsAllocprivate sourcelib.gui.src.paint.accyfillBinIndicesUnsortedprivate sourcelib.gui.src.paint.accyfillCommandTileRangespaint.accyrunPackedTileCountsCpu+8 morepaint.accytileRangeValueCount
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callspaint.accybinShapepaint.accyrunPackedCommandsCpuRegionpaint.accytilesForExtent
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/gui/src/paint/accy.zig

zig
const std = @import("std");const gpu = @import("gpu");const accy = @import("accy");const command = @import("command.zig");const cpu = @import("cpu/root.zig");const gui = @import("../root.zig");const Allocator = std.mem.Allocator;const Color = gui.model.UiColor;const Command = command.Command;const ImageSet = command.ImageSet;const Region = cpu.Region;const Rect = gui.layout.Rect;const kernel = accy.kernel;const scan_library = kernel.library.scan;const Value = kernel.Value;pub const kernel_name = "gui_paint_rgba8_packed";pub const default_threads: u32 = 128;pub const float_lanes: usize = 18;pub const word_lanes: usize = 8;pub const image_lanes: usize = 3;pub const tile_range_kernel_name = "gui_paint_tile_ranges";pub const tile_count_kernel_name = "gui_paint_tile_counts";pub const tile_index_kernel_name = "gui_paint_tile_indices";pub const tile_sort_kernel_name = "gui_paint_tile_sort";pub const tile_range_lanes: usize = 4;pub const tile_size_log2: u5 = 4;pub const tile_size: u32 = 1 << tile_size_log2;const arg_pixels: usize = 0;const arg_floats: usize = 1;const arg_words: usize = 2;const arg_images: usize = 3;const arg_image_pixels: usize = 4;const arg_tile_offsets: usize = 5;const arg_tile_indices: usize = 6;const arg_image_count: usize = 7;const arg_clear_r: usize = 8;const arg_clear_g: usize = 9;const arg_clear_b: usize = 10;const arg_clear_a: usize = 11;const arg_view_x: usize = 12;const arg_view_y: usize = 13;const arg_view_width: usize = 14;const arg_pixel_count: usize = 15;const arg_tiles_x: usize = 16;const arg_output_format: usize = 17;const range_arg_ranges: usize = 0;const range_arg_floats: usize = 1;const range_arg_words: usize = 2;const range_arg_command_count: usize = 3;const range_arg_width: usize = 4;const range_arg_height: usize = 5;const range_arg_region_x: usize = 6;const range_arg_region_y: usize = 7;const range_arg_region_width: usize = 8;const range_arg_region_height: usize = 9;const range_arg_tiles_x: usize = 10;const range_arg_tiles_y: usize = 11;const count_arg_offsets: usize = 0;const count_arg_ranges: usize = 1;const count_arg_command_count: usize = 2;const count_arg_tiles_x: usize = 3;const count_arg_tiles_y: usize = 4;const index_arg_indices: usize = 0;const index_arg_cursors: usize = 1;const index_arg_offsets: usize = 2;const index_arg_ranges: usize = 3;const index_arg_words: usize = 4;const index_arg_command_count: usize = 5;const index_arg_tiles_x: usize = 6;const index_arg_tiles_y: usize = 7;const sort_arg_indices: usize = 0;const sort_arg_offsets: usize = 1;const sort_arg_words: usize = 2;const sort_arg_tile_count: usize = 3;const float_rect_x: usize = 0;const float_rect_y: usize = 1;const float_rect_w: usize = 2;const float_rect_h: usize = 3;const float_clip_x: usize = 4;const float_clip_y: usize = 5;const float_clip_w: usize = 6;const float_clip_h: usize = 7;const float_radius: usize = 8;const float_width: usize = 9;const float_source_x: usize = 10;const float_source_y: usize = 11;const float_source_w: usize = 12;const float_source_h: usize = 13;const float_gradient_start_x: usize = 14;const float_gradient_start_y: usize = 15;const float_gradient_end_x: usize = 16;const float_gradient_end_y: usize = 17;const word_kind: usize = 0;const word_r: usize = 1;const word_g: usize = 2;const word_b: usize = 3;const word_a: usize = 4;const word_image: usize = 5;const word_order: usize = 6;const word_color_end: usize = 7;const image_width: usize = 0;const image_height: usize = 1;const image_offset: usize = 2;const tile_range_x0: usize = 0;const tile_range_y0: usize = 1;const tile_range_x1: usize = 2;const tile_range_y1: usize = 3;pub const PackedCommands = struct {    floats: []f32,    words: []u32,    pub fn deinit(self: *PackedCommands, allocator: Allocator) void {        allocator.free(self.floats);        allocator.free(self.words);        self.* = undefined;    }};pub const PackedImages = struct {    metadata: []u32,    pixels: []u32,    pub fn deinit(self: *PackedImages, allocator: Allocator) void {        allocator.free(self.metadata);        allocator.free(self.pixels);        self.* = undefined;    }};pub const Bins = struct {    offsets: []u32,    indices: []u32,    tiles_x: u32,    tiles_y: u32,    pub fn tileCount(self: Bins) usize {        return @as(usize, self.tiles_x) * @as(usize, self.tiles_y);    }    pub fn pairCount(self: Bins) usize {        return self.indices.len;    }    pub fn view(self: Bins) BinView {        return .{            .offsets = self.offsets,            .indices = self.indices,            .tiles_x = self.tiles_x,            .tiles_y = self.tiles_y,        };    }    pub fn deinit(self: *Bins, allocator: Allocator) void {        allocator.free(self.offsets);        allocator.free(self.indices);        self.* = undefined;    }};pub const BinView = struct {    offsets: []u32,    indices: []u32,    tiles_x: u32,    tiles_y: u32,    pub fn tileCount(self: BinView) usize {        return @as(usize, self.tiles_x) * @as(usize, self.tiles_y);    }    pub fn pairCount(self: BinView) usize {        return self.indices.len;    }};pub const OutputFormat = enum(u32) {    rgba = 0,    bgra = 1,};pub fn commandVisits(bins: BinView, region: Region) usize {    var total: usize = 0;    var tile_y: u32 = 0;    while (tile_y < bins.tiles_y) : (tile_y += 1) {        const local_y = tile_y * tile_size;        const tile_height = @min(tile_size, region.height - local_y);        var tile_x: u32 = 0;        while (tile_x < bins.tiles_x) : (tile_x += 1) {            const local_x = tile_x * tile_size;            const tile_width = @min(tile_size, region.width - local_x);            const tile = @as(usize, tile_y) * bins.tiles_x + tile_x;            const command_count = bins.offsets[tile + 1] - bins.offsets[tile];            total += @as(usize, tile_width) * @as(usize, tile_height) * @as(usize, command_count);        }    }    return total;}pub const BinScratch = struct {    ranges: []u32 = &.{},    offsets: []u32 = &.{},    indices: []u32 = &.{},    cursors: []u32 = &.{},    pub fn binCommands(        self: *BinScratch,        commands: []const Command,        width: u32,        height: u32,        region: Region,    ) !BinView {        const shape = binShape(width, height, region);        const range_count = tileRangeValueCount(commands.len);        if (self.ranges.len < range_count) return error.BufferTooSmall;        if (self.offsets.len < shape.tile_count + 1) return error.BufferTooSmall;        const ranges = self.ranges[0..range_count];        fillCommandTileRanges(commands, width, height, shape, ranges);        const total = countBinPairs(ranges, shape, self.offsets[0 .. shape.tile_count + 1]);        if (self.indices.len < total) return error.BufferTooSmall;        if (shape.active_region.pixelCount() != 0) {            if (self.cursors.len < shape.tile_count) return error.BufferTooSmall;            fillBinIndices(commands, ranges, shape, self.offsets[0 .. shape.tile_count + 1], self.cursors[0..shape.tile_count], self.indices[0..total]);        }        return .{            .offsets = self.offsets[0 .. shape.tile_count + 1],            .indices = self.indices[0..total],            .tiles_x = shape.tiles_x,            .tiles_y = shape.tiles_y,        };    }};pub fn tilesForExtent(extent: u32) u32 {    if (extent == 0) return 1;    return (extent + tile_size - 1) >> tile_size_log2;}pub fn tileRangeValueCount(command_count: usize) usize {    return command_count * tile_range_lanes;}pub fn binPairCount(    commands: []const Command,    width: u32,    height: u32,    region: Region,) error{BufferTooLarge}!usize {    const shape = binShape(width, height, region);    if (shape.active_region.pixelCount() == 0) return 0;    var total: usize = 0;    for (commands) |paint| {        const range = commandTileRange(            paint,            width,            height,            shape.active_region,            shape.tiles_x,            shape.tiles_y,        ) orelse continue;        const columns = @as(usize, range.x1 - range.x0);        const rows = @as(usize, range.y1 - range.y0);        const pairs = std.math.mul(usize, columns, rows) catch return error.BufferTooLarge;        total = std.math.add(usize, total, pairs) catch return error.BufferTooLarge;    }    return total;}pub const BinShape = struct {    active_region: Region,    tiles_x: u32,    tiles_y: u32,    tile_count: usize,};const TileRange = struct {    x0: u32,    y0: u32,    x1: u32,    y1: u32,    fn empty(self: TileRange) bool {        return self.x0 >= self.x1 or self.y0 >= self.y1;    }    fn count(self: TileRange) usize {        if (self.empty()) return 0;        return @as(usize, self.x1 - self.x0) * @as(usize, self.y1 - self.y0);    }};pub fn binShape(width: u32, height: u32, region: Region) BinShape {    const active_region = region.clamped(width, height);    const tiles_x = tilesForExtent(active_region.width);    const tiles_y = tilesForExtent(active_region.height);    return .{        .active_region = active_region,        .tiles_x = tiles_x,        .tiles_y = tiles_y,        .tile_count = @as(usize, tiles_x) * @as(usize, tiles_y),    };}fn fillCommandTileRanges(commands: []const Command, width: u32, height: u32, shape: BinShape, ranges: []u32) void {    std.debug.assert(ranges.len >= tileRangeValueCount(commands.len));    const active_ranges = ranges[0..tileRangeValueCount(commands.len)];    if (shape.active_region.pixelCount() == 0) {        @memset(active_ranges, 0);        return;    }    for (commands, 0..) |paint, command_index| {        storeTileRange(            active_ranges,            command_index,            commandTileRange(paint, width, height, shape.active_region, shape.tiles_x, shape.tiles_y),        );    }}fn commandTileRange(paint: Command, width: u32, height: u32, region: Region, tiles_x: u32, tiles_y: u32) ?TileRange {    const bounds = cpu.clippedBounds(width, height, paint, region) orelse return null;    const local_x0 = bounds.x0 - region.x;    const local_y0 = bounds.y0 - region.y;    const local_x1 = bounds.x1 - region.x;    const local_y1 = bounds.y1 - region.y;    return .{        .x0 = @min(local_x0 >> tile_size_log2, tiles_x - 1),        .y0 = @min(local_y0 >> tile_size_log2, tiles_y - 1),        .x1 = @min(((local_x1 - 1) >> tile_size_log2) + 1, tiles_x),        .y1 = @min(((local_y1 - 1) >> tile_size_log2) + 1, tiles_y),    };}pub fn binCommandsAlloc(    allocator: Allocator,    commands: []const Command,    width: u32,    height: u32,    region: Region,) !Bins {    const shape = binShape(width, height, region);    const ranges = try allocator.alloc(u32, tileRangeValueCount(commands.len));    defer allocator.free(ranges);    fillCommandTileRanges(commands, width, height, shape, ranges);    const offsets = try allocator.alloc(u32, shape.tile_count + 1);    errdefer allocator.free(offsets);    const total = countBinPairs(ranges, shape, offsets);    if (shape.active_region.pixelCount() == 0) {        const indices = try allocator.alloc(u32, 0);        return .{ .offsets = offsets, .indices = indices, .tiles_x = shape.tiles_x, .tiles_y = shape.tiles_y };    }    const indices = try allocator.alloc(u32, total);    errdefer allocator.free(indices);    const cursors = try allocator.alloc(u32, shape.tile_count);    defer allocator.free(cursors);    fillBinIndices(commands, ranges, shape, offsets, cursors, indices);    return .{ .offsets = offsets, .indices = indices, .tiles_x = shape.tiles_x, .tiles_y = shape.tiles_y };}fn countBinPairs(ranges: []const u32, shape: BinShape, offsets: []u32) usize {    countTilePairs(ranges, shape, offsets);    return prefixBinOffsets(offsets[0 .. shape.tile_count + 1]);}fn countTilePairs(ranges: []const u32, shape: BinShape, offsets: []u32) void {    @memset(offsets, 0);    std.debug.assert(offsets.len >= shape.tile_count + 1);    if (shape.active_region.pixelCount() == 0) return;    std.debug.assert(ranges.len % tile_range_lanes == 0);    const command_count = ranges.len / tile_range_lanes;    var command_index: usize = 0;    while (command_index < command_count) : (command_index += 1) {        const range = loadTileRange(ranges, command_index);        var ty = range.y0;        while (ty < range.y1) : (ty += 1) {            var tx = range.x0;            while (tx < range.x1) : (tx += 1) {                offsets[@as(usize, ty) * shape.tiles_x + tx + 1] += 1;            }        }    }}fn prefixBinOffsets(offsets: []u32) usize {    var total: u32 = 0;    for (offsets[1..]) |*entry| {        total += entry.*;        entry.* = total;    }    return total;}fn fillBinIndices(commands: []const Command, ranges: []const u32, shape: BinShape, offsets: []const u32, cursors: []u32, indices: []u32) void {    fillBinIndicesUnsorted(commands, ranges, shape, offsets, cursors, indices);    sortBins(commands, offsets, indices);}fn fillBinIndicesUnsorted(commands: []const Command, ranges: []const u32, shape: BinShape, offsets: []const u32, cursors: []u32, indices: []u32) void {    @memcpy(cursors, offsets[0..shape.tile_count]);    std.debug.assert(ranges.len >= tileRangeValueCount(commands.len));    for (0..commands.len) |command_index| {        const range = loadTileRange(ranges, command_index);        var ty = range.y0;        while (ty < range.y1) : (ty += 1) {            var tx = range.x0;            while (tx < range.x1) : (tx += 1) {                const tile = @as(usize, ty) * shape.tiles_x + tx;                indices[cursors[tile]] = @intCast(command_index);                cursors[tile] += 1;            }        }    }}fn storeTileRange(ranges: []u32, command_index: usize, maybe_range: ?TileRange) void {    const base = tileRangeBase(command_index);    const range = maybe_range orelse TileRange{ .x0 = 0, .y0 = 0, .x1 = 0, .y1 = 0 };    ranges[base + tile_range_x0] = range.x0;    ranges[base + tile_range_y0] = range.y0;    ranges[base + tile_range_x1] = range.x1;    ranges[base + tile_range_y1] = range.y1;}fn loadTileRange(ranges: []const u32, command_index: usize) TileRange {    const base = tileRangeBase(command_index);    return .{        .x0 = ranges[base + tile_range_x0],        .y0 = ranges[base + tile_range_y0],        .x1 = ranges[base + tile_range_x1],        .y1 = ranges[base + tile_range_y1],    };}fn tileRangeBase(command_index: usize) usize {    return command_index * tile_range_lanes;}fn sortBins(commands: []const Command, offsets: []const u32, indices: []u32) void {    if (indices.len <= 1) return;    var tile: usize = 0;    while (tile + 1 < offsets.len) : (tile += 1) {        const start: usize = @intCast(offsets[tile]);        const end: usize = @intCast(offsets[tile + 1]);        sortTile(commands, indices[start..end]);    }}fn sortTile(commands: []const Command, indices: []u32) void {    var index: usize = 1;    while (index < indices.len) : (index += 1) {        const value = indices[index];        var cursor = index;        while (cursor > 0 and before(commands, value, indices[cursor - 1])) : (cursor -= 1) {            indices[cursor] = indices[cursor - 1];        }        indices[cursor] = value;    }}fn before(commands: []const Command, left_index: u32, right_index: u32) bool {    const left = commands[@intCast(left_index)];    const right = commands[@intCast(right_index)];    if (left.order != right.order) return left.order < right.order;    return left_index < right_index;}pub fn packCommandsAlloc(allocator: Allocator, commands: []const Command) !PackedCommands {    const floats = try allocator.alloc(f32, commands.len * float_lanes);    errdefer allocator.free(floats);    const words = try allocator.alloc(u32, commands.len * word_lanes);    errdefer allocator.free(words);    packCommands(commands, floats, words);    return .{ .floats = floats, .words = words };}pub fn packCommands(commands: []const Command, floats: []f32, words: []u32) void {    std.debug.assert(floats.len >= commands.len * float_lanes);    std.debug.assert(words.len >= commands.len * word_lanes);    for (commands, 0..) |paint, index| {        const float_base = index * float_lanes;        const word_base = index * word_lanes;        packCommand(paint, floats[float_base..][0..float_lanes], words[word_base..][0..word_lanes]);    }}pub fn commandsEqualPacked(commands: []const Command, floats: []const f32, words: []const u32) bool {    std.debug.assert(floats.len >= commands.len * float_lanes);    std.debug.assert(words.len >= commands.len * word_lanes);    for (commands, 0..) |paint, index| {        var command_floats: [float_lanes]f32 = undefined;        var command_words: [word_lanes]u32 = undefined;        packCommand(paint, command_floats[0..], command_words[0..]);        const float_base = index * float_lanes;        const word_base = index * word_lanes;        if (!std.mem.eql(u8, std.mem.asBytes(&command_floats), std.mem.sliceAsBytes(floats[float_base..][0..float_lanes]))) return false;        if (!std.mem.eql(u32, command_words[0..], words[word_base..][0..word_lanes])) return false;    }    return true;}fn packCommand(paint: Command, floats: []f32, words: []u32) void {    std.debug.assert(floats.len >= float_lanes);    std.debug.assert(words.len >= word_lanes);    const finite_gradient = std.math.isFinite(paint.gradient_start.x) and        std.math.isFinite(paint.gradient_start.y) and        std.math.isFinite(paint.gradient_end.x) and        std.math.isFinite(paint.gradient_end.y);    floats[float_rect_x] = paint.rect.x;    floats[float_rect_y] = paint.rect.y;    floats[float_rect_w] = paint.rect.width;    floats[float_rect_h] = paint.rect.height;    floats[float_clip_x] = paint.clip.x;    floats[float_clip_y] = paint.clip.y;    floats[float_clip_w] = paint.clip.width;    floats[float_clip_h] = paint.clip.height;    floats[float_radius] = @max(paint.radius, 0);    floats[float_width] = @max(paint.width, 0);    floats[float_source_x] = paint.source.x;    floats[float_source_y] = paint.source.y;    floats[float_source_w] = paint.source.width;    floats[float_source_h] = paint.source.height;    floats[float_gradient_start_x] = if (finite_gradient) paint.gradient_start.x else 0;    floats[float_gradient_start_y] = if (finite_gradient) paint.gradient_start.y else 0;    floats[float_gradient_end_x] = if (finite_gradient) paint.gradient_end.x else 0;    floats[float_gradient_end_y] = if (finite_gradient) paint.gradient_end.y else 0;    words[word_kind] = @backingInt(paint.kind);    words[word_r] = paint.color.r;    words[word_g] = paint.color.g;    words[word_b] = paint.color.b;    words[word_a] = paint.color.a;    words[word_image] = paint.image_index;    words[word_order] = paint.order;    words[word_color_end] = packColor(paint.color_end);}fn packColor(color: Color) u32 {    return @as(u32, color.r) |        (@as(u32, color.g) << 8) |        (@as(u32, color.b) << 16) |        (@as(u32, color.a) << 24);}pub fn packImagesAlloc(allocator: Allocator, images: ImageSet) !PackedImages {    const metadata_count = try imageMetadataCount(images.images.len);    const pixel_count = @max(try imagePixelCount(images), 1);    const metadata = try allocator.alloc(u32, metadata_count);    errdefer allocator.free(metadata);    const pixels = try allocator.alloc(u32, pixel_count);    errdefer allocator.free(pixels);    @memset(metadata, 0);    @memset(pixels, 0);    try packImages(images, metadata, pixels);    return .{ .metadata = metadata, .pixels = pixels };}pub fn imagePixelCount(images: ImageSet) !usize {    var total: usize = 0;    for (images.images) |image| {        try image.validate();        total = std.math.add(usize, total, image.pixelCount()) catch return error.BufferTooSmall;    }    return total;}pub fn packImages(images: ImageSet, metadata: []u32, pixels: []u32) !void {    if (metadata.len < try imageMetadataCount(images.images.len)) return error.BufferTooSmall;    if (pixels.len < @max(try imagePixelCount(images), 1)) return error.BufferTooSmall;    var pixel_offset: usize = 0;    for (images.images, 0..) |image, index| {        const meta_base = index * image_lanes;        metadata[meta_base + image_width] = image.width;        metadata[meta_base + image_height] = image.height;        metadata[meta_base + image_offset] = std.math.cast(u32, pixel_offset) orelse return error.BufferTooSmall;        const count = image.pixelCount();        @memcpy(pixels[pixel_offset .. pixel_offset + count], image.pixels[0..count]);        pixel_offset += count;    }}pub fn imagesEqualPacked(images: ImageSet, metadata: []const u32, pixels: []const u32) !bool {    if (!(try imageMetadataEqualPacked(images, metadata))) return false;    if (pixels.len < @max(try imagePixelCount(images), 1)) return error.BufferTooSmall;    var pixel_offset: usize = 0;    for (images.images) |image| {        const count = image.pixelCount();        if (!std.mem.eql(u32, pixels[pixel_offset .. pixel_offset + count], image.pixels[0..count])) return false;        pixel_offset += count;    }    return true;}pub fn imageMetadataEqualPacked(images: ImageSet, metadata: []const u32) !bool {    if (metadata.len < try imageMetadataCount(images.images.len)) return error.BufferTooSmall;    var pixel_offset: usize = 0;    for (images.images, 0..) |image, index| {        try image.validate();        const meta_base = index * image_lanes;        if (metadata[meta_base + image_width] != image.width) return false;        if (metadata[meta_base + image_height] != image.height) return false;        if (metadata[meta_base + image_offset] != pixel_offset) return false;        pixel_offset = std.math.add(usize, pixel_offset, image.pixelCount()) catch return error.BufferTooSmall;    }    return true;}fn imageMetadataCount(image_count: usize) !usize {    return std.math.mul(usize, @max(image_count, 1), image_lanes) catch error.BufferTooSmall;}pub fn renderCommandsCpu(    allocator: Allocator,    commands: []const Command,    width: u32,    height: u32,    pixels: []u32,    clear: Color,) !void {    try renderCommandsCpuRegionWithImages(allocator, commands, width, height, pixels, clear, .{}, Region.full(width, height));}pub fn renderCommandsCpuWithImages(    allocator: Allocator,    commands: []const Command,    width: u32,    height: u32,    pixels: []u32,    clear: Color,    images: ImageSet,) !void {    try renderCommandsCpuRegionWithImages(allocator, commands, width, height, pixels, clear, images, Region.full(width, height));}pub fn renderCommandsCpuRegionWithImages(    allocator: Allocator,    commands: []const Command,    width: u32,    height: u32,    pixels: []u32,    clear: Color,    images: ImageSet,    region: Region,) !void {    const target_pixel_count = @as(usize, width) * @as(usize, height);    if (pixels.len < target_pixel_count) return error.BufferTooSmall;    const active_region = region.clamped(width, height);    if (active_region.pixelCount() == 0) return;    var encoded = try packCommandsAlloc(allocator, commands);    defer encoded.deinit(allocator);    var encoded_images = try packImagesAlloc(allocator, images);    defer encoded_images.deinit(allocator);    var bins = try binCommandsAlloc(allocator, commands, width, height, active_region);    defer bins.deinit(allocator);    var graph = try buildGraph(allocator, default_threads);    defer graph.deinit();    try runPackedCommandsCpuRegion(allocator, &graph, encoded, encoded_images, bins, images.images.len, commands.len, width, height, pixels, clear, active_region);}pub fn runPackedCommandsCpu(    allocator: Allocator,    graph: *kernel.Graph,    encoded: PackedCommands,    images: PackedImages,    bins: Bins,    image_count: usize,    command_count: usize,    width: u32,    height: u32,    pixels: []u32,    clear: Color,) !void {    try runPackedCommandsCpuRegion(allocator, graph, encoded, images, bins, image_count, command_count, width, height, pixels, clear, Region.full(width, height));}pub fn runPackedCommandsCpuRegion(    allocator: Allocator,    graph: *kernel.Graph,    encoded: PackedCommands,    images: PackedImages,    bins: Bins,    image_count: usize,    command_count: usize,    width: u32,    height: u32,    pixels: []u32,    clear: Color,    region: Region,) !void {    const target_pixel_count = @as(usize, width) * @as(usize, height);    if (pixels.len < target_pixel_count) return error.BufferTooSmall;    const active_region = region.clamped(width, height);    const pixel_count = active_region.pixelCount();    if (pixel_count == 0) return;    const full_region = active_region.x == 0 and active_region.y == 0 and active_region.width == width and active_region.height == height;    if (encoded.floats.len < command_count * float_lanes) return error.BufferTooSmall;    if (encoded.words.len < command_count * word_lanes) return error.BufferTooSmall;    if (images.metadata.len < try imageMetadataCount(image_count)) return error.BufferTooSmall;    if (bins.tiles_x != tilesForExtent(active_region.width)) return error.BinGeometryMismatch;    if (bins.tiles_y != tilesForExtent(active_region.height)) return error.BinGeometryMismatch;    if (bins.offsets.len < bins.tileCount() + 1) return error.BufferTooSmall;    const output = if (full_region)        pixels[0..target_pixel_count]    else        try allocator.alloc(u32, pixel_count);    defer if (!full_region) allocator.free(output);    const indices: []const u32 = if (bins.indices.len == 0) &.{0} else bins.indices;    try graph.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, output),        kernel.argumentBuffer(f32, encoded.floats[0 .. command_count * float_lanes]),        kernel.argumentBuffer(u32, encoded.words[0 .. command_count * word_lanes]),        kernel.argumentBuffer(u32, images.metadata),        kernel.argumentBuffer(u32, images.pixels),        kernel.argumentBuffer(u32, bins.offsets),        kernel.argumentBuffer(u32, @constCast(indices)),        kernel.argumentU32(@intCast(image_count)),        kernel.argumentU32(clear.r),        kernel.argumentU32(clear.g),        kernel.argumentU32(clear.b),        kernel.argumentU32(clear.a),        kernel.argumentU32(active_region.x),        kernel.argumentU32(active_region.y),        kernel.argumentU32(active_region.width),        kernel.argumentU32(@intCast(pixel_count)),        kernel.argumentU32(bins.tiles_x),        kernel.argumentU32(@backingInt(OutputFormat.rgba)),    }, .{        .grid = .{ gridFor(pixel_count, default_threads), 1, 1 },        .block = .{ default_threads, 1, 1 },    });    if (!full_region) copyPackedRegion(pixels, width, active_region, output);}pub fn runPackedTileRangesCpu(    allocator: Allocator,    graph: *kernel.Graph,    encoded: PackedCommands,    command_count: usize,    width: u32,    height: u32,    ranges: []u32,) !void {    try runPackedTileRangesCpuRegion(allocator, graph, encoded, command_count, width, height, ranges, Region.full(width, height));}pub fn runPackedTileRangesCpuRegion(    allocator: Allocator,    graph: *kernel.Graph,    encoded: PackedCommands,    command_count: usize,    width: u32,    height: u32,    ranges: []u32,    region: Region,) !void {    if (encoded.floats.len < command_count * float_lanes) return error.BufferTooSmall;    if (encoded.words.len < command_count * word_lanes) return error.BufferTooSmall;    if (ranges.len < tileRangeValueCount(command_count)) return error.BufferTooSmall;    const active_region = region.clamped(width, height);    const output = ranges[0..tileRangeValueCount(command_count)];    if (command_count == 0 or active_region.pixelCount() == 0) {        @memset(output, 0);        return;    }    const shape = binShape(width, height, active_region);    try graph.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, output),        kernel.argumentBuffer(f32, encoded.floats[0 .. command_count * float_lanes]),        kernel.argumentBuffer(u32, encoded.words[0 .. command_count * word_lanes]),        kernel.argumentU32(@intCast(command_count)),        kernel.argumentU32(width),        kernel.argumentU32(height),        kernel.argumentU32(shape.active_region.x),        kernel.argumentU32(shape.active_region.y),        kernel.argumentU32(shape.active_region.width),        kernel.argumentU32(shape.active_region.height),        kernel.argumentU32(shape.tiles_x),        kernel.argumentU32(shape.tiles_y),    }, .{        .grid = .{ gridFor(command_count, default_threads), 1, 1 },        .block = .{ default_threads, 1, 1 },    });}pub fn runPackedTileCountsCpu(    allocator: Allocator,    graph: *kernel.Graph,    ranges: []u32,    command_count: usize,    tiles_x: u32,    tiles_y: u32,    offsets: []u32,) !usize {    if (ranges.len < tileRangeValueCount(command_count)) return error.BufferTooSmall;    const tile_count = @as(usize, tiles_x) * @as(usize, tiles_y);    if (offsets.len < tile_count + 1) return error.BufferTooSmall;    const output = offsets[0 .. tile_count + 1];    @memset(output, 0);    if (command_count == 0 or tile_count == 0) return 0;    try graph.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, output),        kernel.argumentBuffer(u32, ranges[0..tileRangeValueCount(command_count)]),        kernel.argumentU32(@intCast(command_count)),        kernel.argumentU32(tiles_x),        kernel.argumentU32(tiles_y),    }, .{        .grid = .{ gridFor(tile_count, default_threads), 1, 1 },        .block = .{ default_threads, 1, 1 },    });    var total: usize = 0;    for (output[1..]) |count| total += count;    return total;}pub fn runPackedTileOffsetsCpu(    allocator: Allocator,    graph: *kernel.Graph,    ranges: []u32,    command_count: usize,    tiles_x: u32,    tiles_y: u32,    offsets: []u32,) !usize {    const tile_count = @as(usize, tiles_x) * @as(usize, tiles_y);    if (offsets.len < tile_count + 1) return error.BufferTooSmall;    const output = offsets[0 .. tile_count + 1];    _ = try runPackedTileCountsCpu(allocator, graph, ranges, command_count, tiles_x, tiles_y, output);    const counts = try allocator.dupe(u32, output);    defer allocator.free(counts);    try scanTileOffsetCountsCpu(allocator, counts, output);    return output[tile_count];}pub fn runPackedTileIndicesCpu(    allocator: Allocator,    graph: *kernel.Graph,    ranges: []u32,    words: []u32,    command_count: usize,    tiles_x: u32,    tiles_y: u32,    offsets: []u32,    cursors: []u32,    indices: []u32,) !void {    if (ranges.len < tileRangeValueCount(command_count)) return error.BufferTooSmall;    if (words.len < command_count * word_lanes) return error.BufferTooSmall;    const tile_count = @as(usize, tiles_x) * @as(usize, tiles_y);    if (offsets.len < tile_count + 1) return error.BufferTooSmall;    if (cursors.len < tile_count) return error.BufferTooSmall;    const total: usize = @intCast(offsets[tile_count]);    if (indices.len < total) return error.BufferTooSmall;    @memset(cursors[0..tile_count], 0);    if (command_count == 0 or tile_count == 0 or total == 0) return;    const pair_count = std.math.mul(usize, command_count, tile_count) catch return error.BufferTooLarge;    try graph.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, indices[0..total]),        kernel.argumentBuffer(u32, cursors[0..tile_count]),        kernel.argumentBuffer(u32, offsets[0 .. tile_count + 1]),        kernel.argumentBuffer(u32, ranges[0..tileRangeValueCount(command_count)]),        kernel.argumentBuffer(u32, words[0 .. command_count * word_lanes]),        kernel.argumentU32(@intCast(command_count)),        kernel.argumentU32(tiles_x),        kernel.argumentU32(tiles_y),    }, .{        .grid = .{ gridFor(pair_count, default_threads), 1, 1 },        .block = .{ default_threads, 1, 1 },    });}pub fn runPackedTileSortCpu(    allocator: Allocator,    graph: *kernel.Graph,    words: []u32,    command_count: usize,    offsets: []u32,    indices: []u32,    tile_count: usize,) !void {    if (words.len < command_count * word_lanes) return error.BufferTooSmall;    if (offsets.len < tile_count + 1) return error.BufferTooSmall;    const total: usize = @intCast(offsets[tile_count]);    if (indices.len < total) return error.BufferTooSmall;    if (tile_count == 0 or total <= 1) return;    try graph.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, indices[0..total]),        kernel.argumentBuffer(u32, offsets[0 .. tile_count + 1]),        kernel.argumentBuffer(u32, words[0 .. command_count * word_lanes]),        kernel.argumentU32(@intCast(tile_count)),    }, .{        .grid = .{ gridFor(tile_count, default_threads), 1, 1 },        .block = .{ default_threads, 1, 1 },    });}fn scanTileOffsetCountsCpu(allocator: Allocator, counts: []u32, offsets: []u32) !void {    if (counts.len != offsets.len) return error.BufferTooSmall;    const extent = counts.len;    if (extent == 0) return error.BufferTooSmall;    if (scan_library.prefixSumThreadsForExtent(extent)) |threads| {        try scanTileOffsetCountsPrefixCpu(allocator, counts, offsets, extent, threads);        return;    }    const threads = scan_library.deviceScanThreadsForExtent(extent) orelse return error.UnsupportedDeviceScanInstance;    try scanTileOffsetCountsDeviceCpu(allocator, counts, offsets, extent, threads);}fn scanTileOffsetCountsPrefixCpu(    allocator: Allocator,    counts: []u32,    offsets: []u32,    extent: usize,    threads: u32,) !void {    var graph = try scan_library.PrefixSumRuntimeFamilyU32.build(allocator, kernel.Limits.standard, .{        .extent = 1,        .dtype = .u32,        .mode = .inclusive,        .threads = threads,    });    defer graph.deinit();    try graph.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, offsets),        kernel.argumentBuffer(u32, counts),        kernel.argumentI32(@intCast(extent)),    }, .{        .grid = .{ 1, 1, 1 },        .block = .{ threads, 1, 1 },    });}fn scanTileOffsetCountsDeviceCpu(    allocator: Allocator,    counts: []u32,    offsets: []u32,    extent: usize,    threads: u32,) !void {    const compiled = scan_library.DeviceScan{ .extent = 1, .dtype = .u32, .threads = threads, .mode = .inclusive };    const runtime = scan_library.DeviceScan{ .extent = extent, .dtype = .u32, .threads = threads, .mode = .inclusive };    const stages = try scan_library.deviceScanStages(runtime);    const sums = try allocator.alloc(u32, stages.block_count);    defer allocator.free(sums);    @memset(sums, 0);    const bases = try allocator.alloc(u32, stages.block_count);    defer allocator.free(bases);    @memset(bases, 0);    var block_scan = try scan_library.DeviceScanBlockScanRuntimeFamilyU32.build(allocator, kernel.Limits.standard, compiled);    defer block_scan.deinit();    try block_scan.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, offsets),        kernel.argumentBuffer(u32, counts),        kernel.argumentBuffer(u32, sums),        kernel.argumentI32(@intCast(extent)),    }, .{        .grid = .{ stages.block_count, 1, 1 },        .block = .{ runtime.threads, 1, 1 },    });    var sums_scan = try scan_library.PrefixSumRuntimeFamilyU32.build(allocator, kernel.Limits.standard, .{        .extent = 1,        .dtype = .u32,        .mode = .exclusive,        .threads = stages.sums_scan.threads,    });    defer sums_scan.deinit();    try sums_scan.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, bases),        kernel.argumentBuffer(u32, sums),        kernel.argumentI32(@intCast(stages.block_count)),    }, .{        .grid = .{ 1, 1, 1 },        .block = .{ stages.sums_scan.threads, 1, 1 },    });    var add_base = try scan_library.DeviceScanAddBaseRuntimeFamilyU32.build(allocator, kernel.Limits.standard, compiled);    defer add_base.deinit();    try add_base.runCpuWithLaunch(allocator, &.{        kernel.argumentBuffer(u32, offsets),        kernel.argumentBuffer(u32, bases),        kernel.argumentI32(@intCast(extent)),    }, .{        .grid = .{ stages.block_count, 1, 1 },        .block = .{ runtime.threads, 1, 1 },    });}fn emit_pixel_guard(inner: anytype, ctx: anytype) !void {    try emitPixel(        inner,        ctx.pixels,        ctx.floats,        ctx.words,        ctx.images,        ctx.image_pixels,        ctx.tile_offsets,        ctx.tile_indices,        ctx.gid,    );}fn emit_tile_range_guard(inner: anytype, ctx: anytype) !void {    try emitTileRange(inner, ctx.ranges, ctx.floats, ctx.words, ctx.gid);}fn emit_tile_count_guard(inner: anytype, ctx: anytype) !void {    try emitTileCount(inner, ctx.offsets, ctx.ranges, ctx.tile);}fn emit_tile_index_guard(inner: anytype, ctx: anytype) !void {    try emitTileIndex(        inner,        ctx.indices,        ctx.cursors,        ctx.offsets,        ctx.ranges,        ctx.words,        ctx.pair,    );}fn emit_tile_sort_guard(inner: anytype, ctx: anytype) !void {    try emitTileSort(inner, ctx.indices, ctx.offsets, ctx.words, ctx.tile);}fn count_tile_command(    loop_builder: anytype,    command_index: Value,    current: Value,    ctx: anytype,) !Value {    const covered = try tileCoversCommand(        loop_builder,        ctx.ranges,        command_index,        ctx.tile,        ctx.tiles_x,    );    const increment = try loop_builder.select(covered, ctx.one, ctx.zero);    return loop_builder.add(current, increment);}fn count_prior_command(    loop_builder: anytype,    prior_index: Value,    current: Value,    ctx: anytype,) !Value {    const prior_covered = try tileCoversCommand(        loop_builder,        ctx.ranges,        prior_index,        ctx.tile,        ctx.tiles_x,    );    const prior_before = try commandBeforeDevice(        loop_builder,        ctx.words,        prior_index,        ctx.command_index,    );    const counts_before = try loop_builder.and_(prior_covered, prior_before);    const increment = try loop_builder.select(counts_before, ctx.one, ctx.zero);    return loop_builder.add(current, increment);}fn write_tile_command(inner: anytype, ctx: anytype) !void {    const slot = try inner.castIndex(try inner.add(ctx.start, ctx.local_count));    const command_u32 = try inner.cast(ctx.command_index, .u32);    try ctx.indices.store(inner, command_u32, slot);}fn write_tile_cursor(inner: anytype, ctx: anytype) !void {    try ctx.cursors.store(inner, ctx.tile_total, ctx.tile);}fn find_tile_insertion(    loop_builder: anytype,    candidate: Value,    position: Value,    ctx: anytype,) !Value {    const existing = (try ctx.indices.load(loop_builder, candidate)).raw();    const before_existing = try commandBeforeDevice(        loop_builder,        ctx.words,        ctx.value,        existing,    );    const unset = try loop_builder.compare(.eq, position, ctx.cursor);    const take = try loop_builder.and_(unset, before_existing);    return loop_builder.select(take, candidate, position);}fn shift_tile_index_right(    loop_builder: anytype,    offset: Value,    accumulator: Value,    ctx: anytype,) !Value {    const from = try loop_builder.sub(try loop_builder.sub(ctx.cursor, ctx.one), offset);    const to = try loop_builder.sub(ctx.cursor, offset);    const shifted = (try ctx.indices.load(loop_builder, from)).raw();    try ctx.indices.store(loop_builder, shifted, to);    return accumulator;}pub fn buildGraph(allocator: Allocator, threads: u32) !kernel.Graph {    var b = try kernel.Builder.init(allocator, kernel.Limits.standard, kernel_name, &.{        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.f32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),    });    errdefer b.deinit();    const axis = try b.axis("pixel", @max(threads, 1));    try b.bind(axis, .thread_x);    const gid = try b.globalId(.x);    const pixel_count = try b.castIndex(b.argument(arg_pixel_count));    const active = try b.compare(.lt, gid, pixel_count);    try b.guardDo(active, .{        .pixels = b.bufferArgument(.u32, arg_pixels),        .floats = b.bufferArgument(.f32, arg_floats),        .words = b.bufferArgument(.u32, arg_words),        .images = b.bufferArgument(.u32, arg_images),        .image_pixels = b.bufferArgument(.u32, arg_image_pixels),        .tile_offsets = b.bufferArgument(.u32, arg_tile_offsets),        .tile_indices = b.bufferArgument(.u32, arg_tile_indices),        .gid = gid,    }, emit_pixel_guard);    try b.return_();    return b.finish();}pub fn buildTileRangeGraph(allocator: Allocator, threads: u32) !kernel.Graph {    var b = try kernel.Builder.init(allocator, kernel.Limits.standard, tile_range_kernel_name, &.{        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.f32),        kernel.dynamicBuffer(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),    });    errdefer b.deinit();    const axis = try b.axis("command", @max(threads, 1));    try b.bind(axis, .thread_x);    const gid = try b.globalId(.x);    const command_count = try b.castIndex(b.argument(range_arg_command_count));    const active = try b.compare(.lt, gid, command_count);    try b.guardDo(active, .{        .ranges = b.bufferArgument(.u32, range_arg_ranges),        .floats = b.bufferArgument(.f32, range_arg_floats),        .words = b.bufferArgument(.u32, range_arg_words),        .gid = gid,    }, emit_tile_range_guard);    try b.return_();    return b.finish();}pub fn buildTileCountGraph(allocator: Allocator, threads: u32) !kernel.Graph {    var b = try kernel.Builder.init(allocator, kernel.Limits.standard, tile_count_kernel_name, &.{        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),    });    errdefer b.deinit();    const axis = try b.axis("tile", @max(threads, 1));    try b.bind(axis, .thread_x);    const gid = try b.globalId(.x);    const tile_count = try b.mul(try b.castIndex(b.argument(count_arg_tiles_x)), try b.castIndex(b.argument(count_arg_tiles_y)));    const active = try b.compare(.lt, gid, tile_count);    try b.guardDo(active, .{        .offsets = b.bufferArgument(.u32, count_arg_offsets),        .ranges = b.bufferArgument(.u32, count_arg_ranges),        .tile = gid,    }, emit_tile_count_guard);    try b.return_();    return b.finish();}pub fn buildTileIndexGraph(allocator: Allocator, threads: u32) !kernel.Graph {    var b = try kernel.Builder.init(allocator, kernel.Limits.standard, tile_index_kernel_name, &.{        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),        kernel.scalar(.u32),    });    errdefer b.deinit();    const axis = try b.axis("tile", @max(threads, 1));    try b.bind(axis, .thread_x);    const gid = try b.globalId(.x);    const command_count = try b.castIndex(b.argument(index_arg_command_count));    const tile_count = try b.mul(try b.castIndex(b.argument(index_arg_tiles_x)), try b.castIndex(b.argument(index_arg_tiles_y)));    const pair_count = try b.mul(tile_count, command_count);    const active = try b.compare(.lt, gid, pair_count);    try b.guardDo(active, .{        .indices = b.bufferArgument(.u32, index_arg_indices),        .cursors = b.bufferArgument(.u32, index_arg_cursors),        .offsets = b.bufferArgument(.u32, index_arg_offsets),        .ranges = b.bufferArgument(.u32, index_arg_ranges),        .words = b.bufferArgument(.u32, index_arg_words),        .pair = gid,    }, emit_tile_index_guard);    try b.return_();    return b.finish();}pub fn buildTileSortGraph(allocator: Allocator, threads: u32) !kernel.Graph {    var b = try kernel.Builder.init(allocator, kernel.Limits.standard, tile_sort_kernel_name, &.{        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.dynamicBuffer(.u32),        kernel.scalar(.u32),    });    errdefer b.deinit();    const axis = try b.axis("tile", @max(threads, 1));    try b.bind(axis, .thread_x);    const gid = try b.globalId(.x);    const tile_count = try b.castIndex(b.argument(sort_arg_tile_count));    const active = try b.compare(.lt, gid, tile_count);    try b.guardDo(active, .{        .indices = b.bufferArgument(.u32, sort_arg_indices),        .offsets = b.bufferArgument(.u32, sort_arg_offsets),        .words = b.bufferArgument(.u32, sort_arg_words),        .tile = gid,    }, emit_tile_sort_guard);    try b.return_();    return b.finish();}pub fn createRecordingArtifact(    allocator: Allocator,    format: gpu.ArtifactFormat,) !gpu.KernelArtifact {    var graph = try buildGraph(allocator, default_threads);    defer graph.deinit();    var state = recordingBackendState(allocator, format);    return kernel.createKernelArtifact(allocator, state.handle(), &graph, .{        .artifact_format = format,        .authored_kernel_diagnostic_id = "gui/paint/accy/rgba8-packed",    });}pub fn createTileRangeRecordingArtifact(    allocator: Allocator,    format: gpu.ArtifactFormat,) !gpu.KernelArtifact {    var graph = try buildTileRangeGraph(allocator, default_threads);    defer graph.deinit();    var state = recordingBackendState(allocator, format);    return kernel.createKernelArtifact(allocator, state.handle(), &graph, .{        .artifact_format = format,        .authored_kernel_diagnostic_id = "gui/paint/accy/tile-ranges",    });}pub fn createTileCountRecordingArtifact(    allocator: Allocator,    format: gpu.ArtifactFormat,) !gpu.KernelArtifact {    var graph = try buildTileCountGraph(allocator, default_threads);    defer graph.deinit();    var state = recordingBackendState(allocator, format);    return kernel.createKernelArtifact(allocator, state.handle(), &graph, .{        .artifact_format = format,        .authored_kernel_diagnostic_id = "gui/paint/accy/tile-counts",    });}pub fn createTileIndexRecordingArtifact(    allocator: Allocator,    format: gpu.ArtifactFormat,) !gpu.KernelArtifact {    var graph = try buildTileIndexGraph(allocator, default_threads);    defer graph.deinit();    var state = recordingBackendState(allocator, format);    return kernel.createKernelArtifact(allocator, state.handle(), &graph, .{        .artifact_format = format,        .authored_kernel_diagnostic_id = "gui/paint/accy/tile-indices",    });}pub fn createTileSortRecordingArtifact(    allocator: Allocator,    format: gpu.ArtifactFormat,) !gpu.KernelArtifact {    var graph = try buildTileSortGraph(allocator, default_threads);    defer graph.deinit();    var state = recordingBackendState(allocator, format);    return kernel.createKernelArtifact(allocator, state.handle(), &graph, .{        .artifact_format = format,        .authored_kernel_diagnostic_id = "gui/paint/accy/tile-sort",    });}fn recordingBackendState(allocator: Allocator, format: gpu.ArtifactFormat) gpu.recording.BackendState {    return .{        .allocator = allocator,        .kind = switch (format) {            .cuda_ptx => .cuda,            .vulkan_spirv => .vulkan,            .metal_msl => .metal,            else => .external,        },        .format = format,    };}pub fn gridFor(items: usize, threads: u32) u32 {    if (items == 0) return 1;    return @intCast((items + threads - 1) / threads);}fn emitPixel(    k: anytype,    pixels: kernel.BufferView(.u32),    floats: kernel.BufferView(.f32),    words: kernel.BufferView(.u32),    images: kernel.BufferView(.u32),    image_pixels: kernel.BufferView(.u32),    tile_offsets: kernel.BufferView(.u32),    tile_indices: kernel.BufferView(.u32),    gid: Value,) !void {    const pixel = try pixelForGid(k, gid);    const one = try k.constantIndex(1);    const tile_shift = try u32v(k, tile_size_log2);    const tile_x = try k.ushr(pixel.local_x, tile_shift);    const tile_y = try k.ushr(pixel.local_y, tile_shift);    const tile = try k.add(tile_x, try k.mul(tile_y, k.argument(arg_tiles_x)));    const tile_index = try k.castIndex(tile);    const list_start = try k.castIndex((try tile_offsets.load(k, tile_index)).raw());    const list_end = try k.castIndex((try tile_offsets.load(k, try k.add(tile_index, one))).raw());    const clear = Channels{        .r = k.argument(arg_clear_r),        .g = k.argument(arg_clear_g),        .b = k.argument(arg_clear_b),        .a = k.argument(arg_clear_a),    };    var scope = try k.forScope(        list_start,        list_end,        one,        &.{ clear.r, clear.g, clear.b, clear.a },        &.{ clear.r.valueType(), clear.g.valueType(), clear.b.valueType(), clear.a.valueType() },    );    errdefer scope.abort();    const command_index = try k.castIndex((try tile_indices.load(k, scope.inductionVar())).raw());    const next = try emitCommand(k, floats, words, images, image_pixels, command_index, pixel.coord, .{        .r = scope.iterArg(0).?,        .g = scope.iterArg(1).?,        .b = scope.iterArg(2).?,        .a = scope.iterArg(3).?,    });    try scope.leave(&.{ next.r, next.g, next.b, next.a });    try pixels.store(k, try packPixel(k, .{        .r = scope.result(0).?,        .g = scope.result(1).?,        .b = scope.result(2).?,        .a = scope.result(3).?,    }, k.argument(arg_output_format)), pixel.index);}fn emitTileRange(    k: anytype,    ranges: kernel.BufferView(.u32),    floats: kernel.BufferView(.f32),    words: kernel.BufferView(.u32),    command_index: Value,) !void {    const rect = try loadRect(k, floats, command_index, float_rect_x);    const clip = try loadRect(k, floats, command_index, float_clip_x);    const width = try loadFloat(k, floats, command_index, float_width);    const kind = try loadWord(k, words, command_index, word_kind);    const shadow = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.shadow)));    const bounds = try commandBoundsValue(k, rect, shadow, width);    const region = RectValue{        .x = try k.cast(k.argument(range_arg_region_x), .f32),        .y = try k.cast(k.argument(range_arg_region_y), .f32),        .width = try k.cast(k.argument(range_arg_region_width), .f32),        .height = try k.cast(k.argument(range_arg_region_height), .f32),    };    const x0 = try floorClampValue(k, try k.max(try k.max(bounds.x, clip.x), region.x), k.argument(range_arg_width));    const y0 = try floorClampValue(k, try k.max(try k.max(bounds.y, clip.y), region.y), k.argument(range_arg_height));    const x1 = try ceilClampValue(k, try k.min(try k.min(try k.add(bounds.x, bounds.width), try k.add(clip.x, clip.width)), try k.add(region.x, region.width)), k.argument(range_arg_width));    const y1 = try ceilClampValue(k, try k.min(try k.min(try k.add(bounds.y, bounds.height), try k.add(clip.y, clip.height)), try k.add(region.y, region.height)), k.argument(range_arg_height));    const valid = try k.and_(try k.compare(.lt, x0, x1), try k.compare(.lt, y0, y1));    const tile_x0 = try tileStart(k, x0, k.argument(range_arg_region_x), k.argument(range_arg_tiles_x));    const tile_y0 = try tileStart(k, y0, k.argument(range_arg_region_y), k.argument(range_arg_tiles_y));    const tile_x1 = try tileEnd(k, x1, k.argument(range_arg_region_x), k.argument(range_arg_tiles_x));    const tile_y1 = try tileEnd(k, y1, k.argument(range_arg_region_y), k.argument(range_arg_tiles_y));    const zero = try u32v(k, 0);    const base = try laneIndex(k, command_index, tile_range_lanes, 0);    try ranges.store(k, try k.select(valid, tile_x0, zero), base);    try ranges.store(k, try k.select(valid, tile_y0, zero), try k.add(base, try k.constantIndex(tile_range_y0)));    try ranges.store(k, try k.select(valid, tile_x1, zero), try k.add(base, try k.constantIndex(tile_range_x1)));    try ranges.store(k, try k.select(valid, tile_y1, zero), try k.add(base, try k.constantIndex(tile_range_y1)));}fn emitTileCount(    k: anytype,    offsets: kernel.BufferView(.u32),    ranges: kernel.BufferView(.u32),    tile: Value,) !void {    const zero_index = try k.constantIndex(0);    const one_index = try k.constantIndex(1);    const zero = try u32v(k, 0);    const one = try u32v(k, 1);    const command_count = try k.castIndex(k.argument(count_arg_command_count));    const tiles_x = try k.castIndex(k.argument(count_arg_tiles_x));    const count = try k.fold(zero_index, command_count, one_index, zero, .{        .ranges = ranges,        .tile = tile,        .tiles_x = tiles_x,        .zero = zero,        .one = one,    }, count_tile_command);    try offsets.store(k, count, try k.add(tile, one_index));}fn emitTileIndex(    k: anytype,    indices: kernel.BufferView(.u32),    cursors: kernel.BufferView(.u32),    offsets: kernel.BufferView(.u32),    ranges: kernel.BufferView(.u32),    words: kernel.BufferView(.u32),    pair: Value,) !void {    const zero_index = try k.constantIndex(0);    const one_index = try k.constantIndex(1);    const zero = try u32v(k, 0);    const one = try u32v(k, 1);    const command_count = try k.castIndex(k.argument(index_arg_command_count));    const tiles_x = try k.castIndex(k.argument(index_arg_tiles_x));    const tile = try k.div(pair, command_count);    const command_index = try k.sub(pair, try k.mul(tile, command_count));    const start = (try offsets.load(k, tile)).raw();    const end = (try offsets.load(k, try k.add(tile, one_index))).raw();    const local_count = try k.fold(zero_index, command_count, one_index, zero, .{        .ranges = ranges,        .words = words,        .tile = tile,        .tiles_x = tiles_x,        .command_index = command_index,        .zero = zero,        .one = one,    }, count_prior_command);    const covered = try tileCoversCommand(k, ranges, command_index, tile, tiles_x);    try k.guardDo(covered, .{        .indices = indices,        .start = start,        .local_count = local_count,        .command_index = command_index,    }, write_tile_command);    const tile_total = try k.sub(end, start);    const last_command = try k.sub(command_count, one_index);    const writes_cursor = try k.compare(.eq, command_index, last_command);    try k.guardDo(writes_cursor, .{        .cursors = cursors,        .tile_total = tile_total,        .tile = tile,    }, write_tile_cursor);}fn tileCoversCommand(    k: anytype,    ranges: kernel.BufferView(.u32),    command_index: Value,    tile: Value,    tiles_x: Value,) !Value {    const base = try laneIndex(k, command_index, tile_range_lanes, 0);    const x0 = try k.castIndex((try ranges.load(k, base)).raw());    const y0 = try k.castIndex((try ranges.load(k, try k.add(base, try k.constantIndex(tile_range_y0)))).raw());    const x1 = try k.castIndex((try ranges.load(k, try k.add(base, try k.constantIndex(tile_range_x1)))).raw());    const y1 = try k.castIndex((try ranges.load(k, try k.add(base, try k.constantIndex(tile_range_y1)))).raw());    const tile_y = try k.div(tile, tiles_x);    const tile_x = try k.sub(tile, try k.mul(tile_y, tiles_x));    const x_lower = try k.compare(.le, x0, tile_x);    const x_upper = try k.compare(.lt, tile_x, x1);    const y_lower = try k.compare(.le, y0, tile_y);    const y_upper = try k.compare(.lt, tile_y, y1);    return k.and_(try k.and_(x_lower, x_upper), try k.and_(y_lower, y_upper));}fn emitTileSort(    k: anytype,    indices: kernel.BufferView(.u32),    offsets: kernel.BufferView(.u32),    words: kernel.BufferView(.u32),    tile: Value,) !void {    const one = try k.constantIndex(1);    const zero = try k.constantIndex(0);    const start = try k.castIndex((try offsets.load(k, tile)).raw());    const end = try k.castIndex((try offsets.load(k, try k.add(tile, one))).raw());    const first = try k.min(try k.add(start, one), end);    var outer = try k.forScope(first, end, one, &.{}, &.{});    errdefer outer.abort();    const cursor = outer.inductionVar();    const value = (try indices.load(k, cursor)).raw();    const insert = try k.fold(start, cursor, one, cursor, .{        .indices = indices,        .words = words,        .value = value,        .cursor = cursor,    }, find_tile_insertion);    const shift_count = try k.sub(cursor, insert);    _ = try k.fold(zero, shift_count, one, zero, .{        .indices = indices,        .cursor = cursor,        .one = one,    }, shift_tile_index_right);    try indices.store(k, value, insert);    try outer.leave(&.{});}fn commandBeforeDevice(    k: anytype,    words: kernel.BufferView(.u32),    left_index: Value,    right_index: Value,) !Value {    const left_order = try loadWord(k, words, try k.castIndex(left_index), word_order);    const right_order = try loadWord(k, words, try k.castIndex(right_index), word_order);    const order_before = try k.compare(.lt, left_order, right_order);    const order_equal = try k.compare(.eq, left_order, right_order);    const index_before = try k.compare(.lt, left_index, right_index);    return k.or_(order_before, try k.and_(order_equal, index_before));}const Pixel = struct {    coord: Coord,    index: Value,    local_x: Value,    local_y: Value,};const Coord = struct {    x: Value,    y: Value,};const Channels = struct {    r: Value,    g: Value,    b: Value,    a: Value,};fn emitCommand(    k: anytype,    floats: kernel.BufferView(.f32),    words: kernel.BufferView(.u32),    images: kernel.BufferView(.u32),    image_pixels: kernel.BufferView(.u32),    index: Value,    coord: Coord,    dst: Channels,) !Channels {    const rect = try loadRect(k, floats, index, float_rect_x);    const clip = try loadRect(k, floats, index, float_clip_x);    const radius = try loadFloat(k, floats, index, float_radius);    const width = try loadFloat(k, floats, index, float_width);    const source = try loadRect(k, floats, index, float_source_x);    const gradient_start = Coord{        .x = try loadFloat(k, floats, index, float_gradient_start_x),        .y = try loadFloat(k, floats, index, float_gradient_start_y),    };    const gradient_end = Coord{        .x = try loadFloat(k, floats, index, float_gradient_end_x),        .y = try loadFloat(k, floats, index, float_gradient_end_y),    };    const kind = try loadWord(k, words, index, word_kind);    const image_index = try loadWord(k, words, index, word_image);    const src = Channels{        .r = try loadWord(k, words, index, word_r),        .g = try loadWord(k, words, index, word_g),        .b = try loadWord(k, words, index, word_b),        .a = try loadWord(k, words, index, word_a),    };    const packed_end = try loadWord(k, words, index, word_color_end);    const end = Channels{        .r = try channel(k, packed_end, 0),        .g = try channel(k, packed_end, 8),        .b = try channel(k, packed_end, 16),        .a = try channel(k, packed_end, 24),    };    const fill = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.fill)));    const linear_gradient = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.linear_gradient)));    const stroke = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.stroke)));    const image = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.image)));    const shadow = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.shadow)));    const glyph = try k.compare(.eq, kind, try u32v(k, @backingInt(command.Kind.glyph)));    const image_like = try k.or_(image, glyph);    const fill_like = try k.or_(fill, linear_gradient);    const coverage = try coverageCount(k, rect, clip, radius, width, fill_like, stroke, image, shadow, glyph, coord);    const covered = try k.compare(.gt, coverage, try u32v(k, 0));    const sampled_image = try imageColor(k, images, image_pixels, image_index, source, rect, coord, coverage, src.a);    const sampled_shadow = try shadowColor(k, src, rect, radius, width, coord, coverage);    const sampled_gradient = try linearGradientColor(k, src, end, gradient_start, gradient_end, coord);    const sampled = Channels{        .r = try k.select(linear_gradient, sampled_gradient.r, try k.select(image, sampled_image.r, src.r)),        .g = try k.select(linear_gradient, sampled_gradient.g, try k.select(image, sampled_image.g, src.g)),        .b = try k.select(linear_gradient, sampled_gradient.b, try k.select(image, sampled_image.b, src.b)),        .a = try k.select(            linear_gradient,            try coverageAlpha(k, sampled_gradient.a, coverage),            try k.select(shadow, sampled_shadow.a, try k.select(image_like, sampled_image.a, try coverageAlpha(k, src.a, coverage))),        ),    };    const alpha = try k.compare(.gt, sampled.a, try u32v(k, 0));    const paint = try k.and_(covered, alpha);    const blended = try blend(k, dst, sampled);    return .{        .r = try k.select(paint, blended.r, dst.r),        .g = try k.select(paint, blended.g, dst.g),        .b = try k.select(paint, blended.b, dst.b),        .a = try k.select(paint, blended.a, dst.a),    };}fn linearGradientColor(k: anytype, start: Channels, end: Channels, from: Coord, to: Coord, coord: Coord) !Channels {    const zero = try f32v(k, 0);    const one = try f32v(k, 1);    const dx = try k.sub(to.x, from.x);    const dy = try k.sub(to.y, from.y);    const length_squared = try k.add(try k.mul(dx, dx), try k.mul(dy, dy));    const degenerate = try k.compare(.le, length_squared, try f32v(k, std.math.floatEps(f32)));    const safe_length = try k.select(degenerate, one, length_squared);    const projected = try k.div(        try k.add(            try k.mul(try k.sub(coord.x, from.x), dx),            try k.mul(try k.sub(coord.y, from.y), dy),        ),        safe_length,    );    const t = try k.select(degenerate, one, try k.min(one, try k.max(zero, projected)));    const alpha_value = try interpolateChannelFloat(k, start.a, end.a, t);    const positive_alpha = try k.compare(.gt, alpha_value, zero);    const safe_alpha = try k.select(positive_alpha, alpha_value, one);    return .{        .r = try unpremultipliedChannel(k, start.r, start.a, end.r, end.a, t, safe_alpha, positive_alpha),        .g = try unpremultipliedChannel(k, start.g, start.a, end.g, end.a, t, safe_alpha, positive_alpha),        .b = try unpremultipliedChannel(k, start.b, start.a, end.b, end.a, t, safe_alpha, positive_alpha),        .a = try k.select(positive_alpha, try roundedChannel(k, alpha_value), try u32v(k, 0)),    };}fn unpremultipliedChannel(k: anytype, start: Value, start_alpha: Value, end: Value, end_alpha: Value, t: Value, alpha_value: Value, positive_alpha: Value) !Value {    const start_value = try k.mul(try k.cast(start, .f32), try k.cast(start_alpha, .f32));    const end_value = try k.mul(try k.cast(end, .f32), try k.cast(end_alpha, .f32));    const value = try k.div(try interpolateFloat(k, start_value, end_value, t), alpha_value);    return k.select(positive_alpha, try roundedChannel(k, value), try u32v(k, 0));}fn interpolateChannelFloat(k: anytype, start: Value, end: Value, t: Value) !Value {    return interpolateFloat(k, try k.cast(start, .f32), try k.cast(end, .f32), t);}fn interpolateFloat(k: anytype, start: Value, end: Value, t: Value) !Value {    return k.add(start, try k.mul(try k.sub(end, start), t));}fn roundedChannel(k: anytype, value: Value) !Value {    const clamped = try k.min(try f32v(k, 255), try k.max(try f32v(k, 0), value));    return k.cast(try k.floor(try k.add(clamped, try f32v(k, 0.5))), .u32);}fn pixelForGid(k: anytype, gid: Value) !Pixel {    const gid_u32 = try k.cast(gid, .u32);    const view_width = k.argument(arg_view_width);    const local_y = try k.div(gid_u32, view_width);    const local_x = try k.sub(gid_u32, try k.mul(local_y, view_width));    const target_x = try k.add(k.argument(arg_view_x), local_x);    const target_y = try k.add(k.argument(arg_view_y), local_y);    const half = try f32v(k, 0.5);    return .{        .coord = .{            .x = try k.add(try k.cast(target_x, .f32), half),            .y = try k.add(try k.cast(target_y, .f32), half),        },        .index = gid,        .local_x = local_x,        .local_y = local_y,    };}fn copyPackedRegion(dst: []u32, width: u32, region: Region, src: []const u32) void {    var y: u32 = 0;    while (y < region.height) : (y += 1) {        const dst_start = @as(usize, region.y + y) * width + region.x;        const src_start = @as(usize, y) * region.width;        @memcpy(dst[dst_start .. dst_start + region.width], src[src_start .. src_start + region.width]);    }}const RectValue = struct {    x: Value,    y: Value,    width: Value,    height: Value,};fn commandBoundsValue(k: anytype, rect: RectValue, shadow: Value, width: Value) !RectValue {    const blur = try k.max(width, try f32v(k, 0));    const shadow_bounds = RectValue{        .x = try k.sub(rect.x, blur),        .y = try k.sub(rect.y, blur),        .width = try k.add(rect.width, try k.mul(blur, try f32v(k, 2))),        .height = try k.add(rect.height, try k.mul(blur, try f32v(k, 2))),    };    return selectRect(k, shadow, shadow_bounds, rect);}fn selectRect(k: anytype, condition: Value, true_rect: RectValue, false_rect: RectValue) !RectValue {    return .{        .x = try k.select(condition, true_rect.x, false_rect.x),        .y = try k.select(condition, true_rect.y, false_rect.y),        .width = try k.select(condition, true_rect.width, false_rect.width),        .height = try k.select(condition, true_rect.height, false_rect.height),    };}fn loadRect(k: anytype, floats: kernel.BufferView(.f32), command_index: Value, comptime lane: usize) !RectValue {    return .{        .x = try loadFloat(k, floats, command_index, lane),        .y = try loadFloat(k, floats, command_index, lane + 1),        .width = try loadFloat(k, floats, command_index, lane + 2),        .height = try loadFloat(k, floats, command_index, lane + 3),    };}fn loadFloat(k: anytype, floats: kernel.BufferView(.f32), command_index: Value, comptime lane: usize) !Value {    return (try floats.load(k, try laneIndex(k, command_index, float_lanes, lane))).raw();}fn loadWord(k: anytype, words: kernel.BufferView(.u32), command_index: Value, comptime lane: usize) !Value {    return (try words.load(k, try laneIndex(k, command_index, word_lanes, lane))).raw();}fn laneIndex(k: anytype, command_index: Value, comptime stride: usize, comptime lane: usize) !Value {    const base = try k.mul(command_index, try k.constantIndex(stride));    return k.add(base, try k.constantIndex(lane));}const ClampRound = enum { floor, ceil };fn floorClampValue(k: anytype, value: Value, limit: Value) !Value {    return roundedClampValue(k, value, limit, .floor);}fn ceilClampValue(k: anytype, value: Value, limit: Value) !Value {    return roundedClampValue(k, value, limit, .ceil);}fn roundedClampValue(k: anytype, value: Value, limit: Value, comptime round: ClampRound) !Value {    const zero = try f32v(k, 0);    const limit_f = try k.cast(limit, .f32);    const max_finite = try f32v(k, std.math.floatMax(f32));    const min_finite = try k.neg(max_finite);    const finite = try k.and_(        try k.and_(try k.compare(.eq, value, value), try k.compare(.le, value, max_finite)),        try k.compare(.ge, value, min_finite),    );    const safe_value = try k.select(finite, value, zero);    const rounded = switch (round) {        .floor => try k.floor(safe_value),        .ceil => try k.neg(try k.floor(try k.neg(safe_value))),    };    const clamped = try k.min(try k.max(rounded, zero), limit_f);    const base = try k.cast(clamped, .u32);    const over_limit = try k.compare(.gt, value, limit_f);    return k.select(over_limit, limit, base);}fn tileStart(k: anytype, coord: Value, region_start: Value, tiles: Value) !Value {    const one = try u32v(k, 1);    const local = try k.sub(try k.max(coord, region_start), region_start);    const raw = try k.ushr(local, try u32v(k, tile_size_log2));    return k.min(raw, try k.sub(tiles, one));}fn tileEnd(k: anytype, coord: Value, region_start: Value, tiles: Value) !Value {    const one = try u32v(k, 1);    const local = try k.sub(try k.max(coord, region_start), region_start);    const range_end = try k.add(try k.ushr(try k.sub(try k.max(local, one), one), try u32v(k, tile_size_log2)), one);    return k.min(range_end, tiles);}fn coverageCount(    k: anytype,    rect: RectValue,    clip: RectValue,    radius: Value,    width: Value,    fill: Value,    stroke: Value,    image: Value,    shadow: Value,    glyph: Value,    coord: Coord,) !Value {    const offset = try f32v(k, 0.25);    const left = try k.sub(coord.x, offset);    const right = try k.add(coord.x, offset);    const top = try k.sub(coord.y, offset);    const bottom = try k.add(coord.y, offset);    const a = try coverageBit(k, try sampleCovers(k, rect, clip, radius, width, fill, stroke, image, shadow, glyph, left, top));    const b = try coverageBit(k, try sampleCovers(k, rect, clip, radius, width, fill, stroke, image, shadow, glyph, right, top));    const c = try coverageBit(k, try sampleCovers(k, rect, clip, radius, width, fill, stroke, image, shadow, glyph, left, bottom));    const d = try coverageBit(k, try sampleCovers(k, rect, clip, radius, width, fill, stroke, image, shadow, glyph, right, bottom));    return k.add(try k.add(a, b), try k.add(c, d));}fn sampleCovers(    k: anytype,    rect: RectValue,    clip: RectValue,    radius: Value,    width: Value,    fill: Value,    stroke: Value,    image: Value,    shadow: Value,    glyph: Value,    x: Value,    y: Value,) !Value {    const in_clip = try inside(k, clip, x, y);    const fill_cover = try k.and_(fill, try insideRounded(k, rect, radius, x, y));    const stroke_cover = try k.and_(stroke, try strokeInside(k, rect, radius, width, x, y));    const image_cover = try k.and_(image, try inside(k, rect, x, y));    const shadow_cover = try k.and_(shadow, try shadowInside(k, rect, radius, width, x, y));    const glyph_cover = try k.and_(glyph, try inside(k, rect, x, y));    return k.and_(in_clip, try k.or_(glyph_cover, try k.or_(shadow_cover, try k.or_(image_cover, try k.or_(fill_cover, stroke_cover)))));}fn coverageBit(k: anytype, covered: Value) !Value {    return k.select(covered, try u32v(k, 1), try u32v(k, 0));}fn imageColor(    k: anytype,    images: kernel.BufferView(.u32),    pixels: kernel.BufferView(.u32),    image_index: Value,    source: RectValue,    rect: RectValue,    coord: Coord,    coverage: Value,    opacity: Value,) !Channels {    const image_count = k.argument(arg_image_count);    const valid_index = try k.compare(.lt, image_index, image_count);    const safe_image = try k.select(valid_index, image_index, try u32v(k, 0));    const safe_image_index = try k.castIndex(safe_image);    const meta_base = try k.mul(safe_image_index, try k.constantIndex(image_lanes));    const width = (try images.load(k, try k.add(meta_base, try k.constantIndex(image_width)))).raw();    const height = (try images.load(k, try k.add(meta_base, try k.constantIndex(image_height)))).raw();    const offset = (try images.load(k, try k.add(meta_base, try k.constantIndex(image_offset)))).raw();    const one = try u32v(k, 1);    const safe_width = try k.max(width, one);    const safe_height = try k.max(height, one);    const valid_dims = try k.and_(try k.compare(.gt, width, try u32v(k, 0)), try k.compare(.gt, height, try u32v(k, 0)));    const valid = try k.and_(valid_index, valid_dims);    const source_rect = try imageSourceRect(k, source, safe_width, safe_height);    const u = try k.max(try f32v(k, 0), try k.min(try f32v(k, 0.999999), try k.div(try k.sub(coord.x, rect.x), rect.width)));    const v = try k.max(try f32v(k, 0), try k.min(try f32v(k, 0.999999), try k.div(try k.sub(coord.y, rect.y), rect.height)));    const sx = try imageSampleIndex(k, try k.add(source_rect.x, try k.mul(u, source_rect.width)), safe_width);    const sy = try imageSampleIndex(k, try k.add(source_rect.y, try k.mul(v, source_rect.height)), safe_height);    const pixel_index = try k.add(try k.castIndex(offset), try k.add(sx, try k.mul(sy, try k.castIndex(safe_width))));    const pixel = (try pixels.load(k, pixel_index)).raw();    const image_alpha = try div255(k, try k.mul(try channel(k, pixel, 24), opacity));    const alpha = try coverageAlpha(k, image_alpha, coverage);    return .{        .r = try k.select(valid, try channel(k, pixel, 0), try u32v(k, 0)),        .g = try k.select(valid, try channel(k, pixel, 8), try u32v(k, 0)),        .b = try k.select(valid, try channel(k, pixel, 16), try u32v(k, 0)),        .a = try k.select(valid, alpha, try u32v(k, 0)),    };}fn imageSourceRect(k: anytype, source: RectValue, width: Value, height: Value) !RectValue {    const explicit = try k.and_(try k.compare(.gt, source.width, try f32v(k, 0)), try k.compare(.gt, source.height, try f32v(k, 0)));    return .{        .x = try k.select(explicit, source.x, try f32v(k, 0)),        .y = try k.select(explicit, source.y, try f32v(k, 0)),        .width = try k.select(explicit, source.width, try k.cast(width, .f32)),        .height = try k.select(explicit, source.height, try k.cast(height, .f32)),    };}fn imageSampleIndex(k: anytype, value: Value, limit: Value) !Value {    const limit_f = try k.cast(limit, .f32);    const max_value = try k.sub(limit_f, try f32v(k, 1));    const clamped = try k.min(try k.max(try k.floor(value), try f32v(k, 0)), max_value);    return k.castIndex(try k.cast(clamped, .u32));}fn shadowColor(k: anytype, src: Channels, rect: RectValue, radius: Value, blur: Value, coord: Coord, coverage: Value) !Channels {    const zero = try f32v(k, 0);    const one = try f32v(k, 1);    const distance = try roundedRectOutsideDistance(k, rect, radius, coord.x, coord.y);    const positive_blur = try k.compare(.gt, blur, zero);    const safe_blur = try k.max(blur, one);    const t = try k.max(zero, try k.min(one, try k.sub(one, try k.div(distance, safe_blur))));    const alpha_scale = try k.mul(t, t);    const alpha_f = try k.mul(try k.cast(src.a, .f32), alpha_scale);    const alpha = try k.cast(try k.floor(try k.add(alpha_f, try f32v(k, 0.5))), .u32);    const hard = try k.compare(.le, distance, zero);    const soft_alpha = try coverageAlpha(k, alpha, coverage);    const hard_alpha = try coverageAlpha(k, src.a, coverage);    return .{        .r = src.r,        .g = src.g,        .b = src.b,        .a = try k.select(positive_blur, soft_alpha, try k.select(hard, hard_alpha, try u32v(k, 0))),    };}fn shadowInside(k: anytype, rect: RectValue, radius: Value, blur: Value, x: Value, y: Value) !Value {    const zero = try f32v(k, 0);    const distance = try roundedRectOutsideDistance(k, rect, radius, x, y);    const positive_blur = try k.compare(.gt, blur, zero);    const soft = try k.compare(.lt, distance, blur);    const hard = try k.compare(.le, distance, zero);    return k.or_(try k.and_(positive_blur, soft), try k.and_(try k.not(positive_blur), hard));}fn inside(k: anytype, rect: RectValue, x: Value, y: Value) !Value {    const x0 = try k.compare(.ge, x, rect.x);    const y0 = try k.compare(.ge, y, rect.y);    const x1 = try k.compare(.lt, x, try k.add(rect.x, rect.width));    const y1 = try k.compare(.lt, y, try k.add(rect.y, rect.height));    return k.and_(try k.and_(x0, y0), try k.and_(x1, y1));}fn roundedRectOutsideDistance(k: anytype, rect: RectValue, radius: Value, x: Value, y: Value) !Value {    const zero = try f32v(k, 0);    const half = try f32v(k, 0.5);    const clamped_radius = try k.min(        try k.max(radius, zero),        try k.mul(try k.min(rect.width, rect.height), half),    );    const inner_x0 = try k.add(rect.x, clamped_radius);    const inner_y0 = try k.add(rect.y, clamped_radius);    const inner_x1 = try k.sub(try k.add(rect.x, rect.width), clamped_radius);    const inner_y1 = try k.sub(try k.add(rect.y, rect.height), clamped_radius);    const cx = try k.min(try k.max(x, inner_x0), inner_x1);    const cy = try k.min(try k.max(y, inner_y0), inner_y1);    const dx = try k.sub(x, cx);    const dy = try k.sub(y, cy);    const dist = try k.sqrt(try k.add(try k.mul(dx, dx), try k.mul(dy, dy)));    return k.max(try k.sub(dist, clamped_radius), zero);}fn insideRounded(k: anytype, rect: RectValue, radius: Value, x: Value, y: Value) !Value {    const in_rect = try inside(k, rect, x, y);    const zero = try f32v(k, 0);    const half = try f32v(k, 0.5);    const clamped_radius = try k.min(        try k.max(radius, zero),        try k.mul(try k.min(rect.width, rect.height), half),    );    const inner_x0 = try k.add(rect.x, clamped_radius);    const inner_y0 = try k.add(rect.y, clamped_radius);    const inner_x1 = try k.sub(try k.add(rect.x, rect.width), clamped_radius);    const inner_y1 = try k.sub(try k.add(rect.y, rect.height), clamped_radius);    const cx = try k.min(try k.max(x, inner_x0), inner_x1);    const cy = try k.min(try k.max(y, inner_y0), inner_y1);    const dx = try k.sub(x, cx);    const dy = try k.sub(y, cy);    const dist2 = try k.add(try k.mul(dx, dx), try k.mul(dy, dy));    const radius2 = try k.mul(clamped_radius, clamped_radius);    return k.and_(in_rect, try k.compare(.le, dist2, radius2));}fn strokeInside(k: anytype, rect: RectValue, radius: Value, width: Value, x: Value, y: Value) !Value {    const zero = try f32v(k, 0);    const outer = try insideRounded(k, rect, radius, x, y);    const positive = try k.compare(.gt, width, zero);    const inner = RectValue{        .x = try k.add(rect.x, width),        .y = try k.add(rect.y, width),        .width = try k.max(try k.sub(rect.width, try k.mul(width, try f32v(k, 2))), zero),        .height = try k.max(try k.sub(rect.height, try k.mul(width, try f32v(k, 2))), zero),    };    const has_inner = try k.and_(try k.compare(.gt, inner.width, zero), try k.compare(.gt, inner.height, zero));    const inner_radius = try k.max(try k.sub(radius, width), zero);    const inside_inner = try k.and_(has_inner, try insideRounded(k, inner, inner_radius, x, y));    return k.and_(positive, try k.and_(outer, try k.not(inside_inner)));}fn blend(k: anytype, dst: Channels, src: Channels) !Channels {    const inv = try k.sub(try u32v(k, 255), src.a);    return .{        .r = try blendChannel(k, src.r, src.a, dst.r, inv),        .g = try blendChannel(k, src.g, src.a, dst.g, inv),        .b = try blendChannel(k, src.b, src.a, dst.b, inv),        .a = try k.add(src.a, try div255(k, try k.mul(dst.a, inv))),    };}fn coverageAlpha(k: anytype, alpha: Value, coverage: Value) !Value {    return k.div(try k.add(try k.mul(alpha, coverage), try u32v(k, 2)), try u32v(k, 4));}fn blendChannel(k: anytype, src: Value, alpha: Value, dst: Value, inv: Value) !Value {    return div255(k, try k.add(try k.mul(src, alpha), try k.mul(dst, inv)));}fn div255(k: anytype, value: Value) !Value {    return k.div(try k.add(value, try u32v(k, 127)), try u32v(k, 255));}fn packPixel(k: anytype, channels: Channels, format: Value) !Value {    const bgra = try k.compare(.eq, format, try u32v(k, @backingInt(OutputFormat.bgra)));    const first = try k.select(bgra, channels.b, channels.r);    const third = try k.select(bgra, channels.r, channels.b);    return k.or_(        try k.or_(first, try k.shl(channels.g, try u32v(k, 8))),        try k.or_(try k.shl(third, try u32v(k, 16)), try k.shl(channels.a, try u32v(k, 24))),    );}fn channel(k: anytype, pixel: Value, comptime shift: u32) !Value {    return k.and_(try k.ushr(pixel, try u32v(k, shift)), try u32v(k, 0xff));}fn u32v(k: anytype, value: u32) !Value {    return k.constantInt(.u32, value);}fn f32v(k: anytype, value: f32) !Value {    return k.constantFloat(.f32, value);}test "packCommands encodes geometry and style lanes" {    const commands = [_]Command{.{        .kind = .stroke,        .rect = .{ .x = 1, .y = 2, .width = 3, .height = 4 },        .clip = .{ .x = 5, .y = 6, .width = 7, .height = 8 },        .color = .{ .r = 9, .g = 10, .b = 11, .a = 12 },        .radius = 13,        .width = 14,        .source = .{ .x = 15, .y = 16, .width = 17, .height = 18 },        .color_end = .{ .r = 21, .g = 22, .b = 23, .a = 24 },        .gradient_start = .{ .x = 25, .y = 26 },        .gradient_end = .{ .x = 27, .y = 28 },        .image_index = 19,        .order = 20,    }};    var floats = @as([float_lanes]f32, @splat(0));    var words = @as([word_lanes]u32, @splat(0));    packCommands(commands[0..], floats[0..], words[0..]);    try std.testing.expectEqual(@as(f32, 1), floats[float_rect_x]);    try std.testing.expectEqual(@as(f32, 8), floats[float_clip_h]);    try std.testing.expectEqual(@as(f32, 15), floats[float_source_x]);    try std.testing.expectEqual(@as(f32, 18), floats[float_source_h]);    try std.testing.expectEqual(@as(f32, 25), floats[float_gradient_start_x]);    try std.testing.expectEqual(@as(f32, 28), floats[float_gradient_end_y]);    try std.testing.expectEqual(@as(u32, @backingInt(command.Kind.stroke)), words[word_kind]);    try std.testing.expectEqual(@as(u32, 12), words[word_a]);    try std.testing.expectEqual(@as(u32, 19), words[word_image]);    try std.testing.expectEqual(@as(u32, 20), words[word_order]);    try std.testing.expectEqual(@as(u32, 0x1817_1615), words[word_color_end]);    try std.testing.expect(commandsEqualPacked(commands[0..], floats[0..], words[0..]));    const changed = [_]Command{.{        .kind = .stroke,        .rect = .{ .x = 1, .y = 2, .width = 3, .height = 4 },        .clip = .{ .x = 5, .y = 6, .width = 7, .height = 8 },        .color = .{ .r = 9, .g = 10, .b = 11, .a = 13 },        .radius = 13,        .width = 14,        .source = .{ .x = 15, .y = 16, .width = 17, .height = 18 },        .color_end = .{ .r = 21, .g = 22, .b = 23, .a = 24 },        .gradient_start = .{ .x = 25, .y = 26 },        .gradient_end = .{ .x = 27, .y = 28 },        .image_index = 19,        .order = 20,    }};    try std.testing.expect(!commandsEqualPacked(changed[0..], floats[0..], words[0..]));}test "gradient command packing reuses reserved storage without allocation" {    const commands = [_]Command{.{        .kind = .linear_gradient,        .rect = .{ .width = 16, .height = 8 },        .clip = .{ .width = 16, .height = 8 },        .color = .{ .r = 20, .g = 40, .b = 80, .a = 255 },        .color_end = .{ .r = 120, .g = 160, .b = 220, .a = 255 },        .gradient_end = .{ .x = 16, .y = 8 },    }};    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    const allocator = failing.allocator();    const floats = try allocator.alloc(f32, commands.len * float_lanes);    defer allocator.free(floats);    const words = try allocator.alloc(u32, commands.len * word_lanes);    defer allocator.free(words);    packCommands(commands[0..], floats, words);    failing.fail_index = failing.alloc_index;    failing.resize_fail_index = failing.resize_index;    for (0..8) |_| {        packCommands(commands[0..], floats, words);        try std.testing.expect(commandsEqualPacked(commands[0..], floats, words));    }    try std.testing.expect(!failing.has_induced_failure);}test "gradient command packing makes non-finite geometry degenerate" {    const commands = [_]Command{.{        .kind = .linear_gradient,        .rect = .{ .width = 16, .height = 8 },        .clip = .{ .width = 16, .height = 8 },        .color = .{ .r = 10, .g = 20, .b = 40, .a = 255 },        .color_end = .{ .r = 20, .g = 40, .b = 80, .a = 255 },        .gradient_start = .{ .x = std.math.nan(f32), .y = 4 },        .gradient_end = .{ .x = 12, .y = std.math.inf(f32) },    }};    var floats = @as([float_lanes]f32, @splat(1));    var words = @as([word_lanes]u32, @splat(0));    packCommands(commands[0..], floats[0..], words[0..]);    try std.testing.expectEqual(@as(f32, 0), floats[float_gradient_start_x]);    try std.testing.expectEqual(@as(f32, 0), floats[float_gradient_start_y]);    try std.testing.expectEqual(@as(f32, 0), floats[float_gradient_end_x]);    try std.testing.expectEqual(@as(f32, 0), floats[float_gradient_end_y]);}test "packImages encodes metadata and pixels" {    const first = [_]u32{ 0x1122_3344, 0x5566_7788 };    const second = [_]u32{0x99aa_bbcc};    const images = ImageSet{ .images = &.{        .{ .width = 2, .height = 1, .pixels = first[0..] },        .{ .width = 1, .height = 1, .pixels = second[0..] },    } };    var metadata = @as([(2 * image_lanes)]u32, @splat(0));    var pixels = @as([3]u32, @splat(0));    try packImages(images, metadata[0..], pixels[0..]);    try std.testing.expectEqual(@as(u32, 2), metadata[image_width]);    try std.testing.expectEqual(@as(u32, 1), metadata[image_height]);    try std.testing.expectEqual(@as(u32, 0), metadata[image_offset]);    try std.testing.expectEqual(@as(u32, 1), metadata[image_lanes + image_width]);    try std.testing.expectEqual(@as(u32, 1), metadata[image_lanes + image_height]);    try std.testing.expectEqual(@as(u32, 2), metadata[image_lanes + image_offset]);    try std.testing.expectEqualSlices(u32, &.{ 0x1122_3344, 0x5566_7788, 0x99aa_bbcc }, pixels[0..]);    try std.testing.expect(try imageMetadataEqualPacked(images, metadata[0..]));    try std.testing.expect(try imagesEqualPacked(images, metadata[0..], pixels[0..]));    const changed_extent = ImageSet{ .images = &.{        .{ .width = 1, .height = 2, .pixels = first[0..] },        .{ .width = 1, .height = 1, .pixels = second[0..] },    } };    const changed_pixels = [_]u32{ 0x1122_3344, 0x5566_7789 };    const changed_content = ImageSet{ .images = &.{        .{ .width = 2, .height = 1, .pixels = changed_pixels[0..] },        .{ .width = 1, .height = 1, .pixels = second[0..] },    } };    try std.testing.expect(!try imageMetadataEqualPacked(changed_extent, metadata[0..]));    try std.testing.expect(try imageMetadataEqualPacked(changed_content, metadata[0..]));    try std.testing.expect(!try imagesEqualPacked(changed_extent, metadata[0..], pixels[0..]));    try std.testing.expect(!try imagesEqualPacked(changed_content, metadata[0..], pixels[0..]));}test "binCommandsAlloc orders tile command lists by paint order" {    const allocator = std.testing.allocator;    var commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 0, .y = 0, .width = 6, .height = 6 },            .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },            .order = 1,        },        .{            .kind = .fill,            .rect = .{ .x = 10, .y = 0, .width = 4, .height = 4 },            .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },            .color = .{ .r = 0, .g = 200, .b = 0, .a = 255 },            .order = 0,        },        .{            .kind = .fill,            .rect = .{ .x = 1, .y = 1, .width = 3, .height = 3 },            .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },            .color = .{ .r = 0, .g = 0, .b = 200, .a = 255 },            .order = 2,        },    };    var bins = try binCommandsAlloc(allocator, commands[0..], 16, 16, Region.full(16, 16));    defer bins.deinit(allocator);    try std.testing.expectEqualSlices(u32, &.{ 1, 0, 2 }, bins.indices);}test "commandVisits counts per-pixel tile command work" {    const allocator = std.testing.allocator;    var commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 0, .y = 0, .width = 32, .height = 16 },            .clip = .{ .x = 0, .y = 0, .width = 32, .height = 16 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },            .order = 0,        },        .{            .kind = .fill,            .rect = .{ .x = 16, .y = 0, .width = 16, .height = 16 },            .clip = .{ .x = 0, .y = 0, .width = 32, .height = 16 },            .color = .{ .r = 0, .g = 200, .b = 0, .a = 255 },            .order = 1,        },    };    var bins = try binCommandsAlloc(allocator, commands[0..], 32, 16, Region.full(32, 16));    defer bins.deinit(allocator);    try std.testing.expectEqual(@as(usize, 768), commandVisits(bins.view(), Region.full(32, 16)));}test "BinScratch reuses tile bin storage across builds" {    var commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 0, .y = 0, .width = 6, .height = 6 },            .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },            .order = 1,        },        .{            .kind = .fill,            .rect = .{ .x = 10, .y = 0, .width = 4, .height = 4 },            .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },            .color = .{ .r = 0, .g = 200, .b = 0, .a = 255 },            .order = 0,        },        .{            .kind = .fill,            .rect = .{ .x = 1, .y = 1, .width = 3, .height = 3 },            .clip = .{ .x = 0, .y = 0, .width = 16, .height = 16 },            .color = .{ .r = 0, .g = 0, .b = 200, .a = 255 },            .order = 2,        },    };    var ranges: [3 * tile_range_lanes]u32 = undefined;    var offsets: [2]u32 = undefined;    var indices: [3]u32 = undefined;    var cursors: [1]u32 = undefined;    var scratch = BinScratch{        .ranges = ranges[0..],        .offsets = offsets[0..],        .indices = indices[0..],        .cursors = cursors[0..],    };    const first = try scratch.binCommands(commands[0..], 16, 16, Region.full(16, 16));    const ranges_ptr = @intFromPtr(scratch.ranges.ptr);    const offsets_ptr = @intFromPtr(scratch.offsets.ptr);    const indices_ptr = @intFromPtr(scratch.indices.ptr);    const cursors_ptr = @intFromPtr(scratch.cursors.ptr);    try std.testing.expectEqualSlices(u32, &.{ 1, 0, 2 }, first.indices);    const second = try scratch.binCommands(commands[0..], 16, 16, Region.full(16, 16));    try std.testing.expectEqual(ranges_ptr, @intFromPtr(scratch.ranges.ptr));    try std.testing.expectEqual(offsets_ptr, @intFromPtr(scratch.offsets.ptr));    try std.testing.expectEqual(indices_ptr, @intFromPtr(scratch.indices.ptr));    try std.testing.expectEqual(cursors_ptr, @intFromPtr(scratch.cursors.ptr));    try std.testing.expectEqualSlices(u32, &.{ 1, 0, 2 }, second.indices);}test "binPairCount surveys exact command tile pairs without scratch" {    const commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 0, .y = 0, .width = 32, .height = 16 },            .clip = .{ .x = 0, .y = 0, .width = 32, .height = 16 },            .color = .{},        },        .{            .kind = .fill,            .rect = .{ .x = 16, .y = 0, .width = 16, .height = 16 },            .clip = .{ .x = 0, .y = 0, .width = 32, .height = 16 },            .color = .{},        },    };    try std.testing.expectEqual(        @as(usize, 3),        try binPairCount(commands[0..], 32, 16, Region.full(32, 16)),    );}test "Accy tile range graph matches host command ranges" {    const allocator = std.testing.allocator;    const inf = std.math.inf(f32);    var commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 5, .y = 4, .width = 20, .height = 18 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },        },        .{            .kind = .shadow,            .rect = .{ .x = 34, .y = 26, .width = 8, .height = 7 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 0, .g = 0, .b = 0, .a = 120 },            .width = 6,        },        .{            .kind = .image,            .rect = .{ .x = 0, .y = 0, .width = 2, .height = 2 },            .clip = .{ .x = 50, .y = 0, .width = 4, .height = 4 },            .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },        },        .{            .kind = .stroke,            .rect = .{ .x = 18, .y = 7, .width = 19, .height = 4 },            .clip = .{ .x = 16, .y = 0, .width = 12, .height = 40 },            .color = .{ .r = 0, .g = 120, .b = 200, .a = 255 },            .width = 2,        },        .{            .kind = .fill,            .rect = .{ .x = inf, .y = 1, .width = 5, .height = 5 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 20, .g = 40, .b = 60, .a = 255 },        },    };    const width: u32 = 64;    const height: u32 = 48;    const region = Region{ .x = 4, .y = 3, .width = 40, .height = 32 };    const range_count = tileRangeValueCount(commands.len);    const expected = try allocator.alloc(u32, range_count);    defer allocator.free(expected);    fillCommandTileRanges(commands[0..], width, height, binShape(width, height, region), expected);    var encoded = try packCommandsAlloc(allocator, commands[0..]);    defer encoded.deinit(allocator);    const actual = try allocator.alloc(u32, range_count);    defer allocator.free(actual);    @memset(actual, 0xffff_ffff);    var graph = try buildTileRangeGraph(allocator, default_threads);    defer graph.deinit();    try runPackedTileRangesCpuRegion(allocator, &graph, encoded, commands.len, width, height, actual, region);    try std.testing.expectEqualSlices(u32, expected, actual);}test "Accy tile count graph matches host range counts" {    const allocator = std.testing.allocator;    const commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 5, .y = 4, .width = 20, .height = 18 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },        },        .{            .kind = .shadow,            .rect = .{ .x = 34, .y = 26, .width = 8, .height = 7 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 0, .g = 0, .b = 0, .a = 120 },            .width = 6,        },        .{            .kind = .image,            .rect = .{ .x = 0, .y = 0, .width = 2, .height = 2 },            .clip = .{ .x = 50, .y = 0, .width = 4, .height = 4 },            .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },        },        .{            .kind = .stroke,            .rect = .{ .x = 18, .y = 7, .width = 19, .height = 4 },            .clip = .{ .x = 16, .y = 0, .width = 12, .height = 40 },            .color = .{ .r = 0, .g = 120, .b = 200, .a = 255 },            .width = 2,        },    };    const width: u32 = 64;    const height: u32 = 48;    const region = Region{ .x = 4, .y = 3, .width = 40, .height = 32 };    const shape = binShape(width, height, region);    const ranges = try allocator.alloc(u32, tileRangeValueCount(commands.len));    defer allocator.free(ranges);    fillCommandTileRanges(commands[0..], width, height, shape, ranges);    const expected = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(expected);    countTilePairs(ranges, shape, expected);    var expected_total: usize = 0;    for (expected[1..]) |count| expected_total += count;    const actual = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(actual);    @memset(actual, 0xffff_ffff);    var graph = try buildTileCountGraph(allocator, default_threads);    defer graph.deinit();    const actual_total = try runPackedTileCountsCpu(allocator, &graph, ranges, commands.len, shape.tiles_x, shape.tiles_y, actual);    try std.testing.expectEqual(expected_total, actual_total);    try std.testing.expectEqualSlices(u32, expected, actual);    const prefixed = try allocator.dupe(u32, actual);    defer allocator.free(prefixed);    try std.testing.expectEqual(actual_total, prefixBinOffsets(prefixed));}test "Accy tile count scan matches host CSR offsets" {    const allocator = std.testing.allocator;    const commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 5, .y = 4, .width = 20, .height = 18 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },        },        .{            .kind = .shadow,            .rect = .{ .x = 34, .y = 26, .width = 8, .height = 7 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 0, .g = 0, .b = 0, .a = 120 },            .width = 6,        },        .{            .kind = .stroke,            .rect = .{ .x = 18, .y = 7, .width = 19, .height = 4 },            .clip = .{ .x = 16, .y = 0, .width = 12, .height = 40 },            .color = .{ .r = 0, .g = 120, .b = 200, .a = 255 },            .width = 2,        },    };    const width: u32 = 64;    const height: u32 = 48;    const region = Region{ .x = 4, .y = 3, .width = 40, .height = 32 };    const shape = binShape(width, height, region);    const ranges = try allocator.alloc(u32, tileRangeValueCount(commands.len));    defer allocator.free(ranges);    fillCommandTileRanges(commands[0..], width, height, shape, ranges);    const expected = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(expected);    countTilePairs(ranges, shape, expected);    const expected_total = prefixBinOffsets(expected);    const actual = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(actual);    @memset(actual, 0xffff_ffff);    var graph = try buildTileCountGraph(allocator, default_threads);    defer graph.deinit();    const actual_total = try runPackedTileOffsetsCpu(allocator, &graph, ranges, commands.len, shape.tiles_x, shape.tiles_y, actual);    try std.testing.expectEqual(expected_total, actual_total);    try std.testing.expectEqualSlices(u32, expected, actual);}test "Accy tile count scan covers multi-block CSR offsets" {    const allocator = std.testing.allocator;    const width: u32 = tile_size * 33;    const height: u32 = tile_size * 32;    const commands = [_]Command{.{        .kind = .fill,        .rect = .{ .x = 0, .y = 0, .width = @floatFromInt(width), .height = @floatFromInt(height) },        .clip = .{ .x = 0, .y = 0, .width = @floatFromInt(width), .height = @floatFromInt(height) },        .color = .{ .r = 20, .g = 80, .b = 160, .a = 255 },    }};    const shape = binShape(width, height, Region.full(width, height));    try std.testing.expect(shape.tile_count + 1 > scan_library.prefix_sum_max_threads);    const ranges = try allocator.alloc(u32, tileRangeValueCount(commands.len));    defer allocator.free(ranges);    fillCommandTileRanges(commands[0..], width, height, shape, ranges);    const expected = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(expected);    countTilePairs(ranges, shape, expected);    const expected_total = prefixBinOffsets(expected);    const actual = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(actual);    @memset(actual, 0xffff_ffff);    var graph = try buildTileCountGraph(allocator, default_threads);    defer graph.deinit();    const actual_total = try runPackedTileOffsetsCpu(allocator, &graph, ranges, commands.len, shape.tiles_x, shape.tiles_y, actual);    try std.testing.expectEqual(expected_total, actual_total);    try std.testing.expectEqual(@as(usize, shape.tile_count), actual_total);    try std.testing.expectEqualSlices(u32, expected, actual);}test "Accy tile index graph fills scanned CSR command lists" {    const allocator = std.testing.allocator;    const commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 5, .y = 4, .width = 20, .height = 18 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },            .order = 2,        },        .{            .kind = .shadow,            .rect = .{ .x = 34, .y = 26, .width = 8, .height = 7 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 0, .g = 0, .b = 0, .a = 120 },            .width = 6,            .order = 0,        },        .{            .kind = .image,            .rect = .{ .x = 0, .y = 0, .width = 2, .height = 2 },            .clip = .{ .x = 50, .y = 0, .width = 4, .height = 4 },            .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },            .order = 1,        },        .{            .kind = .stroke,            .rect = .{ .x = 18, .y = 7, .width = 19, .height = 4 },            .clip = .{ .x = 16, .y = 0, .width = 12, .height = 40 },            .color = .{ .r = 0, .g = 120, .b = 200, .a = 255 },            .width = 2,            .order = 3,        },    };    const width: u32 = 64;    const height: u32 = 48;    const region = Region{ .x = 4, .y = 3, .width = 40, .height = 32 };    const shape = binShape(width, height, region);    var encoded = try packCommandsAlloc(allocator, commands[0..]);    defer encoded.deinit(allocator);    const ranges = try allocator.alloc(u32, tileRangeValueCount(commands.len));    defer allocator.free(ranges);    fillCommandTileRanges(commands[0..], width, height, shape, ranges);    const offsets = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(offsets);    var count_graph = try buildTileCountGraph(allocator, default_threads);    defer count_graph.deinit();    const total = try runPackedTileOffsetsCpu(allocator, &count_graph, ranges, commands.len, shape.tiles_x, shape.tiles_y, offsets);    const expected_indices = try allocator.alloc(u32, total);    defer allocator.free(expected_indices);    const expected_cursors = try allocator.alloc(u32, shape.tile_count);    defer allocator.free(expected_cursors);    fillBinIndices(commands[0..], ranges, shape, offsets, expected_cursors, expected_indices);    const actual_indices = try allocator.alloc(u32, total);    defer allocator.free(actual_indices);    @memset(actual_indices, 0xffff_ffff);    const actual_cursors = try allocator.alloc(u32, shape.tile_count);    defer allocator.free(actual_cursors);    @memset(actual_cursors, 0xffff_ffff);    var index_graph = try buildTileIndexGraph(allocator, default_threads);    defer index_graph.deinit();    try runPackedTileIndicesCpu(allocator, &index_graph, ranges, encoded.words, commands.len, shape.tiles_x, shape.tiles_y, offsets, actual_cursors, actual_indices);    try std.testing.expectEqualSlices(u32, expected_indices, actual_indices);    var tile: usize = 0;    while (tile < shape.tile_count) : (tile += 1) {        try std.testing.expectEqual(offsets[tile + 1] - offsets[tile], actual_cursors[tile]);    }}test "Accy tile sort graph orders scattered CSR command lists" {    const allocator = std.testing.allocator;    const commands = [_]Command{        .{            .kind = .fill,            .rect = .{ .x = 5, .y = 4, .width = 20, .height = 18 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 200, .g = 0, .b = 0, .a = 255 },            .order = 2,        },        .{            .kind = .shadow,            .rect = .{ .x = 34, .y = 26, .width = 8, .height = 7 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 0, .g = 0, .b = 0, .a = 120 },            .width = 6,            .order = 0,        },        .{            .kind = .image,            .rect = .{ .x = 0, .y = 0, .width = 2, .height = 2 },            .clip = .{ .x = 50, .y = 0, .width = 4, .height = 4 },            .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },            .order = 1,        },        .{            .kind = .stroke,            .rect = .{ .x = 18, .y = 7, .width = 19, .height = 4 },            .clip = .{ .x = 16, .y = 0, .width = 12, .height = 40 },            .color = .{ .r = 0, .g = 120, .b = 200, .a = 255 },            .width = 2,            .order = 3,        },        .{            .kind = .fill,            .rect = .{ .x = 20, .y = 8, .width = 5, .height = 5 },            .clip = .{ .x = 0, .y = 0, .width = 64, .height = 48 },            .color = .{ .r = 60, .g = 40, .b = 20, .a = 255 },            .order = 1,        },    };    const width: u32 = 64;    const height: u32 = 48;    const region = Region{ .x = 4, .y = 3, .width = 40, .height = 32 };    const shape = binShape(width, height, region);    var encoded = try packCommandsAlloc(allocator, commands[0..]);    defer encoded.deinit(allocator);    const ranges = try allocator.alloc(u32, tileRangeValueCount(commands.len));    defer allocator.free(ranges);    fillCommandTileRanges(commands[0..], width, height, shape, ranges);    const offsets = try allocator.alloc(u32, shape.tile_count + 1);    defer allocator.free(offsets);    var count_graph = try buildTileCountGraph(allocator, default_threads);    defer count_graph.deinit();    const total = try runPackedTileOffsetsCpu(allocator, &count_graph, ranges, commands.len, shape.tiles_x, shape.tiles_y, offsets);    const expected_indices = try allocator.alloc(u32, total);    defer allocator.free(expected_indices);    const expected_cursors = try allocator.alloc(u32, shape.tile_count);    defer allocator.free(expected_cursors);    fillBinIndices(commands[0..], ranges, shape, offsets, expected_cursors, expected_indices);    const actual_indices = try allocator.alloc(u32, total);    defer allocator.free(actual_indices);    @memset(actual_indices, 0xffff_ffff);    const actual_cursors = try allocator.alloc(u32, shape.tile_count);    defer allocator.free(actual_cursors);    @memset(actual_cursors, 0xffff_ffff);    fillBinIndicesUnsorted(commands[0..], ranges, shape, offsets, actual_cursors, actual_indices);    var sort_graph = try buildTileSortGraph(allocator, default_threads);    defer sort_graph.deinit();    try runPackedTileSortCpu(allocator, &sort_graph, encoded.words, commands.len, offsets, actual_indices, shape.tile_count);    try std.testing.expectEqualSlices(u32, expected_indices, actual_indices);}test "Accy compositor matches CPU packed rasterization" {    const allocator = std.testing.allocator;    const image_pixels = [_]u32{        cpu.packRgba(.{ .r = 255, .g = 0, .b = 0, .a = 255 }),        cpu.packRgba(.{ .r = 0, .g = 255, .b = 0, .a = 255 }),        cpu.packRgba(.{ .r = 0, .g = 0, .b = 255, .a = 128 }),        cpu.packRgba(.{ .r = 255, .g = 255, .b = 255, .a = 255 }),    };    const images = ImageSet{ .images = &.{.{ .width = 2, .height = 2, .pixels = image_pixels[0..] }} };    const commands = [_]Command{        .{            .kind = .shadow,            .rect = .{ .x = 1, .y = 1, .width = 5, .height = 3 },            .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },            .color = .{ .r = 0, .g = 0, .b = 0, .a = 90 },            .radius = 1,            .width = 2,        },        .{            .kind = .fill,            .rect = .{ .x = 1, .y = 1, .width = 5, .height = 3 },            .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },            .color = .{ .r = 220, .g = 40, .b = 20, .a = 255 },        },        .{            .kind = .stroke,            .rect = .{ .x = 2, .y = 1, .width = 5, .height = 4 },            .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },            .color = .{ .r = 0, .g = 80, .b = 210, .a = 160 },            .radius = 2,            .width = 1,        },        .{            .kind = .fill,            .rect = .{ .x = 0.5, .y = 0.5, .width = 2.25, .height = 2.25 },            .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },            .color = .{ .r = 40, .g = 200, .b = 90, .a = 220 },        },        .{            .kind = .image,            .rect = .{ .x = 5, .y = 1, .width = 2, .height = 2 },            .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },            .source = .{ .x = 0, .y = 0, .width = 2, .height = 2 },            .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },            .image_index = 0,        },        .{            .kind = .glyph,            .rect = .{ .x = 6, .y = 3, .width = 1, .height = 1 },            .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },            .source = .{ .x = 0, .y = 1, .width = 1, .height = 1 },            .color = .{ .r = 230, .g = 20, .b = 40, .a = 200 },            .image_index = 0,        },    };    var expected = @as([(8 * 5)]u32, @splat(0));    var actual = @as([(8 * 5)]u32, @splat(0));    try cpu.renderCommandsPackedWithImages(commands[0..], .{ .width = 8, .height = 5, .pixels = expected[0..] }, .{ .a = 0 }, images);    try renderCommandsCpuWithImages(allocator, commands[0..], 8, 5, actual[0..], .{ .a = 0 }, images);    try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);}test "Accy compositor region preserves pixels outside damage" {    const allocator = std.testing.allocator;    const commands = [_]Command{.{        .kind = .fill,        .rect = .{ .x = 0, .y = 0, .width = 6, .height = 4 },        .clip = .{ .x = 0, .y = 0, .width = 6, .height = 4 },        .color = .{ .r = 80, .g = 90, .b = 100, .a = 255 },    }};    const region = Region{ .x = 2, .y = 1, .width = 2, .height = 2 };    var expected = @as([(6 * 4)]u32, @splat(0x1122_3344));    var actual = @as([(6 * 4)]u32, @splat(0x1122_3344));    try cpu.renderCommandsPackedRegionWithImages(commands[0..], .{ .width = 6, .height = 4, .pixels = expected[0..] }, .{ .a = 0 }, .{}, region);    try renderCommandsCpuRegionWithImages(allocator, commands[0..], 6, 4, actual[0..], .{ .a = 0 }, .{}, region);    try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);    try std.testing.expectEqual(@as(u32, 0x1122_3344), actual[0]);}const steady_frame_commands = [_]Command{    .{        .kind = .fill,        .rect = .{ .x = 0, .y = 0, .width = 8, .height = 5 },        .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },        .color = .{ .r = 18, .g = 22, .b = 28, .a = 255 },    },    .{        .kind = .fill,        .rect = .{ .x = 1, .y = 1, .width = 5, .height = 3 },        .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },        .color = .{ .r = 220, .g = 40, .b = 20, .a = 255 },        .radius = 1,    },    .{        .kind = .stroke,        .rect = .{ .x = 2, .y = 1, .width = 5, .height = 4 },        .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 },        .color = .{ .r = 0, .g = 80, .b = 210, .a = 160 },        .radius = 2,        .width = 1,    },};test "Accy steady frames make no allocator calls at a fixed surface epoch" {    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    const allocator = failing.allocator();    var graph = try buildGraph(allocator, default_threads);    defer graph.deinit();    var encoded = try packCommandsAlloc(allocator, steady_frame_commands[0..]);    defer encoded.deinit(allocator);    var encoded_images = try packImagesAlloc(allocator, .{});    defer encoded_images.deinit(allocator);    var bins = try binCommandsAlloc(allocator, steady_frame_commands[0..], 8, 5, Region.full(8, 5));    defer bins.deinit(allocator);    var expected = @as([(8 * 5)]u32, @splat(0));    try cpu.renderCommandsPacked(steady_frame_commands[0..], .{ .width = 8, .height = 5, .pixels = expected[0..] }, .{ .a = 0 });    var actual = @as([(8 * 5)]u32, @splat(0));    try runPackedCommandsCpu(allocator, &graph, encoded, encoded_images, bins, 0, steady_frame_commands.len, 8, 5, actual[0..], .{ .a = 0 });    try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);    failing.fail_index = failing.alloc_index;    failing.resize_fail_index = failing.resize_index;    for (0..8) |_| {        @memset(actual[0..], 0xffff_ffff);        try runPackedCommandsCpu(allocator, &graph, encoded, encoded_images, bins, 0, steady_frame_commands.len, 8, 5, actual[0..], .{ .a = 0 });        try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);    }    try std.testing.expect(!failing.has_induced_failure);}test "Accy surface epochs stay allocation-free after one full-block frame" {    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    const allocator = failing.allocator();    var graph = try buildGraph(allocator, default_threads);    defer graph.deinit();    var encoded = try packCommandsAlloc(allocator, steady_frame_commands[0..]);    defer encoded.deinit(allocator);    var encoded_images = try packImagesAlloc(allocator, .{});    defer encoded_images.deinit(allocator);    var warm_bins = try binCommandsAlloc(allocator, steady_frame_commands[0..], 16, 8, Region.full(16, 8));    defer warm_bins.deinit(allocator);    var shrunk_bins = try binCommandsAlloc(allocator, steady_frame_commands[0..], 8, 5, Region.full(8, 5));    defer shrunk_bins.deinit(allocator);    var grown_bins = try binCommandsAlloc(allocator, steady_frame_commands[0..], 32, 20, Region.full(32, 20));    defer grown_bins.deinit(allocator);    comptime std.debug.assert(16 * 8 == default_threads);    var warm_pixels = @as([(16 * 8)]u32, @splat(0));    try runPackedCommandsCpu(allocator, &graph, encoded, encoded_images, warm_bins, 0, steady_frame_commands.len, 16, 8, warm_pixels[0..], .{ .a = 0 });    var shrunk_expected = @as([(8 * 5)]u32, @splat(0));    try cpu.renderCommandsPacked(steady_frame_commands[0..], .{ .width = 8, .height = 5, .pixels = shrunk_expected[0..] }, .{ .a = 0 });    var grown_expected = @as([(32 * 20)]u32, @splat(0));    try cpu.renderCommandsPacked(steady_frame_commands[0..], .{ .width = 32, .height = 20, .pixels = grown_expected[0..] }, .{ .a = 0 });    failing.fail_index = failing.alloc_index;    failing.resize_fail_index = failing.resize_index;    var shrunk_pixels = @as([(8 * 5)]u32, @splat(0));    try runPackedCommandsCpu(allocator, &graph, encoded, encoded_images, shrunk_bins, 0, steady_frame_commands.len, 8, 5, shrunk_pixels[0..], .{ .a = 0 });    try std.testing.expectEqualSlices(u32, shrunk_expected[0..], shrunk_pixels[0..]);    var grown_pixels = @as([(32 * 20)]u32, @splat(0));    try runPackedCommandsCpu(allocator, &graph, encoded, encoded_images, grown_bins, 0, steady_frame_commands.len, 32, 20, grown_pixels[0..], .{ .a = 0 });    try std.testing.expectEqualSlices(u32, grown_expected[0..], grown_pixels[0..]);    try std.testing.expect(!failing.has_induced_failure);}test "Accy failed frame acquisition leaves the graph reusable" {    var graph = try buildGraph(std.testing.allocator, default_threads);    defer graph.deinit();    var encoded = try packCommandsAlloc(std.testing.allocator, steady_frame_commands[0..]);    defer encoded.deinit(std.testing.allocator);    var encoded_images = try packImagesAlloc(std.testing.allocator, .{});    defer encoded_images.deinit(std.testing.allocator);    var bins = try binCommandsAlloc(std.testing.allocator, steady_frame_commands[0..], 8, 5, Region.full(8, 5));    defer bins.deinit(std.testing.allocator);    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });    var actual = @as([(8 * 5)]u32, @splat(0));    try std.testing.expectError(error.OutOfMemory, runPackedCommandsCpu(failing.allocator(), &graph, encoded, encoded_images, bins, 0, steady_frame_commands.len, 8, 5, actual[0..], .{ .a = 0 }));    try std.testing.expect(failing.has_induced_failure);    failing.fail_index = std.math.maxInt(usize);    try runPackedCommandsCpu(failing.allocator(), &graph, encoded, encoded_images, bins, 0, steady_frame_commands.len, 8, 5, actual[0..], .{ .a = 0 });    var expected = @as([(8 * 5)]u32, @splat(0));    try cpu.renderCommandsPacked(steady_frame_commands[0..], .{ .width = 8, .height = 5, .pixels = expected[0..] }, .{ .a = 0 });    try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);}fn runPaintFrameLifecycle(allocator: Allocator, expected: *const [8 * 5]u32) !void {    var graph = try buildGraph(std.testing.allocator, default_threads);    defer graph.deinit();    var encoded = try packCommandsAlloc(allocator, steady_frame_commands[0..]);    defer encoded.deinit(allocator);    var encoded_images = try packImagesAlloc(allocator, .{});    defer encoded_images.deinit(allocator);    var bins = try binCommandsAlloc(allocator, steady_frame_commands[0..], 8, 5, Region.full(8, 5));    defer bins.deinit(allocator);    var actual = @as([(8 * 5)]u32, @splat(0));    try runPackedCommandsCpu(allocator, &graph, encoded, encoded_images, bins, 0, steady_frame_commands.len, 8, 5, actual[0..], .{ .a = 0 });    if (!std.mem.eql(u32, expected[0..], actual[0..])) return error.TestUnexpectedResult;}test "Accy frame lifecycle releases every acquisition failure and retries" {    var expected = @as([(8 * 5)]u32, @splat(0));    try cpu.renderCommandsPacked(steady_frame_commands[0..], .{ .width = 8, .height = 5, .pixels = expected[0..] }, .{ .a = 0 });    try runPaintFrameLifecycle(std.testing.allocator, &expected);    try std.testing.checkAllAllocationFailures(std.testing.allocator, runPaintFrameLifecycle, .{&expected});    try runPaintFrameLifecycle(std.testing.allocator, &expected);}test "Accy zero-size regions render without allocator use" {    var graph = try buildGraph(std.testing.allocator, default_threads);    defer graph.deinit();    var encoded = try packCommandsAlloc(std.testing.allocator, steady_frame_commands[0..]);    defer encoded.deinit(std.testing.allocator);    var encoded_images = try packImagesAlloc(std.testing.allocator, .{});    defer encoded_images.deinit(std.testing.allocator);    var bins = try binCommandsAlloc(std.testing.allocator, steady_frame_commands[0..], 8, 5, Region.full(8, 5));    defer bins.deinit(std.testing.allocator);    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });    var pixels = @as([(8 * 5)]u32, @splat(0xdead_beef));    try runPackedCommandsCpuRegion(failing.allocator(), &graph, encoded, encoded_images, bins, 0, steady_frame_commands.len, 8, 5, pixels[0..], .{ .a = 0 }, .{ .x = 0, .y = 0, .width = 0, .height = 0 });    try std.testing.expect(!failing.has_induced_failure);    try std.testing.expectEqualSlices(u32, &(@as([(8 * 5)]u32, @splat(0xdead_beef))), pixels[0..]);}test "Accy compositor matches recorded frame rasterization" {    const allocator = std.testing.allocator;    const rows = [_]gui.model.UiNode{        .{            .widget_id = 4,            .paint = .{                .background = .{ .r = 60, .g = 90, .b = 132, .a = 210 },                .border = .{ .r = 20, .g = 30, .b = 42, .a = 120 },                .border_width = 1,                .corner_roundness = 3,            },            .size = .{ .height = 8 },        },        .{            .widget_id = 5,            .paint = .{                .background = .{ .r = 112, .g = 78, .b = 132, .a = 190 },                .border = .{ .r = 30, .g = 24, .b = 44, .a = 100 },                .border_width = 1,                .corner_roundness = 3,            },            .size = .{ .height = 8 },        },    };    const children = [_]gui.model.UiNode{        .{            .widget_id = 2,            .paint = .{                .background = .{ .r = 34, .g = 40, .b = 54, .a = 240 },                .border = .{ .r = 90, .g = 120, .b = 150, .a = 160 },                .border_width = 1,                .corner_roundness = 4,            },            .size = .{ .height = 10 },        },        .{            .widget_id = 3,            .style = .{                .flex_direction = .column,                .gap = 2,                .padding = .{ .top = 2, .left = 2, .right = 2, .bottom = 2 },                .flex_grow = 1,            },            .paint = .{                .background = .{ .r = 225, .g = 230, .b = 236, .a = 240 },                .border = .{ .r = 40, .g = 50, .b = 62, .a = 100 },                .border_width = 1,                .corner_roundness = 5,                .shadow = .{                    .color = .{ .r = 0, .g = 0, .b = 0, .a = 64 },                    .offset_y = 1,                    .blur_radius = 3,                },            },            .children = rows[0..],        },    };    const surface = gui.model.UiSurfaceTree{        .available_size = .{ .width = 48, .height = 40 },        .root = .{            .widget_id = 1,            .style = .{                .flex_direction = .column,                .gap = 3,                .padding = .{ .top = 3, .left = 3, .right = 3, .bottom = 3 },            },            .paint = .{ .background = .{ .r = 16, .g = 18, .b = 22, .a = 255 } },            .children = children[0..],        },    };    var frame_workspace = gui.frame.Workspace.init(allocator);    defer frame_workspace.deinit();    const frame = try frame_workspace.buildSurface(&surface, .{});    var commands = command.CommandBuffer.init(allocator);    defer commands.deinit();    try commands.appendFrame(frame, 1, 48, 40, .{});    var expected = @as([(48 * 40)]u32, @splat(0));    var actual = @as([(48 * 40)]u32, @splat(0));    try cpu.renderCommandsPacked(commands.items(), .{ .width = 48, .height = 40, .pixels = expected[0..] }, .{ .a = 0 });    try renderCommandsCpu(allocator, commands.items(), 48, 40, actual[0..], .{ .a = 0 });    try std.testing.expect(commands.items().len > 0);    try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);}test "Accy compositor creates recording artifacts for native GPU targets" {    const allocator = std.testing.allocator;    inline for (.{ gpu.ArtifactFormat.cuda_ptx, .vulkan_spirv, .metal_msl }) |format| {        var artifact = try createRecordingArtifact(allocator, format);        defer artifact.deinit();        try std.testing.expectEqual(format, artifact.format);        try std.testing.expectEqualStrings(kernel_name, artifact.entry_name);        var range_artifact = try createTileRangeRecordingArtifact(allocator, format);        defer range_artifact.deinit();        try std.testing.expectEqual(format, range_artifact.format);        try std.testing.expectEqualStrings(tile_range_kernel_name, range_artifact.entry_name);        var count_artifact = try createTileCountRecordingArtifact(allocator, format);        defer count_artifact.deinit();        try std.testing.expectEqual(format, count_artifact.format);        try std.testing.expectEqualStrings(tile_count_kernel_name, count_artifact.entry_name);        var index_artifact = try createTileIndexRecordingArtifact(allocator, format);        defer index_artifact.deinit();        try std.testing.expectEqual(format, index_artifact.format);        try std.testing.expectEqualStrings(tile_index_kernel_name, index_artifact.entry_name);        var sort_artifact = try createTileSortRecordingArtifact(allocator, format);        defer sort_artifact.deinit();        try std.testing.expectEqual(format, sort_artifact.format);        try std.testing.expectEqualStrings(tile_sort_kernel_name, sort_artifact.entry_name);    }}

Source: lib/gui/src/paint/root.zig:1

zig
pub const accy = @import("accy.zig");

Complete caller list for paint.accy.binCommandsAlloc

8 direct callers.

Complete caller list for paint.accy.binShape

10 direct callers.

Complete caller list for paint.accy.buildGraph

7 direct callers.

Complete caller list for paint.accy.packCommandsAlloc

9 direct callers.

Complete caller list for paint.accy.tileRangeValueCount

13 direct callers.

Audit

Definitions65
Public names65
Members22
Version26.7.0
Revisiondaab053ee433