tiny.gui.paint.accy
Defined in paint.
API (64)
Actions
Public operations.
BinScratch.binCommandsBinView.pairCountBinView.tileCountBins.deinitBins.pairCountBins.tileCountBins.viewPackedCommands.deinitPackedImages.deinitbinCommandsAllocbinPairCountbinShapebuildGraphbuildTileCountGraphbuildTileIndexGraphbuildTileRangeGraphbuildTileSortGraphcommandVisitscommandsEqualPackedcreateRecordingArtifactcreateTileCountRecordingArtifactcreateTileIndexRecordingArtifactcreateTileRangeRecordingArtifactcreateTileSortRecordingArtifactgridForimageMetadataEqualPackedimagePixelCountimagesEqualPackedpackCommandspackCommandsAllocpackImagespackImagesAllocrenderCommandsCpurenderCommandsCpuRegionWithImagesrenderCommandsCpuWithImagesrunPackedCommandsCpurunPackedCommandsCpuRegionrunPackedTileCountsCpurunPackedTileIndicesCpurunPackedTileOffsetsCpurunPackedTileRangesCpurunPackedTileRangesCpuRegionrunPackedTileSortCputileRangeValueCounttilesForExtent
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
default_threadsfloat_lanesimage_laneskernel_nametile_count_kernel_nametile_index_kernel_nametile_range_kernel_nametile_range_lanestile_sizetile_size_log2tile_sort_kernel_nameword_lanes
Source
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.
tiny.gui.paint.accy.renderCommandsCpuRegionWithImages[function] atlib/gui/src/paint/accy.zig:648lib.gui.src.paint.accy.runPaintFrameLifecycle[function] — private source atlib/gui/src/paint/accy.zig:2919in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_failed_frame_acquisition_leaves_the_graph_reusable[function] — test source atlib/gui/src/paint/accy.zig:2896in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_steady_frames_make_no_allocator_calls_at_a_fixed_surface_epoch[function] — test source atlib/gui/src/paint/accy.zig:2830in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_surface_epochs_stay_allocation-free_after_one_full-block_frame[function] — test source atlib/gui/src/paint/accy.zig:2859in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_zero-size_regions_render_without_allocator_use[function] — test source atlib/gui/src/paint/accy.zig:2943in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_binCommandsAlloc_orders_tile_command_lists_by_paint_order[function] — test source atlib/gui/src/paint/accy.zig:2232in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_commandVisits_counts_per-pixel_tile_command_work[function] — test source atlib/gui/src/paint/accy.zig:2264in nearest public ownertiny.gui.paint.accy
Complete caller list for paint.accy.binShape
10 direct callers.
tiny.gui.paint.accy.BinScratch.binCommands[method] atlib/gui/src/paint/accy.zig:217tiny.gui.paint.accy.binCommandsAlloc[function] atlib/gui/src/paint/accy.zig:345tiny.gui.paint.accy.binPairCount[function] atlib/gui/src/paint/accy.zig:254tiny.gui.paint.accy.runPackedTileRangesCpuRegion[function] atlib/gui/src/paint/accy.zig:761lib.gui.src.paint.accy.test_Accy_tile_count_graph_matches_host_range_counts[function] — test source atlib/gui/src/paint/accy.zig:2420in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_count_scan_covers_multi-block_CSR_offsets[function] — test source atlib/gui/src/paint/accy.zig:2532in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_count_scan_matches_host_CSR_offsets[function] — test source atlib/gui/src/paint/accy.zig:2482in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_index_graph_fills_scanned_CSR_command_lists[function] — test source atlib/gui/src/paint/accy.zig:2567in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_range_graph_matches_host_command_ranges[function] — test source atlib/gui/src/paint/accy.zig:2360in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_sort_graph_orders_scattered_CSR_command_lists[function] — test source atlib/gui/src/paint/accy.zig:2643in nearest public ownertiny.gui.paint.accy
Complete caller list for paint.accy.buildGraph
7 direct callers.
tiny.gui.paint.accy.createRecordingArtifact[function] atlib/gui/src/paint/accy.zig:1283tiny.gui.paint.accy.renderCommandsCpuRegionWithImages[function] atlib/gui/src/paint/accy.zig:648lib.gui.src.paint.accy.runPaintFrameLifecycle[function] — private source atlib/gui/src/paint/accy.zig:2919in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_failed_frame_acquisition_leaves_the_graph_reusable[function] — test source atlib/gui/src/paint/accy.zig:2896in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_steady_frames_make_no_allocator_calls_at_a_fixed_surface_epoch[function] — test source atlib/gui/src/paint/accy.zig:2830in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_surface_epochs_stay_allocation-free_after_one_full-block_frame[function] — test source atlib/gui/src/paint/accy.zig:2859in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_zero-size_regions_render_without_allocator_use[function] — test source atlib/gui/src/paint/accy.zig:2943in nearest public ownertiny.gui.paint.accy
Complete caller list for paint.accy.packCommandsAlloc
9 direct callers.
tiny.gui.paint.accy.renderCommandsCpuRegionWithImages[function] atlib/gui/src/paint/accy.zig:648lib.gui.src.paint.accy.runPaintFrameLifecycle[function] — private source atlib/gui/src/paint/accy.zig:2919in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_failed_frame_acquisition_leaves_the_graph_reusable[function] — test source atlib/gui/src/paint/accy.zig:2896in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_steady_frames_make_no_allocator_calls_at_a_fixed_surface_epoch[function] — test source atlib/gui/src/paint/accy.zig:2830in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_surface_epochs_stay_allocation-free_after_one_full-block_frame[function] — test source atlib/gui/src/paint/accy.zig:2859in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_index_graph_fills_scanned_CSR_command_lists[function] — test source atlib/gui/src/paint/accy.zig:2567in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_range_graph_matches_host_command_ranges[function] — test source atlib/gui/src/paint/accy.zig:2360in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_sort_graph_orders_scattered_CSR_command_lists[function] — test source atlib/gui/src/paint/accy.zig:2643in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_zero-size_regions_render_without_allocator_use[function] — test source atlib/gui/src/paint/accy.zig:2943in nearest public ownertiny.gui.paint.accy
Complete caller list for paint.accy.tileRangeValueCount
13 direct callers.
tiny.gui.paint.accy.BinScratch.binCommands[method] atlib/gui/src/paint/accy.zig:217tiny.gui.paint.accy.binCommandsAlloc[function] atlib/gui/src/paint/accy.zig:345lib.gui.src.paint.accy.fillBinIndicesUnsorted[function] — private source atlib/gui/src/paint/accy.zig:412in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.fillCommandTileRanges[function] — private source atlib/gui/src/paint/accy.zig:315in nearest public ownertiny.gui.paint.accytiny.gui.paint.accy.runPackedTileCountsCpu[function] atlib/gui/src/paint/accy.zig:800tiny.gui.paint.accy.runPackedTileIndicesCpu[function] atlib/gui/src/paint/accy.zig:849tiny.gui.paint.accy.runPackedTileRangesCpuRegion[function] atlib/gui/src/paint/accy.zig:761lib.gui.src.paint.accy.test_Accy_tile_count_graph_matches_host_range_counts[function] — test source atlib/gui/src/paint/accy.zig:2420in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_count_scan_covers_multi-block_CSR_offsets[function] — test source atlib/gui/src/paint/accy.zig:2532in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_count_scan_matches_host_CSR_offsets[function] — test source atlib/gui/src/paint/accy.zig:2482in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_index_graph_fills_scanned_CSR_command_lists[function] — test source atlib/gui/src/paint/accy.zig:2567in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_range_graph_matches_host_command_ranges[function] — test source atlib/gui/src/paint/accy.zig:2360in nearest public ownertiny.gui.paint.accylib.gui.src.paint.accy.test_Accy_tile_sort_graph_orders_scattered_CSR_command_lists[function] — test source atlib/gui/src/paint/accy.zig:2643in nearest public ownertiny.gui.paint.accy
Audit
| Definitions | 65 |
|---|---|
| Public names | 65 |
| Members | 22 |
| Version | 26.7.0 |
| Revision | daab053ee433 |