lib/gui/src/surface/capacity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const gui = @import("../root.zig");
3
4 const UiNode = gui.model.UiNode;
5 const UiSurfaceTree = gui.model.UiSurfaceTree;
6 const UiTextRun = gui.model.UiTextRun;
7 const UiTextStyle = gui.model.UiTextStyle;
8 const UiImage = gui.model.UiImage;
9
10 pub const Facts = struct {
11 nodes: usize = 0,
12 runs: usize = 0,
13 styles: usize = 0,
14 images: usize = 0,
15 image_pixels: usize = 0,
16 semantic_bytes: usize = 0,
17 };
18
19 pub const Limits = struct {
20 nodes: usize,
21 runs: usize = 0,
22 styles: usize = 0,
23 images: usize = 0,
24 image_pixels: usize = 0,
25 semantic_bytes: usize,
26
27 pub fn admits(self: Limits, facts: Facts) bool {
28 return facts.nodes <= self.nodes and
29 facts.runs <= self.runs and
30 facts.styles <= self.styles and
31 facts.images <= self.images and
32 facts.image_pixels <= self.image_pixels and
33 facts.semantic_bytes <= self.semantic_bytes;
34 }
35 };
36
37 pub const DeriveError = error{
38 InvalidNodeLimit,
39 CapacityOverflow,
40 };
41
42 pub const storage_alignment: usize = @max(
43 @alignOf(UiSurfaceTree),
44 @max(
45 @alignOf(UiNode),
46 @max(@alignOf(UiTextRun), @max(@alignOf(UiTextStyle), @alignOf(UiImage))),
47 ),
48 );
49
50 pub const Capacity = struct {
51 nodes: usize,
52 runs: usize,
53 styles: usize,
54 images: usize,
55 image_pixels: usize,
56 semantic_bytes: usize,
57 surface_offset: usize,
58 surface_bytes: usize,
59 node_offset: usize,
60 node_slots: usize,
61 node_bytes: usize,
62 run_offset: usize,
63 run_slots: usize,
64 run_bytes: usize,
65 style_offset: usize,
66 style_slots: usize,
67 style_bytes: usize,
68 image_offset: usize,
69 image_slots: usize,
70 image_bytes: usize,
71 image_pixel_offset: usize,
72 image_pixel_slots: usize,
73 image_pixel_bytes: usize,
74 semantic_offset: usize,
75 storage_bytes: usize,
76
77 pub fn derive(limits: Limits) DeriveError!Capacity {
78 if (limits.nodes == 0) return error.InvalidNodeLimit;
79 const surface = try placed(UiSurfaceTree, 0, 1);
80 const nodes = try placed(UiNode, surface.end, limits.nodes - 1);
81 const runs = try placed(UiTextRun, nodes.end, limits.runs);
82 const styles = try placed(UiTextStyle, runs.end, limits.styles);
83 const images = try placed(UiImage, styles.end, limits.images);
84 const image_pixels = try placed(u32, images.end, limits.image_pixels);
85 const semantics = try placed(u8, image_pixels.end, limits.semantic_bytes);
86 return .{
87 .nodes = limits.nodes,
88 .runs = limits.runs,
89 .styles = limits.styles,
90 .images = limits.images,
91 .image_pixels = limits.image_pixels,
92 .semantic_bytes = limits.semantic_bytes,
93 .surface_offset = surface.start,
94 .surface_bytes = surface.bytes,
95 .node_offset = nodes.start,
96 .node_slots = limits.nodes - 1,
97 .node_bytes = nodes.bytes,
98 .run_offset = runs.start,
99 .run_slots = limits.runs,
100 .run_bytes = runs.bytes,
101 .style_offset = styles.start,
102 .style_slots = limits.styles,
103 .style_bytes = styles.bytes,
104 .image_offset = images.start,
105 .image_slots = limits.images,
106 .image_bytes = images.bytes,
107 .image_pixel_offset = image_pixels.start,
108 .image_pixel_slots = limits.image_pixels,
109 .image_pixel_bytes = image_pixels.bytes,
110 .semantic_offset = semantics.start,
111 .storage_bytes = semantics.end,
112 };
113 }
114
115 pub fn admits(self: Capacity, facts: Facts) bool {
116 return self.toLimits().admits(facts);
117 }
118
119 pub fn toLimits(self: Capacity) Limits {
120 return .{
121 .nodes = self.nodes,
122 .runs = self.runs,
123 .styles = self.styles,
124 .images = self.images,
125 .image_pixels = self.image_pixels,
126 .semantic_bytes = self.semantic_bytes,
127 };
128 }
129 };
130
131 const Region = struct {
132 start: usize,
133 bytes: usize,
134 end: usize,
135 };
136
137 fn aligned(offset: usize, alignment: usize) DeriveError!usize {
138 const mask = alignment - 1;
139 const padded = std.math.add(usize, offset, mask) catch return error.CapacityOverflow;
140 return padded & ~mask;
141 }
142
143 fn placed(comptime T: type, offset: usize, count: usize) DeriveError!Region {
144 const start = try aligned(offset, @alignOf(T));
145 const bytes = std.math.mul(usize, count, @sizeOf(T)) catch return error.CapacityOverflow;
146 const end = std.math.add(usize, start, bytes) catch return error.CapacityOverflow;
147 return .{ .start = start, .bytes = bytes, .end = end };
148 }
149
150 fn modelCapacity(limits: Limits) DeriveError!Capacity {
151 if (limits.nodes == 0) return error.InvalidNodeLimit;
152 const surface_offset = alignedModel(0, @alignOf(UiSurfaceTree));
153 const surface_bytes = @as(u128, @sizeOf(UiSurfaceTree));
154 const node_offset = alignedModel(surface_offset + surface_bytes, @alignOf(UiNode));
155 const node_slots = @as(u128, limits.nodes - 1);
156 const node_bytes = node_slots * @sizeOf(UiNode);
157 const run_offset = alignedModel(node_offset + node_bytes, @alignOf(UiTextRun));
158 const run_slots = @as(u128, limits.runs);
159 const run_bytes = run_slots * @sizeOf(UiTextRun);
160 const style_offset = alignedModel(run_offset + run_bytes, @alignOf(UiTextStyle));
161 const style_slots = @as(u128, limits.styles);
162 const style_bytes = style_slots * @sizeOf(UiTextStyle);
163 const image_offset = alignedModel(style_offset + style_bytes, @alignOf(UiImage));
164 const image_slots = @as(u128, limits.images);
165 const image_bytes = image_slots * @sizeOf(UiImage);
166 const image_pixel_offset = alignedModel(image_offset + image_bytes, @alignOf(u32));
167 const image_pixel_slots = @as(u128, limits.image_pixels);
168 const image_pixel_bytes = image_pixel_slots * @sizeOf(u32);
169 const semantic_offset = alignedModel(image_pixel_offset + image_pixel_bytes, @alignOf(u8));
170 const storage_bytes = semantic_offset + limits.semantic_bytes;
171 return .{
172 .nodes = limits.nodes,
173 .runs = limits.runs,
174 .styles = limits.styles,
175 .images = limits.images,
176 .image_pixels = limits.image_pixels,
177 .semantic_bytes = limits.semantic_bytes,
178 .surface_offset = try narrowed(surface_offset),
179 .surface_bytes = try narrowed(surface_bytes),
180 .node_offset = try narrowed(node_offset),
181 .node_slots = try narrowed(node_slots),
182 .node_bytes = try narrowed(node_bytes),
183 .run_offset = try narrowed(run_offset),
184 .run_slots = try narrowed(run_slots),
185 .run_bytes = try narrowed(run_bytes),
186 .style_offset = try narrowed(style_offset),
187 .style_slots = try narrowed(style_slots),
188 .style_bytes = try narrowed(style_bytes),
189 .image_offset = try narrowed(image_offset),
190 .image_slots = try narrowed(image_slots),
191 .image_bytes = try narrowed(image_bytes),
192 .image_pixel_offset = try narrowed(image_pixel_offset),
193 .image_pixel_slots = try narrowed(image_pixel_slots),
194 .image_pixel_bytes = try narrowed(image_pixel_bytes),
195 .semantic_offset = try narrowed(semantic_offset),
196 .storage_bytes = try narrowed(storage_bytes),
197 };
198 }
199
200 fn alignedModel(offset: u128, alignment: usize) u128 {
201 const mask = @as(u128, alignment - 1);
202 return (offset + mask) & ~mask;
203 }
204
205 fn narrowed(value: u128) DeriveError!usize {
206 if (value > std.math.maxInt(usize)) return error.CapacityOverflow;
207 return @intCast(value);
208 }
209
210 test "surface clone capacity matches an independent aligned byte model" {
211 comptime {
212 @stardustClaim(
213 @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "gui_surface_clone_capacity"),
214 null,
215 null,
216 null,
217 null,
218 null,
219 null,
220 );
221 }
222
223 const cases = [_]Limits{
224 .{ .nodes = 1, .semantic_bytes = 0 },
225 .{
226 .nodes = 29,
227 .runs = 41,
228 .styles = 12,
229 .images = 4,
230 .image_pixels = 16_384,
231 .semantic_bytes = 1_440,
232 },
233 .{
234 .nodes = 257,
235 .runs = 2_048,
236 .styles = 512,
237 .images = 16,
238 .image_pixels = 65_536,
239 .semantic_bytes = 65_536,
240 },
241 };
242 for (cases) |limits| {
243 try std.testing.expectEqual(try modelCapacity(limits), try Capacity.derive(limits));
244 }
245 }
246
247 test "surface clone capacity rejects invalid and overflowing limits" {
248 try std.testing.expectError(error.InvalidNodeLimit, Capacity.derive(.{
249 .nodes = 0,
250 .semantic_bytes = 0,
251 }));
252 try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
253 .nodes = std.math.maxInt(usize),
254 .runs = std.math.maxInt(usize),
255 .styles = std.math.maxInt(usize),
256 .images = std.math.maxInt(usize),
257 .image_pixels = std.math.maxInt(usize),
258 .semantic_bytes = std.math.maxInt(usize),
259 }));
260 }
261
262 comptime {
263 std.debug.assert(std.math.isPowerOfTwo(storage_alignment));
264 }