tiny.gui.paint.executor
Defined in paint.
API (49)
Actions
Public operations.
Capacity.admitsCapacity.deriveExecutor.deinitExecutor.expandImageShadowsAllocExecutor.initExecutor.prepareCommandsPackedLaunchExecutor.prepareCommandsPackedLaunchWithImageShadowsExecutor.prepareCommandsSurfaceLaunchExecutor.prepareCommandsSurfaceLaunchWithImageShadowsExecutor.preparedLaunchInfoExecutor.readPreparedPackedPixelsExecutor.renderCommandsExecutor.renderCommandsPackedExecutor.renderCommandsPackedRegionWithImageShadowsExecutor.renderCommandsPackedRegionWithImagesExecutor.renderCommandsPackedWithImageShadowsExecutor.renderCommandsPackedWithImagesExecutor.renderCommandsRegionWithImageShadowsExecutor.renderCommandsRegionWithImagesExecutor.renderCommandsSurfaceFrameExecutor.renderCommandsSurfaceFrameWithImageShadowsExecutor.renderCommandsSurfaceFrameWithImagesExecutor.renderCommandsWithImageShadowsExecutor.renderCommandsWithImagesExecutor.storageStatusExecutor.submitPreparedLaunchExecutor.submitPreparedLaunchQueuedExecutor.submitPreparedLaunchQueuedWithEventsExecutor.submitPreparedLaunchTimedExecutor.writePreparedSurfaceFrameExecutor.writePreparedSurfaceFrameWithEventsExecutorHostStorage.activateExecutorHostStorage.deinitExecutorHostStorage.initLimits.worstCasestorageModeForArtifactFormat
Types and contracts
Public types and contracts.
CapacityExecutorExecutorHostStorageExecutorHostStorage.CapacityExecutorHostStorage.LimitsLimitsOptionsPreparedLaunchInfoPreparedLaunchTimingPreparedPackedLaunchStorageModeStorageStatus
Values and defaults
Public values and defaults.
Source
Source: lib/gui/src/paint/executor.zig
zig
const std = @import("std");const gpu = @import("gpu");const choir_abi = @import("choir_abi");const accy = @import("accy");const alloc_phase = @import("alloc_phase");const accy_paint = @import("accy.zig");const command = @import("command.zig");const cpu = @import("cpu/root.zig");const gui = @import("../root.zig");const paint_image = @import("image.zig");const Allocator = std.mem.Allocator;const Color = gui.model.UiColor;const Command = command.Command;const ImageSet = command.ImageSet;const kernel = accy.kernel;const scan_library = kernel.library.scan;const host_loop_launch_shape_arg_count: usize = choir_abi.launch_shape_argument_count;const PaintTarget = struct { width: u32, height: u32, output_format: accy_paint.OutputFormat,};const LoadedKernel = struct { artifact: gpu.KernelArtifact, loaded: gpu.LoadedArtifact, fn deinit(self: *LoadedKernel, handle: gpu.BackendHandle) void { handle.destroyObject(self.loaded.id); self.artifact.deinit(); self.* = undefined; }};const DeviceScanKernels = struct { instance: scan_library.DeviceScan, stages: scan_library.DeviceScanStages, block_scan: LoadedKernel, sums_scan: LoadedKernel, add_base: LoadedKernel, fn deinit(self: *DeviceScanKernels, handle: gpu.BackendHandle) void { self.add_base.deinit(handle); self.sums_scan.deinit(handle); self.block_scan.deinit(handle); self.* = undefined; }};pub const PreparedPackedLaunch = struct { pixels: gpu.BufferHandle, pixel_count: usize, target_width: u32, target_height: u32, region_x: u32, region_y: u32, region_width: u32, region_height: u32, output_format: accy_paint.OutputFormat, generation: u64, loaded_artifact_id: gpu.BackendObjectId, bindings: [7]gpu.BufferBinding, scalar_storage: [11 + host_loop_launch_shape_arg_count]choir_abi.ScalarArgument, scalar_count: usize, geometry: choir_abi.LaunchGeometry, command_visits: usize, device_csr_prepared: bool, fn request( self: *const PreparedPackedLaunch, artifact: *gpu.KernelArtifact, loaded: gpu.LoadedArtifact, ) gpu.LaunchRequest { return .{ .artifact = artifact, .loaded_artifact = loaded, .buffers = self.bindings[0..], .scalar_arguments = self.scalar_storage[0..self.scalar_count], .geometry = self.geometry, }; }};pub const PreparedLaunchTiming = struct { elapsed_ns: u64, stream_id: gpu.BackendObjectId,};pub const PreparedLaunchInfo = struct { entry_name: []const u8, artifact_format: gpu.ArtifactFormat, artifact_payload_bytes: usize, pixel_count: usize, geometry: choir_abi.LaunchGeometry, device_csr_prepared: bool,};pub const StorageMode = enum { host_bins, device_csr,};pub fn storageModeForArtifactFormat(format: gpu.ArtifactFormat) StorageMode { return if (gpu.artifactFormatUsesHostLoopLaunch(format)) .host_bins else .device_csr;}const ExecutorLimits = struct { mode: StorageMode, commands: usize = 0, pixels: usize = 0, images: usize = 0, image_pixels: usize = 0, tiles: usize = 0, tile_pairs: usize = 0, pub fn worstCase( mode: StorageMode, commands: usize, pixels: usize, images: usize, image_pixels: usize, tiles: usize, ) error{CapacityOverflow}!Limits { return .{ .mode = mode, .commands = commands, .pixels = pixels, .images = images, .image_pixels = image_pixels, .tiles = tiles, .tile_pairs = std.math.mul(usize, commands, tiles) catch return error.CapacityOverflow, }; } fn merged(self: Limits, demand: Limits) error{StorageModeMismatch}!Limits { if (self.mode != demand.mode) return error.StorageModeMismatch; return .{ .mode = self.mode, .commands = @max(self.commands, demand.commands), .pixels = @max(self.pixels, demand.pixels), .images = @max(self.images, demand.images), .image_pixels = @max(self.image_pixels, demand.image_pixels), .tiles = @max(self.tiles, demand.tiles), .tile_pairs = @max(self.tile_pairs, demand.tile_pairs), }; }};pub const Limits = ExecutorLimits;const ExecutorCapacity = struct { limits: Limits, command_capacity: usize, pixel_capacity: usize, image_capacity: usize, image_pixel_capacity: usize, tile_range_capacity: usize, tile_offset_capacity: usize, tile_count_capacity: usize, tile_index_capacity: usize, float_count: usize, word_count: usize, image_metadata_count: usize, host_range_count: usize, host_offset_count: usize, host_index_count: usize, host_cursor_count: usize, host_storage_bytes: usize, device_storage_bytes: usize, device_buffer_count: usize, total_storage_bytes: usize, pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity { const command_capacity = @max(limits.commands, 1); const pixel_capacity = @max(limits.pixels, 1); const image_capacity = @max(limits.images, 1); const image_pixel_capacity = @max(limits.image_pixels, 1); const tile_range_capacity = @max( std.math.mul(usize, limits.commands, accy_paint.tile_range_lanes) catch return error.CapacityOverflow, 1, ); const tile_offset_capacity = @max( std.math.add(usize, limits.tiles, 1) catch return error.CapacityOverflow, 2, ); const tile_count_capacity = @max(limits.tiles, 1); const tile_index_capacity = @max(limits.tile_pairs, 1); const float_count = std.math.mul(usize, command_capacity, accy_paint.float_lanes) catch return error.CapacityOverflow; const word_count = std.math.mul(usize, command_capacity, accy_paint.word_lanes) catch return error.CapacityOverflow; const image_metadata_count = std.math.mul(usize, image_capacity, accy_paint.image_lanes) catch return error.CapacityOverflow; const host_range_count = if (limits.mode == .host_bins) tile_range_capacity else 0; const host_offset_count = if (limits.mode == .host_bins) tile_offset_capacity else 0; const host_index_count = if (limits.mode == .host_bins) tile_index_capacity else 0; const host_cursor_count = if (limits.mode == .host_bins) tile_count_capacity else 0; const host_element_count = try sumChecked(&.{ float_count, word_count, image_metadata_count, image_pixel_capacity, pixel_capacity, tile_offset_capacity, host_range_count, host_offset_count, host_index_count, host_cursor_count, }); const host_storage_bytes = std.math.mul(usize, host_element_count, @sizeOf(u32)) catch return error.CapacityOverflow; const common_device_elements = try sumChecked(&.{ pixel_capacity, float_count, word_count, image_metadata_count, image_pixel_capacity, tile_offset_capacity, tile_index_capacity, }); const device_csr_elements = if (limits.mode == .device_csr) try sumChecked(&.{ tile_range_capacity, tile_offset_capacity, tile_count_capacity, scan_library.device_scan_max_blocks, scan_library.device_scan_max_blocks, }) else 0; const device_elements = std.math.add(usize, common_device_elements, device_csr_elements) catch return error.CapacityOverflow; const device_storage_bytes = std.math.mul(usize, device_elements, @sizeOf(u32)) catch return error.CapacityOverflow; return .{ .limits = limits, .command_capacity = command_capacity, .pixel_capacity = pixel_capacity, .image_capacity = image_capacity, .image_pixel_capacity = image_pixel_capacity, .tile_range_capacity = tile_range_capacity, .tile_offset_capacity = tile_offset_capacity, .tile_count_capacity = tile_count_capacity, .tile_index_capacity = tile_index_capacity, .float_count = float_count, .word_count = word_count, .image_metadata_count = image_metadata_count, .host_range_count = host_range_count, .host_offset_count = host_offset_count, .host_index_count = host_index_count, .host_cursor_count = host_cursor_count, .host_storage_bytes = host_storage_bytes, .device_storage_bytes = device_storage_bytes, .device_buffer_count = if (limits.mode == .device_csr) 12 else 7, .total_storage_bytes = std.math.add(usize, host_storage_bytes, device_storage_bytes) catch return error.CapacityOverflow, }; } pub fn admits(self: Capacity, demand: Limits) bool { return self.limits.mode == demand.mode and self.limits.commands >= demand.commands and self.limits.pixels >= demand.pixels and self.limits.images >= demand.images and self.limits.image_pixels >= demand.image_pixels and self.limits.tiles >= demand.tiles and self.limits.tile_pairs >= demand.tile_pairs; }};pub const Capacity = ExecutorCapacity;pub const StorageStatus = struct { limits: ?Limits, capacity: ?Capacity, replacements: usize, image_processor: ?paint_image.StorageStatus,};fn sumChecked(values: []const usize) error{CapacityOverflow}!usize { var total: usize = 0; for (values) |value| { total = std.math.add(usize, total, value) catch return error.CapacityOverflow; } return total;}pub const Options = struct { artifact_format: ?gpu.ArtifactFormat = null, threads: u32 = accy_paint.default_threads, initial_storage: ?Limits = null, image_storage: ?paint_image.Limits = null,};pub const Executor = struct { allocator: Allocator, handle: gpu.BackendHandle, artifact: gpu.KernelArtifact, loaded: gpu.LoadedArtifact, tile_range: ?LoadedKernel = null, tile_count: ?LoadedKernel = null, tile_index: ?LoadedKernel = null, tile_scan: ?DeviceScanKernels = null, threads: u32, buffers: ?Buffers = null, image_processor: ?paint_image.Processor = null, shadow_expansion: ?CachedShadowExpansion = null, storage_replacements: usize = 0, prepared_generation: u64 = 0, pub fn init(allocator: Allocator, handle: gpu.BackendHandle, options: Options) !Executor { const format = options.artifact_format orelse try defaultFormat(handle); const threads = @max(options.threads, 1); var graph = try accy_paint.buildGraph(allocator, threads); defer graph.deinit(); var artifact = try kernel.createKernelArtifact(allocator, handle, &graph, .{ .artifact_format = format, .authored_kernel_diagnostic_id = "gui/paint/executor/rgba8-packed", }); errdefer artifact.deinit(); const loaded = try handle.loadArtifact(&artifact); errdefer handle.destroyObject(loaded.id); var tile_range: ?LoadedKernel = null; errdefer if (tile_range) |*value| value.deinit(handle); var tile_count: ?LoadedKernel = null; errdefer if (tile_count) |*value| value.deinit(handle); var tile_index: ?LoadedKernel = null; errdefer if (tile_index) |*value| value.deinit(handle); var tile_scan: ?DeviceScanKernels = null; errdefer if (tile_scan) |*value| value.deinit(handle); if (!gpu.artifactFormatUsesHostLoopLaunch(format)) { tile_range = try loadGraphKernel(allocator, handle, format, threads, accy_paint.buildTileRangeGraph, "gui/paint/executor/tile-ranges"); tile_count = try loadGraphKernel(allocator, handle, format, threads, accy_paint.buildTileCountGraph, "gui/paint/executor/tile-counts"); tile_index = try loadGraphKernel(allocator, handle, format, threads, accy_paint.buildTileIndexGraph, "gui/paint/executor/tile-indices"); tile_scan = try loadDeviceScanKernels(allocator, handle, format); } var result = Executor{ .allocator = allocator, .handle = handle, .artifact = artifact, .loaded = loaded, .tile_range = tile_range, .tile_count = tile_count, .tile_index = tile_index, .tile_scan = tile_scan, .threads = threads, }; if (options.initial_storage) |limits| { if (limits.mode != storageModeForArtifactFormat(format)) return error.StorageModeMismatch; result.buffers = try Buffers.init(allocator, handle, limits); } errdefer result.releaseBuffers(); if (options.image_storage) |limits| { result.image_processor = try paint_image.Processor.init(allocator, handle, .{ .artifact_format = format, .initial_storage = limits, }); } return result; } pub fn deinit(self: *Executor) void { if (self.shadow_expansion) |*cached| cached.deinit(self.allocator); if (self.image_processor) |*processor| processor.deinit(); self.releaseBuffers(); if (self.tile_scan) |*value| value.deinit(self.handle); if (self.tile_index) |*value| value.deinit(self.handle); if (self.tile_count) |*value| value.deinit(self.handle); if (self.tile_range) |*value| value.deinit(self.handle); self.handle.destroyObject(self.loaded.id); self.artifact.deinit(); self.* = undefined; } pub fn storageStatus(self: *const Executor) StorageStatus { return .{ .limits = if (self.buffers) |buffers| buffers.capacity.limits else null, .capacity = if (self.buffers) |buffers| buffers.capacity else null, .replacements = self.storage_replacements, .image_processor = if (self.image_processor) |*processor| processor.storageStatus() else null, }; } pub fn renderCommands(self: *Executor, commands: []const Command, target: cpu.Target, clear: Color) !void { try self.renderCommandsWithImages(commands, target, clear, .{}); } pub fn renderCommandsWithImageShadows( self: *Executor, commands: []const Command, target: cpu.Target, clear: Color, images: ImageSet, shadow_options: paint_image.ShadowExpansionOptions, ) !void { const expansion = try self.cachedImageShadows(commands, images, shadow_options); try self.renderCommandsWithImages(expansion.commands, target, clear, expansion.imageSet()); } pub fn renderCommandsWithImages(self: *Executor, commands: []const Command, target: cpu.Target, clear: Color, images: ImageSet) !void { try target.validate(); const pixel_count = try pixelCount(target.width, target.height); if (pixel_count == 0) return; const pixels = try self.renderPacked(commands, target.width, target.height, clear, images, cpu.Region.full(target.width, target.height)); try cpu.rgba8FromPacked(target.rgba8, pixels); } pub fn renderCommandsRegionWithImageShadows( self: *Executor, commands: []const Command, target: cpu.Target, clear: Color, images: ImageSet, region: cpu.Region, shadow_options: paint_image.ShadowExpansionOptions, ) !void { const expansion = try self.cachedImageShadows(commands, images, shadow_options); try self.renderCommandsRegionWithImages(expansion.commands, target, clear, expansion.imageSet(), region); } pub fn renderCommandsRegionWithImages(self: *Executor, commands: []const Command, target: cpu.Target, clear: Color, images: ImageSet, region: cpu.Region) !void { try target.validate(); const pixel_count = try pixelCount(target.width, target.height); if (pixel_count == 0) return; const active_region = region.clamped(target.width, target.height); if (active_region.pixelCount() == 0) return; const pixels = try self.renderPacked(commands, target.width, target.height, clear, images, active_region); try copyRgba8Region(target.rgba8, target.width, active_region, pixels); } pub fn renderCommandsPacked(self: *Executor, commands: []const Command, target: cpu.PackedTarget, clear: Color) !void { try self.renderCommandsPackedWithImages(commands, target, clear, .{}); } pub fn renderCommandsPackedWithImageShadows( self: *Executor, commands: []const Command, target: cpu.PackedTarget, clear: Color, images: ImageSet, shadow_options: paint_image.ShadowExpansionOptions, ) !void { const expansion = try self.cachedImageShadows(commands, images, shadow_options); try self.renderCommandsPackedWithImages(expansion.commands, target, clear, expansion.imageSet()); } pub fn renderCommandsPackedWithImages(self: *Executor, commands: []const Command, target: cpu.PackedTarget, clear: Color, images: ImageSet) !void { try target.validate(); const pixel_count = try pixelCount(target.width, target.height); if (pixel_count == 0) return; const pixels = try self.renderPacked(commands, target.width, target.height, clear, images, cpu.Region.full(target.width, target.height)); @memcpy(target.pixels[0..pixel_count], pixels); } pub fn renderCommandsSurfaceFrame(self: *Executor, commands: []const Command, surface_frame: gpu.SurfaceFrame, clear: Color) !void { try self.renderCommandsSurfaceFrameWithImages(commands, surface_frame, clear, .{}); } pub fn renderCommandsSurfaceFrameWithImageShadows( self: *Executor, commands: []const Command, surface_frame: gpu.SurfaceFrame, clear: Color, images: ImageSet, shadow_options: paint_image.ShadowExpansionOptions, ) !void { var prepared = (try self.prepareCommandsSurfaceLaunchWithImageShadows(commands, surface_frame.surface, clear, images, shadow_options)) orelse return; try self.submitPreparedLaunchQueued(&prepared); try self.writePreparedSurfaceFrame(surface_frame, &prepared); } pub fn renderCommandsSurfaceFrameWithImages(self: *Executor, commands: []const Command, surface_frame: gpu.SurfaceFrame, clear: Color, images: ImageSet) !void { const target = try surfaceFramePaintTarget(surface_frame); var prepared = (try self.prepareCommandsFormattedLaunch(commands, target.width, target.height, clear, images, cpu.Region.full(target.width, target.height), target.output_format)) orelse return; try self.submitPreparedLaunchQueued(&prepared); try self.writePreparedSurfaceFrame(surface_frame, &prepared); } pub fn renderCommandsPackedRegionWithImageShadows( self: *Executor, commands: []const Command, target: cpu.PackedTarget, clear: Color, images: ImageSet, region: cpu.Region, shadow_options: paint_image.ShadowExpansionOptions, ) !void { const expansion = try self.cachedImageShadows(commands, images, shadow_options); try self.renderCommandsPackedRegionWithImages(expansion.commands, target, clear, expansion.imageSet(), region); } pub fn renderCommandsPackedRegionWithImages(self: *Executor, commands: []const Command, target: cpu.PackedTarget, clear: Color, images: ImageSet, region: cpu.Region) !void { try target.validate(); const pixel_count = try pixelCount(target.width, target.height); if (pixel_count == 0) return; const active_region = region.clamped(target.width, target.height); if (active_region.pixelCount() == 0) return; const pixels = try self.renderPacked(commands, target.width, target.height, clear, images, active_region); copyPackedRegion(target.pixels, target.width, active_region, pixels); } pub fn expandImageShadowsAlloc( self: *Executor, commands: []const Command, images: ImageSet, options: paint_image.ShadowExpansionOptions, ) !paint_image.ShadowExpansion { const processor = try self.imageProcessor(); return processor.expandShadowsAlloc(commands, images, options); } fn cachedImageShadows( self: *Executor, commands: []const Command, images: ImageSet, options: paint_image.ShadowExpansionOptions, ) !*const paint_image.ShadowExpansion { if (self.shadow_expansion) |*cached| { if (try cached.matches(commands, images, options)) return &cached.expansion; if (try cached.refreshReusable(commands, images, options)) return &cached.expansion; } const processor = try self.imageProcessor(); var next = try CachedShadowExpansion.init(self.allocator, processor, commands, images, options); errdefer next.deinit(self.allocator); if (self.shadow_expansion) |*cached| { cached.deinit(self.allocator); self.shadow_expansion = null; } self.shadow_expansion = next; if (self.shadow_expansion) |*cached| return &cached.expansion; unreachable; } fn imageProcessor(self: *Executor) !*paint_image.Processor { if (self.image_processor) |*processor| return processor; self.image_processor = try paint_image.Processor.init(self.allocator, self.handle, .{ .artifact_format = self.artifact.format, }); if (self.image_processor) |*processor| return processor; unreachable; } fn renderPacked(self: *Executor, commands: []const Command, width: u32, height: u32, clear: Color, images: ImageSet, region: cpu.Region) ![]const u32 { var prepared = (try self.prepareCommandsPackedLaunch(commands, width, height, clear, images, region)) orelse return &.{}; try self.submitPreparedLaunch(&prepared); return self.readPreparedPackedPixels(&prepared); } pub fn prepareCommandsPackedLaunch(self: *Executor, commands: []const Command, width: u32, height: u32, clear: Color, images: ImageSet, region: cpu.Region) !?PreparedPackedLaunch { return self.prepareCommandsFormattedLaunch(commands, width, height, clear, images, region, .rgba); } pub fn prepareCommandsPackedLaunchWithImageShadows( self: *Executor, commands: []const Command, width: u32, height: u32, clear: Color, images: ImageSet, region: cpu.Region, shadow_options: paint_image.ShadowExpansionOptions, ) !?PreparedPackedLaunch { const expansion = try self.cachedImageShadows(commands, images, shadow_options); return self.prepareCommandsPackedLaunch(expansion.commands, width, height, clear, expansion.imageSet(), region); } pub fn prepareCommandsSurfaceLaunch(self: *Executor, commands: []const Command, surface: gpu.SurfaceHandle, clear: Color, images: ImageSet) !?PreparedPackedLaunch { const target = try surfacePaintTarget(surface); return self.prepareCommandsFormattedLaunch(commands, target.width, target.height, clear, images, cpu.Region.full(target.width, target.height), target.output_format); } pub fn prepareCommandsSurfaceLaunchWithImageShadows( self: *Executor, commands: []const Command, surface: gpu.SurfaceHandle, clear: Color, images: ImageSet, shadow_options: paint_image.ShadowExpansionOptions, ) !?PreparedPackedLaunch { const target = try surfacePaintTarget(surface); const expansion = try self.cachedImageShadows(commands, images, shadow_options); return self.prepareCommandsFormattedLaunch(expansion.commands, target.width, target.height, clear, expansion.imageSet(), cpu.Region.full(target.width, target.height), target.output_format); } fn preparedLaunchRequest(self: *Executor, prepared: *const PreparedPackedLaunch) !gpu.LaunchRequest { try self.validatePreparedLaunch(prepared); return prepared.request(&self.artifact, self.loaded); } pub fn submitPreparedLaunchQueuedWithEvents( self: *Executor, prepared: *const PreparedPackedLaunch, wait_events: []const gpu.EventHandle, signal_event: ?gpu.EventHandle, ) !void { var request = try self.preparedLaunchRequest(prepared); request.wait_events = wait_events; request.signal_event = signal_event; try self.handle.launch(request); } pub fn submitPreparedLaunchQueued(self: *Executor, prepared: *const PreparedPackedLaunch) !void { try self.submitPreparedLaunchQueuedWithEvents(prepared, &.{}, null); } pub fn submitPreparedLaunchTimed(self: *Executor, prepared: *const PreparedPackedLaunch) !PreparedLaunchTiming { try self.validatePreparedLaunch(prepared); const stream = try self.handle.createStream(.{}); defer self.handle.destroyObject(stream.id); const start = try self.handle.createEvent(.{}); defer self.handle.destroyObject(start.id); const end = try self.handle.createEvent(.{}); defer self.handle.destroyObject(end.id); try self.handle.recordEvent(.{ .stream = stream, .event = start, }); var request = prepared.request(&self.artifact, self.loaded); request.stream = stream; try self.handle.launch(request); try self.handle.recordEvent(.{ .stream = stream, .event = end, }); try self.handle.synchronize(.{ .scope = .event, .event = end, }); const elapsed_ns = try self.handle.elapsedEventNs(.{ .start = start, .end = end, }); return .{ .elapsed_ns = elapsed_ns, .stream_id = stream.id, }; } pub fn preparedLaunchInfo(self: *const Executor, prepared: *const PreparedPackedLaunch) !PreparedLaunchInfo { try self.validatePreparedLaunch(prepared); return .{ .entry_name = self.artifact.entry_name, .artifact_format = self.artifact.format, .artifact_payload_bytes = artifactPayloadBytes(&self.artifact), .pixel_count = prepared.pixel_count, .geometry = prepared.geometry, .device_csr_prepared = prepared.device_csr_prepared, }; } pub fn submitPreparedLaunch(self: *Executor, prepared: *const PreparedPackedLaunch) !void { try self.submitPreparedLaunchQueued(prepared); try self.handle.synchronize(.{ .scope = .device }); } pub fn readPreparedPackedPixels(self: *Executor, prepared: *const PreparedPackedLaunch) ![]const u32 { try self.validatePreparedLaunch(prepared); const buffers = if (self.buffers) |*value| value else unreachable; try self.handle.readBuffer(.{ .handle = prepared.pixels, .bytes = std.mem.sliceAsBytes(buffers.readback[0..buffers.pixel_capacity]), }); return buffers.readback[0..prepared.pixel_count]; } pub fn writePreparedSurfaceFrameWithEvents( self: *Executor, surface_frame: gpu.SurfaceFrame, prepared: *const PreparedPackedLaunch, wait_events: []const gpu.EventHandle, signal_event: ?gpu.EventHandle, ) !void { try self.validatePreparedLaunch(prepared); const target = try surfaceFramePaintTarget(surface_frame); try validatePreparedSurfaceTarget(prepared, target); const operations = [_]gpu.SurfaceFrameWriteOp{.{ .copy_buffer = prepared.pixels }}; try self.handle.writeSurfaceFrame(.{ .surface = surface_frame.surface, .frame = surface_frame, .operations = operations[0..], .wait_events = wait_events, .signal_event = signal_event, }); } pub fn writePreparedSurfaceFrame(self: *Executor, surface_frame: gpu.SurfaceFrame, prepared: *const PreparedPackedLaunch) !void { try self.writePreparedSurfaceFrameWithEvents(surface_frame, prepared, &.{}, null); } fn prepareCommandsFormattedLaunch( self: *Executor, commands: []const Command, width: u32, height: u32, clear: Color, images: ImageSet, region: cpu.Region, output_format: accy_paint.OutputFormat, ) !?PreparedPackedLaunch { const target_pixel_count = try pixelCount(width, height); if (target_pixel_count == 0) return null; const active_region = region.clamped(width, height); const region_pixel_count = active_region.pixelCount(); if (region_pixel_count == 0) return null; const command_count_u32 = std.math.cast(u32, commands.len) orelse return error.CommandCountTooLarge; const image_count = std.math.cast(u32, images.images.len) orelse return error.ImageCountTooLarge; const region_pixel_count_u32 = std.math.cast(u32, region_pixel_count) orelse return error.DimensionsTooLarge; const image_pixels = try accy_paint.imagePixelCount(images); const shape = accy_paint.binShape(width, height, active_region); const tile_offset_count = std.math.add(usize, shape.tile_count, 1) catch return error.BufferTooLarge; const tile_pair_capacity = std.math.mul(usize, commands.len, shape.tile_count) catch return error.BufferTooLarge; _ = std.math.cast(u32, tile_pair_capacity) orelse return error.BufferTooLarge; const use_device_csr = !gpu.artifactFormatUsesHostLoopLaunch(self.artifact.format); const storage_mode: StorageMode = if (use_device_csr) .device_csr else .host_bins; if (use_device_csr and tile_offset_count > scan_library.deviceScanMaxExtent(self.tile_scan.?.instance)) return error.UnsupportedDeviceScanInstance; var host_bins: accy_paint.BinView = undefined; var reuse_host_tile_csr = false; var host_tile_index_count: usize = 0; if (!use_device_csr) { if (self.buffers) |*existing| { const resident_demand = Limits{ .mode = storage_mode, .commands = commands.len, .pixels = region_pixel_count, .images = images.images.len, .image_pixels = image_pixels, .tiles = shape.tile_count, .tile_pairs = existing.tile_csr.tile_index_count, }; if (existing.commandsResident(commands) and existing.tile_csr.matches(commands.len, width, height, active_region, shape) and existing.capacity.admits(resident_demand)) { reuse_host_tile_csr = true; host_tile_index_count = existing.tile_csr.tile_index_count; } } if (!reuse_host_tile_csr) { host_tile_index_count = try accy_paint.binPairCount(commands, width, height, active_region); } } const tile_index_count = if (use_device_csr) tile_pair_capacity else host_tile_index_count; const demand = Limits{ .mode = storage_mode, .commands = commands.len, .pixels = region_pixel_count, .images = images.images.len, .image_pixels = image_pixels, .tiles = shape.tile_count, .tile_pairs = tile_index_count, }; const capacity_changed = try self.ensureCapacity(demand); if (reuse_host_tile_csr) std.debug.assert(!capacity_changed); const buffers = if (self.buffers) |*value| value else unreachable; if (!use_device_csr and !reuse_host_tile_csr) { host_bins = try buffers.host_bins.binCommands(commands, width, height, active_region); std.debug.assert(host_bins.indices.len == host_tile_index_count); } const upload_commands = if (reuse_host_tile_csr) false else buffers.prepareCommandUpload(commands); const image_upload = try buffers.prepareImageUpload(images, image_pixels); const reuse_tile_csr = !upload_commands and buffers.tile_csr.matches(commands.len, width, height, active_region, shape); const resident_changed = capacity_changed or upload_commands or image_upload != .none or !reuse_tile_csr; const generation = if (resident_changed) generation: { try self.ensurePreparedGenerationAvailable(); break :generation self.advancePreparedGeneration(); } else self.prepared_generation; if (commands.len != 0 and upload_commands) { try self.handle.writeBuffer(.{ .handle = buffers.floats, .bytes = std.mem.sliceAsBytes(buffers.staging.floats[0 .. commands.len * accy_paint.float_lanes]), }); try self.handle.writeBuffer(.{ .handle = buffers.words, .bytes = std.mem.sliceAsBytes(buffers.staging.words[0 .. commands.len * accy_paint.word_lanes]), }); } if (image_upload == .metadata_and_pixels) { try self.handle.writeBuffer(.{ .handle = buffers.image_metadata, .bytes = std.mem.sliceAsBytes(buffers.images.metadata[0 .. images.images.len * accy_paint.image_lanes]), }); } if (image_upload != .none) { try self.handle.writeBuffer(.{ .handle = buffers.image_pixels, .bytes = std.mem.sliceAsBytes(buffers.images.pixels[0..@max(image_pixels, 1)]), }); buffers.markImageUpload(images.images.len, image_pixels); } var device_csr_prepared = false; var command_visits: usize = 0; if (use_device_csr) { if (!reuse_tile_csr) { try self.prepareTileCsr(commands.len, command_count_u32, width, height, active_region, shape, tile_offset_count); buffers.markTileCsr(commands.len, width, height, active_region, shape, tile_pair_capacity, 0); device_csr_prepared = true; } } else if (!reuse_tile_csr) { try self.handle.writeBuffer(.{ .handle = buffers.tile_offsets, .bytes = std.mem.sliceAsBytes(host_bins.offsets), }); if (host_bins.indices.len != 0) { try self.handle.writeBuffer(.{ .handle = buffers.tile_indices, .bytes = std.mem.sliceAsBytes(host_bins.indices), }); } command_visits = accy_paint.commandVisits(host_bins, active_region); buffers.markTileCsr(commands.len, width, height, active_region, shape, host_bins.indices.len, command_visits); } else if (!use_device_csr) { command_visits = buffers.tile_csr.command_visits; } const bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.pixels, .read_write), bufferBinding(buffers.floats, .read_only), bufferBinding(buffers.words, .read_only), bufferBinding(buffers.image_metadata, .read_only), bufferBinding(buffers.image_pixels, .read_only), bufferBinding(buffers.tile_offsets, .read_only), bufferBinding(buffers.tile_indices, .read_only), }; const base_scalars = [_]choir_abi.ScalarArgument{ .{ .u32 = image_count }, .{ .u32 = clear.r }, .{ .u32 = clear.g }, .{ .u32 = clear.b }, .{ .u32 = clear.a }, .{ .u32 = active_region.x }, .{ .u32 = active_region.y }, .{ .u32 = active_region.width }, .{ .u32 = region_pixel_count_u32 }, .{ .u32 = shape.tiles_x }, .{ .u32 = @backingInt(output_format) }, }; var scalar_storage: [base_scalars.len + host_loop_launch_shape_arg_count]choir_abi.ScalarArgument = undefined; @memcpy(scalar_storage[0..base_scalars.len], base_scalars[0..]); var scalar_count: usize = base_scalars.len; const geometry = choir_abi.LaunchGeometry{ .grid = .{ accy_paint.gridFor(region_pixel_count, self.threads), 1, 1 }, .threadgroup = .{ self.threads, 1, 1 }, }; if (gpu.artifactFormatUsesHostLoopLaunch(self.artifact.format)) { const launch_shape = try choir_abi.launchShape(region_pixel_count, geometry); try launch_shape.scalarArguments(scalar_storage[scalar_count..]); scalar_count += host_loop_launch_shape_arg_count; } return .{ .pixels = buffers.pixels, .pixel_count = region_pixel_count, .target_width = width, .target_height = height, .region_x = active_region.x, .region_y = active_region.y, .region_width = active_region.width, .region_height = active_region.height, .output_format = output_format, .generation = generation, .loaded_artifact_id = self.loaded.id, .bindings = bindings, .scalar_storage = scalar_storage, .scalar_count = scalar_count, .geometry = geometry, .command_visits = command_visits, .device_csr_prepared = device_csr_prepared, }; } fn prepareTileCsr( self: *Executor, command_count: usize, command_count_u32: u32, width: u32, height: u32, active_region: cpu.Region, shape: accy_paint.BinShape, tile_offset_count: usize, ) !void { const buffers = if (self.buffers) |*value| value else unreachable; if (command_count == 0) { try buffers.writeZeroes(self.handle, buffers.tile_offsets, tile_offset_count); return; } try buffers.writeZeroes(self.handle, buffers.tile_counts.?, tile_offset_count); const range_bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.tile_ranges.?, .read_write), bufferBinding(buffers.floats, .read_only), bufferBinding(buffers.words, .read_only), }; const range_scalars = [_]choir_abi.ScalarArgument{ .{ .u32 = command_count_u32 }, .{ .u32 = width }, .{ .u32 = height }, .{ .u32 = active_region.x }, .{ .u32 = active_region.y }, .{ .u32 = active_region.width }, .{ .u32 = active_region.height }, .{ .u32 = shape.tiles_x }, .{ .u32 = shape.tiles_y }, }; try self.launchLoadedKernel(&self.tile_range.?, range_bindings[0..], range_scalars[0..], .{ .grid = .{ accy_paint.gridFor(command_count, self.threads), 1, 1 }, .threadgroup = .{ self.threads, 1, 1 }, }, command_count); const count_bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.tile_counts.?, .read_write), bufferBinding(buffers.tile_ranges.?, .read_only), }; const count_scalars = [_]choir_abi.ScalarArgument{ .{ .u32 = command_count_u32 }, .{ .u32 = shape.tiles_x }, .{ .u32 = shape.tiles_y }, }; try self.launchLoadedKernel(&self.tile_count.?, count_bindings[0..], count_scalars[0..], .{ .grid = .{ accy_paint.gridFor(shape.tile_count, self.threads), 1, 1 }, .threadgroup = .{ self.threads, 1, 1 }, }, shape.tile_count); try self.launchTileOffsetScan(buffers, tile_offset_count); try buffers.writeZeroes(self.handle, buffers.tile_cursors.?, shape.tile_count); const tile_pair_count = std.math.mul(usize, command_count, shape.tile_count) catch return error.BufferTooLarge; const index_bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.tile_indices, .read_write), bufferBinding(buffers.tile_cursors.?, .read_write), bufferBinding(buffers.tile_offsets, .read_only), bufferBinding(buffers.tile_ranges.?, .read_only), bufferBinding(buffers.words, .read_only), }; const index_scalars = [_]choir_abi.ScalarArgument{ .{ .u32 = command_count_u32 }, .{ .u32 = shape.tiles_x }, .{ .u32 = shape.tiles_y }, }; try self.launchLoadedKernel(&self.tile_index.?, index_bindings[0..], index_scalars[0..], .{ .grid = .{ accy_paint.gridFor(tile_pair_count, self.threads), 1, 1 }, .threadgroup = .{ self.threads, 1, 1 }, }, tile_pair_count); } fn launchTileOffsetScan(self: *Executor, buffers: *Buffers, tile_offset_count: usize) !void { const extent_i32 = std.math.cast(i32, tile_offset_count) orelse return error.BufferTooLarge; const tile_scan = &self.tile_scan.?; const block_count = std.math.cast(u32, scan_library.deviceScanBlockCount(tile_offset_count, tile_scan.instance.threads)) orelse return error.UnsupportedDeviceScanInstance; const block_bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.tile_offsets, .read_write), bufferBinding(buffers.tile_counts.?, .read_only), bufferBinding(buffers.scan_sums.?, .read_write), }; const block_scalars = [_]choir_abi.ScalarArgument{ .{ .i32 = extent_i32 }, }; const block_geometry = choir_abi.LaunchGeometry{ .grid = .{ block_count, 1, 1 }, .threadgroup = .{ tile_scan.instance.threads, 1, 1 }, }; try self.launchLoadedKernel(&tile_scan.block_scan, block_bindings[0..], block_scalars[0..], block_geometry, try block_geometry.threadCount()); const sums_bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.scan_bases.?, .read_write), bufferBinding(buffers.scan_sums.?, .read_only), }; const sums_scalars = [_]choir_abi.ScalarArgument{ .{ .i32 = @intCast(block_count) }, }; const sums_geometry = choir_abi.LaunchGeometry{ .grid = .{ 1, 1, 1 }, .threadgroup = .{ tile_scan.stages.sums_scan.threads, 1, 1 }, }; try self.launchLoadedKernel(&tile_scan.sums_scan, sums_bindings[0..], sums_scalars[0..], sums_geometry, try sums_geometry.threadCount()); const add_bindings = [_]gpu.BufferBinding{ bufferBinding(buffers.tile_offsets, .read_write), bufferBinding(buffers.scan_bases.?, .read_only), }; const add_scalars = [_]choir_abi.ScalarArgument{ .{ .i32 = extent_i32 }, }; const add_geometry = choir_abi.LaunchGeometry{ .grid = .{ block_count, 1, 1 }, .threadgroup = .{ tile_scan.instance.threads, 1, 1 }, }; try self.launchLoadedKernel(&tile_scan.add_base, add_bindings[0..], add_scalars[0..], add_geometry, try add_geometry.threadCount()); } fn launchLoadedKernel( self: *Executor, loaded_kernel: *LoadedKernel, bindings: []const gpu.BufferBinding, scalars: []const choir_abi.ScalarArgument, geometry: choir_abi.LaunchGeometry, total_count: u64, ) !void { var scalar_storage: [16 + host_loop_launch_shape_arg_count]choir_abi.ScalarArgument = undefined; if (scalars.len > 16) return error.LaunchArgumentMismatch; @memcpy(scalar_storage[0..scalars.len], scalars); var scalar_count = scalars.len; if (gpu.artifactFormatUsesHostLoopLaunch(loaded_kernel.artifact.format)) { const shape = try choir_abi.launchShape(total_count, geometry); try shape.scalarArguments(scalar_storage[scalar_count..]); scalar_count += host_loop_launch_shape_arg_count; } try self.handle.launch(.{ .artifact = &loaded_kernel.artifact, .loaded_artifact = loaded_kernel.loaded, .buffers = bindings, .scalar_arguments = scalar_storage[0..scalar_count], .geometry = geometry, }); } fn ensureCapacity(self: *Executor, demand: Limits) !bool { if (self.buffers) |buffers| { if (buffers.capacity.admits(demand)) return false; } const next_limits = if (self.buffers) |buffers| try buffers.capacity.limits.merged(demand) else demand; const next = try Buffers.init(self.allocator, self.handle, next_limits); const replacing = self.buffers != null; self.releaseBuffers(); self.buffers = next; if (replacing) self.storage_replacements += 1; return true; } fn ensurePreparedGenerationAvailable(self: *const Executor) !void { if (self.prepared_generation == std.math.maxInt(u64)) return error.PreparedLaunchGenerationExhausted; } fn advancePreparedGeneration(self: *Executor) u64 { std.debug.assert(self.prepared_generation != std.math.maxInt(u64)); self.prepared_generation += 1; return self.prepared_generation; } fn validatePreparedLaunch(self: *const Executor, prepared: *const PreparedPackedLaunch) !void { if (prepared.loaded_artifact_id != self.loaded.id) return error.InvalidPreparedLaunch; if (prepared.generation != self.prepared_generation) return error.StalePreparedLaunch; } fn releaseBuffers(self: *Executor) void { if (self.buffers) |*buffers| { buffers.deinit(self.allocator, self.handle); self.buffers = null; } }};pub const ExecutorHostStorage = struct { pub const Limits = ExecutorLimits; pub const Capacity = ExecutorCapacity; pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "gui.paint_executor_host_storage", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "command_and_image_upload_staging_for_one_prepared_paint_epoch", .lifetime = .steady, .detail = "command and image upload staging for one prepared paint epoch", }, .{ .id = "packed_readback_zero_fill_and_host_tile_bin_scratch", .lifetime = .steady, .detail = "packed readback zero-fill and host tile-bin scratch", }, }, .excluded = &.{ "common and mode-specific backend buffers and their foreign allocation", "compiled and loaded paint kernel artifacts", "image Processor and cached shadow expansion storage", "caller-owned commands images targets surfaces and prepared values", "transactional complete-epoch replacement by Executor", }, }, .capacity = .{ .inputs = &.{ alloc_phase.capacity.bindInput(ExecutorLimits, "commands", "commands"), alloc_phase.capacity.bindInput(ExecutorLimits, "image_pixels", "image_pixels"), alloc_phase.capacity.bindInput(ExecutorLimits, "images", "images"), alloc_phase.capacity.bindInput(ExecutorLimits, "pixels", "pixels"), alloc_phase.capacity.bindInput(ExecutorLimits, "tile_pairs", "tile_pairs"), alloc_phase.capacity.bindInput(ExecutorLimits, "tiles", "tiles"), }, .type_selectors = &.{}, .nodes = &.{ .{ .input = 0 }, .{ .input = 1 }, .{ .input = 2 }, .{ .input = 3 }, .{ .input = 4 }, .{ .input = 5 }, .{ .add = .{ .left = 0, .right = 1 } }, .{ .add = .{ .left = 6, .right = 2 } }, .{ .add = .{ .left = 7, .right = 3 } }, .{ .add = .{ .left = 8, .right = 4 } }, .{ .add = .{ .left = 9, .right = 5 } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .upper_bound, .expression = 10, }}, }, .overload = .{ .kind = .reject_before_seal, .detail = "checked capacity derivation and allocation failure reject before host storage activation; Executor may acquire a separate larger epoch", }, .risks = .{ .transitive = .{ .status = .open, .detail = "packing binning and backend transfer helpers are exercised but lack a machine-checked call-graph closure certificate", }, .foreign = .{ .status = .open, .detail = "backend buffer acquisition is capacity-accounted by Executor but remains outside this host-storage claim", }, }, .obligations = &.{ .{ .key = "gui_paint_executor_capacity_capacity_model", .role = .capacity_model }, .{ .key = "gui_paint_executor_capacity_overload", .role = .overload }, .{ .key = "gui_paint_executor_acquisition", .role = .custom }, .{ .key = "gui_paint_executor_host_oom", .role = .overload }, .{ .key = "gui_paint_executor_boundary", .role = .custom }, .{ .key = "gui_paint_executor_atomic", .role = .custom }, .{ .key = "gui_paint_executor_steady", .role = .custom }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, }, }; phase: alloc_phase.capacity.Phase, capacity: ExecutorCapacity, limits: ExecutorLimits, bytes: []align(@alignOf(u32)) u8, pub fn init(allocator: Allocator, limits: ExecutorLimits) !ExecutorHostStorage { const capacity = try ExecutorCapacity.derive(limits); const bytes = if (capacity.host_storage_bytes == 0) @as([]align(@alignOf(u32)) u8, &.{}) else try allocator.alignedAlloc( u8, .fromByteUnits(@alignOf(u32)), capacity.host_storage_bytes, ); return .{ .phase = .initialization, .capacity = capacity, .limits = limits, .bytes = bytes, }; } pub fn activate(self: *ExecutorHostStorage) void { std.debug.assert(self.phase == .initialization); std.debug.assert(std.meta.eql(self.capacity.limits, self.limits)); std.debug.assert(self.bytes.len == self.capacity.host_storage_bytes); self.phase = .steady; } pub fn deinit(self: *ExecutorHostStorage, allocator: Allocator) void { std.debug.assert(self.phase != .teardown); self.phase = .teardown; allocator.free(self.bytes); self.* = undefined; }};comptime { alloc_phase.capacity.requireAllocatorExactOwnerShape(ExecutorHostStorage);}const HostCursor = struct { bytes: []align(@alignOf(u32)) u8, offset: usize = 0, fn take(self: *HostCursor, comptime T: type, count: usize) []T { comptime std.debug.assert(@sizeOf(T) == @sizeOf(u32)); comptime std.debug.assert(@alignOf(T) <= @alignOf(u32)); const byte_count = count * @sizeOf(T); std.debug.assert(self.offset + byte_count <= self.bytes.len); const pointer: [*]T = @ptrCast(@alignCast(self.bytes.ptr + self.offset)); self.offset += byte_count; return pointer[0..count]; }};const Buffers = struct { capacity: Capacity, command_capacity: usize, pixel_capacity: usize, image_capacity: usize, image_pixel_capacity: usize, tile_range_capacity: usize, tile_offset_capacity: usize, tile_count_capacity: usize, tile_index_capacity: usize, pixels: gpu.BufferHandle, floats: gpu.BufferHandle, words: gpu.BufferHandle, image_metadata: gpu.BufferHandle, image_pixels: gpu.BufferHandle, tile_ranges: ?gpu.BufferHandle, tile_counts: ?gpu.BufferHandle, tile_offsets: gpu.BufferHandle, tile_cursors: ?gpu.BufferHandle, tile_indices: gpu.BufferHandle, scan_sums: ?gpu.BufferHandle, scan_bases: ?gpu.BufferHandle, host: ExecutorHostStorage, host_bins: accy_paint.BinScratch, readback: []u32, zeroes: []u32, staging: accy_paint.PackedCommands, images: accy_paint.PackedImages, command_upload: CommandUploadState = .{}, tile_csr: TileCsrState = .{}, image_upload: ImageUploadState = .{}, fn init( allocator: Allocator, handle: gpu.BackendHandle, limits: Limits, ) !Buffers { var host_storage = try ExecutorHostStorage.init(allocator, limits); errdefer host_storage.deinit(allocator); const capacity = host_storage.capacity; var host = HostCursor{ .bytes = host_storage.bytes }; const floats_staging = host.take(f32, capacity.float_count); const words_staging = host.take(u32, capacity.word_count); const image_metadata_staging = host.take(u32, capacity.image_metadata_count); const image_pixels_staging = host.take(u32, capacity.image_pixel_capacity); const readback = host.take(u32, capacity.pixel_capacity); const zeroes = host.take(u32, capacity.tile_offset_capacity); const host_ranges = host.take(u32, capacity.host_range_count); const host_offsets = host.take(u32, capacity.host_offset_count); const host_indices = host.take(u32, capacity.host_index_count); const host_cursors = host.take(u32, capacity.host_cursor_count); std.debug.assert(host.offset == host_storage.bytes.len); const pixels = try allocateDeviceBuffer(handle, u32, .u32, capacity.pixel_capacity); errdefer handle.destroyObject(pixels.id); const floats = try allocateDeviceBuffer(handle, f32, .f32, capacity.float_count); errdefer handle.destroyObject(floats.id); const words = try allocateDeviceBuffer(handle, u32, .u32, capacity.word_count); errdefer handle.destroyObject(words.id); const image_metadata = try allocateDeviceBuffer(handle, u32, .u32, capacity.image_metadata_count); errdefer handle.destroyObject(image_metadata.id); const image_pixels = try allocateDeviceBuffer(handle, u32, .u32, capacity.image_pixel_capacity); errdefer handle.destroyObject(image_pixels.id); const tile_offsets = try allocateDeviceBuffer(handle, u32, .u32, capacity.tile_offset_capacity); errdefer handle.destroyObject(tile_offsets.id); const tile_indices = try allocateDeviceBuffer(handle, u32, .u32, capacity.tile_index_capacity); errdefer handle.destroyObject(tile_indices.id); var tile_ranges: ?gpu.BufferHandle = null; errdefer if (tile_ranges) |buffer| handle.destroyObject(buffer.id); var tile_counts: ?gpu.BufferHandle = null; errdefer if (tile_counts) |buffer| handle.destroyObject(buffer.id); var tile_cursors: ?gpu.BufferHandle = null; errdefer if (tile_cursors) |buffer| handle.destroyObject(buffer.id); var scan_sums: ?gpu.BufferHandle = null; errdefer if (scan_sums) |buffer| handle.destroyObject(buffer.id); var scan_bases: ?gpu.BufferHandle = null; errdefer if (scan_bases) |buffer| handle.destroyObject(buffer.id); if (limits.mode == .device_csr) { tile_ranges = try allocateDeviceBuffer(handle, u32, .u32, capacity.tile_range_capacity); tile_counts = try allocateDeviceBuffer(handle, u32, .u32, capacity.tile_offset_capacity); tile_cursors = try allocateDeviceBuffer(handle, u32, .u32, capacity.tile_count_capacity); scan_sums = try allocateDeviceBuffer(handle, u32, .u32, scan_library.device_scan_max_blocks); scan_bases = try allocateDeviceBuffer(handle, u32, .u32, scan_library.device_scan_max_blocks); } host_storage.activate(); return .{ .capacity = capacity, .command_capacity = capacity.command_capacity, .pixel_capacity = capacity.pixel_capacity, .image_capacity = capacity.image_capacity, .image_pixel_capacity = capacity.image_pixel_capacity, .tile_range_capacity = capacity.tile_range_capacity, .tile_offset_capacity = capacity.tile_offset_capacity, .tile_count_capacity = capacity.tile_count_capacity, .tile_index_capacity = capacity.tile_index_capacity, .pixels = pixels, .floats = floats, .words = words, .image_metadata = image_metadata, .image_pixels = image_pixels, .tile_ranges = tile_ranges, .tile_counts = tile_counts, .tile_offsets = tile_offsets, .tile_cursors = tile_cursors, .tile_indices = tile_indices, .scan_sums = scan_sums, .scan_bases = scan_bases, .host = host_storage, .host_bins = .{ .ranges = host_ranges, .offsets = host_offsets, .indices = host_indices, .cursors = host_cursors, }, .readback = readback, .zeroes = zeroes, .staging = .{ .floats = floats_staging, .words = words_staging }, .images = .{ .metadata = image_metadata_staging, .pixels = image_pixels_staging }, }; } fn deinit(self: *Buffers, allocator: Allocator, handle: gpu.BackendHandle) void { handle.destroyObject(self.pixels.id); handle.destroyObject(self.floats.id); handle.destroyObject(self.words.id); handle.destroyObject(self.image_metadata.id); handle.destroyObject(self.image_pixels.id); if (self.tile_ranges) |buffer| handle.destroyObject(buffer.id); if (self.tile_counts) |buffer| handle.destroyObject(buffer.id); handle.destroyObject(self.tile_offsets.id); if (self.tile_cursors) |buffer| handle.destroyObject(buffer.id); handle.destroyObject(self.tile_indices.id); if (self.scan_sums) |buffer| handle.destroyObject(buffer.id); if (self.scan_bases) |buffer| handle.destroyObject(buffer.id); self.host.deinit(allocator); self.* = undefined; } fn writeZeroes(self: *Buffers, handle: gpu.BackendHandle, buffer: gpu.BufferHandle, count: usize) !void { if (count == 0) return; if (self.zeroes.len < count) return error.BufferTooSmall; @memset(self.zeroes[0..count], 0); try handle.writeBuffer(.{ .handle = buffer, .bytes = std.mem.sliceAsBytes(self.zeroes[0..count]), }); } fn commandsResident(self: *const Buffers, commands: []const Command) bool { if (!self.command_upload.valid or self.command_upload.command_count != commands.len) return false; const float_count = std.math.mul(usize, commands.len, accy_paint.float_lanes) catch return false; const word_count = std.math.mul(usize, commands.len, accy_paint.word_lanes) catch return false; if (float_count > self.staging.floats.len or word_count > self.staging.words.len) return false; return accy_paint.commandsEqualPacked(commands, self.staging.floats[0..float_count], self.staging.words[0..word_count]); } fn prepareCommandUpload(self: *Buffers, commands: []const Command) bool { const floats = self.staging.floats[0 .. commands.len * accy_paint.float_lanes]; const words = self.staging.words[0 .. commands.len * accy_paint.word_lanes]; const upload = !self.command_upload.valid or self.command_upload.command_count != commands.len or !accy_paint.commandsEqualPacked(commands, floats, words); if (upload) { accy_paint.packCommands(commands, floats, words); self.command_upload = .{ .valid = true, .command_count = commands.len, }; self.tile_csr.valid = false; } return upload; } fn markTileCsr(self: *Buffers, command_count: usize, width: u32, height: u32, active_region: cpu.Region, shape: accy_paint.BinShape, tile_index_count: usize, command_visits: usize) void { self.tile_csr = .{ .valid = true, .command_count = command_count, .width = width, .height = height, .active_region = active_region, .tiles_x = shape.tiles_x, .tiles_y = shape.tiles_y, .tile_count = shape.tile_count, .tile_index_count = tile_index_count, .command_visits = command_visits, }; } fn prepareImageUpload(self: *Buffers, images: ImageSet, image_pixels: usize) !ImageUploadPlan { if (images.images.len == 0) return .none; if (!self.image_upload.valid or self.image_upload.image_count != images.images.len or self.image_upload.pixel_count != image_pixels) { try accy_paint.packImages(images, self.images.metadata, self.images.pixels); return .metadata_and_pixels; } if (try accy_paint.imagesEqualPacked(images, self.images.metadata, self.images.pixels)) return .none; const metadata_matches = try accy_paint.imageMetadataEqualPacked(images, self.images.metadata); try accy_paint.packImages(images, self.images.metadata, self.images.pixels); return if (metadata_matches) .pixels else .metadata_and_pixels; } fn markImageUpload(self: *Buffers, image_count: usize, pixel_count: usize) void { self.image_upload = .{ .valid = true, .image_count = image_count, .pixel_count = pixel_count, }; }};const CommandUploadState = struct { valid: bool = false, command_count: usize = 0,};const TileCsrState = struct { valid: bool = false, command_count: usize = 0, width: u32 = 0, height: u32 = 0, active_region: cpu.Region = .{}, tiles_x: u32 = 0, tiles_y: u32 = 0, tile_count: usize = 0, tile_index_count: usize = 0, command_visits: usize = 0, fn matches(self: TileCsrState, command_count: usize, width: u32, height: u32, active_region: cpu.Region, shape: accy_paint.BinShape) bool { return self.valid and self.command_count == command_count and self.width == width and self.height == height and self.active_region.x == active_region.x and self.active_region.y == active_region.y and self.active_region.width == active_region.width and self.active_region.height == active_region.height and self.tiles_x == shape.tiles_x and self.tiles_y == shape.tiles_y and self.tile_count == shape.tile_count; }};const ImageUploadState = struct { valid: bool = false, image_count: usize = 0, pixel_count: usize = 0,};const ImageUploadPlan = enum { none, pixels, metadata_and_pixels,};const CachedShadowExpansion = struct { source_commands: []Command, source_images: []command.Image, source_pixels: []u32, options: paint_image.ShadowExpansionOptions, expansion: paint_image.ShadowExpansion, fn init( allocator: Allocator, processor: *paint_image.Processor, commands: []const Command, images: ImageSet, options: paint_image.ShadowExpansionOptions, ) !CachedShadowExpansion { const source_commands = try allocator.dupe(Command, commands); errdefer allocator.free(source_commands); var owned_images = try copyImageSet(allocator, images); errdefer owned_images.deinit(allocator); var expansion = try processor.expandShadowsAlloc(commands, .{ .images = owned_images.images }, options); errdefer expansion.deinit(); return .{ .source_commands = source_commands, .source_images = owned_images.images, .source_pixels = owned_images.pixels, .options = options, .expansion = expansion, }; } fn deinit(self: *CachedShadowExpansion, allocator: Allocator) void { self.expansion.deinit(); allocator.free(self.source_commands); allocator.free(self.source_images); allocator.free(self.source_pixels); self.* = undefined; } fn matches(self: *const CachedShadowExpansion, commands: []const Command, images: ImageSet, options: paint_image.ShadowExpansionOptions) !bool { return shadowExpansionOptionsEqual(self.options, options) and commandsEqual(self.source_commands, commands) and try imageSetsEqual(self.source_images, images); } fn refreshReusable(self: *CachedShadowExpansion, commands: []const Command, images: ImageSet, options: paint_image.ShadowExpansionOptions) !bool { if (!shadowExpansionOptionsEqual(self.options, options)) return false; if (!(try imageSetsEqual(self.source_images, images))) return false; if (!commandsReusableForShadowExpansion(self.source_commands, commands)) return false; try self.expansion.refreshReusableCommands(commands); @memcpy(self.source_commands, commands); return true; }};const OwnedImageSet = struct { images: []command.Image, pixels: []u32, fn deinit(self: *OwnedImageSet, allocator: Allocator) void { allocator.free(self.images); allocator.free(self.pixels); self.* = undefined; }};fn copyImageSet(allocator: Allocator, images: ImageSet) !OwnedImageSet { const image_entries = try allocator.alloc(command.Image, images.images.len); errdefer allocator.free(image_entries); const pixel_count = try accy_paint.imagePixelCount(images); const image_pixels = try allocator.alloc(u32, pixel_count); errdefer allocator.free(image_pixels); var pixel_offset: usize = 0; for (images.images, 0..) |image, index| { try image.validate(); const count = image.pixelCount(); @memcpy(image_pixels[pixel_offset .. pixel_offset + count], image.pixels[0..count]); image_entries[index] = .{ .width = image.width, .height = image.height, .pixels = image_pixels[pixel_offset .. pixel_offset + count], }; pixel_offset += count; } return .{ .images = image_entries, .pixels = image_pixels };}fn shadowExpansionOptionsEqual(left: paint_image.ShadowExpansionOptions, right: paint_image.ShadowExpansionOptions) bool { return optionalF32Equal(left.sigma, right.sigma);}fn optionalF32Equal(left: ?f32, right: ?f32) bool { if (left) |left_value| { if (right) |right_value| return left_value == right_value; return false; } return right == null;}fn commandsEqual(left: []const Command, right: []const Command) bool { if (left.len != right.len) return false; for (left, right) |left_command, right_command| { if (!commandEqual(left_command, right_command)) return false; } return true;}fn commandsReusableForShadowExpansion(left: []const Command, right: []const Command) bool { if (left.len != right.len) return false; for (left, right) |left_command, right_command| { const left_shadow = left_command.kind == .shadow; const right_shadow = right_command.kind == .shadow; if (left_shadow != right_shadow) return false; if (left_shadow) { if (!shadowCommandReusableForRetint(left_command, right_command)) return false; } } return true;}fn shadowCommandReusableForRetint(left: Command, right: Command) bool { return left.kind == right.kind and rectEqual(left.rect, right.rect) and rectEqual(left.clip, right.clip) and rectEqual(left.source, right.source) and left.color.a == right.color.a and colorEqual(left.color_end, right.color_end) and pointEqual(left.gradient_start, right.gradient_start) and pointEqual(left.gradient_end, right.gradient_end) and left.radius == right.radius and left.width == right.width and left.image_index == right.image_index and left.order == right.order;}fn commandEqual(left: Command, right: Command) bool { return left.kind == right.kind and rectEqual(left.rect, right.rect) and rectEqual(left.clip, right.clip) and rectEqual(left.source, right.source) and colorEqual(left.color, right.color) and colorEqual(left.color_end, right.color_end) and pointEqual(left.gradient_start, right.gradient_start) and pointEqual(left.gradient_end, right.gradient_end) and left.radius == right.radius and left.width == right.width and left.image_index == right.image_index and left.order == right.order;}fn rectEqual(left: gui.layout.Rect, right: gui.layout.Rect) bool { return left.x == right.x and left.y == right.y and left.width == right.width and left.height == right.height;}fn colorEqual(left: Color, right: Color) bool { return left.r == right.r and left.g == right.g and left.b == right.b and left.a == right.a;}fn pointEqual(left: gui.model.UiPoint, right: gui.model.UiPoint) bool { return left.x == right.x and left.y == right.y;}fn expectShadowPixelsTinted(pixels: []const u32, color: Color) !void { try std.testing.expect(pixels.len != 0); for (pixels) |pixel| { const unpacked = cpu.pixel.unpackRgba(pixel); try std.testing.expectEqual(color.r, unpacked.r); try std.testing.expectEqual(color.g, unpacked.g); try std.testing.expectEqual(color.b, unpacked.b); }}fn imageSetsEqual(cached: []const command.Image, images: ImageSet) !bool { if (cached.len != images.images.len) return false; for (cached, images.images) |left, right| { try right.validate(); if (left.width != right.width or left.height != right.height) return false; const count = right.pixelCount(); if (!std.mem.eql(u32, left.pixels[0..count], right.pixels[0..count])) return false; } return true;}fn defaultFormat(handle: gpu.BackendHandle) !gpu.ArtifactFormat { const kind = handle.backendKind() orelse return error.UnsupportedArtifactFormat; return switch (kind) { .cuda => .cuda_ptx, .vulkan => .vulkan_spirv, .metal => .metal_msl, else => error.UnsupportedArtifactFormat, };}fn loadGraphKernel( allocator: Allocator, handle: gpu.BackendHandle, format: gpu.ArtifactFormat, threads: u32, comptime build: anytype, diagnostic_id: []const u8,) !LoadedKernel { var graph = try build(allocator, threads); defer graph.deinit(); var artifact = try kernel.createKernelArtifact(allocator, handle, &graph, .{ .artifact_format = format, .authored_kernel_diagnostic_id = diagnostic_id, }); errdefer artifact.deinit(); const loaded = try handle.loadArtifact(&artifact); errdefer handle.destroyObject(loaded.id); return .{ .artifact = artifact, .loaded = loaded };}fn loadDeviceScanKernels( allocator: Allocator, handle: gpu.BackendHandle, format: gpu.ArtifactFormat,) !DeviceScanKernels { const instance = scan_library.DeviceScan{ .extent = @as(u64, scan_library.prefix_sum_max_threads) * scan_library.device_scan_max_blocks, .dtype = .u32, .threads = scan_library.prefix_sum_max_threads, .mode = .inclusive, }; const stages = try scan_library.deviceScanStages(instance); var artifacts = try scan_library.createDeviceScanPipelineArtifacts(allocator, handle, instance, .{ .limits = kernel.Limits.standard, .format = format, }); defer artifacts.deinit(); const entries = artifacts.entries(); var block_scan = try loadKernelCallEntry(allocator, handle, entries[0], "gui/paint/executor/tile-offset-scan-block"); errdefer block_scan.deinit(handle); var sums_scan = try loadKernelCallEntry(allocator, handle, entries[1], "gui/paint/executor/tile-offset-scan-sums"); errdefer sums_scan.deinit(handle); const add_base = try loadKernelCallEntry(allocator, handle, entries[2], "gui/paint/executor/tile-offset-scan-add"); return .{ .instance = instance, .stages = stages, .block_scan = block_scan, .sums_scan = sums_scan, .add_base = add_base, };}fn loadKernelCallEntry( allocator: Allocator, handle: gpu.BackendHandle, entry: anytype, diagnostic_id: []const u8,) !LoadedKernel { var artifact = try kernel.createBackendArtifactFromKernelCallEntry(allocator, handle, entry, diagnostic_id); errdefer artifact.deinit(); const loaded = try handle.loadArtifact(&artifact); errdefer handle.destroyObject(loaded.id); return .{ .artifact = artifact, .loaded = loaded };}fn pixelCount(width: u32, height: u32) !usize { return std.math.mul(usize, @as(usize, width), @as(usize, height)) catch return error.DimensionsTooLarge;}fn allocateDeviceBuffer(handle: gpu.BackendHandle, comptime T: type, dtype: choir_abi.DType, count: usize) !gpu.BufferHandle { const byte_size = std.math.mul(usize, count, @sizeOf(T)) catch return error.BufferTooLarge; return handle.allocateBuffer(.{ .byte_size = byte_size, .alignment = 256, .dtype = dtype, .element_count = std.math.cast(u64, count) orelse return error.BufferTooLarge, });}fn surfacePaintTarget(surface: gpu.SurfaceHandle) !PaintTarget { if (!surface.extent.valid()) return error.InvalidSurface; return .{ .width = surface.extent.width, .height = surface.extent.height, .output_format = outputFormatForTexture(surface.format) orelse return error.InvalidSurface, };}fn surfaceFramePaintTarget(frame: gpu.SurfaceFrame) !PaintTarget { if (!frame.texture.extent.valid()) return error.InvalidSurfaceFrame; if (frame.texture.extent.depth != 1) return error.InvalidSurfaceFrame; return .{ .width = frame.texture.extent.width, .height = frame.texture.extent.height, .output_format = outputFormatForTexture(frame.texture.format) orelse return error.InvalidSurfaceFrame, };}fn validatePreparedSurfaceTarget(prepared: *const PreparedPackedLaunch, target: PaintTarget) !void { const target_pixel_count = try pixelCount(target.width, target.height); if (prepared.target_width != target.width) return error.InvalidSurfaceFrame; if (prepared.target_height != target.height) return error.InvalidSurfaceFrame; if (prepared.output_format != target.output_format) return error.InvalidSurfaceFrame; if (prepared.region_x != 0 or prepared.region_y != 0) return error.InvalidSurfaceFrame; if (prepared.region_width != target.width or prepared.region_height != target.height) return error.InvalidSurfaceFrame; if (prepared.pixel_count != target_pixel_count) return error.InvalidSurfaceFrame;}fn artifactPayloadBytes(artifact: *const gpu.KernelArtifact) usize { return switch (artifact.payload) { .none => 0, .bytes => |bytes| bytes.len, .words_u32 => |words| words.len * @sizeOf(u32), .text => |text| text.len, .external => 0, };}/// The paint output a color texture holds; a depth texture holds none.fn outputFormatForTexture(format: gpu.TextureFormat) ?accy_paint.OutputFormat { return switch (format) { .rgba8_unorm, .rgba8_srgb => .rgba, .bgra8_unorm, .bgra8_srgb => .bgra, .depth32_float => null, };}fn bufferBinding(buffer: gpu.BufferHandle, access: gpu.BufferAccess) gpu.BufferBinding { return .{ .handle = buffer, .access = access, .ownership = buffer.ownership, .byte_size = buffer.byte_size, };}fn copyRgba8Region(dst: []u8, width: u32, region: cpu.Region, src: []const u32) !void { const required_pixels = @as(usize, region.y + region.height) * width; const required_bytes = std.math.mul(usize, required_pixels, 4) catch return error.BufferTooSmall; if (dst.len < required_bytes) return error.BufferTooSmall; var y: u32 = 0; while (y < region.height) : (y += 1) { var x: u32 = 0; while (x < region.width) : (x += 1) { const src_index = @as(usize, y) * region.width + x; const dst_index = @as(usize, region.y + y) * width + region.x + x; writePackedRgba8(dst, dst_index, src[src_index]); } }}fn copyPackedRegion(dst: []u32, width: u32, region: cpu.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]); }}fn writePackedRgba8(dst: []u8, index: usize, pixel: u32) void { const base = index * 4; dst[base] = @truncate(pixel); dst[base + 1] = @truncate(pixel >> 8); dst[base + 2] = @truncate(pixel >> 16); dst[base + 3] = @truncate(pixel >> 24);}test "paint Executor capacity matches an independent host and device byte model" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_capacity_capacity_model"), null, null, null, null, null, null, ); } comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_capacity_overload"), null, null, null, null, null, null, ); } const cases = [_]Limits{ .{ .mode = .host_bins }, .{ .mode = .host_bins, .commands = 7, .pixels = 4096, .images = 3, .image_pixels = 600, .tiles = 16, .tile_pairs = 41 }, .{ .mode = .device_csr, .commands = 11, .pixels = 2304, .images = 2, .image_pixels = 257, .tiles = 9, .tile_pairs = 99 }, }; for (cases) |limits| { const capacity = try Capacity.derive(limits); const commands = @max(@as(u128, limits.commands), 1); const pixels = @max(@as(u128, limits.pixels), 1); const images = @max(@as(u128, limits.images), 1); const image_pixels = @max(@as(u128, limits.image_pixels), 1); const tile_ranges = @max(@as(u128, limits.commands) * accy_paint.tile_range_lanes, 1); const tile_offsets = @max(@as(u128, limits.tiles) + 1, 2); const tiles = @max(@as(u128, limits.tiles), 1); const tile_pairs = @max(@as(u128, limits.tile_pairs), 1); const floats = commands * accy_paint.float_lanes; const words = commands * accy_paint.word_lanes; const metadata = images * accy_paint.image_lanes; const host_bins = if (limits.mode == .host_bins) tile_ranges + tile_offsets + tile_pairs + tiles else 0; const host_elements = floats + words + metadata + image_pixels + pixels + tile_offsets + host_bins; const common_device = pixels + floats + words + metadata + image_pixels + tile_offsets + tile_pairs; const device_csr = if (limits.mode == .device_csr) tile_ranges + tile_offsets + tiles + 2 * scan_library.device_scan_max_blocks else 0; try std.testing.expectEqual(@as(usize, @intCast(host_elements * @sizeOf(u32))), capacity.host_storage_bytes); try std.testing.expectEqual(@as(usize, @intCast((common_device + device_csr) * @sizeOf(u32))), capacity.device_storage_bytes); try std.testing.expectEqual( capacity.host_storage_bytes + capacity.device_storage_bytes, capacity.total_storage_bytes, ); try std.testing.expectEqual(@as(usize, if (limits.mode == .device_csr) 12 else 7), capacity.device_buffer_count); }}test "paint Executor rejects overflowing storage limits" { try std.testing.expectError( error.CapacityOverflow, Limits.worstCase(.host_bins, std.math.maxInt(usize), 1, 0, 0, 2), ); try std.testing.expectError( error.CapacityOverflow, Capacity.derive(.{ .mode = .host_bins, .commands = std.math.maxInt(usize) }), );}test "paint Executor rejects initial storage for another artifact mode" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; try std.testing.expectError( error.StorageModeMismatch, Executor.init(allocator, state.handle(), .{ .artifact_format = .vulkan_spirv, .initial_storage = .{ .mode = .host_bins }, }), );}test "paint Executor host storage rejects initialization OOM and seals on retry" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_host_oom"), null, null, null, null, null, null, ); } var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); const limits = Limits{ .mode = .device_csr, .commands = 1, .pixels = 16, .tiles = 1, .tile_pairs = 1, }; failing.fail_index = failing.alloc_index; try std.testing.expectError( error.OutOfMemory, ExecutorHostStorage.init(failing.allocator(), limits), ); try std.testing.expect(failing.has_induced_failure); failing.fail_index = std.math.maxInt(usize); var storage = try ExecutorHostStorage.init(failing.allocator(), limits); defer storage.deinit(failing.allocator()); try std.testing.expectEqual(alloc_phase.capacity.Phase.initialization, storage.phase); storage.activate(); try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, storage.phase); try std.testing.expectEqual(storage.capacity.host_storage_bytes, storage.bytes.len);}test "paint Executor initial storage exposes the exact chosen capacity" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_acquisition"), null, null, null, null, null, null, ); } const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const limits = Limits{ .mode = .device_csr, .commands = 3, .pixels = 64, .images = 2, .image_pixels = 32, .tiles = 4, .tile_pairs = 12, }; var executor = try Executor.init(allocator, state.handle(), .{ .initial_storage = limits }); defer executor.deinit(); const status = executor.storageStatus(); try std.testing.expectEqual(limits, status.limits.?); try std.testing.expectEqual(try Capacity.derive(limits), status.capacity.?); try std.testing.expectEqual(@as(usize, 0), status.replacements); try std.testing.expectEqual(status.capacity.?.host_storage_bytes, executor.buffers.?.host.bytes.len); try std.testing.expectEqual(status.capacity.?.device_buffer_count, state.buffer_allocate_count);}test "paint Executor eagerly acquires and reports configured image storage" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const limits = paint_image.Limits{ .dst_pixels = 64, .src_pixels = 32, .scratch_pixels = 64, .weight_taps = 7, }; var executor = try Executor.init(allocator, state.handle(), .{ .image_storage = limits }); defer executor.deinit(); const status = executor.storageStatus(); try std.testing.expectEqual(@as(?Limits, null), status.limits); try std.testing.expectEqual(limits, status.image_processor.?.limits.?); try std.testing.expectEqual(try paint_image.Capacity.derive(limits), status.image_processor.?.capacity.?); try std.testing.expectEqual(status.image_processor.?.capacity.?.device_buffer_count, state.buffer_allocate_count);}test "paint Executor replaces storage at max plus one and retains its high water mark" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_boundary"), null, null, null, null, null, null, ); } const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const limits = Limits{ .mode = .device_csr, .commands = 1, .pixels = 16, .tiles = 1, .tile_pairs = 1, }; var executor = try Executor.init(allocator, state.handle(), .{ .initial_storage = limits }); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 5, .height = 5 }, .clip = .{ .x = 0, .y = 0, .width = 5, .height = 5 }, .color = .{ .a = 255 }, }}; _ = try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{}, .{}, cpu.Region.full(4, 4)); try std.testing.expectEqual(@as(usize, 0), executor.storageStatus().replacements); _ = try executor.prepareCommandsPackedLaunch(commands[0..], 5, 5, .{}, .{}, cpu.Region.full(5, 5)); const grown = executor.storageStatus(); try std.testing.expectEqual(@as(usize, 1), grown.replacements); try std.testing.expectEqual(@as(usize, 25), grown.limits.?.pixels); _ = try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{}, .{}, cpu.Region.full(4, 4)); try std.testing.expectEqual(grown, executor.storageStatus());}test "paint Executor failed storage replacement preserves the prior epoch" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_atomic"), null, null, null, null, null, null, ); } var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var state = gpu.recording.BackendState{ .allocator = failing.allocator(), .kind = .vulkan, .format = .vulkan_spirv, }; const limits = Limits{ .mode = .device_csr, .commands = 1, .pixels = 16, .tiles = 1, .tile_pairs = 1, }; var executor = try Executor.init(failing.allocator(), state.handle(), .{ .initial_storage = limits }); defer executor.deinit(); const before = executor.storageStatus(); const pixels_id = executor.buffers.?.pixels.id; const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 17, .height = 17 }, .clip = .{ .x = 0, .y = 0, .width = 17, .height = 17 }, .color = .{ .a = 255 }, }}; failing.fail_index = failing.alloc_index; try std.testing.expectError( error.OutOfMemory, executor.prepareCommandsPackedLaunch(commands[0..], 17, 17, .{}, .{}, cpu.Region.full(17, 17)), ); try std.testing.expect(failing.has_induced_failure); try std.testing.expectEqual(before, executor.storageStatus()); try std.testing.expectEqual(pixels_id, executor.buffers.?.pixels.id); failing.fail_index = std.math.maxInt(usize); _ = try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{}, .{}, cpu.Region.full(4, 4));}test "paint Executor admitted prepared launch makes no allocator calls" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(ExecutorHostStorage, "gui_paint_executor_steady"), null, null, null, null, null, null, ); } var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var state = gpu.recording.BackendState{ .allocator = failing.allocator(), .kind = .vulkan, .format = .vulkan_spirv, }; const limits = Limits{ .mode = .device_csr, .commands = 1, .pixels = 16, .tiles = 1, .tile_pairs = 1, }; var executor = try Executor.init(failing.allocator(), state.handle(), .{ .initial_storage = limits }); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .a = 255 }, }}; failing.fail_index = failing.alloc_index; failing.resize_fail_index = failing.resize_index; _ = try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{}, .{}, cpu.Region.full(4, 4)); try std.testing.expect(!failing.has_induced_failure);}test "paint executor initializes native recording artifact" { const allocator = std.testing.allocator; inline for (.{ gpu.ArtifactFormat.cuda_ptx, .vulkan_spirv, .metal_msl }) |format| { var state = gpu.recording.BackendState{ .allocator = allocator, .kind = switch (format) { .cuda_ptx => .cuda, .vulkan_spirv => .vulkan, .metal_msl => .metal, else => unreachable, }, .format = format, }; var executor = try Executor.init(allocator, state.handle(), .{}); var cleanup = true; defer if (cleanup) executor.deinit(); const loaded_id = executor.loaded.id; try std.testing.expectEqual(format, executor.artifact.format); try std.testing.expectEqual(@as(usize, 4), state.create_count); try std.testing.expectEqual(@as(usize, 7), state.load_count); executor.deinit(); cleanup = false; try std.testing.expectEqual(@as(usize, 7), state.destroy_count); try std.testing.expectEqual(loaded_id, state.last_destroyed_id.?); }}test "paint executor initializes native CPU object artifact" { const allocator = std.testing.allocator; var state = gpu.cpu.State.init(allocator); defer state.deinit(); var executor = try Executor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer executor.deinit(); try std.testing.expectEqual(gpu.ArtifactFormat.cpu_object, executor.artifact.format);}test "paint executor renders packed commands with native CPU object artifact" { const allocator = std.testing.allocator; var state = gpu.cpu.State.init(allocator); defer state.deinit(); var executor = try Executor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 1, .y = 1, .width = 2, .height = 2 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 20, .g = 40, .b = 60, .a = 255 }, }}; var expected = @as([(4 * 4)]u32, @splat(0)); var actual = @as([(4 * 4)]u32, @splat(0)); const clear = Color{ .r = 1, .g = 2, .b = 3, .a = 255 }; try cpu.renderCommandsPacked(commands[0..], .{ .width = 4, .height = 4, .pixels = expected[0..] }, clear); try executor.renderCommandsPacked(commands[0..], .{ .width = 4, .height = 4, .pixels = actual[0..] }, clear); try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]);}test "paint executor renders packed commands with image shadows through CPU object artifact" { const allocator = std.testing.allocator; var state = gpu.cpu.State.init(allocator); defer state.deinit(); var actual_executor = try Executor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer actual_executor.deinit(); var expected_executor = try Executor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer expected_executor.deinit(); var processor = try paint_image.Processor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer processor.deinit(); const commands = [_]Command{ .{ .kind = .shadow, .rect = .{ .x = 3, .y = 2, .width = 4, .height = 3 }, .clip = .{ .x = 0, .y = 0, .width = 12, .height = 10 }, .color = .{ .r = 90, .g = 10, .b = 20, .a = 180 }, .radius = 1, .width = 1, .order = 0, }, .{ .kind = .fill, .rect = .{ .x = 5, .y = 4, .width = 3, .height = 2 }, .clip = .{ .x = 0, .y = 0, .width = 12, .height = 10 }, .color = .{ .r = 20, .g = 120, .b = 60, .a = 220 }, .order = 1, }, }; const clear = Color{ .r = 1, .g = 2, .b = 3, .a = 255 }; var actual = @as([(12 * 10)]u32, @splat(0)); var expected = @as([(12 * 10)]u32, @splat(0)); try actual_executor.renderCommandsPackedWithImageShadows(commands[0..], .{ .width = 12, .height = 10, .pixels = actual[0..], }, clear, .{}, .{}); var expansion = try processor.expandShadowsAlloc(commands[0..], .{}, .{}); defer expansion.deinit(); try expected_executor.renderCommandsPackedWithImages(expansion.commands, .{ .width = 12, .height = 10, .pixels = expected[0..], }, clear, expansion.imageSet()); try std.testing.expectEqualSlices(u32, expected[0..], actual[0..]); try std.testing.expect(actual[3 * 12 + 4] != cpu.packRgba(clear));}test "paint executor launches packed commands through backend handle" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); var pixels = @as([(8 * 5)]u32, @splat(0xffff_ffff)); const commands = [_]Command{ .{ .kind = .fill, .rect = .{ .x = 1, .y = 1, .width = 3, .height = 2 }, .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 }, .color = .{ .r = 11, .g = 22, .b = 33, .a = 44 }, }, .{ .kind = .stroke, .rect = .{ .x = 2, .y = 1, .width = 4, .height = 3 }, .clip = .{ .x = 0, .y = 0, .width = 8, .height = 5 }, .color = .{ .r = 99, .g = 88, .b = 77, .a = 66 }, .radius = 2, .width = 1, }, }; try executor.renderCommandsPacked(commands[0..], .{ .width = 8, .height = 5, .pixels = pixels[0..] }, .{ .r = 1, .g = 2, .b = 3, .a = 4, }); try std.testing.expectEqual(@as(usize, 12), state.buffer_allocate_count); try std.testing.expectEqual(@as(usize, 4), state.write_count); try std.testing.expectEqual(@as(usize, 7), state.launch_count); try std.testing.expectEqual(@as(usize, 1), state.sync_count); try std.testing.expectEqual(@as(usize, 1), state.read_count); try std.testing.expectEqual(@as(usize, 7), state.last_launch_buffer_count); try std.testing.expectEqual(@as(usize, 11), state.last_launch_scalar_count); try std.testing.expectEqual(gpu.BufferAccess.read_write, state.last_buffer_access[0]); try std.testing.expectEqual(gpu.BufferAccess.read_only, state.last_buffer_access[1]); try std.testing.expectEqual(gpu.BufferAccess.read_only, state.last_buffer_access[2]); try std.testing.expectEqual(gpu.BufferAccess.read_only, state.last_buffer_access[3]); try std.testing.expectEqual(gpu.BufferAccess.read_only, state.last_buffer_access[4]); try std.testing.expectEqual(gpu.BufferAccess.read_only, state.last_buffer_access[5]); try std.testing.expectEqual(gpu.BufferAccess.read_only, state.last_buffer_access[6]); try std.testing.expectEqual(@as(u32, 0), state.last_launch_scalar_u32_values[0]); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[1]); try std.testing.expectEqual(@as(u32, 2), state.last_launch_scalar_u32_values[2]); try std.testing.expectEqual(@as(u32, 3), state.last_launch_scalar_u32_values[3]); try std.testing.expectEqual(@as(u32, 4), state.last_launch_scalar_u32_values[4]); try std.testing.expectEqual(@as(u32, 0), state.last_launch_scalar_u32_values[5]); try std.testing.expectEqual(@as(u32, 0), state.last_launch_scalar_u32_values[6]); try std.testing.expectEqual(@as(u32, 8), state.last_launch_scalar_u32_values[7]); try std.testing.expectEqual(@as(u32, 40), state.last_launch_scalar_u32_values[8]); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[9]); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.rgba)), state.last_launch_scalar_u32_values[10]); try std.testing.expectEqual(@as(u32, 1), state.last_launch_grid[0]); try std.testing.expectEqual(@as(u32, accy_paint.default_threads), state.last_launch_threadgroup[0]); try std.testing.expectEqual(@as(usize, pixels.len * @sizeOf(u32)), state.last_read_byte_count); try std.testing.expectEqualSlices(u32, &@as([(8 * 5)]u32, @splat(0)), pixels[0..]);}test "paint executor exposes prepared packed launch after device CSR preparation" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var prepared = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(@as(usize, 4), state.write_count); try std.testing.expectEqual(@as(usize, 6), state.launch_count); try std.testing.expectEqual(@as(usize, 0), state.sync_count); try std.testing.expectEqual(@as(usize, 0), state.read_count); try std.testing.expectEqual(executor.buffers.?.tile_cursors.?.id, state.last_write_buffer_id.?); try std.testing.expectEqual(executor.tile_index.?.loaded.id, state.last_launch_loaded_id.?); try std.testing.expectEqual(@as(usize, 7), prepared.bindings.len); try std.testing.expectEqual(@as(usize, 11), prepared.scalar_count); try std.testing.expectEqual(@as(u32, 16), prepared.scalar_storage[8].u32); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.rgba)), prepared.scalar_storage[10].u32); try std.testing.expectEqual(@as(u32, 1), prepared.geometry.grid[0]); try std.testing.expectEqual(@as(u32, accy_paint.default_threads), prepared.geometry.threadgroup[0]); try std.testing.expectEqual(@as(usize, 0), prepared.command_visits); try std.testing.expect(prepared.device_csr_prepared); try executor.submitPreparedLaunchQueued(&prepared); try std.testing.expectEqual(@as(usize, 7), state.launch_count); try std.testing.expectEqual(@as(usize, 0), state.sync_count); try std.testing.expectEqual(@as(usize, 0), state.read_count); try std.testing.expectEqual(@as(usize, 7), state.last_launch_buffer_count); try std.testing.expectEqual(@as(usize, 11), state.last_launch_scalar_count); try executor.submitPreparedLaunch(&prepared); try std.testing.expectEqual(@as(usize, 8), state.launch_count); try std.testing.expectEqual(@as(usize, 1), state.sync_count); try std.testing.expectEqual(@as(usize, 0), state.read_count);}test "paint executor preserves prepared launch generations across resident device prepares" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var first = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expect(first.device_csr_prepared); const first_generation = first.generation; const first_write_count = state.write_count; const first_launch_count = state.launch_count; const second = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(first_generation, second.generation); try std.testing.expect(!second.device_csr_prepared); try std.testing.expectEqual(first_write_count, state.write_count); try std.testing.expectEqual(first_launch_count, state.launch_count); const info = try executor.preparedLaunchInfo(&first); try std.testing.expectEqual(@as(usize, 16), info.pixel_count); try executor.submitPreparedLaunchQueued(&first); try std.testing.expectEqual(first_launch_count + 1, state.launch_count); const changed = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 8, .g = 8, .b = 9, .a = 255 }, }}; const third = (try executor.prepareCommandsPackedLaunch(changed[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(first_generation + 1, third.generation); try std.testing.expect(third.device_csr_prepared); try std.testing.expect(state.write_count > first_write_count); try std.testing.expectError(error.StalePreparedLaunch, executor.preparedLaunchInfo(&first));}test "paint executor preserves prepared launch generations across resident host-loop prepares" { const allocator = std.testing.allocator; var state = gpu.cpu.State.init(allocator); defer state.deinit(); var executor = try Executor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer executor.deinit(); const commands = [_]Command{}; const first_clear = Color{ .r = 3, .g = 5, .b = 7, .a = 255 }; const second_clear = Color{ .r = 11, .g = 13, .b = 17, .a = 255 }; var first = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, first_clear, .{}, cpu.Region.full(4, 4))).?; try std.testing.expect(!first.device_csr_prepared); try std.testing.expectEqual(@as(usize, 0), first.command_visits); const first_generation = first.generation; var second = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, second_clear, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(first_generation, second.generation); try std.testing.expect(!second.device_csr_prepared); try std.testing.expectEqual(@as(usize, 0), second.command_visits); try executor.submitPreparedLaunch(&first); const first_pixels = try executor.readPreparedPackedPixels(&first); try std.testing.expectEqual(cpu.packRgba(first_clear), first_pixels[0]); try executor.submitPreparedLaunch(&second); const second_pixels = try executor.readPreparedPackedPixels(&second); try std.testing.expectEqual(cpu.packRgba(second_clear), second_pixels[0]); const changed = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 2, .height = 2 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 19, .g = 23, .b = 29, .a = 255 }, }}; const third = (try executor.prepareCommandsPackedLaunch(changed[0..], 4, 4, first_clear, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(first_generation + 1, third.generation); try std.testing.expectError(error.StalePreparedLaunch, executor.preparedLaunchInfo(&first));}test "paint executor reuses resident host-loop tile bins without rebuilding scratch" { const allocator = std.testing.allocator; var state = gpu.cpu.State.init(allocator); defer state.deinit(); var executor = try Executor.init(allocator, state.handle(), .{ .artifact_format = .cpu_object, }); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 19, .g = 23, .b = 29, .a = 255 }, }}; const clear = Color{ .a = 0 }; const first = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, clear, .{}, cpu.Region.full(4, 4))).?; try std.testing.expect(first.command_visits > 0); try std.testing.expect(executor.buffers.?.host_bins.ranges.len != 0); const first_generation = first.generation; const first_tile_index_count = executor.buffers.?.tile_csr.tile_index_count; const first_command_visits = executor.buffers.?.tile_csr.command_visits; const retained_host_bins = executor.buffers.?.host_bins; executor.buffers.?.host_bins = .{}; const second = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, clear, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(first_generation, second.generation); try std.testing.expectEqual(first_tile_index_count, executor.buffers.?.tile_csr.tile_index_count); try std.testing.expectEqual(first_command_visits, second.command_visits); try std.testing.expectEqual(@as(usize, 0), executor.buffers.?.host_bins.ranges.len); executor.buffers.?.host_bins = retained_host_bins; try executor.submitPreparedLaunch(&second); const pixels = try executor.readPreparedPackedPixels(&second); try std.testing.expectEqual(cpu.packRgba(commands[0].color), pixels[0]);}test "paint executor times prepared launches through retained owner" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, .event_elapsed_ns = 123_456, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var prepared = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; const launch_count = state.launch_count; const timing = try executor.submitPreparedLaunchTimed(&prepared); try std.testing.expectEqual(@as(u64, 123_456), timing.elapsed_ns); try std.testing.expectEqual(@as(usize, 1), state.created_stream_count); try std.testing.expectEqual(@as(usize, 2), state.created_event_count); try std.testing.expectEqual(@as(usize, 2), state.record_event_count); try std.testing.expectEqual(@as(usize, 1), state.elapsed_event_count); try std.testing.expectEqual(@as(usize, 1), state.sync_count); try std.testing.expectEqual(launch_count + 1, state.launch_count); try std.testing.expectEqual(timing.stream_id, state.last_launch_stream.?); try std.testing.expectEqual(timing.stream_id, state.record_streams[0].?); try std.testing.expectEqual(timing.stream_id, state.record_streams[1].?); try std.testing.expectEqual(state.record_events[0].?, state.elapsed_start_events[0].?); try std.testing.expectEqual(state.record_events[1].?, state.elapsed_end_events[0].?);}test "paint executor describes prepared launches through retained owner" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; const prepared = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; const info = try executor.preparedLaunchInfo(&prepared); try std.testing.expect(info.entry_name.len != 0); try std.testing.expectEqual(gpu.ArtifactFormat.vulkan_spirv, info.artifact_format); try std.testing.expectEqual(artifactPayloadBytes(&executor.artifact), info.artifact_payload_bytes); try std.testing.expectEqual(@as(usize, 16), info.pixel_count); try std.testing.expectEqual(@as(u32, 1), info.geometry.grid[0]); try std.testing.expectEqual(@as(u32, accy_paint.default_threads), info.geometry.threadgroup[0]); try std.testing.expect(info.device_csr_prepared);}test "paint executor reads prepared packed launches through retained capacity" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{}; var large = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try executor.submitPreparedLaunch(&large); const large_pixels = try executor.readPreparedPackedPixels(&large); try std.testing.expectEqual(@as(usize, 16), large_pixels.len); try std.testing.expectEqual(@as(usize, 1), state.read_count); try std.testing.expectEqual(@as(usize, 16 * @sizeOf(u32)), state.last_read_byte_count); const retained_pixel_capacity = executor.buffers.?.pixel_capacity; try std.testing.expectEqual(@as(usize, 16), retained_pixel_capacity); var small = (try executor.prepareCommandsPackedLaunch(commands[0..], 2, 2, .{ .a = 0 }, .{}, cpu.Region.full(2, 2))).?; try executor.submitPreparedLaunch(&small); const small_pixels = try executor.readPreparedPackedPixels(&small); try std.testing.expectEqual(@as(usize, 4), small_pixels.len); try std.testing.expectEqual(@as(usize, 2), state.read_count); try std.testing.expectEqual(@as(usize, 16 * @sizeOf(u32)), state.last_read_byte_count); try std.testing.expectEqual(retained_pixel_capacity, executor.buffers.?.pixel_capacity);}test "paint executor rejects stale prepared launch generations" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var first = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(@as(u64, 1), first.generation); const first_pixels_id = first.pixels.id; var second = (try executor.prepareCommandsPackedLaunch(commands[0..], 8, 8, .{ .a = 0 }, .{}, cpu.Region.full(8, 8))).?; try std.testing.expectEqual(@as(u64, 2), second.generation); try std.testing.expect(first_pixels_id != second.pixels.id); const launch_count = state.launch_count; const read_count = state.read_count; const surface_write_count = state.surface_write_count; try std.testing.expectError(error.StalePreparedLaunch, executor.preparedLaunchInfo(&first)); try std.testing.expectError(error.StalePreparedLaunch, executor.submitPreparedLaunchTimed(&first)); try std.testing.expectError(error.StalePreparedLaunch, executor.submitPreparedLaunchQueued(&first)); try std.testing.expectError(error.StalePreparedLaunch, executor.readPreparedPackedPixels(&first)); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); try std.testing.expectError(error.StalePreparedLaunch, executor.writePreparedSurfaceFrame(frame, &first)); try std.testing.expectEqual(launch_count, state.launch_count); try std.testing.expectEqual(read_count, state.read_count); try std.testing.expectEqual(surface_write_count, state.surface_write_count); try executor.submitPreparedLaunchQueued(&second); try std.testing.expectEqual(launch_count + 1, state.launch_count); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = frame, }); try handle.destroySurface(surface);}test "paint executor rejects prepared launches from another executor" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var first_executor = try Executor.init(allocator, handle, .{}); defer first_executor.deinit(); var second_executor = try Executor.init(allocator, handle, .{}); defer second_executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var first_prepared = (try first_executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; var second_prepared = (try second_executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(@as(u64, 1), first_prepared.generation); try std.testing.expectEqual(first_prepared.generation, second_prepared.generation); try std.testing.expect(first_prepared.loaded_artifact_id != second_prepared.loaded_artifact_id); const launch_count = state.launch_count; const read_count = state.read_count; const surface_write_count = state.surface_write_count; try std.testing.expectError(error.InvalidPreparedLaunch, second_executor.preparedLaunchInfo(&first_prepared)); try std.testing.expectError(error.InvalidPreparedLaunch, second_executor.submitPreparedLaunchTimed(&first_prepared)); try std.testing.expectError(error.InvalidPreparedLaunch, second_executor.submitPreparedLaunchQueued(&first_prepared)); try std.testing.expectError(error.InvalidPreparedLaunch, second_executor.readPreparedPackedPixels(&first_prepared)); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); try std.testing.expectError(error.InvalidPreparedLaunch, second_executor.writePreparedSurfaceFrame(frame, &first_prepared)); try std.testing.expectEqual(launch_count, state.launch_count); try std.testing.expectEqual(read_count, state.read_count); try std.testing.expectEqual(surface_write_count, state.surface_write_count); try second_executor.submitPreparedLaunchQueued(&second_prepared); try std.testing.expectEqual(launch_count + 1, state.launch_count); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = frame, }); try handle.destroySurface(surface);}test "paint executor reuses prepared device CSR for unchanged commands" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; const changed_commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 10, .a = 255 }, }}; const first = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; const first_writes = state.write_count; const first_launches = state.launch_count; try std.testing.expect(first.device_csr_prepared); try std.testing.expectEqual(@as(usize, 4), first_writes); try std.testing.expectEqual(@as(usize, 6), first_launches); const second = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expect(!second.device_csr_prepared); try std.testing.expectEqual(first_writes, state.write_count); try std.testing.expectEqual(first_launches, state.launch_count); const changed = (try executor.prepareCommandsPackedLaunch(changed_commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expect(changed.device_csr_prepared); try std.testing.expectEqual(first_writes + 4, state.write_count); try std.testing.expectEqual(first_launches + 6, state.launch_count); const region_changed = (try executor.prepareCommandsPackedLaunch(changed_commands[0..], 4, 4, .{ .a = 0 }, .{}, .{ .x = 1, .y = 1, .width = 2, .height = 2 })).?; try std.testing.expect(region_changed.device_csr_prepared); try std.testing.expectEqual(first_writes + 6, state.write_count); try std.testing.expectEqual(first_launches + 12, state.launch_count);}test "paint executor writes packed commands into surface frame without readback" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); const image_pixels = [_]u32{cpu.packRgba(.{ .r = 30, .g = 40, .b = 50, .a = 255 })}; const images = ImageSet{ .images = &.{.{ .width = 1, .height = 1, .pixels = image_pixels[0..] }} }; const commands = [_]Command{ .{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }, .{ .kind = .image, .rect = .{ .x = 1, .y = 1, .width = 1, .height = 1 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .source = .{ .x = 0, .y = 0, .width = 1, .height = 1 }, .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 }, .image_index = 0, }, }; try executor.renderCommandsSurfaceFrameWithImages(commands[0..], frame, .{ .r = 1, .g = 2, .b = 3, .a = 255 }, images); const buffers = executor.buffers.?; try std.testing.expectEqual(@as(usize, 7), state.launch_count); try std.testing.expectEqual(@as(usize, 0), state.sync_count); try std.testing.expectEqual(@as(usize, 0), state.read_count); try std.testing.expectEqual(@as(usize, 1), state.surface_write_count); try std.testing.expectEqual(frame.id, state.last_written_frame_id.?); try std.testing.expectEqual(@as(usize, 1), state.last_surface_write_op_count); try std.testing.expectEqual(buffers.pixels.id, state.last_surface_write_copy_buffer_id.?); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[0]); try std.testing.expectEqual(@as(u32, 4), state.last_launch_scalar_u32_values[7]); try std.testing.expectEqual(@as(u32, 16), state.last_launch_scalar_u32_values[8]); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.rgba)), state.last_launch_scalar_u32_values[10]); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = frame, }); try std.testing.expectEqual(@as(usize, 1), state.surface_present_count); try handle.destroySurface(surface);}test "paint executor writes prepared surface frames with events" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); const launch_done = try handle.createEvent(.{}); const write_done = try handle.createEvent(.{}); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var prepared = (try executor.prepareCommandsSurfaceLaunch(commands[0..], surface, .{ .a = 0 }, .{})).?; const prepare_launches = state.launch_count; try executor.submitPreparedLaunchQueuedWithEvents(&prepared, &.{}, launch_done); try std.testing.expectEqual(prepare_launches + 1, state.launch_count); try std.testing.expectEqual(launch_done.id, state.last_launch_signal_event.?); try std.testing.expect(try handle.queryEvent(.{ .event = launch_done })); try std.testing.expect(!try handle.queryEvent(.{ .event = write_done })); try executor.writePreparedSurfaceFrameWithEvents(frame, &prepared, &.{launch_done}, write_done); try std.testing.expectEqual(@as(usize, 1), state.surface_write_count); try std.testing.expectEqual(@as(usize, 1), state.last_surface_write_wait_count); try std.testing.expectEqual(launch_done.id, state.last_surface_write_wait_events[0]); try std.testing.expectEqual(write_done.id, state.last_surface_write_signal_event.?); try std.testing.expect(try handle.queryEvent(.{ .event = write_done })); try std.testing.expectEqual(@as(usize, 0), state.read_count); try std.testing.expectEqual(@as(usize, 0), state.sync_count); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = frame, }); try handle.destroySurface(surface);}test "paint executor writes bgra surface frame with bgra output format" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .bgra8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; var prepared = (try executor.prepareCommandsSurfaceLaunch(commands[0..], surface, .{ .r = 1, .g = 2, .b = 3, .a = 255 }, .{})).?; try executor.submitPreparedLaunchQueued(&prepared); try executor.writePreparedSurfaceFrame(frame, &prepared); const buffers = executor.buffers.?; try std.testing.expectEqual(@as(usize, 16), prepared.pixel_count); try std.testing.expectEqual(@as(u32, 4), prepared.target_width); try std.testing.expectEqual(@as(u32, 4), prepared.target_height); try std.testing.expectEqual(@as(u32, 0), prepared.region_x); try std.testing.expectEqual(@as(u32, 0), prepared.region_y); try std.testing.expectEqual(@as(u32, 4), prepared.region_width); try std.testing.expectEqual(@as(u32, 4), prepared.region_height); try std.testing.expectEqual(accy_paint.OutputFormat.bgra, prepared.output_format); try std.testing.expectEqual(@as(usize, 7), state.launch_count); try std.testing.expectEqual(@as(usize, 0), state.sync_count); try std.testing.expectEqual(@as(usize, 0), state.read_count); try std.testing.expectEqual(@as(usize, 1), state.surface_write_count); try std.testing.expectEqual(frame.id, state.last_written_frame_id.?); try std.testing.expectEqual(@as(usize, 1), state.last_surface_write_op_count); try std.testing.expectEqual(buffers.pixels.id, state.last_surface_write_copy_buffer_id.?); try std.testing.expectEqual(@as(usize, 11), state.last_launch_scalar_count); try std.testing.expectEqual(@as(u32, 16), state.last_launch_scalar_u32_values[8]); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.bgra)), state.last_launch_scalar_u32_values[10]); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = frame, }); try handle.destroySurface(surface);}test "paint executor writes smaller surface frames through retained pixel capacity" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; const large_surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const large_frame = try handle.acquireSurfaceFrame(.{ .surface = large_surface }); var large = (try executor.prepareCommandsSurfaceLaunch(commands[0..], large_surface, .{ .a = 0 }, .{})).?; try executor.submitPreparedLaunchQueued(&large); try executor.writePreparedSurfaceFrame(large_frame, &large); try handle.presentSurfaceFrame(.{ .surface = large_surface, .frame = large_frame, }); try handle.destroySurface(large_surface); const retained_pixel_capacity = executor.buffers.?.pixel_capacity; const retained_pixels = executor.buffers.?.pixels; try std.testing.expectEqual(@as(usize, 16), large.pixel_count); try std.testing.expectEqual(@as(usize, 16), retained_pixel_capacity); try std.testing.expectEqual(@as(usize, 1), state.surface_write_count); try std.testing.expectEqual(retained_pixels.id, state.last_surface_write_copy_buffer_id.?); const small_surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 2, .height = 2 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const small_frame = try handle.acquireSurfaceFrame(.{ .surface = small_surface }); var small = (try executor.prepareCommandsSurfaceLaunch(commands[0..], small_surface, .{ .a = 0 }, .{})).?; try executor.submitPreparedLaunchQueued(&small); try executor.writePreparedSurfaceFrame(small_frame, &small); try std.testing.expectEqual(@as(usize, 4), small.pixel_count); try std.testing.expectEqual(retained_pixel_capacity, executor.buffers.?.pixel_capacity); try std.testing.expectEqual(retained_pixels.id, executor.buffers.?.pixels.id); try std.testing.expectEqual(@as(usize, 2), state.surface_write_count); try std.testing.expectEqual(small_frame.id, state.last_written_frame_id.?); try std.testing.expectEqual(retained_pixels.id, state.last_surface_write_copy_buffer_id.?); try handle.presentSurfaceFrame(.{ .surface = small_surface, .frame = small_frame, }); try handle.destroySurface(small_surface);}test "paint executor rejects prepared surface frame target mismatches" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; const bgra_surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .bgra8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const bgra_frame = try handle.acquireSurfaceFrame(.{ .surface = bgra_surface }); var rgba_prepared = (try executor.prepareCommandsPackedLaunch(commands[0..], 4, 4, .{ .a = 0 }, .{}, cpu.Region.full(4, 4))).?; try std.testing.expectEqual(accy_paint.OutputFormat.rgba, rgba_prepared.output_format); try std.testing.expectError(error.InvalidSurfaceFrame, executor.writePreparedSurfaceFrame(bgra_frame, &rgba_prepared)); try std.testing.expectEqual(@as(usize, 0), state.surface_write_count); try handle.presentSurfaceFrame(.{ .surface = bgra_surface, .frame = bgra_frame, }); try handle.destroySurface(bgra_surface); const rgba_surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 4, .height = 4 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const rgba_frame = try handle.acquireSurfaceFrame(.{ .surface = rgba_surface }); var small_prepared = (try executor.prepareCommandsPackedLaunch(commands[0..], 2, 2, .{ .a = 0 }, .{}, cpu.Region.full(2, 2))).?; try std.testing.expectEqual(@as(u32, 2), small_prepared.target_width); try std.testing.expectError(error.InvalidSurfaceFrame, executor.writePreparedSurfaceFrame(rgba_frame, &small_prepared)); try std.testing.expectEqual(@as(usize, 0), state.surface_write_count); try handle.presentSurfaceFrame(.{ .surface = rgba_surface, .frame = rgba_frame, }); try handle.destroySurface(rgba_surface);}test "paint executor writes image shadows into surface frame without paint readback" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 10, .height = 10 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); const commands = [_]Command{.{ .kind = .shadow, .rect = .{ .x = 2, .y = 2, .width = 4, .height = 3 }, .clip = .{ .x = 0, .y = 0, .width = 10, .height = 10 }, .color = .{ .r = 30, .g = 40, .b = 50, .a = 210 }, .radius = 1, .width = 2, .order = 4, }}; const load_before = state.load_count; const launch_before = state.launch_count; const sync_before = state.sync_count; const read_before = state.read_count; try executor.renderCommandsSurfaceFrameWithImageShadows(commands[0..], frame, .{ .a = 0 }, .{}, .{}); const buffers = executor.buffers.?; try std.testing.expectEqual(@as(usize, 2), state.load_count - load_before); try std.testing.expectEqual(@as(usize, 9), state.launch_count - launch_before); try std.testing.expectEqual(@as(usize, 1), state.sync_count - sync_before); try std.testing.expectEqual(@as(usize, 1), state.read_count - read_before); try std.testing.expectEqual(@as(usize, 1), state.surface_write_count); try std.testing.expectEqual(frame.id, state.last_written_frame_id.?); try std.testing.expectEqual(buffers.pixels.id, state.last_surface_write_copy_buffer_id.?); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[0]); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.rgba)), state.last_launch_scalar_u32_values[10]); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = frame, }); try handle.destroySurface(surface);}test "paint executor prepares image-shadow surface launches with cached expansion" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const handle = state.handle(); var executor = try Executor.init(allocator, handle, .{}); defer executor.deinit(); const surface = try handle.createSurface(.{ .platform = .{ .headless = .{} }, .extent = .{ .width = 10, .height = 10 }, .format = .rgba8_unorm, .usage = .{ .present = true, .copy_dst = true }, }); const commands = [_]Command{.{ .kind = .shadow, .rect = .{ .x = 2, .y = 2, .width = 4, .height = 3 }, .clip = .{ .x = 0, .y = 0, .width = 10, .height = 10 }, .color = .{ .r = 30, .g = 40, .b = 50, .a = 210 }, .radius = 1, .width = 2, .order = 4, }}; const load_before = state.load_count; const launch_before = state.launch_count; const sync_before = state.sync_count; const read_before = state.read_count; var first = (try executor.prepareCommandsSurfaceLaunchWithImageShadows(commands[0..], surface, .{ .a = 0 }, .{}, .{})).?; try std.testing.expect(first.device_csr_prepared); try std.testing.expectEqual(@as(usize, 100), first.pixel_count); try std.testing.expectEqual(@as(usize, 2), state.load_count - load_before); try std.testing.expectEqual(@as(usize, 8), state.launch_count - launch_before); try std.testing.expectEqual(@as(usize, 1), state.sync_count - sync_before); try std.testing.expectEqual(@as(usize, 1), state.read_count - read_before); try executor.submitPreparedLaunchQueued(&first); const first_frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); try executor.writePreparedSurfaceFrame(first_frame, &first); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = first_frame, }); const load_after_first = state.load_count; const launch_after_first = state.launch_count; const sync_after_first = state.sync_count; const read_after_first = state.read_count; const write_after_first = state.write_count; const surface_write_after_first = state.surface_write_count; var second = (try executor.prepareCommandsSurfaceLaunchWithImageShadows(commands[0..], surface, .{ .a = 0 }, .{}, .{})).?; try std.testing.expect(!second.device_csr_prepared); try std.testing.expectEqual(@as(usize, 100), second.pixel_count); try std.testing.expectEqual(load_after_first, state.load_count); try std.testing.expectEqual(launch_after_first, state.launch_count); try std.testing.expectEqual(sync_after_first, state.sync_count); try std.testing.expectEqual(read_after_first, state.read_count); try std.testing.expectEqual(write_after_first, state.write_count); try std.testing.expectEqual(surface_write_after_first, state.surface_write_count); try executor.submitPreparedLaunchQueued(&second); const second_frame = try handle.acquireSurfaceFrame(.{ .surface = surface }); try executor.writePreparedSurfaceFrame(second_frame, &second); try handle.presentSurfaceFrame(.{ .surface = surface, .frame = second_frame, }); try std.testing.expectEqual(launch_after_first + 1, state.launch_count); try std.testing.expectEqual(sync_after_first, state.sync_count); try std.testing.expectEqual(read_after_first, state.read_count); try std.testing.expectEqual(surface_write_after_first + 1, state.surface_write_count); const packed_launch_before = state.launch_count; const packed_write_before = state.write_count; const packed_launch = (try executor.prepareCommandsPackedLaunchWithImageShadows(commands[0..], 10, 10, .{ .a = 0 }, .{}, cpu.Region.full(10, 10), .{})).?; try std.testing.expect(!packed_launch.device_csr_prepared); try std.testing.expectEqual(@as(usize, 100), packed_launch.pixel_count); try std.testing.expectEqual(packed_launch_before, state.launch_count); try std.testing.expectEqual(packed_write_before, state.write_count); try handle.destroySurface(surface);}test "paint executor reuses image-shadow family kernels across renders" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .shadow, .rect = .{ .x = 2, .y = 2, .width = 4, .height = 3 }, .clip = .{ .x = 0, .y = 0, .width = 10, .height = 10 }, .color = .{ .r = 30, .g = 40, .b = 50, .a = 210 }, .radius = 1, .width = 2, .order = 4, }}; var changed = commands; changed[0].width = 3; var pixels = @as([(10 * 10)]u32, @splat(0)); const load_before = state.load_count; try executor.renderCommandsPackedWithImageShadows(commands[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); const first_load_count = state.load_count; try std.testing.expectEqual(@as(usize, 2), first_load_count - load_before); try executor.renderCommandsPackedWithImageShadows(commands[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); try std.testing.expectEqual(first_load_count, state.load_count); try executor.renderCommandsPackedWithImageShadows(changed[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); try std.testing.expectEqual(first_load_count + 2, state.load_count);}test "paint executor reuses image-shadow expansions across renders" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{.{ .kind = .shadow, .rect = .{ .x = 2, .y = 2, .width = 4, .height = 3 }, .clip = .{ .x = 0, .y = 0, .width = 10, .height = 10 }, .color = .{ .r = 30, .g = 40, .b = 50, .a = 210 }, .radius = 1, .width = 2, .order = 4, }}; var changed = commands; changed[0].color.r = 31; var changed_alpha = changed; changed_alpha[0].color.a = 211; var pixels = @as([(10 * 10)]u32, @splat(0)); try executor.renderCommandsPackedWithImageShadows(commands[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); const cached = &executor.shadow_expansion.?; const cached_commands_ptr = cached.expansion.commands.ptr; const cached_images_ptr = cached.expansion.image_entries.ptr; const cached_shadows_ptr = cached.expansion.shadows.ptr; const cached_shadow_pixels_ptr = cached.expansion.shadows[0].pixels.ptr; const first_launch_count = state.launch_count; const first_write_count = state.write_count; const first_read_count = state.read_count; try executor.renderCommandsPackedWithImageShadows(commands[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); try std.testing.expectEqual(first_launch_count + 1, state.launch_count); try std.testing.expectEqual(first_write_count, state.write_count); try std.testing.expectEqual(first_read_count + 1, state.read_count); try executor.renderCommandsPackedWithImageShadows(changed[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); const changed_cached = &executor.shadow_expansion.?; try std.testing.expectEqual(cached_commands_ptr, changed_cached.expansion.commands.ptr); try std.testing.expectEqual(cached_images_ptr, changed_cached.expansion.image_entries.ptr); try std.testing.expectEqual(cached_shadows_ptr, changed_cached.expansion.shadows.ptr); try std.testing.expectEqual(cached_shadow_pixels_ptr, changed_cached.expansion.shadows[0].pixels.ptr); try expectShadowPixelsTinted(changed_cached.expansion.shadows[0].pixels, changed[0].color); try std.testing.expectEqual(first_launch_count + 2, state.launch_count); try std.testing.expectEqual(first_write_count + 1, state.write_count); try std.testing.expectEqual(first_read_count + 2, state.read_count); try executor.renderCommandsPackedWithImageShadows(changed[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); try std.testing.expectEqual(cached_commands_ptr, executor.shadow_expansion.?.expansion.commands.ptr); try std.testing.expectEqual(first_launch_count + 3, state.launch_count); try std.testing.expectEqual(first_write_count + 1, state.write_count); try std.testing.expectEqual(first_read_count + 3, state.read_count); try executor.renderCommandsPackedWithImageShadows(changed_alpha[0..], .{ .width = 10, .height = 10, .pixels = pixels[0..], }, .{ .a = 0 }, .{}, .{}); try std.testing.expect(cached_commands_ptr != executor.shadow_expansion.?.expansion.commands.ptr);}test "paint executor converts readback into rgba8 targets" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .cuda, .format = .cuda_ptx, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); var rgba = @as([(4 * 4)]u8, @splat(0xff)); const commands = [_]Command{}; try executor.renderCommands(commands[0..], .{ .width = 2, .height = 2, .rgba8 = rgba[0..] }, .{ .r = 9, .g = 8, .b = 7, .a = 6, }); try std.testing.expectEqual(@as(usize, 1), state.write_count); try std.testing.expectEqual(@as(usize, 1), state.launch_count); try std.testing.expectEqual(@as(u32, 0), state.last_launch_scalar_u32_values[0]); try std.testing.expectEqual(@as(u32, 9), state.last_launch_scalar_u32_values[1]); try std.testing.expectEqual(@as(u32, 8), state.last_launch_scalar_u32_values[2]); try std.testing.expectEqual(@as(u32, 7), state.last_launch_scalar_u32_values[3]); try std.testing.expectEqual(@as(u32, 6), state.last_launch_scalar_u32_values[4]); try std.testing.expectEqual(@as(u32, 0), state.last_launch_scalar_u32_values[5]); try std.testing.expectEqual(@as(u32, 2), state.last_launch_scalar_u32_values[7]); try std.testing.expectEqual(@as(u32, 4), state.last_launch_scalar_u32_values[8]); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[9]); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.rgba)), state.last_launch_scalar_u32_values[10]); try std.testing.expectEqualSlices(u8, &@as([(4 * 4)]u8, @splat(0)), rgba[0..]);}test "paint executor reads full reused pixel buffers" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const commands = [_]Command{}; var large = @as([16]u32, @splat(0xffff_ffff)); var small = @as([4]u32, @splat(0xffff_ffff)); try executor.renderCommandsPacked(commands[0..], .{ .width = 4, .height = 4, .pixels = large[0..] }, .{ .a = 0 }); try std.testing.expectEqual(@as(usize, 16 * @sizeOf(u32)), state.last_read_byte_count); try executor.renderCommandsPacked(commands[0..], .{ .width = 2, .height = 2, .pixels = small[0..] }, .{ .a = 0 }); try std.testing.expectEqual(@as(usize, 16 * @sizeOf(u32)), state.last_read_byte_count); try std.testing.expectEqualSlices(u32, &@as([4]u32, @splat(0)), small[0..]);}test "paint executor uploads image metadata and pixels" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const image_pixels = [_]u32{ cpu.packRgba(.{ .r = 10, .g = 20, .b = 30, .a = 255 }), cpu.packRgba(.{ .r = 40, .g = 50, .b = 60, .a = 255 }), cpu.packRgba(.{ .r = 70, .g = 80, .b = 90, .a = 255 }), cpu.packRgba(.{ .r = 100, .g = 110, .b = 120, .a = 255 }), }; const images = ImageSet{ .images = &.{.{ .width = 2, .height = 2, .pixels = image_pixels[0..] }} }; const changed_image_pixels = [_]u32{ cpu.packRgba(.{ .r = 10, .g = 20, .b = 30, .a = 255 }), cpu.packRgba(.{ .r = 40, .g = 50, .b = 61, .a = 255 }), cpu.packRgba(.{ .r = 70, .g = 80, .b = 90, .a = 255 }), cpu.packRgba(.{ .r = 100, .g = 110, .b = 120, .a = 255 }), }; const changed_images = ImageSet{ .images = &.{.{ .width = 2, .height = 2, .pixels = changed_image_pixels[0..] }} }; const second_image_pixels = [_]u32{ cpu.packRgba(.{ .r = 1, .g = 2, .b = 3, .a = 255 }), cpu.packRgba(.{ .r = 4, .g = 5, .b = 6, .a = 255 }), cpu.packRgba(.{ .r = 7, .g = 8, .b = 9, .a = 255 }), cpu.packRgba(.{ .r = 10, .g = 11, .b = 12, .a = 255 }), }; const two_images = ImageSet{ .images = &.{ .{ .width = 2, .height = 2, .pixels = image_pixels[0..] }, .{ .width = 2, .height = 2, .pixels = second_image_pixels[0..] }, } }; const commands = [_]Command{.{ .kind = .image, .rect = .{ .x = 0, .y = 0, .width = 2, .height = 2 }, .clip = .{ .x = 0, .y = 0, .width = 2, .height = 2 }, .source = .{ .x = 0, .y = 0, .width = 2, .height = 2 }, .color = .{ .r = 255, .g = 255, .b = 255, .a = 255 }, .image_index = 0, }}; var pixels = @as([4]u32, @splat(0)); var larger_pixels = @as([16]u32, @splat(0)); try executor.renderCommandsPackedWithImages(commands[0..], .{ .width = 2, .height = 2, .pixels = pixels[0..] }, .{ .a = 0 }, images); try std.testing.expectEqual(@as(usize, 6), state.write_count); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[0]); try std.testing.expectEqual(@as(usize, 1 * @sizeOf(u32)), state.last_write_byte_count); const first_buffers = executor.buffers.?; try std.testing.expect(first_buffers.image_upload.valid); try std.testing.expect(try accy_paint.imagesEqualPacked(images, first_buffers.images.metadata, first_buffers.images.pixels)); try executor.renderCommandsPackedWithImages(commands[0..], .{ .width = 2, .height = 2, .pixels = pixels[0..] }, .{ .a = 0 }, images); try std.testing.expectEqual(@as(usize, 6), state.write_count); try executor.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = larger_pixels[0..] }, .{ .a = 0 }, images); try std.testing.expectEqual(@as(usize, 12), state.write_count); const resized_buffers = executor.buffers.?; try std.testing.expect(try accy_paint.imagesEqualPacked(images, resized_buffers.images.metadata, resized_buffers.images.pixels)); try executor.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = larger_pixels[0..] }, .{ .a = 0 }, changed_images); try std.testing.expectEqual(@as(usize, 13), state.write_count); try executor.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = larger_pixels[0..] }, .{ .a = 0 }, two_images); try std.testing.expectEqual(@as(usize, 19), state.write_count); try executor.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = larger_pixels[0..] }, .{ .a = 0 }, images); try std.testing.expectEqual(@as(usize, 21), state.write_count);}test "paint executor launches packed damage region without seed pixels" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); var pixels = @as([(4 * 4)]u32, @splat(0x1122_3344)); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; try executor.renderCommandsPackedRegionWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = pixels[0..] }, .{ .a = 0 }, .{}, .{ .x = 1, .y = 1, .width = 2, .height = 2 }); try std.testing.expectEqual(@as(usize, 4), state.write_count); try std.testing.expectEqual(@as(usize, 11), state.last_launch_scalar_count); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[5]); try std.testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[6]); try std.testing.expectEqual(@as(u32, 2), state.last_launch_scalar_u32_values[7]); try std.testing.expectEqual(@as(u32, 4), state.last_launch_scalar_u32_values[8]); try std.testing.expectEqual(@as(u32, @backingInt(accy_paint.OutputFormat.rgba)), state.last_launch_scalar_u32_values[10]); try std.testing.expectEqual(@as(usize, 4 * @sizeOf(u32)), state.last_read_byte_count);}test "paint executor reuses device CSR buffers across same shape renders" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); var pixels = @as([16]u32, @splat(0)); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .clip = .{ .x = 0, .y = 0, .width = 4, .height = 4 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; try executor.renderCommandsPacked(commands[0..], .{ .width = 4, .height = 4, .pixels = pixels[0..] }, .{ .a = 0 }); const buffer_allocate_count = state.buffer_allocate_count; const first_buffers = executor.buffers.?; const ranges_id = first_buffers.tile_ranges.?.id; const counts_id = first_buffers.tile_counts.?.id; const offsets_id = first_buffers.tile_offsets.id; const cursors_id = first_buffers.tile_cursors.?.id; const indices_id = first_buffers.tile_indices.id; const sums_id = first_buffers.scan_sums.?.id; const bases_id = first_buffers.scan_bases.?.id; try executor.renderCommandsPacked(commands[0..], .{ .width = 4, .height = 4, .pixels = pixels[0..] }, .{ .a = 0 }); const second_buffers = executor.buffers.?; try std.testing.expectEqual(buffer_allocate_count, state.buffer_allocate_count); try std.testing.expectEqual(ranges_id, second_buffers.tile_ranges.?.id); try std.testing.expectEqual(counts_id, second_buffers.tile_counts.?.id); try std.testing.expectEqual(offsets_id, second_buffers.tile_offsets.id); try std.testing.expectEqual(cursors_id, second_buffers.tile_cursors.?.id); try std.testing.expectEqual(indices_id, second_buffers.tile_indices.id); try std.testing.expectEqual(sums_id, second_buffers.scan_sums.?.id); try std.testing.expectEqual(bases_id, second_buffers.scan_bases.?.id);}test "paint executor grows target buffers without rebuilding artifact" { const allocator = std.testing.allocator; var state = gpu.recording.BackendState{ .allocator = allocator, .kind = .metal, .format = .metal_msl, }; var executor = try Executor.init(allocator, state.handle(), .{}); defer executor.deinit(); const load_count = state.load_count; var small = @as([4]u32, @splat(0)); var large = @as([64]u32, @splat(0)); const commands = [_]Command{.{ .kind = .fill, .rect = .{ .x = 0, .y = 0, .width = 8, .height = 8 }, .clip = .{ .x = 0, .y = 0, .width = 8, .height = 8 }, .color = .{ .r = 7, .g = 8, .b = 9, .a = 255 }, }}; try executor.renderCommandsPacked(commands[0..], .{ .width = 2, .height = 2, .pixels = small[0..] }, .{ .a = 0 }); try executor.renderCommandsPacked(commands[0..], .{ .width = 8, .height = 8, .pixels = large[0..] }, .{ .a = 0 }); try std.testing.expectEqual(@as(usize, 4), state.create_count); try std.testing.expectEqual(load_count, state.load_count); try std.testing.expectEqual(@as(usize, 24), state.buffer_allocate_count); try std.testing.expect(state.destroy_count >= 11);}Source: lib/gui/src/paint/root.zig:5
zig
pub const executor = @import("executor.zig");Complete caller list for paint.Executor.deinit
35 direct callers.
lib.gui.src.paint.executor.test_paint_Executor_admitted_prepared_launch_makes_no_allocator_calls[function] — test source atlib/gui/src/paint/executor.zig:2121in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_eagerly_acquires_and_reports_configured_image_storage[function] — test source atlib/gui/src/paint/executor.zig:2007in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_failed_storage_replacement_preserves_the_prior_epoch[function] — test source atlib/gui/src/paint/executor.zig:2073in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_initial_storage_exposes_the_exact_chosen_capacity[function] — test source atlib/gui/src/paint/executor.zig:1969in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_replaces_storage_at_max_plus_one_and_retains_its_high_water_mark[function] — test source atlib/gui/src/paint/executor.zig:2029in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_converts_readback_into_rgba8_targets[function] — test source atlib/gui/src/paint/executor.zig:3317in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_describes_prepared_launches_through_retained_owner[function] — test source atlib/gui/src/paint/executor.zig:2560in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_exposes_prepared_packed_launch_after_device_CSR_preparation[function] — test source atlib/gui/src/paint/executor.zig:2350in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_grows_target_buffers_without_rebuilding_artifact[function] — test source atlib/gui/src/paint/executor.zig:3513in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_initializes_native_CPU_object_artifact[function] — test source atlib/gui/src/paint/executor.zig:2188in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_initializes_native_recording_artifact[function] — test source atlib/gui/src/paint/executor.zig:2161in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_launches_packed_commands_through_backend_handle[function] — test source atlib/gui/src/paint/executor.zig:2285in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_launches_packed_damage_region_without_seed_pixels[function] — test source atlib/gui/src/paint/executor.zig:3443in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_prepares_image-shadow_surface_launches_with_cached_expansion[function] — test source atlib/gui/src/paint/executor.zig:3104in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_device_prepares[function] — test source atlib/gui/src/paint/executor.zig:2398in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_host-loop_prepares[function] — test source atlib/gui/src/paint/executor.zig:2444in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reads_full_reused_pixel_buffers[function] — test source atlib/gui/src/paint/executor.zig:3351in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reads_prepared_packed_launches_through_retained_capacity[function] — test source atlib/gui/src/paint/executor.zig:2588in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_launches_from_another_executor[function] — test source atlib/gui/src/paint/executor.zig:2672in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_surface_frame_target_mismatches[function] — test source atlib/gui/src/paint/executor.zig:3001in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_stale_prepared_launch_generations[function] — test source atlib/gui/src/paint/executor.zig:2617in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_renders_packed_commands_with_image_shadows_through_CPU_object_artifact[function] — test source atlib/gui/src/paint/executor.zig:2227in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_renders_packed_commands_with_native_CPU_object_artifact[function] — test source atlib/gui/src/paint/executor.zig:2201in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_device_CSR_buffers_across_same_shape_renders[function] — test source atlib/gui/src/paint/executor.zig:3472in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_image-shadow_expansions_across_renders[function] — test source atlib/gui/src/paint/executor.zig:3237in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_image-shadow_family_kernels_across_renders[function] — test source atlib/gui/src/paint/executor.zig:3191in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_prepared_device_CSR_for_unchanged_commands[function] — test source atlib/gui/src/paint/executor.zig:2727in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_resident_host-loop_tile_bins_without_rebuilding_scratch[function] — test source atlib/gui/src/paint/executor.zig:2486in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_times_prepared_launches_through_retained_owner[function] — test source atlib/gui/src/paint/executor.zig:2525in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_uploads_image_metadata_and_pixels[function] — test source atlib/gui/src/paint/executor.zig:3372in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_bgra_surface_frame_with_bgra_output_format[function] — test source atlib/gui/src/paint/executor.zig:2881in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_image_shadows_into_surface_frame_without_paint_readback[function] — test source atlib/gui/src/paint/executor.zig:3053in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_packed_commands_into_surface_frame_without_readback[function] — test source atlib/gui/src/paint/executor.zig:2772in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_prepared_surface_frames_with_events[function] — test source atlib/gui/src/paint/executor.zig:2831in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_smaller_surface_frames_through_retained_pixel_capacity[function] — test source atlib/gui/src/paint/executor.zig:2936in nearest public ownertiny.gui.paint.executor
Complete caller list for paint.Executor.init
36 direct callers.
lib.gui.src.paint.executor.test_paint_Executor_admitted_prepared_launch_makes_no_allocator_calls[function] — test source atlib/gui/src/paint/executor.zig:2121in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_eagerly_acquires_and_reports_configured_image_storage[function] — test source atlib/gui/src/paint/executor.zig:2007in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_failed_storage_replacement_preserves_the_prior_epoch[function] — test source atlib/gui/src/paint/executor.zig:2073in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_initial_storage_exposes_the_exact_chosen_capacity[function] — test source atlib/gui/src/paint/executor.zig:1969in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_rejects_initial_storage_for_another_artifact_mode[function] — test source atlib/gui/src/paint/executor.zig:1917in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_replaces_storage_at_max_plus_one_and_retains_its_high_water_mark[function] — test source atlib/gui/src/paint/executor.zig:2029in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_converts_readback_into_rgba8_targets[function] — test source atlib/gui/src/paint/executor.zig:3317in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_describes_prepared_launches_through_retained_owner[function] — test source atlib/gui/src/paint/executor.zig:2560in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_exposes_prepared_packed_launch_after_device_CSR_preparation[function] — test source atlib/gui/src/paint/executor.zig:2350in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_grows_target_buffers_without_rebuilding_artifact[function] — test source atlib/gui/src/paint/executor.zig:3513in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_initializes_native_CPU_object_artifact[function] — test source atlib/gui/src/paint/executor.zig:2188in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_initializes_native_recording_artifact[function] — test source atlib/gui/src/paint/executor.zig:2161in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_launches_packed_commands_through_backend_handle[function] — test source atlib/gui/src/paint/executor.zig:2285in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_launches_packed_damage_region_without_seed_pixels[function] — test source atlib/gui/src/paint/executor.zig:3443in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_prepares_image-shadow_surface_launches_with_cached_expansion[function] — test source atlib/gui/src/paint/executor.zig:3104in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_device_prepares[function] — test source atlib/gui/src/paint/executor.zig:2398in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_host-loop_prepares[function] — test source atlib/gui/src/paint/executor.zig:2444in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reads_full_reused_pixel_buffers[function] — test source atlib/gui/src/paint/executor.zig:3351in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reads_prepared_packed_launches_through_retained_capacity[function] — test source atlib/gui/src/paint/executor.zig:2588in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_launches_from_another_executor[function] — test source atlib/gui/src/paint/executor.zig:2672in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_surface_frame_target_mismatches[function] — test source atlib/gui/src/paint/executor.zig:3001in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_stale_prepared_launch_generations[function] — test source atlib/gui/src/paint/executor.zig:2617in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_renders_packed_commands_with_image_shadows_through_CPU_object_artifact[function] — test source atlib/gui/src/paint/executor.zig:2227in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_renders_packed_commands_with_native_CPU_object_artifact[function] — test source atlib/gui/src/paint/executor.zig:2201in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_device_CSR_buffers_across_same_shape_renders[function] — test source atlib/gui/src/paint/executor.zig:3472in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_image-shadow_expansions_across_renders[function] — test source atlib/gui/src/paint/executor.zig:3237in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_image-shadow_family_kernels_across_renders[function] — test source atlib/gui/src/paint/executor.zig:3191in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_prepared_device_CSR_for_unchanged_commands[function] — test source atlib/gui/src/paint/executor.zig:2727in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_resident_host-loop_tile_bins_without_rebuilding_scratch[function] — test source atlib/gui/src/paint/executor.zig:2486in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_times_prepared_launches_through_retained_owner[function] — test source atlib/gui/src/paint/executor.zig:2525in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_uploads_image_metadata_and_pixels[function] — test source atlib/gui/src/paint/executor.zig:3372in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_bgra_surface_frame_with_bgra_output_format[function] — test source atlib/gui/src/paint/executor.zig:2881in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_image_shadows_into_surface_frame_without_paint_readback[function] — test source atlib/gui/src/paint/executor.zig:3053in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_packed_commands_into_surface_frame_without_readback[function] — test source atlib/gui/src/paint/executor.zig:2772in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_prepared_surface_frames_with_events[function] — test source atlib/gui/src/paint/executor.zig:2831in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_smaller_surface_frames_through_retained_pixel_capacity[function] — test source atlib/gui/src/paint/executor.zig:2936in nearest public ownertiny.gui.paint.executor
Complete caller list for paint.Executor.prepareCommandsPackedLaunch
16 direct callers.
tiny.gui.paint.Executor.prepareCommandsPackedLaunchWithImageShadows[method] atlib/gui/src/paint/executor.zig:553lib.gui.src.paint.executor.Executor.renderPacked[method] — private source atlib/gui/src/paint/executor.zig:543in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_admitted_prepared_launch_makes_no_allocator_calls[function] — test source atlib/gui/src/paint/executor.zig:2121in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_failed_storage_replacement_preserves_the_prior_epoch[function] — test source atlib/gui/src/paint/executor.zig:2073in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_Executor_replaces_storage_at_max_plus_one_and_retains_its_high_water_mark[function] — test source atlib/gui/src/paint/executor.zig:2029in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_describes_prepared_launches_through_retained_owner[function] — test source atlib/gui/src/paint/executor.zig:2560in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_exposes_prepared_packed_launch_after_device_CSR_preparation[function] — test source atlib/gui/src/paint/executor.zig:2350in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_device_prepares[function] — test source atlib/gui/src/paint/executor.zig:2398in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_host-loop_prepares[function] — test source atlib/gui/src/paint/executor.zig:2444in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reads_prepared_packed_launches_through_retained_capacity[function] — test source atlib/gui/src/paint/executor.zig:2588in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_launches_from_another_executor[function] — test source atlib/gui/src/paint/executor.zig:2672in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_surface_frame_target_mismatches[function] — test source atlib/gui/src/paint/executor.zig:3001in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_stale_prepared_launch_generations[function] — test source atlib/gui/src/paint/executor.zig:2617in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_prepared_device_CSR_for_unchanged_commands[function] — test source atlib/gui/src/paint/executor.zig:2727in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_reuses_resident_host-loop_tile_bins_without_rebuilding_scratch[function] — test source atlib/gui/src/paint/executor.zig:2486in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_times_prepared_launches_through_retained_owner[function] — test source atlib/gui/src/paint/executor.zig:2525in nearest public ownertiny.gui.paint.executor
Complete caller list for paint.Executor.submitPreparedLaunchQueued
10 direct callers.
tiny.gui.paint.Executor.renderCommandsSurfaceFrameWithImageShadows[method] atlib/gui/src/paint/executor.zig:458tiny.gui.paint.Executor.renderCommandsSurfaceFrameWithImages[method] atlib/gui/src/paint/executor.zig:471tiny.gui.paint.Executor.submitPreparedLaunch[method] atlib/gui/src/paint/executor.zig:652lib.gui.src.paint.executor.test_paint_executor_exposes_prepared_packed_launch_after_device_CSR_preparation[function] — test source atlib/gui/src/paint/executor.zig:2350in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_prepares_image-shadow_surface_launches_with_cached_expansion[function] — test source atlib/gui/src/paint/executor.zig:3104in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_preserves_prepared_launch_generations_across_resident_device_prepares[function] — test source atlib/gui/src/paint/executor.zig:2398in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_launches_from_another_executor[function] — test source atlib/gui/src/paint/executor.zig:2672in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_stale_prepared_launch_generations[function] — test source atlib/gui/src/paint/executor.zig:2617in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_bgra_surface_frame_with_bgra_output_format[function] — test source atlib/gui/src/paint/executor.zig:2881in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_smaller_surface_frames_through_retained_pixel_capacity[function] — test source atlib/gui/src/paint/executor.zig:2936in nearest public ownertiny.gui.paint.executor
Complete caller list for paint.Executor.writePreparedSurfaceFrame
8 direct callers.
tiny.gui.paint.Executor.renderCommandsSurfaceFrameWithImageShadows[method] atlib/gui/src/paint/executor.zig:458tiny.gui.paint.Executor.renderCommandsSurfaceFrameWithImages[method] atlib/gui/src/paint/executor.zig:471lib.gui.src.paint.executor.test_paint_executor_prepares_image-shadow_surface_launches_with_cached_expansion[function] — test source atlib/gui/src/paint/executor.zig:3104in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_launches_from_another_executor[function] — test source atlib/gui/src/paint/executor.zig:2672in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_prepared_surface_frame_target_mismatches[function] — test source atlib/gui/src/paint/executor.zig:3001in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_rejects_stale_prepared_launch_generations[function] — test source atlib/gui/src/paint/executor.zig:2617in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_bgra_surface_frame_with_bgra_output_format[function] — test source atlib/gui/src/paint/executor.zig:2881in nearest public ownertiny.gui.paint.executorlib.gui.src.paint.executor.test_paint_executor_writes_smaller_surface_frames_through_retained_pixel_capacity[function] — test source atlib/gui/src/paint/executor.zig:2936in nearest public ownertiny.gui.paint.executor
Audit
| Definitions | 50 |
|---|---|
| Public names | 99 |
| Members | 80 |
| Version | 26.7.0 |
| Revision | daab053ee433 |