main shar/altaica / compositor / src / altaica_shm.zig
  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}