1extern "env" fn log_error(ptr: [*]const u8, len: u32) void;
2
3const MAX_PIXELS = 4096 * 1440;
4const MAX_POOLS = 32;
5const MAX_BUFFERS = 64;
6const MAX_FREE = 64;
7
8const FreeBlock = struct { offset: usize, size: usize };
9
10pub const Pool = struct {
11 id: u32,
12 client_id: u32,
13 width: u32,
14 height: u32,
15 format: u32,
16 pixels: []u32,
17 pixel_offset: usize,
18};
19
20const BufferRef = struct {
21 pool_id: u32,
22 pool_client: u32,
23 x: u32, y: u32,
24 w: u32, h: u32,
25};
26
27var pixel_heap: [MAX_PIXELS]u32 align(4) = undefined;
28var pixel_used: usize = 0;
29var free_blocks: [MAX_FREE]FreeBlock = undefined;
30var free_count: u32 = 0;
31
32fn allocPixels(count: usize) ?[]u32 {
33 // Check free list first
34 for (0..free_count) |i| {
35 const fb = free_blocks[i];
36 if (fb.size >= count) {
37 const slice = pixel_heap[fb.offset..fb.offset + count];
38 if (fb.size == count) {
39 // Exact match — remove from free list
40 var j = i;
41 while (j + 1 < free_count) : (j += 1) free_blocks[j] = free_blocks[j + 1];
42 free_count -= 1;
43 } else {
44 free_blocks[i] = .{ .offset = fb.offset + count, .size = fb.size - count };
45 }
46 return slice;
47 }
48 }
49 // Bump allocate
50 if (pixel_used + count > MAX_PIXELS) {
51 const msg = "altaica_shm: pixel heap full\n";
52 log_error(msg, msg.len);
53 return null;
54 }
55 const slice = pixel_heap[pixel_used..pixel_used + count];
56 pixel_used += count;
57 return slice;
58}
59
60fn freePixels(offset: usize, size: usize) void {
61 if (size == 0) return;
62 // Coalesce with adjacent free blocks
63 var coalesced_offset = offset;
64 var coalesced_size = size;
65 var added = false;
66 var i: usize = 0;
67 while (i < free_count) {
68 const fb = free_blocks[i];
69 const fb_end = fb.offset + fb.size;
70 if (fb_end == coalesced_offset) {
71 coalesced_offset = fb.offset;
72 coalesced_size += fb.size;
73 // Remove this block
74 var j = i;
75 while (j + 1 < free_count) : (j += 1) free_blocks[j] = free_blocks[j + 1];
76 free_count -= 1;
77 added = true;
78 } else if (coalesced_offset + coalesced_size == fb.offset) {
79 coalesced_size += fb.size;
80 var j = i;
81 while (j + 1 < free_count) : (j += 1) free_blocks[j] = free_blocks[j + 1];
82 free_count -= 1;
83 added = true;
84 } else {
85 i += 1;
86 }
87 }
88 // If the coalesced block is at the end of the used heap, pop it.
89 if (coalesced_offset + coalesced_size == pixel_used) {
90 pixel_used = coalesced_offset;
91 return;
92 }
93 // Store the coalesced block
94 if (!added) {
95 if (free_count < MAX_FREE) {
96 free_blocks[free_count] = .{ .offset = coalesced_offset, .size = coalesced_size };
97 free_count += 1;
98 }
99 }
100}
101
102fn pixelsOffset(pixels: []u32) usize {
103 const heap_start = @intFromPtr(&pixel_heap);
104 const pix_start = @intFromPtr(pixels.ptr);
105 return (pix_start - heap_start) / @sizeOf(u32);
106}
107
108pub const FORMAT_ARGB8888: u32 = 0;
109pub const FORMAT_XRGB8888: u32 = 1;
110
111fn isValidFormat(fmt: u32) bool {
112 return fmt == FORMAT_ARGB8888 or fmt == FORMAT_XRGB8888;
113}
114
115fn formatHasAlpha(fmt: u32) bool {
116 return fmt == FORMAT_ARGB8888;
117}
118
119var pools: [MAX_POOLS]?Pool = [_]?Pool{null} ** MAX_POOLS;
120var buffers: [MAX_BUFFERS]?BufferRef = [_]?BufferRef{null} ** MAX_BUFFERS;
121
122pub fn findPool(client_id: u32, pool_id: u32) ?*Pool {
123 for (&pools) |*maybe| {
124 const p = &(maybe.* orelse continue);
125 if (p.client_id == client_id and p.id == pool_id) return p;
126 }
127 return null;
128}
129
130pub fn createPool(client_id: u32, id: u32, width: u32, height: u32, format: u32) bool {
131 if (!isValidFormat(format)) {
132 const msg = "altaica_shm: invalid format\n";
133 log_error(msg, msg.len);
134 return false;
135 }
136 for (&pools) |*maybe| {
137 if (maybe.* == null) {
138 const pixels = allocPixels(width * height) orelse {
139 const msg = "altaica_shm: pixel heap full\n";
140 log_error(msg, msg.len);
141 return false;
142 };
143 @memset(pixels, if (formatHasAlpha(format)) 0x00000000 else 0xFF000000);
144 maybe.* = Pool{
145 .id = id, .client_id = client_id,
146 .width = width, .height = height,
147 .format = format, .pixels = pixels,
148 .pixel_offset = pixelsOffset(pixels),
149 };
150 return true;
151 }
152 }
153 const msg = "altaica_shm: no free pool slots\n";
154 log_error(msg, msg.len);
155 return false;
156}
157
158pub fn destroyPool(client_id: u32, pool_id: u32) void {
159 for (&pools) |*maybe| {
160 const p = &(maybe.* orelse continue);
161 if (p.client_id == client_id and p.id == pool_id) {
162 // Free all buffers referencing this pool
163 for (&buffers) |*b| {
164 const bref = b.* orelse continue;
165 if (bref.pool_id == pool_id and bref.pool_client == client_id) {
166 b.* = null;
167 }
168 }
169 // Free pixel memory
170 freePixels(p.pixel_offset, p.width * p.height);
171 maybe.* = null;
172 return;
173 }
174 }
175}
176
177pub fn resizePool(client_id: u32, pool_id: u32, new_w: u32, new_h: u32) bool {
178 const pool = findPool(client_id, pool_id) orelse return false;
179 const new_count = new_w * new_h;
180 const old_count = pool.width * pool.height;
181 if (new_count <= old_count) {
182 pool.width = new_w;
183 pool.height = new_h;
184 return true;
185 }
186 const old_offset = pool.pixel_offset;
187 const new_pixels = allocPixels(new_count) orelse return false;
188 const copy_rows = @min(pool.height, new_h);
189 const copy_cols = @min(pool.width, new_w);
190 for (0..copy_rows) |row| {
191 for (0..copy_cols) |col| {
192 new_pixels[row * new_w + col] = pool.pixels[row * pool.width + col];
193 }
194 }
195 for (old_count..new_count) |i| new_pixels[i] = 0xFF000000;
196 freePixels(old_offset, old_count);
197 pool.pixels = new_pixels;
198 pool.pixel_offset = pixelsOffset(new_pixels);
199 pool.width = new_w;
200 pool.height = new_h;
201 return true;
202}
203
204pub fn uploadPixels(client_id: u32, pool_id: u32, x: u32, y: u32, w: u32, h: u32, data: []const u8) void {
205 const pool = findPool(client_id, pool_id) orelse return;
206 if (x + w > pool.width or y + h > pool.height) return;
207 if (data.len < w * h * 4) return;
208
209 for (0..h) |row| {
210 const dst_row = y + row;
211 for (0..w) |col| {
212 const dst_col = x + col;
213 const si = (row * w + col) * 4;
214 const a = data[si + 3];
215 const r = data[si + 2];
216 const g = data[si + 1];
217 const b = data[si + 0];
218 const di = dst_row * pool.width + dst_col;
219 pool.pixels[di] = (@as(u32, a) << 24) | (@as(u32, r) << 16) | (@as(u32, g) << 8) | b;
220 }
221 }
222}
223
224pub fn findBuffer(idx: u32) ?BufferRef {
225 if (idx >= MAX_BUFFERS) return null;
226 return buffers[idx];
227}
228
229pub fn allocBufferIdx() ?u32 {
230 for (&buffers, 0..) |*maybe, i| {
231 if (maybe.* == null) {
232 return @as(u32, @intCast(i));
233 }
234 }
235 return null;
236}
237
238pub fn setBuffer(idx: u32, ref: BufferRef) void {
239 if (idx < MAX_BUFFERS) buffers[idx] = ref;
240}
241
242pub fn freeBuffer(idx: u32) void {
243 if (idx < MAX_BUFFERS) buffers[idx] = null;
244}
245
246/// Copy a sub-region from pool to a destination surface with different strides
247pub fn copyRegion(
248 pool_id: u32,
249 pool_client: u32,
250 dst: []u32,
251 dst_stride: u32,
252 src_x: u32,
253 src_y: u32,
254 w: u32,
255 h: u32,
256 dst_x: u32,
257 dst_y: u32,
258) bool {
259 const pool = findPool(pool_client, pool_id) orelse return false;
260 if (src_x + w > pool.width or src_y + h > pool.height) return false;
261 for (0..h) |row| {
262 for (0..w) |col| {
263 const si = (src_y + row) * pool.width + (src_x + col);
264 const di = (dst_y + row) * dst_stride + (dst_x + col);
265 if (di >= dst.len) return false;
266 dst[di] = pool.pixels[si];
267 }
268 }
269 return true;
270}
271
272pub fn copyFromPool(pool_id: u32, pool_client: u32, dst: []u32, w: u32, h: u32, sx: u32, sy: u32) bool {
273 const pool = findPool(pool_client, pool_id) orelse return false;
274 if (sx + w > pool.width or sy + h > pool.height) return false;
275 if (dst.len < w * h) return false;
276 for (0..h) |row| {
277 for (0..w) |col| {
278 const si = (sy + row) * pool.width + (sx + col);
279 dst[row * w + col] = pool.pixels[si];
280 }
281 }
282 return true;
283}
284
285pub fn copyFromPoolScaled(pool_id: u32, pool_client: u32, dst: []u32, dst_w: u32, dst_h: u32, src_x: u32, src_y: u32, src_w: u32, src_h: u32) bool {
286 const pool = findPool(pool_client, pool_id) orelse return false;
287 if (src_x + src_w > pool.width or src_y + src_h > pool.height) return false;
288 if (dst.len < dst_w * dst_h) return false;
289 if (src_w == 0 or src_h == 0 or dst_w == 0 or dst_h == 0) return false;
290 for (0..dst_h) |dy| {
291 const sy = (dy * src_h) / dst_h;
292 for (0..dst_w) |dx| {
293 const sx = (dx * src_w) / dst_w;
294 const si = (src_y + sy) * pool.width + (src_x + sx);
295 dst[dy * dst_w + dx] = pool.pixels[si];
296 }
297 }
298 return true;
299}