tiny.gui.paint.compositor
Defined in paint.
API (17)
Actions
Public operations.
Compositor.deinitCompositor.initCompositor.initCpuCompositor.initWithHandleCompositor.kindCompositor.renderCommandsCompositor.renderCommandsPackedCompositor.renderCommandsPackedRegionWithImagesCompositor.renderCommandsPackedWithImagesCompositor.renderCommandsRegionWithImagesCompositor.renderCommandsWithImagesCompositor.status
Types and contracts
Public types and contracts.
Source
Source: lib/gui/src/paint/compositor.zig
zig
const std = @import("std");const gpu = @import("gpu");const command = @import("command.zig");const cpu = @import("cpu/root.zig");const executor = @import("executor.zig");const gui = @import("../root.zig");const paint_image = @import("image.zig");const Allocator = std.mem.Allocator;const BackendHandle = gpu.BackendHandle;const Color = gui.model.UiColor;const Command = command.Command;const Executor = executor.Executor;const ImageSet = command.ImageSet;const Region = cpu.Region;const Target = cpu.Target;const PackedTarget = cpu.PackedTarget;pub const CompositorOptions = struct { prefer_accy: bool = true, device_index: i32 = 0, executor: executor.Options = .{},};pub const Kind = enum { cpu, accy,};pub const Fallback = struct { init_error: ?anyerror = null, render_error: ?anyerror = null, render_fallbacks: usize = 0,};pub const Status = struct { kind: Kind, init_error: ?anyerror, render_error: ?anyerror, render_fallbacks: usize, executor_storage: ?executor.StorageStatus,};pub const Compositor = struct { engine: Engine = .cpu, fallback: Fallback = .{}, pub fn init(allocator: Allocator, options: CompositorOptions) Compositor { if (options.prefer_accy) { if (AccyEngine.initVulkan(allocator, options.device_index, options.executor)) |engine| { return .{ .engine = .{ .accy = engine } }; } else |err| { return .{ .fallback = .{ .init_error = err } }; } } return initCpu(); } pub fn initCpu() Compositor { return .{}; } pub fn initWithHandle(allocator: Allocator, handle: BackendHandle, options: executor.Options) !Compositor { return .{ .engine = .{ .accy = try AccyEngine.initWithHandle(allocator, handle, options) } }; } pub fn deinit(self: *Compositor) void { switch (self.engine) { .cpu => {}, .accy => |*engine| engine.deinit(), } self.* = .{}; } pub fn kind(self: *const Compositor) Kind { return switch (self.engine) { .cpu => .cpu, .accy => .accy, }; } pub fn status(self: *const Compositor) Status { return .{ .kind = self.kind(), .init_error = self.fallback.init_error, .render_error = self.fallback.render_error, .render_fallbacks = self.fallback.render_fallbacks, .executor_storage = switch (self.engine) { .cpu => null, .accy => |*engine| engine.executor.storageStatus(), }, }; } fn noteRenderFallback(self: *Compositor, err: anyerror) void { self.fallback.render_error = err; self.fallback.render_fallbacks += 1; } pub fn renderCommands(self: *Compositor, commands: []const Command, target: Target, clear: Color) !void { switch (self.engine) { .cpu => try cpu.renderCommands(commands, target, clear), .accy => |*engine| engine.executor.renderCommands(commands, target, clear) catch |err| { self.noteRenderFallback(err); try cpu.renderCommands(commands, target, clear); }, } } pub fn renderCommandsWithImages( self: *Compositor, commands: []const Command, target: Target, clear: Color, images: ImageSet, ) !void { switch (self.engine) { .cpu => try cpu.renderCommandsWithImages(commands, target, clear, images), .accy => |*engine| engine.executor.renderCommandsWithImages(commands, target, clear, images) catch |err| { self.noteRenderFallback(err); try cpu.renderCommandsWithImages(commands, target, clear, images); }, } } pub fn renderCommandsRegionWithImages( self: *Compositor, commands: []const Command, target: Target, clear: Color, images: ImageSet, region: Region, ) !void { switch (self.engine) { .cpu => try cpu.renderCommandsRegionWithImages(commands, target, clear, images, region), .accy => |*engine| engine.executor.renderCommandsRegionWithImages(commands, target, clear, images, region) catch |err| { self.noteRenderFallback(err); try cpu.renderCommandsRegionWithImages(commands, target, clear, images, region); }, } } pub fn renderCommandsPacked(self: *Compositor, commands: []const Command, target: PackedTarget, clear: Color) !void { switch (self.engine) { .cpu => try cpu.renderCommandsPacked(commands, target, clear), .accy => |*engine| engine.executor.renderCommandsPacked(commands, target, clear) catch |err| { self.noteRenderFallback(err); try cpu.renderCommandsPacked(commands, target, clear); }, } } pub fn renderCommandsPackedWithImages( self: *Compositor, commands: []const Command, target: PackedTarget, clear: Color, images: ImageSet, ) !void { switch (self.engine) { .cpu => try cpu.renderCommandsPackedWithImages(commands, target, clear, images), .accy => |*engine| engine.executor.renderCommandsPackedWithImages(commands, target, clear, images) catch |err| { self.noteRenderFallback(err); try cpu.renderCommandsPackedWithImages(commands, target, clear, images); }, } } pub fn renderCommandsPackedRegionWithImages( self: *Compositor, commands: []const Command, target: PackedTarget, clear: Color, images: ImageSet, region: Region, ) !void { switch (self.engine) { .cpu => try cpu.renderCommandsPackedRegionWithImages(commands, target, clear, images, region), .accy => |*engine| engine.executor.renderCommandsPackedRegionWithImages(commands, target, clear, images, region) catch |err| { self.noteRenderFallback(err); try cpu.renderCommandsPackedRegionWithImages(commands, target, clear, images, region); }, } }};const Engine = union(enum) { cpu, accy: AccyEngine,};const AccyEngine = struct { allocator: Allocator, executor: Executor, vulkan_state: ?*gpu.vulkan.State = null, fn initWithHandle(allocator: Allocator, handle: BackendHandle, options: executor.Options) !AccyEngine { var paint_executor = try Executor.init(allocator, handle, options); errdefer paint_executor.deinit(); return .{ .allocator = allocator, .executor = paint_executor, }; } fn initVulkan(allocator: Allocator, device_index: i32, options: executor.Options) !AccyEngine { const state = try allocator.create(gpu.vulkan.State); errdefer allocator.destroy(state); state.* = try gpu.vulkan.State.initDevice(allocator, device_index); errdefer state.deinit(); var paint_executor = try Executor.init(allocator, state.handle(), options); errdefer paint_executor.deinit(); return .{ .allocator = allocator, .executor = paint_executor, .vulkan_state = state, }; } fn deinit(self: *AccyEngine) void { self.executor.deinit(); if (self.vulkan_state) |state| { state.deinit(); self.allocator.destroy(state); } self.* = undefined; }};test "paint compositor CPU path matches rgba8 renderer" { const testing = std.testing; var expected = @as([(8 * 4 * 4)]u8, @splat(0)); var actual = @as([(8 * 4 * 4)]u8, @splat(0)); const commands = [_]Command{ .{ .kind = .fill, .rect = .{ .x = 1, .y = 1, .width = 4, .height = 2 }, .clip = .{ .x = 0, .y = 0, .width = 8, .height = 4 }, .color = .{ .r = 200, .g = 80, .b = 30, .a = 255 }, }, }; const clear = Color{ .r = 1, .g = 2, .b = 3, .a = 255 }; try cpu.renderCommandsWithImages(commands[0..], .{ .width = 8, .height = 4, .rgba8 = expected[0..] }, clear, .{}); var compositor = Compositor.initCpu(); defer compositor.deinit(); try compositor.renderCommandsWithImages(commands[0..], .{ .width = 8, .height = 4, .rgba8 = actual[0..] }, clear, .{}); try testing.expectEqualSlices(u8, expected[0..], actual[0..]);}test "paint compositor CPU region preserves packed pixels outside damage" { const testing = std.testing; var pixels = @as([16]u32, @splat(0xeeee_eeee)); 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 = 10, .g = 20, .b = 30, .a = 255 }, }, }; var compositor = Compositor.initCpu(); defer compositor.deinit(); try compositor.renderCommandsPackedRegionWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = pixels[0..], }, .{ .r = 1, .g = 2, .b = 3, .a = 255 }, .{}, .{ .x = 1, .y = 1, .width = 2, .height = 2, }); const painted = cpu.packRgba(.{ .r = 10, .g = 20, .b = 30, .a = 255 }); try testing.expectEqual(@as(u32, 0xeeee_eeee), pixels[0]); try testing.expectEqual(painted, pixels[5]); try testing.expectEqual(painted, pixels[6]); try testing.expectEqual(@as(u32, 0xeeee_eeee), pixels[15]);}test "paint compositor CPU status reports no fallback" { var compositor = Compositor.initCpu(); defer compositor.deinit(); const state = compositor.status(); try std.testing.expectEqual(Kind.cpu, state.kind); try std.testing.expectEqual(@as(?anyerror, null), state.init_error); try std.testing.expectEqual(@as(?anyerror, null), state.render_error); try std.testing.expectEqual(@as(usize, 0), state.render_fallbacks); try std.testing.expectEqual(@as(?executor.StorageStatus, null), state.executor_storage);}test "paint compositor records render fallback and still paints through CPU" { const testing = std.testing; var state = gpu.recording.BackendState{ .allocator = testing.allocator, .kind = .vulkan, .format = .vulkan_spirv, }; var compositor = try Compositor.initWithHandle(testing.allocator, state.handle(), .{}); defer compositor.deinit(); state.fail_launch_after_count = state.launch_count; var expected = @as([16]u32, @splat(0xeeee_eeee)); var actual = @as([16]u32, @splat(0xeeee_eeee)); 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 = 10, .g = 20, .b = 30, .a = 255 }, }, }; const clear = Color{ .r = 1, .g = 2, .b = 3, .a = 255 }; try cpu.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = expected[0..] }, clear, .{}); try compositor.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = actual[0..] }, clear, .{}); try testing.expectEqualSlices(u32, expected[0..], actual[0..]); const after = compositor.status(); try testing.expectEqual(Kind.accy, after.kind); try testing.expectEqual(@as(?anyerror, error.RuntimeUnavailable), after.render_error); try testing.expectEqual(@as(usize, 1), after.render_fallbacks); try compositor.renderCommandsPackedWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = actual[0..] }, clear, .{}); try testing.expectEqual(@as(usize, 2), compositor.status().render_fallbacks);}test "paint compositor launches Accy executor through backend handle" { const testing = std.testing; var state = gpu.recording.BackendState{ .allocator = testing.allocator, .kind = .vulkan, .format = .vulkan_spirv, }; const executor_limits = executor.Limits{ .mode = .device_csr, .commands = 1, .pixels = 4, .tiles = 1, .tile_pairs = 1, }; const image_limits = paint_image.Limits{ .dst_pixels = 4, .src_pixels = 4 }; var compositor = try Compositor.initWithHandle(testing.allocator, state.handle(), .{ .initial_storage = executor_limits, .image_storage = image_limits, }); defer compositor.deinit(); const initial_status = compositor.status().executor_storage.?; try testing.expectEqual(executor_limits, initial_status.limits.?); try testing.expectEqual(image_limits, initial_status.image_processor.?.limits.?); var pixels = @as([16]u32, @splat(0xffff_ffff)); 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 = 255, .g = 0, .b = 0, .a = 255 }, }, }; try testing.expectEqual(Kind.accy, compositor.kind()); try compositor.renderCommandsPackedRegionWithImages(commands[0..], .{ .width = 4, .height = 4, .pixels = pixels[0..], }, .{ .r = 0, .g = 0, .b = 0, .a = 255 }, .{}, .{ .x = 1, .y = 1, .width = 2, .height = 2, }); try testing.expectEqual(@as(usize, 7), state.launch_count); try testing.expectEqual(@as(usize, 1), state.sync_count); try testing.expectEqual(@as(usize, 1), state.read_count); try testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[5]); try testing.expectEqual(@as(u32, 1), state.last_launch_scalar_u32_values[6]); try testing.expectEqual(@as(u32, 2), state.last_launch_scalar_u32_values[7]); try testing.expectEqual(@as(u32, 4), state.last_launch_scalar_u32_values[8]); try testing.expectEqual(@as(u32, 0xffff_ffff), pixels[0]); try testing.expectEqual(@as(u32, 0), pixels[5]); try testing.expectEqual(@as(u32, 0), pixels[6]); try testing.expectEqual(@as(u32, 0xffff_ffff), pixels[15]);}Source: lib/gui/src/paint/root.zig:3
zig
pub const compositor = @import("compositor.zig");Audit
| Definitions | 18 |
|---|---|
| Public names | 34 |
| Members | 15 |
| Version | 26.7.0 |
| Revision | daab053ee433 |