1 /*
2  * graphics.c
3  * Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #include "graphics.h"
9 #include "state.h"
10 #include "disk-cache.h"
11 #include "iqsort.h"
12 #include "safe-wrappers.h"
13 
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <sys/mman.h>
18 #include <stdlib.h>
19 
20 #include <zlib.h>
21 #include <structmember.h>
22 #include "png-reader.h"
23 PyTypeObject GraphicsManager_Type;
24 
25 #define DEFAULT_STORAGE_LIMIT 320u * (1024u * 1024u)
26 #define REPORT_ERROR(...) { log_error(__VA_ARGS__); }
27 #define FREE_CFD_AFTER_FUNCTION __attribute__((cleanup(cfd_free)))
28 
29 // caching {{{
30 #define CACHE_KEY_BUFFER_SIZE 32
31 
32 static size_t
cache_key(const ImageAndFrame x,char * key)33 cache_key(const ImageAndFrame x, char *key) {
34     return snprintf(key, CACHE_KEY_BUFFER_SIZE, "%llx:%x", x.image_id, x.frame_id);
35 }
36 #define CK(x) key, cache_key(x, key)
37 
38 static bool
add_to_cache(GraphicsManager * self,const ImageAndFrame x,const void * data,const size_t sz)39 add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) {
40     char key[CACHE_KEY_BUFFER_SIZE];
41     return add_to_disk_cache(self->disk_cache, CK(x), data, sz);
42 }
43 
44 static bool
remove_from_cache(GraphicsManager * self,const ImageAndFrame x)45 remove_from_cache(GraphicsManager *self, const ImageAndFrame x) {
46     char key[CACHE_KEY_BUFFER_SIZE];
47     return remove_from_disk_cache(self->disk_cache, CK(x));
48 }
49 
50 static bool
read_from_cache(const GraphicsManager * self,const ImageAndFrame x,void ** data,size_t * sz)51 read_from_cache(const GraphicsManager *self, const ImageAndFrame x, void **data, size_t *sz) {
52     char key[CACHE_KEY_BUFFER_SIZE];
53     return read_from_disk_cache_simple(self->disk_cache, CK(x), data, sz, false);
54 }
55 
56 static size_t
cache_size(const GraphicsManager * self)57 cache_size(const GraphicsManager *self) { return disk_cache_total_size(self->disk_cache); }
58 #undef CK
59 // }}}
60 
61 
62 GraphicsManager*
grman_alloc()63 grman_alloc() {
64     GraphicsManager *self = (GraphicsManager *)GraphicsManager_Type.tp_alloc(&GraphicsManager_Type, 0);
65     self->images_capacity = self->capacity = 64;
66     self->images = calloc(self->images_capacity, sizeof(Image));
67     self->render_data = calloc(self->capacity, sizeof(ImageRenderData));
68     self->storage_limit = DEFAULT_STORAGE_LIMIT;
69     if (self->images == NULL || self->render_data == NULL) {
70         PyErr_NoMemory();
71         Py_CLEAR(self); return NULL;
72     }
73     self->disk_cache = create_disk_cache();
74     if (!self->disk_cache) { Py_CLEAR(self); return NULL; }
75     return self;
76 }
77 
78 static void
free_refs_data(Image * img)79 free_refs_data(Image *img) {
80     free(img->refs); img->refs = NULL;
81     img->refcnt = 0; img->refcap = 0;
82 }
83 
84 static void
free_load_data(LoadData * ld)85 free_load_data(LoadData *ld) {
86     free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0; ld->buf = NULL;
87     if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz);
88     ld->mapped_file = NULL; ld->mapped_file_sz = 0;
89     ld->loading_for = (const ImageAndFrame){0};
90 }
91 
92 static void
free_image(GraphicsManager * self,Image * img)93 free_image(GraphicsManager *self, Image *img) {
94     if (img->texture_id) free_texture(&img->texture_id);
95     ImageAndFrame key = { .image_id=img->internal_id, .frame_id = img->root_frame.id };
96     if (!remove_from_cache(self, key) && PyErr_Occurred()) PyErr_Print();
97     for (unsigned i = 0; i < img->extra_framecnt; i++) {
98         key.frame_id = img->extra_frames[i].id;
99         if (!remove_from_cache(self, key) && PyErr_Occurred()) PyErr_Print();
100     }
101     if (img->extra_frames) {
102         free(img->extra_frames);
103         img->extra_frames = NULL;
104     }
105     free_refs_data(img);
106     self->used_storage -= img->used_storage;
107 }
108 
109 
110 static void
dealloc(GraphicsManager * self)111 dealloc(GraphicsManager* self) {
112     size_t i;
113     if (self->images) {
114         for (i = 0; i < self->image_count; i++) free_image(self, self->images + i);
115         free(self->images);
116     }
117     free(self->render_data);
118     Py_CLEAR(self->disk_cache);
119     Py_TYPE(self)->tp_free((PyObject*)self);
120 }
121 
122 static id_type internal_id_counter = 1;
123 
124 static Image*
img_by_internal_id(GraphicsManager * self,id_type id)125 img_by_internal_id(GraphicsManager *self, id_type id) {
126     for (size_t i = 0; i < self->image_count; i++) {
127         if (self->images[i].internal_id == id) return self->images + i;
128     }
129     return NULL;
130 }
131 
132 static Image*
img_by_client_id(GraphicsManager * self,uint32_t id)133 img_by_client_id(GraphicsManager *self, uint32_t id) {
134     for (size_t i = 0; i < self->image_count; i++) {
135         if (self->images[i].client_id == id) return self->images + i;
136     }
137     return NULL;
138 }
139 
140 static Image*
img_by_client_number(GraphicsManager * self,uint32_t number)141 img_by_client_number(GraphicsManager *self, uint32_t number) {
142     // get the newest image with the specified number
143     for (size_t i = self->image_count; i-- > 0; ) {
144         if (self->images[i].client_number == number) return self->images + i;
145     }
146     return NULL;
147 }
148 
149 
150 static void
remove_image(GraphicsManager * self,size_t idx)151 remove_image(GraphicsManager *self, size_t idx) {
152     free_image(self, self->images + idx);
153     remove_i_from_array(self->images, idx, self->image_count);
154     self->layers_dirty = true;
155 }
156 
157 static void
remove_images(GraphicsManager * self,bool (* predicate)(Image *),id_type skip_image_internal_id)158 remove_images(GraphicsManager *self, bool(*predicate)(Image*), id_type skip_image_internal_id) {
159     for (size_t i = self->image_count; i-- > 0;) {
160         Image *img = self->images + i;
161         if (img->internal_id != skip_image_internal_id && predicate(img)) {
162             remove_image(self, i);
163         }
164     }
165 }
166 
167 
168 // Loading image data {{{
169 
170 static bool
trim_predicate(Image * img)171 trim_predicate(Image *img) {
172     return !img->root_frame_data_loaded || !img->refcnt;
173 }
174 
175 
176 static void
apply_storage_quota(GraphicsManager * self,size_t storage_limit,id_type currently_added_image_internal_id)177 apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type currently_added_image_internal_id) {
178     // First remove unreferenced images, even if they have an id
179     remove_images(self, trim_predicate, currently_added_image_internal_id);
180     if (self->used_storage < storage_limit) return;
181 
182 #define oldest_last(a, b) ((b)->atime < (a)->atime)
183     QSORT(Image, self->images, self->image_count, oldest_last)
184 #undef oldest_last
185     while (self->used_storage > storage_limit && self->image_count > 0) {
186         remove_image(self, self->image_count - 1);
187     }
188     if (!self->image_count) self->used_storage = 0;  // sanity check
189 }
190 
191 static char command_response[512] = {0};
192 
193 static void
set_command_failed_response(const char * code,const char * fmt,...)194 set_command_failed_response(const char *code, const char *fmt, ...) {
195     va_list args;
196     va_start(args, fmt);
197     const size_t sz = sizeof(command_response)/sizeof(command_response[0]);
198     const int num = snprintf(command_response, sz, "%s:", code);
199     vsnprintf(command_response + num, sz - num, fmt, args);
200     va_end(args);
201 }
202 
203 // Decode formats {{{
204 #define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; }
205 
206 static bool
mmap_img_file(GraphicsManager * self,int fd,size_t sz,off_t offset)207 mmap_img_file(GraphicsManager *self, int fd, size_t sz, off_t offset) {
208     if (!sz) {
209         struct stat s;
210         if (fstat(fd, &s) != 0) ABRT(EBADF, "Failed to fstat() the fd: %d file with error: [%d] %s", fd, errno, strerror(errno));
211         sz = s.st_size;
212     }
213     void *addr = mmap(0, sz, PROT_READ, MAP_SHARED, fd, offset);
214     if (addr == MAP_FAILED) ABRT(EBADF, "Failed to map image file fd: %d at offset: %zd with size: %zu with error: [%d] %s", fd, offset, sz, errno, strerror(errno));
215     self->currently_loading.mapped_file = addr;
216     self->currently_loading.mapped_file_sz = sz;
217     return true;
218 err:
219     return false;
220 }
221 
222 
223 static const char*
zlib_strerror(int ret)224 zlib_strerror(int ret) {
225 #define Z(x) case x: return #x;
226     static char buf[128];
227     switch(ret) {
228         case Z_ERRNO:
229             return strerror(errno);
230         default:
231             snprintf(buf, sizeof(buf)/sizeof(buf[0]), "Unknown error: %d", ret);
232             return buf;
233         Z(Z_STREAM_ERROR);
234         Z(Z_DATA_ERROR);
235         Z(Z_MEM_ERROR);
236         Z(Z_BUF_ERROR);
237         Z(Z_VERSION_ERROR);
238     }
239 #undef Z
240 }
241 
242 static bool
inflate_zlib(LoadData * load_data,uint8_t * buf,size_t bufsz)243 inflate_zlib(LoadData *load_data, uint8_t *buf, size_t bufsz) {
244     bool ok = false;
245     z_stream z;
246     uint8_t *decompressed = malloc(load_data->data_sz);
247     if (decompressed == NULL) fatal("Out of memory allocating decompression buffer");
248     z.zalloc = Z_NULL;
249     z.zfree = Z_NULL;
250     z.opaque = Z_NULL;
251     z.avail_in = bufsz;
252     z.next_in = (Bytef*)buf;
253     z.avail_out = load_data->data_sz;
254     z.next_out = decompressed;
255     int ret;
256     if ((ret = inflateInit(&z)) != Z_OK) ABRT(ENOMEM, "Failed to initialize inflate with error: %s", zlib_strerror(ret));
257     if ((ret = inflate(&z, Z_FINISH)) != Z_STREAM_END) ABRT(EINVAL, "Failed to inflate image data with error: %s", zlib_strerror(ret));
258     if (z.avail_out) ABRT(EINVAL, "Image data size post inflation does not match expected size");
259     free_load_data(load_data);
260     load_data->buf_capacity = load_data->data_sz;
261     load_data->buf = decompressed;
262     load_data->buf_used = load_data->data_sz;
263     ok = true;
264 err:
265     inflateEnd(&z);
266     if (!ok) free(decompressed);
267     return ok;
268 }
269 
270 static void
png_error_handler(const char * code,const char * msg)271 png_error_handler(const char *code, const char *msg) {
272     set_command_failed_response(code, "%s", msg);
273 }
274 
275 static bool
inflate_png(LoadData * load_data,uint8_t * buf,size_t bufsz)276 inflate_png(LoadData *load_data, uint8_t *buf, size_t bufsz) {
277     png_read_data d = {.err_handler=png_error_handler};
278     inflate_png_inner(&d, buf, bufsz);
279     if (d.ok) {
280         free_load_data(load_data);
281         load_data->buf = d.decompressed;
282         load_data->buf_capacity = d.sz;
283         load_data->buf_used = d.sz;
284         load_data->data_sz = d.sz;
285         load_data->width = d.width; load_data->height = d.height;
286     }
287     else free(d.decompressed);
288     free(d.row_pointers);
289     return d.ok;
290 }
291 #undef ABRT
292 // }}}
293 
294 static bool
add_trim_predicate(Image * img)295 add_trim_predicate(Image *img) {
296     return !img->root_frame_data_loaded || (!img->client_id && !img->refcnt);
297 }
298 
299 bool
png_path_to_bitmap(const char * path,uint8_t ** data,unsigned int * width,unsigned int * height,size_t * sz)300 png_path_to_bitmap(const char* path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz) {
301     FILE* fp = fopen(path, "r");
302     if (fp == NULL) {
303         log_error("The PNG image: %s could not be opened with error: %s", path, strerror(errno));
304         return false;
305     }
306     size_t capacity = 16*1024, pos = 0;
307     unsigned char *buf = malloc(capacity);
308     if (!buf) { log_error("Out of memory reading PNG file at: %s", path); fclose(fp); return false; }
309     while (!feof(fp)) {
310         if (capacity - pos < 1024) {
311             capacity *= 2;
312             unsigned char *new_buf = realloc(buf, capacity);
313             if (!new_buf) {
314                 free(buf);
315                 log_error("Out of memory reading PNG file at: %s", path); fclose(fp); return false;
316             }
317             buf = new_buf;
318         }
319         pos += fread(buf + pos, sizeof(char), capacity - pos, fp);
320         int saved_errno = errno;
321         if (ferror(fp) && saved_errno != EINTR) {
322             log_error("Failed while reading from file: %s with error: %s", path, strerror(saved_errno));
323             fclose(fp);
324             free(buf);
325             return false;
326         }
327     }
328     fclose(fp); fp = NULL;
329     png_read_data d = {0};
330     inflate_png_inner(&d, buf, pos);
331     free(buf);
332     if (!d.ok) {
333         free(d.decompressed); free(d.row_pointers);
334         log_error("Failed to decode PNG image at: %s", path);
335         return false;
336     }
337     *data = d.decompressed;
338     free(d.row_pointers);
339     *sz = d.sz;
340     *height = d.height; *width = d.width;
341     return true;
342 }
343 
344 
345 static Image*
find_or_create_image(GraphicsManager * self,uint32_t id,bool * existing)346 find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
347     if (id) {
348         for (size_t i = 0; i < self->image_count; i++) {
349             if (self->images[i].client_id == id) {
350                 *existing = true;
351                 return self->images + i;
352             }
353         }
354     }
355     *existing = false;
356     ensure_space_for(self, images, Image, self->image_count + 1, images_capacity, 64, true);
357     Image *ans = self->images + self->image_count++;
358     zero_at_ptr(ans);
359     return ans;
360 }
361 
362 static uint32_t
get_free_client_id(const GraphicsManager * self)363 get_free_client_id(const GraphicsManager *self) {
364     if (!self->image_count) return 1;
365     uint32_t *client_ids = malloc(sizeof(uint32_t) * self->image_count);
366     size_t count = 0;
367     for (size_t i = 0; i < self->image_count; i++) {
368         Image *q = self->images + i;
369         if (q->client_id) client_ids[count++] = q->client_id;
370     }
371     if (!count) { free(client_ids); return 1; }
372 #define int_lt(a, b) ((*a)<(*b))
373     QSORT(uint32_t, client_ids, count, int_lt)
374 #undef int_lt
375     uint32_t prev_id = 0, ans = 1;
376     for (size_t i = 0; i < count; i++) {
377         if (client_ids[i] == prev_id) continue;
378         prev_id = client_ids[i];
379         if (client_ids[i] != ans) break;
380         ans = client_ids[i] + 1;
381     }
382     free(client_ids);
383     return ans;
384 }
385 
386 #define ABRT(code, ...) { set_command_failed_response(code, __VA_ARGS__); self->currently_loading.loading_completed_successfully = false; free_load_data(&self->currently_loading); return NULL; }
387 
388 #define MAX_DATA_SZ (4u * 100000000u)
389 enum FORMATS { RGB=24, RGBA=32, PNG=100 };
390 
391 static Image*
load_image_data(GraphicsManager * self,Image * img,const GraphicsCommand * g,const unsigned char transmission_type,const uint32_t data_fmt,const uint8_t * payload)392 load_image_data(GraphicsManager *self, Image *img, const GraphicsCommand *g, const unsigned char transmission_type, const uint32_t data_fmt, const uint8_t *payload) {
393     int fd;
394     static char fname[2056] = {0};
395     LoadData *load_data = &self->currently_loading;
396 
397     switch(transmission_type) {
398         case 'd':  // direct
399             if (load_data->buf_capacity - load_data->buf_used < g->payload_sz) {
400                 if (load_data->buf_used + g->payload_sz > MAX_DATA_SZ || data_fmt != PNG) ABRT("EFBIG", "Too much data");
401                 load_data->buf_capacity = MIN(2 * load_data->buf_capacity, MAX_DATA_SZ);
402                 load_data->buf = realloc(load_data->buf, load_data->buf_capacity);
403                 if (load_data->buf == NULL) {
404                     load_data->buf_capacity = 0; load_data->buf_used = 0;
405                     ABRT("ENOMEM", "Out of memory");
406                 }
407             }
408             memcpy(load_data->buf + load_data->buf_used, payload, g->payload_sz);
409             load_data->buf_used += g->payload_sz;
410             if (!g->more) { load_data->loading_completed_successfully = true; load_data->loading_for = (const ImageAndFrame){0}; }
411             break;
412         case 'f': // file
413         case 't': // temporary file
414         case 's': // POSIX shared memory
415             if (g->payload_sz > 2048) ABRT("EINVAL", "Filename too long");
416             snprintf(fname, sizeof(fname)/sizeof(fname[0]), "%.*s", (int)g->payload_sz, payload);
417             if (transmission_type == 's') fd = safe_shm_open(fname, O_RDONLY, 0);
418             else fd = safe_open(fname, O_CLOEXEC | O_RDONLY, 0);
419             if (fd == -1) ABRT("EBADF", "Failed to open file for graphics transmission with error: [%d] %s", errno, strerror(errno));
420             load_data->loading_completed_successfully = mmap_img_file(self, fd, g->data_sz, g->data_offset);
421             safe_close(fd, __FILE__, __LINE__);
422             if (transmission_type == 't') {
423                 if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
424                 else unlink(fname);
425             }
426             else if (transmission_type == 's') shm_unlink(fname);
427             if (!load_data->loading_completed_successfully) return NULL;
428             break;
429         default:
430             ABRT("EINVAL", "Unknown transmission type: %c", g->transmission_type);
431     }
432     return img;
433 }
434 
435 static Image*
process_image_data(GraphicsManager * self,Image * img,const GraphicsCommand * g,const unsigned char transmission_type,const uint32_t data_fmt)436 process_image_data(GraphicsManager *self, Image* img, const GraphicsCommand *g, const unsigned char transmission_type, const uint32_t data_fmt) {
437     bool needs_processing = g->compressed || data_fmt == PNG;
438     if (needs_processing) {
439         uint8_t *buf; size_t bufsz;
440 #define IB { if (self->currently_loading.buf) { buf = self->currently_loading.buf; bufsz = self->currently_loading.buf_used; } else { buf = self->currently_loading.mapped_file; bufsz = self->currently_loading.mapped_file_sz; } }
441         switch(g->compressed) {
442             case 'z':
443                 IB;
444                 if (!inflate_zlib(&self->currently_loading, buf, bufsz)) {
445                     self->currently_loading.loading_completed_successfully = false; return NULL;
446                 }
447                 break;
448             case 0:
449                 break;
450             default:
451                 ABRT("EINVAL", "Unknown image compression: %c", g->compressed);
452         }
453         switch(data_fmt) {
454             case PNG:
455                 IB;
456                 if (!inflate_png(&self->currently_loading, buf, bufsz)) {
457                     self->currently_loading.loading_completed_successfully = false; return NULL;
458                 }
459                 break;
460             default: break;
461         }
462 #undef IB
463         self->currently_loading.data = self->currently_loading.buf;
464         if (self->currently_loading.buf_used < self->currently_loading.data_sz) {
465             ABRT("ENODATA", "Insufficient image data: %zu < %zu", self->currently_loading.buf_used, self->currently_loading.data_sz);
466         }
467         if (self->currently_loading.mapped_file) {
468             munmap(self->currently_loading.mapped_file, self->currently_loading.mapped_file_sz);
469             self->currently_loading.mapped_file = NULL; self->currently_loading.mapped_file_sz = 0;
470         }
471     } else {
472         if (transmission_type == 'd') {
473             if (self->currently_loading.buf_used < self->currently_loading.data_sz) {
474                 ABRT("ENODATA", "Insufficient image data: %zu < %zu",  self->currently_loading.buf_used, self->currently_loading.data_sz);
475             } else self->currently_loading.data = self->currently_loading.buf;
476         } else {
477             if (self->currently_loading.mapped_file_sz < self->currently_loading.data_sz) {
478                 ABRT("ENODATA", "Insufficient image data: %zu < %zu",  self->currently_loading.mapped_file_sz, self->currently_loading.data_sz);
479             } else self->currently_loading.data = self->currently_loading.mapped_file;
480         }
481         self->currently_loading.loading_completed_successfully = true;
482     }
483     return img;
484 }
485 
486 static Image*
initialize_load_data(GraphicsManager * self,const GraphicsCommand * g,Image * img,const unsigned char transmission_type,const uint32_t data_fmt,const uint32_t frame_id)487 initialize_load_data(GraphicsManager *self, const GraphicsCommand *g, Image *img, const unsigned char transmission_type, const uint32_t data_fmt, const uint32_t frame_id) {
488     free_load_data(&self->currently_loading);
489     self->currently_loading = (const LoadData){0};
490     self->currently_loading.start_command = *g;
491     self->currently_loading.width = g->data_width; self->currently_loading.height = g->data_height;
492     switch(data_fmt) {
493         case PNG:
494             if (g->data_sz > MAX_DATA_SZ) ABRT("EINVAL", "PNG data size too large");
495             self->currently_loading.is_4byte_aligned = true;
496             self->currently_loading.is_opaque = false;
497             self->currently_loading.data_sz = g->data_sz ? g->data_sz : 1024 * 100;
498             break;
499         case RGB:
500         case RGBA:
501             self->currently_loading.data_sz = (size_t)g->data_width * g->data_height * (data_fmt / 8);
502             if (!self->currently_loading.data_sz) ABRT("EINVAL", "Zero width/height not allowed");
503             self->currently_loading.is_4byte_aligned = data_fmt == RGBA || (self->currently_loading.width % 4 == 0);
504             self->currently_loading.is_opaque = data_fmt == RGB;
505             break;
506         default:
507             ABRT("EINVAL", "Unknown image format: %u", data_fmt);
508     }
509     self->currently_loading.loading_for.image_id = img->internal_id;
510     self->currently_loading.loading_for.frame_id = frame_id;
511     if (transmission_type == 'd') {
512         self->currently_loading.buf_capacity = self->currently_loading.data_sz + (g->compressed ? 1024 : 10);  // compression header
513         self->currently_loading.buf = malloc(self->currently_loading.buf_capacity);
514         self->currently_loading.buf_used = 0;
515         if (self->currently_loading.buf == NULL) {
516             self->currently_loading.buf_capacity = 0; self->currently_loading.buf_used = 0;
517             ABRT("ENOMEM", "Out of memory");
518         }
519     }
520     return img;
521 }
522 
523 #define INIT_CHUNKED_LOAD { \
524     self->currently_loading.start_command.more = g->more; \
525     self->currently_loading.start_command.payload_sz = g->payload_sz; \
526     g = &self->currently_loading.start_command; \
527     tt = g->transmission_type ? g->transmission_type : 'd'; \
528     fmt = g->format ? g->format : RGBA; \
529 }
530 #define MAX_IMAGE_DIMENSION 10000u
531 
532 static void
upload_to_gpu(GraphicsManager * self,Image * img,const bool is_opaque,const bool is_4byte_aligned,const uint8_t * data)533 upload_to_gpu(GraphicsManager *self, Image *img, const bool is_opaque, const bool is_4byte_aligned, const uint8_t *data) {
534     if (!self->context_made_current_for_this_command) {
535         if (!self->window_id) return;
536         if (!make_window_context_current(self->window_id)) return;
537         self->context_made_current_for_this_command = true;
538     }
539     send_image_to_gpu(&img->texture_id, data, img->width, img->height, is_opaque, is_4byte_aligned, false, REPEAT_CLAMP);
540 }
541 
542 static Image*
handle_add_command(GraphicsManager * self,const GraphicsCommand * g,const uint8_t * payload,bool * is_dirty,uint32_t iid)543 handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) {
544     bool existing, init_img = true;
545     Image *img = NULL;
546     unsigned char tt = g->transmission_type ? g->transmission_type : 'd';
547     uint32_t fmt = g->format ? g->format : RGBA;
548     if (tt == 'd' && self->currently_loading.loading_for.image_id) init_img = false;
549     if (init_img) {
550         self->currently_loading.loading_for = (const ImageAndFrame){0};
551         if (g->data_width > MAX_IMAGE_DIMENSION || g->data_height > MAX_IMAGE_DIMENSION) ABRT("EINVAL", "Image too large");
552         remove_images(self, add_trim_predicate, 0);
553         img = find_or_create_image(self, iid, &existing);
554         if (existing) {
555             img->root_frame_data_loaded = false;
556             img->is_drawn = false;
557             img->current_frame_shown_at = 0;
558             free_refs_data(img);
559             *is_dirty = true;
560             self->layers_dirty = true;
561         } else {
562             img->internal_id = internal_id_counter++;
563             img->client_id = iid;
564             img->client_number = g->image_number;
565             if (!img->client_id && img->client_number) {
566                 img->client_id = get_free_client_id(self);
567                 iid = img->client_id;
568             }
569         }
570         img->atime = monotonic(); img->used_storage = 0;
571         if (!initialize_load_data(self, g, img, tt, fmt, 0)) return NULL;
572         self->currently_loading.start_command.id = iid;
573     } else {
574         INIT_CHUNKED_LOAD;
575         img = img_by_internal_id(self, self->currently_loading.loading_for.image_id);
576         if (img == NULL) {
577             self->currently_loading.loading_for = (const ImageAndFrame){0};
578             ABRT("EILSEQ", "More payload loading refers to non-existent image");
579         }
580     }
581     img = load_image_data(self, img, g, tt, fmt, payload);
582     if (!img || !self->currently_loading.loading_completed_successfully) return NULL;
583         self->currently_loading.loading_for = (const ImageAndFrame){0};
584     img = process_image_data(self, img, g, tt, fmt);
585     if (!img) return NULL;
586     size_t required_sz = (size_t)(self->currently_loading.is_opaque ? 3 : 4) * self->currently_loading.width * self->currently_loading.height;
587     if (self->currently_loading.data_sz != required_sz) ABRT("EINVAL", "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", self->currently_loading.width, self->currently_loading.height, self->currently_loading.data_sz, required_sz);
588     if (self->currently_loading.loading_completed_successfully) {
589         img->width = self->currently_loading.width;
590         img->height = self->currently_loading.height;
591         if (img->root_frame.id) remove_from_cache(self, (const ImageAndFrame){.image_id=img->internal_id, .frame_id=img->root_frame.id});
592         img->root_frame = (const Frame){
593             .id = ++img->frame_id_counter,
594             .is_opaque = self->currently_loading.is_opaque,
595             .is_4byte_aligned = self->currently_loading.is_4byte_aligned,
596             .width = img->width, .height = img->height,
597         };
598         if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, self->currently_loading.data, self->currently_loading.data_sz)) {
599             if (PyErr_Occurred()) PyErr_Print();
600             ABRT("ENOSPC", "Failed to store image data in disk cache");
601         }
602         upload_to_gpu(self, img, img->root_frame.is_opaque, img->root_frame.is_4byte_aligned, self->currently_loading.data);
603         self->used_storage += required_sz;
604         img->used_storage = required_sz;
605         img->root_frame_data_loaded = true;
606     }
607     return img;
608 #undef MAX_DATA_SZ
609 }
610 
611 static const char*
finish_command_response(const GraphicsCommand * g,bool data_loaded)612 finish_command_response(const GraphicsCommand *g, bool data_loaded) {
613     static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
614     bool is_ok_response = !command_response[0];
615     if (g->quiet) {
616         if (is_ok_response || g->quiet > 1) return NULL;
617     }
618     if (g->id || g->image_number) {
619         if (is_ok_response) {
620             if (!data_loaded) return NULL;
621             snprintf(command_response, 10, "OK");
622         }
623         size_t pos = 0;
624         rbuf[pos++] = 'G';
625 #define print(fmt, ...) if (arraysz(rbuf) - 1 > pos) pos += snprintf(rbuf + pos, arraysz(rbuf) - 1 - pos, fmt, __VA_ARGS__)
626         if (g->id) print("i=%u", g->id);
627         if (g->image_number) print(",I=%u", g->image_number);
628         if (g->placement_id) print(",p=%u", g->placement_id);
629         if (g->num_lines && (g->action == 'f' || g->action == 'a')) print(",r=%u", g->num_lines);
630         print(";%s", command_response);
631         return rbuf;
632 #undef print
633     }
634     return NULL;
635 }
636 
637 // }}}
638 
639 // Displaying images {{{
640 
641 static void
update_src_rect(ImageRef * ref,Image * img)642 update_src_rect(ImageRef *ref, Image *img) {
643     // The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image
644     ref->src_rect.left = (float)ref->src_x / (float)img->width;
645     ref->src_rect.right = (float)(ref->src_x + ref->src_width) / (float)img->width;
646     ref->src_rect.top = (float)ref->src_y / (float)img->height;
647     ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height;
648 }
649 
650 static void
update_dest_rect(ImageRef * ref,uint32_t num_cols,uint32_t num_rows,CellPixelSize cell)651 update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) {
652     uint32_t t;
653     if (num_cols == 0) {
654         t = ref->src_width + ref->cell_x_offset;
655         num_cols = t / cell.width;
656         if (t > num_cols * cell.width) num_cols += 1;
657     }
658     if (num_rows == 0) {
659         t = ref->src_height + ref->cell_y_offset;
660         num_rows = t / cell.height;
661         if (t > num_rows * cell.height) num_rows += 1;
662     }
663     ref->effective_num_rows = num_rows;
664     ref->effective_num_cols = num_cols;
665 }
666 
667 
668 static uint32_t
handle_put_command(GraphicsManager * self,const GraphicsCommand * g,Cursor * c,bool * is_dirty,Image * img,CellPixelSize cell)669 handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
670     if (img == NULL) {
671         if (g->id) img = img_by_client_id(self, g->id);
672         else if (g->image_number) img = img_by_client_number(self, g->image_number);
673         if (img == NULL) { set_command_failed_response("ENOENT", "Put command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); return g->id; }
674     }
675     if (!img->root_frame_data_loaded) { set_command_failed_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return img->client_id; }
676     ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
677     *is_dirty = true;
678     self->layers_dirty = true;
679     ImageRef *ref = NULL;
680     if (g->placement_id && img->client_id) {
681         for (size_t i=0; i < img->refcnt; i++) {
682             if (img->refs[i].client_id == g->placement_id) {
683                 ref = img->refs + i;
684                 break;
685             }
686         }
687     }
688     if (ref == NULL) {
689         ref = img->refs + img->refcnt++;
690         zero_at_ptr(ref);
691     }
692     img->atime = monotonic();
693     ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height;
694     ref->src_width = MIN(ref->src_width, img->width - (img->width > ref->src_x ? ref->src_x : img->width));
695     ref->src_height = MIN(ref->src_height, img->height - (img->height > ref->src_y ? ref->src_y : img->height));
696     ref->z_index = g->z_index;
697     ref->start_row = c->y; ref->start_column = c->x;
698     ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1);
699     ref->cell_y_offset = MIN(g->cell_y_offset, cell.height - 1);
700     ref->num_cols = g->num_cells; ref->num_rows = g->num_lines;
701     if (img->client_id) ref->client_id = g->placement_id;
702     update_src_rect(ref, img);
703     update_dest_rect(ref, g->num_cells, g->num_lines, cell);
704     // Move the cursor, the screen will take care of ensuring it is in bounds
705     if (g->cursor_movement != 1) {
706         c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
707     }
708     return img->client_id;
709 }
710 
711 static void
set_vertex_data(ImageRenderData * rd,const ImageRef * ref,const ImageRect * dest_rect)712 set_vertex_data(ImageRenderData *rd, const ImageRef *ref, const ImageRect *dest_rect) {
713 #define R(n, a, b) rd->vertices[n*4] = ref->src_rect.a; rd->vertices[n*4 + 1] = ref->src_rect.b; rd->vertices[n*4 + 2] = dest_rect->a; rd->vertices[n*4 + 3] = dest_rect->b;
714         R(0, right, top); R(1, right, bottom); R(2, left, bottom); R(3, left, top);
715 #undef R
716 }
717 
718 void
gpu_data_for_centered_image(ImageRenderData * ans,unsigned int screen_width_px,unsigned int screen_height_px,unsigned int width,unsigned int height)719 gpu_data_for_centered_image(ImageRenderData *ans, unsigned int screen_width_px, unsigned int screen_height_px, unsigned int width, unsigned int height) {
720     static const ImageRef source_rect = { .src_rect = { .left=0, .top=0, .bottom=1, .right=1 }};
721     const ImageRef *ref = &source_rect;
722     float width_frac = 2 * MIN(1, width / (float)screen_width_px), height_frac = 2 * MIN(1, height / (float)screen_height_px);
723     float hmargin = (2 - width_frac) / 2;
724     float vmargin = (2 - height_frac) / 2;
725     const ImageRect r = { .left = -1 + hmargin, .right = -1 + hmargin + width_frac, .top = 1 - vmargin, .bottom = 1 - vmargin - height_frac };
726     set_vertex_data(ans, ref, &r);
727 }
728 
729 bool
grman_update_layers(GraphicsManager * self,unsigned int scrolled_by,float screen_left,float screen_top,float dx,float dy,unsigned int num_cols,unsigned int num_rows,CellPixelSize cell)730 grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) {
731     if (self->last_scrolled_by != scrolled_by) self->layers_dirty = true;
732     self->last_scrolled_by = scrolled_by;
733     if (!self->layers_dirty) return false;
734     self->layers_dirty = false;
735     size_t i, j;
736     self->num_of_below_refs = 0;
737     self->num_of_negative_refs = 0;
738     self->num_of_positive_refs = 0;
739     Image *img; ImageRef *ref;
740     ImageRect r;
741     float screen_width = dx * num_cols, screen_height = dy * num_rows;
742     float screen_bottom = screen_top - screen_height;
743     float screen_width_px = num_cols * cell.width;
744     float screen_height_px = num_rows * cell.height;
745     float y0 = screen_top - dy * scrolled_by;
746 
747     // Iterate over all visible refs and create render data
748     self->count = 0;
749     for (i = 0; i < self->image_count; i++) {
750         img = self->images + i;
751         bool was_drawn = img->is_drawn;
752         img->is_drawn = false;
753 
754         for (j = 0; j < img->refcnt; j++) { ref = img->refs + j;
755             r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height;
756             if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy;
757             else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px;
758             if (r.top <= screen_bottom || r.bottom >= screen_top) continue;  // not visible
759 
760             r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width;
761             if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx;
762             else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px;
763 
764             if (ref->z_index < ((int32_t)INT32_MIN/2))
765                 self->num_of_below_refs++;
766             else if (ref->z_index < 0)
767                 self->num_of_negative_refs++;
768             else
769                 self->num_of_positive_refs++;
770             ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 64, true);
771             ImageRenderData *rd = self->render_data + self->count;
772             zero_at_ptr(rd);
773             set_vertex_data(rd, ref, &r);
774             self->count++;
775             rd->z_index = ref->z_index; rd->image_id = img->internal_id;
776             rd->texture_id = img->texture_id;
777             img->is_drawn = true;
778         }
779         if (img->is_drawn && !was_drawn && img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->animation_duration) {
780             self->has_images_needing_animation = true;
781             global_state.check_for_active_animated_images = true;
782         }
783     }
784     if (!self->count) return false;
785     // Sort visible refs in draw order (z-index, img)
786 #define lt(a, b) ( (a)->z_index < (b)->z_index || ((a)->z_index == (b)->z_index && (a)->image_id < (b)->image_id) )
787     QSORT(ImageRenderData, self->render_data, self->count, lt);
788 #undef lt
789     // Calculate the group counts
790     i = 0;
791     while (i < self->count) {
792         id_type image_id = self->render_data[i].image_id, start = i;
793         if (start == self->count - 1) i = self->count;
794         else {
795             while (i < self->count - 1 && self->render_data[++i].image_id == image_id) {}
796         }
797         self->render_data[start].group_count = i - start;
798     }
799     return true;
800 }
801 
802 // }}}
803 
804 // Animation {{{
805 #define DEFAULT_GAP 40
806 
807 static Frame*
current_frame(Image * img)808 current_frame(Image *img) {
809     if (img->current_frame_index > img->extra_framecnt) return NULL;
810     return img->current_frame_index ? img->extra_frames + img->current_frame_index - 1 : &img->root_frame;
811 }
812 
813 static Frame*
frame_for_id(Image * img,const uint32_t frame_id)814 frame_for_id(Image *img, const uint32_t frame_id) {
815     if (img->root_frame.id == frame_id) return &img->root_frame;
816     for (unsigned i = 0; i < img->extra_framecnt; i++) {
817         if (img->extra_frames[i].id == frame_id) return img->extra_frames + i;
818     }
819     return NULL;
820 }
821 
822 static Frame*
frame_for_number(Image * img,const uint32_t frame_number)823 frame_for_number(Image *img, const uint32_t frame_number) {
824     switch(frame_number) {
825         case 1:
826             return &img->root_frame;
827         case 0:
828             return NULL;
829         default:
830             if (frame_number - 2 < img->extra_framecnt) return img->extra_frames + frame_number - 2;
831             return NULL;
832     }
833 }
834 
835 static void
change_gap(Image * img,Frame * f,int32_t gap)836 change_gap(Image *img, Frame *f, int32_t gap) {
837     uint32_t prev_gap = f->gap;
838     f->gap = MAX(0, gap);
839     img->animation_duration = prev_gap < img->animation_duration ? img->animation_duration - prev_gap : 0;
840     img->animation_duration += f->gap;
841 }
842 
843 typedef struct {
844     uint8_t *buf;
845     bool is_4byte_aligned, is_opaque;
846 } CoalescedFrameData;
847 
848 static void
blend_on_opaque(uint8_t * under_px,const uint8_t * over_px)849 blend_on_opaque(uint8_t *under_px, const uint8_t *over_px) {
850     const float alpha = (float)over_px[3] / 255.f;
851     const float alpha_op = 1.f - alpha;
852     for (unsigned i = 0; i < 3; i++) under_px[i] = (uint8_t)(over_px[i] * alpha + under_px[i] * alpha_op);
853 }
854 
855 static void
alpha_blend(uint8_t * dest_px,const uint8_t * src_px)856 alpha_blend(uint8_t *dest_px, const uint8_t *src_px) {
857     if (src_px[3]) {
858         const float dest_a = (float)dest_px[3] / 255.f, src_a = (float)src_px[3] / 255.f;
859         const float alpha = src_a + dest_a * (1.f - src_a);
860         dest_px[3] = (uint8_t)(255 * alpha);
861         if (!dest_px[3]) { dest_px[0] = 0; dest_px[1] = 0; dest_px[2] = 0; return; }
862         for (unsigned i = 0; i < 3; i++) dest_px[i] = (uint8_t)((src_px[i] * src_a + dest_px[i] * dest_a * (1.f - src_a))/alpha);
863     }
864 }
865 
866 typedef struct {
867     bool needs_blending;
868     uint32_t over_px_sz, under_px_sz;
869     uint32_t over_width, over_height, under_width, under_height, over_offset_x, over_offset_y, under_offset_x, under_offset_y;
870     uint32_t stride;
871 } ComposeData;
872 
873 #define COPY_RGB under_px[0] = over_px[0]; under_px[1] = over_px[1]; under_px[2] = over_px[2];
874 #define COPY_PIXELS \
875     if (d.needs_blending) { \
876         if (d.under_px_sz == 3) { \
877             ROW_ITER PIX_ITER blend_on_opaque(under_px, over_px); }} \
878         } else { \
879             ROW_ITER PIX_ITER alpha_blend(under_px, over_px); }} \
880         } \
881     } else { \
882         if (d.under_px_sz == 4) { \
883             if (d.over_px_sz == 4) { \
884                 ROW_ITER PIX_ITER COPY_RGB under_px[3] = over_px[3]; }} \
885             } else { \
886                 ROW_ITER PIX_ITER COPY_RGB under_px[3] = 255; }} \
887             } \
888         } else { \
889             ROW_ITER PIX_ITER COPY_RGB }} \
890         } \
891     } \
892 
893 
894 static void
compose_rectangles(const ComposeData d,uint8_t * under_data,const uint8_t * over_data)895 compose_rectangles(const ComposeData d, uint8_t *under_data, const uint8_t *over_data) {
896     // compose two equal sized, non-overlapping rectangles at different offsets
897     // does not do bounds checking on the data arrays
898     const bool can_copy_rows = !d.needs_blending && d.over_px_sz == d.under_px_sz;
899     const unsigned min_width = MIN(d.under_width, d.over_width);
900 #define ROW_ITER for (unsigned y = 0; y < d.under_height && y < d.over_height; y++) { \
901         uint8_t *under_row = under_data + (y + d.under_offset_y) * d.under_px_sz * d.stride + (d.under_offset_x * d.under_px_sz); \
902         const uint8_t *over_row = over_data + (y + d.over_offset_y) * d.over_px_sz * d.stride + (d.over_offset_x * d.over_px_sz);
903     if (can_copy_rows) {
904         ROW_ITER memcpy(under_row, over_row, (size_t)d.over_px_sz * min_width);}
905         return;
906     }
907 #define PIX_ITER for (unsigned x = 0; x < min_width; x++) { \
908         uint8_t *under_px = under_row + (d.under_px_sz * x); \
909         const uint8_t *over_px = over_row + (d.over_px_sz * x);
910     COPY_PIXELS
911 #undef PIX_ITER
912 #undef ROW_ITER
913 }
914 
915 static void
916 compose(const ComposeData d, uint8_t *under_data, const uint8_t *over_data) {
917     const bool can_copy_rows = !d.needs_blending && d.over_px_sz == d.under_px_sz;
918     unsigned min_row_sz = d.over_offset_x < d.under_width ? d.under_width - d.over_offset_x : 0;
919     min_row_sz = MIN(min_row_sz, d.over_width);
920 #define ROW_ITER for (unsigned y = 0; y + d.over_offset_y < d.under_height && y < d.over_height; y++) { \
921         uint8_t *under_row = under_data + (y + d.over_offset_y) * d.under_px_sz * d.under_width + d.under_px_sz * d.over_offset_x; \
922         const uint8_t *over_row = over_data + y * d.over_px_sz * d.over_width;
923     if (can_copy_rows) {
924         ROW_ITER memcpy(under_row, over_row, (size_t)d.over_px_sz * min_row_sz);}
925         return;
926     }
927 #define PIX_ITER for (unsigned x = 0; x < min_row_sz; x++) { \
928         uint8_t *under_px = under_row + (d.under_px_sz * x); \
929         const uint8_t *over_px = over_row + (d.over_px_sz * x);
930     COPY_PIXELS
931 #undef COPY_RGB
932 #undef PIX_ITER
933 #undef ROW_ITER
934 }
935 
936 static CoalescedFrameData
937 get_coalesced_frame_data_standalone(const Image *img, const Frame *f, uint8_t *frame_data) {
938     CoalescedFrameData ans = {0};
939     bool is_full_frame = f->width == img->width && f->height == img->height && !f->x && !f->y;
940     if (is_full_frame) {
941         ans.buf = frame_data;
942         ans.is_4byte_aligned = f->is_4byte_aligned;
943         ans.is_opaque = f->is_opaque;
944         return ans;
945     }
946     const unsigned bytes_per_pixel = f->is_opaque ? 3 : 4;
947     uint8_t *base;
948     if (f->bgcolor) {
949         base = malloc((size_t)img->width * img->height * bytes_per_pixel);
950         if (base) {
951             uint8_t *p = base;
952             const uint8_t r = (f->bgcolor >> 24) & 0xff,
953                   g = (f->bgcolor >> 16) & 0xff, b = (f->bgcolor >> 8) & 0xff, a = f->bgcolor & 0xff;
954             if (bytes_per_pixel == 4) {
955                 for (uint32_t i = 0; i < img->width * img->height; i++) {
956                     *(p++) = r; *(p++) = g; *(p++) = b; *(p++) = a;
957                 }
958             } else {
959                 for (uint32_t i = 0; i < img->width * img->height; i++) {
960                     *(p++) = r; *(p++) = g; *(p++) = b;
961                 }
962             }
963         }
964     } else base = calloc((size_t)img->width * img->height, bytes_per_pixel);
965     if (!base) { free(frame_data); return ans; }
966     ComposeData d = {
967         .over_px_sz = bytes_per_pixel, .under_px_sz = bytes_per_pixel,
968         .over_width = f->width, .over_height = f->height, .over_offset_x = f->x, .over_offset_y = f->y,
969         .under_width = img->width, .under_height = img->height,
970         .needs_blending = f->alpha_blend && !f->is_opaque
971     };
972     compose(d, base, frame_data);
973     ans.buf = base;
974     ans.is_4byte_aligned = bytes_per_pixel == 4 || (img->width % 4) == 0;
975     ans.is_opaque = f->is_opaque;
976     free(frame_data);
977     return ans;
978 }
979 
980 
981 static CoalescedFrameData
982 get_coalesced_frame_data_impl(GraphicsManager *self, Image *img, const Frame *f, unsigned count) {
983     CoalescedFrameData ans = {0};
984     if (count > 32) return ans;  // prevent stack overflows, infinite recursion
985     size_t frame_data_sz; void *frame_data;
986     ImageAndFrame key = {.image_id = img->internal_id, .frame_id = f->id};
987     if (!read_from_cache(self, key, &frame_data, &frame_data_sz)) return ans;
988     if (!f->base_frame_id) return get_coalesced_frame_data_standalone(img, f, frame_data);
989     Frame *base = frame_for_id(img, f->base_frame_id);
990     if (!base) { free(frame_data); return ans; }
991     CoalescedFrameData base_data = get_coalesced_frame_data_impl(self, img, base, count + 1);
992     if (!base_data.buf) { free(frame_data); return ans; }
993     ComposeData d = {
994         .over_px_sz = f->is_opaque ? 3 : 4,
995         .under_px_sz = base_data.is_opaque ? 3 : 4,
996         .over_width = f->width, .over_height = f->height, .over_offset_x = f->x, .over_offset_y = f->y,
997         .under_width = img->width, .under_height = img->height,
998         .needs_blending = f->alpha_blend && !f->is_opaque
999     };
1000     compose(d, base_data.buf, frame_data);
1001     free(frame_data);
1002     return base_data;
1003 }
1004 
1005 static CoalescedFrameData
1006 get_coalesced_frame_data(GraphicsManager *self, Image *img, const Frame *f) {
1007     return get_coalesced_frame_data_impl(self, img, f, 0);
1008 }
1009 
1010 static void
1011 update_current_frame(GraphicsManager *self, Image *img, const CoalescedFrameData *data) {
1012     bool needs_load = data == NULL;
1013     CoalescedFrameData cfd;
1014     if (needs_load) {
1015         Frame *f = current_frame(img);
1016         if (f == NULL) return;
1017         cfd = get_coalesced_frame_data(self, img, f);
1018         if (!cfd.buf) {
1019             if (PyErr_Occurred()) PyErr_Print();
1020             return;
1021         }
1022         data = &cfd;
1023     }
1024     upload_to_gpu(self, img, data->is_opaque, data->is_4byte_aligned, data->buf);
1025     if (needs_load) free(data->buf);
1026     img->current_frame_shown_at = monotonic();
1027 }
1028 
1029 static bool
1030 reference_chain_too_large(Image *img, const Frame *frame) {
1031     uint32_t limit = img->width * img->height * 2;
1032     uint32_t drawn_area = frame->width * frame->height;
1033     unsigned num = 1;
1034     while (drawn_area < limit && num < 5) {
1035         if (!frame->base_frame_id || !(frame = frame_for_id(img, frame->base_frame_id))) break;
1036         drawn_area += frame->width * frame->height;
1037         num++;
1038     }
1039     return num >= 5 || drawn_area >= limit;
1040 }
1041 
1042 static Image*
1043 handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, Image *img, const uint8_t *payload, bool *is_dirty) {
1044     uint32_t frame_number = g->frame_number, fmt = g->format ? g->format : RGBA;
1045     if (!frame_number || frame_number > img->extra_framecnt + 2) frame_number = img->extra_framecnt + 2;
1046     bool is_new_frame = frame_number == img->extra_framecnt + 2;
1047     g->frame_number = frame_number;
1048     unsigned char tt = g->transmission_type ? g->transmission_type : 'd';
1049     if (tt == 'd' && self->currently_loading.loading_for.image_id == img->internal_id) {
1050         INIT_CHUNKED_LOAD;
1051     } else {
1052         self->currently_loading.loading_for = (const ImageAndFrame){0};
1053         if (g->data_width > MAX_IMAGE_DIMENSION || g->data_height > MAX_IMAGE_DIMENSION) ABRT("EINVAL", "Image too large");
1054         if (!initialize_load_data(self, g, img, tt, fmt, frame_number - 1)) return NULL;
1055     }
1056     LoadData *load_data = &self->currently_loading;
1057     img = load_image_data(self, img, g, tt, fmt, payload);
1058     if (!img || !load_data->loading_completed_successfully) return NULL;
1059     self->currently_loading.loading_for = (const ImageAndFrame){0};
1060     img = process_image_data(self, img, g, tt, fmt);
1061     if (!img || !load_data->loading_completed_successfully) return img;
1062 
1063     const unsigned long bytes_per_pixel = load_data->is_opaque ? 3 : 4;
1064     if (load_data->data_sz < bytes_per_pixel * load_data->width * load_data->height)
1065         ABRT("ENODATA", "Insufficient image data %zu < %zu", load_data->data_sz, bytes_per_pixel * g->data_width, g->data_height);
1066     if (load_data->width > img->width)
1067         ABRT("EINVAL", "Frame width %u larger than image width: %u", load_data->width, img->width);
1068     if (load_data->height > img->height)
1069         ABRT("EINVAL", "Frame height %u larger than image height: %u", load_data->height, img->height);
1070     if (is_new_frame && cache_size(self) + load_data->data_sz > self->storage_limit * 5) {
1071         remove_images(self, trim_predicate, img->internal_id);
1072         if (is_new_frame && cache_size(self) + load_data->data_sz > self->storage_limit * 5)
1073             ABRT("ENOSPC", "Cache size exceeded cannot add new frames");
1074     }
1075 
1076     Frame transmitted_frame = {
1077         .width = load_data->width, .height = load_data->height,
1078         .x = g->x_offset, .y = g->y_offset,
1079         .is_4byte_aligned = load_data->is_4byte_aligned,
1080         .is_opaque = load_data->is_opaque,
1081         .alpha_blend = g->blend_mode != 1 && !load_data->is_opaque,
1082         .gap = g->gap > 0 ? g->gap : (g->gap < 0) ? 0 : DEFAULT_GAP,
1083         .bgcolor = g->bgcolor,
1084     };
1085     Frame *frame;
1086     if (is_new_frame) {
1087         transmitted_frame.id = ++img->frame_id_counter;
1088         Frame *frames = realloc(img->extra_frames, sizeof(img->extra_frames[0]) * (img->extra_framecnt + 1));
1089         if (!frames) ABRT("ENOMEM", "Out of memory");
1090         img->extra_frames = frames;
1091         img->extra_framecnt++;
1092         frame = img->extra_frames + frame_number - 2;
1093         const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = transmitted_frame.id };
1094         if (g->other_frame_number) {
1095             Frame *other_frame = frame_for_number(img, g->other_frame_number);
1096             if (!other_frame) {
1097                 img->extra_framecnt--;
1098                 ABRT("EINVAL", "No frame with number: %u found", g->other_frame_number);
1099             }
1100             if (other_frame->base_frame_id && reference_chain_too_large(img, other_frame)) {
1101                 // since there is a long reference chain to render this frame, make
1102                 // it a fully coalesced key frame, for performance
1103                 CoalescedFrameData cfd = get_coalesced_frame_data(self, img, other_frame);
1104                 if (!cfd.buf) ABRT("EINVAL", "Failed to get data from frame referenced by frame: %u", frame_number);
1105                 ComposeData d = {
1106                     .over_px_sz = transmitted_frame.is_opaque ? 3 : 4, .under_px_sz = cfd.is_opaque ? 3: 4,
1107                     .over_width = transmitted_frame.width, .over_height = transmitted_frame.height,
1108                     .over_offset_x = transmitted_frame.x, .over_offset_y = transmitted_frame.y,
1109                     .under_width = img->width, .under_height = img->height,
1110                     .needs_blending = transmitted_frame.alpha_blend && !transmitted_frame.is_opaque
1111                 };
1112                 compose(d, cfd.buf, load_data->data);
1113                 free_load_data(load_data);
1114                 load_data->data = cfd.buf; load_data->data_sz = (size_t)img->width * img->height * d.under_px_sz;
1115                 transmitted_frame.width = img->width; transmitted_frame.height = img->height;
1116                 transmitted_frame.x = 0; transmitted_frame.y = 0;
1117                 transmitted_frame.is_4byte_aligned = cfd.is_4byte_aligned;
1118                 transmitted_frame.is_opaque = cfd.is_opaque;
1119             } else {
1120                 transmitted_frame.base_frame_id = other_frame->id;
1121             }
1122         }
1123         *frame = transmitted_frame;
1124         if (!add_to_cache(self, key, load_data->data, load_data->data_sz)) {
1125             img->extra_framecnt--;
1126             if (PyErr_Occurred()) PyErr_Print();
1127             ABRT("ENOSPC", "Failed to cache data for image frame");
1128         }
1129         img->animation_duration += frame->gap;
1130         if (img->animation_state == ANIMATION_LOADING) {
1131             self->has_images_needing_animation = true;
1132             global_state.check_for_active_animated_images = true;
1133         }
1134     } else {
1135         frame = frame_for_number(img, frame_number);
1136         if (!frame) ABRT("EINVAL", "No frame with number: %u found", frame_number);
1137         if (g->gap != 0) change_gap(img, frame, transmitted_frame.gap);
1138         CoalescedFrameData cfd = get_coalesced_frame_data(self, img, frame);
1139         if (!cfd.buf) ABRT("EINVAL", "No data associated with frame number: %u", frame_number);
1140         frame->alpha_blend = false; frame->base_frame_id = 0; frame->bgcolor = 0;
1141         frame->is_opaque = cfd.is_opaque; frame->is_4byte_aligned = cfd.is_4byte_aligned;
1142         frame->x = 0; frame->y = 0; frame->width = img->width; frame->height = img->height;
1143         const unsigned bytes_per_pixel = frame->is_opaque ? 3: 4;
1144         ComposeData d = {
1145             .over_px_sz = transmitted_frame.is_opaque ? 3 : 4, .under_px_sz = bytes_per_pixel,
1146             .over_width = transmitted_frame.width, .over_height = transmitted_frame.height,
1147             .over_offset_x = transmitted_frame.x, .over_offset_y = transmitted_frame.y,
1148             .under_width = frame->width, .under_height = frame->height,
1149             .needs_blending = transmitted_frame.alpha_blend && !transmitted_frame.is_opaque
1150         };
1151         compose(d, cfd.buf, load_data->data);
1152         const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = frame->id };
1153         bool added = add_to_cache(self, key, cfd.buf, (size_t)bytes_per_pixel * frame->width * frame->height);
1154         if (added && frame == current_frame(img)) {
1155             update_current_frame(self, img, &cfd);
1156             *is_dirty = true;
1157         }
1158         free(cfd.buf);
1159         if (!added) {
1160             if (PyErr_Occurred()) PyErr_Print();
1161             ABRT("ENOSPC", "Failed to cache data for image frame");
1162         }
1163     }
1164     return img;
1165 }
1166 
1167 #undef ABRT
1168 
1169 static Image*
1170 handle_delete_frame_command(GraphicsManager *self, const GraphicsCommand *g, bool *is_dirty) {
1171     if (!g->id && !g->image_number) {
1172         REPORT_ERROR("Delete frame data command without image id or number");
1173         return NULL;
1174     }
1175     Image *img = g->id ? img_by_client_id(self, g->id) : img_by_client_number(self, g->image_number);
1176     if (!img) {
1177         REPORT_ERROR("Animation command refers to non-existent image with id: %u and number: %u", g->id, g->image_number);
1178         return NULL;
1179     }
1180     uint32_t frame_number = MIN(img->extra_framecnt + 1, g->frame_number);
1181     if (!frame_number) frame_number = 1;
1182     if (!img->extra_framecnt) return g->delete_action == 'F' ? img : NULL;
1183     *is_dirty = true;
1184     ImageAndFrame key = {.image_id=img->internal_id};
1185     bool remove_root = frame_number == 1;
1186     uint32_t removed_gap = 0;
1187     if (remove_root) {
1188         key.frame_id = img->root_frame.id;
1189         remove_from_cache(self, key);
1190         if (PyErr_Occurred()) PyErr_Print();
1191         removed_gap = img->root_frame.gap;
1192         img->root_frame = img->extra_frames[0];
1193     }
1194     unsigned removed_idx = remove_root ? 0 : frame_number - 2;
1195     if (!remove_root) {
1196         key.frame_id = img->extra_frames[removed_idx].id;
1197         removed_gap = img->extra_frames[removed_idx].gap;
1198         remove_from_cache(self, key);
1199     }
1200     img->animation_duration = removed_gap < img->animation_duration ? img->animation_duration - removed_gap : 0;
1201     if (PyErr_Occurred()) PyErr_Print();
1202     if (removed_idx < img->extra_framecnt - 1) memmove(img->extra_frames + removed_idx, img->extra_frames + removed_idx + 1, sizeof(img->extra_frames[0]) * (img->extra_framecnt - 1 - removed_idx));
1203     img->extra_framecnt--;
1204     if (img->current_frame_index > img->extra_framecnt) {
1205         img->current_frame_index = img->extra_framecnt;
1206         update_current_frame(self, img, NULL);
1207         return NULL;
1208     }
1209     if (removed_idx == img->current_frame_index) update_current_frame(self, img, NULL);
1210     else if (removed_idx < img->current_frame_index) img->current_frame_index--;
1211     return NULL;
1212 }
1213 
1214 static void
1215 handle_animation_control_command(GraphicsManager *self, bool *is_dirty, const GraphicsCommand *g, Image *img) {
1216     if (g->frame_number) {
1217         uint32_t frame_idx = g->frame_number - 1;
1218         if (frame_idx <= img->extra_framecnt) {
1219             Frame *f = frame_idx ? img->extra_frames + frame_idx - 1 : &img->root_frame;
1220             if (g->gap) change_gap(img, f, g->gap);
1221         }
1222     }
1223     if (g->other_frame_number) {
1224         uint32_t frame_idx = g->other_frame_number - 1;
1225         if (frame_idx != img->current_frame_index && frame_idx <= img->extra_framecnt) {
1226             img->current_frame_index = frame_idx;
1227             *is_dirty = true;
1228             update_current_frame(self, img, NULL);
1229         }
1230     }
1231     if (g->animation_state) {
1232         AnimationState old_state = img->animation_state;
1233         switch(g->animation_state) {
1234             case 1:
1235                 img->animation_state = ANIMATION_STOPPED; break;
1236             case 2:
1237                 img->animation_state = ANIMATION_LOADING; break;
1238             case 3:
1239                 img->animation_state = ANIMATION_RUNNING; break;
1240             default:
1241                 break;
1242         }
1243         if (img->animation_state == ANIMATION_STOPPED) {
1244             img->current_loop = 0;
1245         } else {
1246             if (old_state == ANIMATION_STOPPED) img->current_frame_shown_at = monotonic();
1247             self->has_images_needing_animation = true;
1248             global_state.check_for_active_animated_images = true;
1249         }
1250         img->current_loop = 0;
1251     }
1252     if (g->loop_count) {
1253         img->max_loops = g->loop_count - 1;
1254         global_state.check_for_active_animated_images = true;
1255     }
1256 }
1257 
1258 static bool
1259 image_is_animatable(const Image *img) {
1260     return img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->is_drawn && img->animation_duration && (
1261             !img->max_loops || img->current_loop < img->max_loops);
1262 }
1263 
1264 bool
1265 scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t *minimum_gap, bool os_window_context_set) {
1266     bool dirtied = false;
1267     *minimum_gap = MONOTONIC_T_MAX;
1268     if (!self->has_images_needing_animation) return dirtied;
1269     self->has_images_needing_animation = false;
1270     self->context_made_current_for_this_command = os_window_context_set;
1271     for (size_t i = self->image_count; i-- > 0;) {
1272         Image *img = self->images + i;
1273         if (image_is_animatable(img)) {
1274             Frame *f = current_frame(img);
1275             if (f) {
1276                 self->has_images_needing_animation = true;
1277                 monotonic_t next_frame_at = img->current_frame_shown_at + ms_to_monotonic_t(f->gap);
1278                 if (now >= next_frame_at) {
1279                     do {
1280                         uint32_t next = (img->current_frame_index + 1) % (img->extra_framecnt + 1);
1281                         if (!next) {
1282                             if (img->animation_state == ANIMATION_LOADING) goto skip_image;
1283                             if (++img->current_loop >= img->max_loops && img->max_loops) goto skip_image;
1284                         }
1285                         img->current_frame_index = next;
1286                     } while (!current_frame(img)->gap);
1287                     dirtied = true;
1288                     update_current_frame(self, img, NULL);
1289                     f = current_frame(img);
1290                     next_frame_at = img->current_frame_shown_at + ms_to_monotonic_t(f->gap);
1291                 }
1292                 if (next_frame_at > now && next_frame_at - now < *minimum_gap) *minimum_gap = next_frame_at - now;
1293             }
1294         }
1295         skip_image:;
1296     }
1297     return dirtied;
1298 }
1299 // }}}
1300 
1301 // {{{ composition a=c
1302 static void
1303 cfd_free(void *p) { free(((CoalescedFrameData*)p)->buf); }
1304 
1305 static void
1306 handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsCommand *g, Image *img) {
1307     Frame *src_frame = frame_for_number(img, g->frame_number);
1308     if (!src_frame) {
1309         set_command_failed_response("ENOENT", "No source frame number %u exists in image id: %u\n", g->frame_number, img->client_id);
1310         return;
1311     }
1312     Frame *dest_frame = frame_for_number(img, g->other_frame_number);
1313     if (!dest_frame) {
1314         set_command_failed_response("ENOENT", "No destination frame number %u exists in image id: %u\n", g->other_frame_number, img->client_id);
1315         return;
1316     }
1317     const unsigned int width = g->width ? g->width : img->width;
1318     const unsigned int height = g->height ? g->height : img->height;
1319     const unsigned int dest_x = g->x_offset, dest_y = g->y_offset, src_x = g->cell_x_offset, src_y = g->cell_y_offset;
1320     if (dest_x + width > img->width || dest_y + height > img->height) {
1321         set_command_failed_response("EINVAL", "The destination rectangle is out of bounds");
1322         return;
1323     }
1324     if (src_x + width > img->width || src_y + height > img->height) {
1325         set_command_failed_response("EINVAL", "The source rectangle is out of bounds");
1326         return;
1327     }
1328     if (src_frame == dest_frame) {
1329         bool x_overlaps = MAX(src_x, dest_x) < (MIN(src_x, dest_x) + width);
1330         bool y_overlaps = MAX(src_y, dest_y) < (MIN(src_y, dest_y) + height);
1331         if (x_overlaps && y_overlaps) {
1332             set_command_failed_response("EINVAL", "The source and destination rectangles overlap and the src and destination frames are the same");
1333             return;
1334         }
1335     }
1336 
1337     FREE_CFD_AFTER_FUNCTION CoalescedFrameData src_data = get_coalesced_frame_data(self, img, src_frame);
1338     if (!src_data.buf) {
1339         set_command_failed_response("EINVAL", "Failed to get data for src frame: %u", g->frame_number - 1);
1340         return;
1341     }
1342     FREE_CFD_AFTER_FUNCTION CoalescedFrameData dest_data = get_coalesced_frame_data(self, img, dest_frame);
1343     if (!dest_data.buf) {
1344         set_command_failed_response("EINVAL", "Failed to get data for destination frame: %u", g->other_frame_number - 1);
1345         return;
1346     }
1347     ComposeData d = {
1348         .over_px_sz = src_data.is_opaque ? 3 : 4, .under_px_sz = dest_data.is_opaque ? 3: 4,
1349         .needs_blending = !g->compose_mode && !src_data.is_opaque,
1350         .over_offset_x = src_x, .over_offset_y = src_y,
1351         .under_offset_x = dest_x, .under_offset_y = dest_y,
1352         .over_width = width, .over_height = height, .under_width = width, .under_height = height,
1353         .stride = img->width
1354     };
1355     compose_rectangles(d, dest_data.buf, src_data.buf);
1356     const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = dest_frame->id };
1357     if (!add_to_cache(self, key, dest_data.buf, ((size_t)(dest_data.is_opaque ? 3 : 4)) * img->width * img->height)) {
1358         if (PyErr_Occurred()) PyErr_Print();
1359         set_command_failed_response("ENOSPC", "Failed to store image data in disk cache");
1360     }
1361     // frame is now a fully coalesced frame
1362     dest_frame->x = 0; dest_frame->y = 0; dest_frame->width = img->width; dest_frame->height = img->height;
1363     dest_frame->base_frame_id = 0; dest_frame->bgcolor = 0;
1364     *is_dirty = (g->other_frame_number - 1) == img->current_frame_index;
1365     if (*is_dirty) update_current_frame(self, img, &dest_data);
1366 }
1367 // }}}
1368 
1369 // Image lifetime/scrolling {{{
1370 
1371 static void
1372 filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image) {
1373     bool matched = false;
1374     for (size_t i = self->image_count; i-- > 0;) {
1375         Image *img = self->images + i;
1376         for (size_t j = img->refcnt; j-- > 0;) {
1377             ImageRef *ref = img->refs + j;
1378             if (filter_func(ref, img, data, cell)) {
1379                 remove_i_from_array(img->refs, j, img->refcnt);
1380                 self->layers_dirty = true;
1381                 matched = true;
1382             }
1383         }
1384         if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
1385         if (only_first_image && matched) break;
1386     }
1387 }
1388 
1389 
1390 static void
1391 modify_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
1392     for (size_t i = self->image_count; i-- > 0;) {
1393         Image *img = self->images + i;
1394         for (size_t j = img->refcnt; j-- > 0;) {
1395             if (filter_func(img->refs + j, img, data, cell)) remove_i_from_array(img->refs, j, img->refcnt);
1396         }
1397         if (img->refcnt == 0 && img->client_id == 0 && img->client_number == 0) {
1398             // references have all scrolled off the history buffer and the image has no way to reference it
1399             // to create new references so remove it.
1400             remove_image(self, i);
1401         }
1402     }
1403 }
1404 
1405 
1406 static bool
1407 scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
1408     ScrollData *d = (ScrollData*)data;
1409     ref->start_row += d->amt;
1410     return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit;
1411 }
1412 
1413 static bool
1414 ref_within_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
1415     return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom;
1416 }
1417 
1418 static bool
1419 ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
1420     return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom;
1421 }
1422 
1423 static bool
1424 scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) {
1425     ScrollData *d = (ScrollData*)data;
1426     if (ref_within_region(ref, d->margin_top, d->margin_bottom)) {
1427         ref->start_row += d->amt;
1428         if (ref_outside_region(ref, d->margin_top, d->margin_bottom)) return true;
1429         // Clip the image if scrolling has resulted in part of it being outside the page area
1430         uint32_t clip_amt, clipped_rows;
1431         if (ref->start_row < (int32_t)d->margin_top) {
1432             // image moved up
1433             clipped_rows = d->margin_top - ref->start_row;
1434             clip_amt = cell.height * clipped_rows;
1435             if (ref->src_height <= clip_amt) return true;
1436             ref->src_y += clip_amt; ref->src_height -= clip_amt;
1437             ref->effective_num_rows -= clipped_rows;
1438             update_src_rect(ref, img);
1439             ref->start_row += clipped_rows;
1440         } else if (ref->start_row + ref->effective_num_rows > d->margin_bottom) {
1441             // image moved down
1442             clipped_rows = ref->start_row + ref->effective_num_rows - d->margin_bottom;
1443             clip_amt = cell.height * clipped_rows;
1444             if (ref->src_height <= clip_amt) return true;
1445             ref->src_height -= clip_amt;
1446             ref->effective_num_rows -= clipped_rows;
1447             update_src_rect(ref, img);
1448         }
1449         return ref_outside_region(ref, d->margin_top, d->margin_bottom);
1450     }
1451     return false;
1452 }
1453 
1454 void
1455 grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize cell) {
1456     if (self->image_count) {
1457         self->layers_dirty = true;
1458         modify_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func, cell);
1459     }
1460 }
1461 
1462 static bool
1463 clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
1464     return ref->start_row + (int32_t)ref->effective_num_rows > 0;
1465 }
1466 
1467 static bool
1468 clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
1469     return true;
1470 }
1471 
1472 void
1473 grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
1474     filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false);
1475 }
1476 
1477 static bool
1478 id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
1479     const GraphicsCommand *g = data;
1480     if (g->id && img->client_id == g->id) return !g->placement_id || ref->client_id == g->placement_id;
1481     return false;
1482 }
1483 
1484 static bool
1485 number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
1486     const GraphicsCommand *g = data;
1487     if (g->image_number && img->client_number == g->image_number) return !g->placement_id || ref->client_id == g->placement_id;
1488     return false;
1489 }
1490 
1491 
1492 static bool
1493 x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
1494     const GraphicsCommand *g = data;
1495     return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
1496 }
1497 
1498 static bool
1499 y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
1500     const GraphicsCommand *g = data;
1501     return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
1502 }
1503 
1504 static bool
1505 z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
1506     const GraphicsCommand *g = data;
1507     return ref->z_index == g->z_index;
1508 }
1509 
1510 
1511 static bool
1512 point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
1513     return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
1514 }
1515 
1516 static bool
1517 point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
1518     return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
1519 }
1520 
1521 
1522 static void
1523 handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, CellPixelSize cell) {
1524     GraphicsCommand d;
1525     bool only_first_image = false;
1526     switch (g->delete_action) {
1527 #define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell, only_first_image); *is_dirty = true; break
1528 #define D(l, u, data, func) case l: case u: I(u, data, func)
1529 #define G(l, u, func) D(l, u, g, func)
1530         case 0:
1531         D('a', 'A', NULL, clear_filter_func);
1532         G('i', 'I', id_filter_func);
1533         G('p', 'P', point_filter_func);
1534         G('q', 'Q', point3d_filter_func);
1535         G('x', 'X', x_filter_func);
1536         G('y', 'Y', y_filter_func);
1537         G('z', 'Z', z_filter_func);
1538         case 'c':
1539         case 'C':
1540             d.x_offset = c->x + 1; d.y_offset = c->y + 1;
1541             I('C', &d, point_filter_func);
1542         case 'n':
1543         case 'N':
1544             only_first_image = true;
1545             I('N', g, number_filter_func);
1546         case 'f':
1547         case 'F':
1548             if (handle_delete_frame_command(self, g, is_dirty) != NULL) {
1549                 filter_refs(self, g, true, id_filter_func, cell, true);
1550                 *is_dirty = true;
1551             }
1552             break;
1553         default:
1554             REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
1555             break;
1556 #undef G
1557 #undef D
1558 #undef I
1559     }
1560     if (!self->image_count && self->count) self->count = 0;
1561 }
1562 
1563 // }}}
1564 
1565 void
1566 grman_resize(GraphicsManager *self, index_type UNUSED old_lines, index_type UNUSED lines, index_type UNUSED old_columns, index_type UNUSED columns) {
1567     self->layers_dirty = true;
1568 }
1569 
1570 void
1571 grman_rescale(GraphicsManager *self, CellPixelSize cell) {
1572     ImageRef *ref; Image *img;
1573     self->layers_dirty = true;
1574     for (size_t i = self->image_count; i-- > 0;) {
1575         img = self->images + i;
1576         for (size_t j = img->refcnt; j-- > 0;) {
1577             ref = img->refs + j;
1578             ref->cell_x_offset = MIN(ref->cell_x_offset, cell.width - 1);
1579             ref->cell_y_offset = MIN(ref->cell_y_offset, cell.height - 1);
1580             update_dest_rect(ref, ref->num_cols, ref->num_rows, cell);
1581         }
1582     }
1583 }
1584 
1585 const char*
1586 grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) {
1587     const char *ret = NULL;
1588     command_response[0] = 0;
1589     self->context_made_current_for_this_command = false;
1590 
1591     if (g->id && g->image_number) {
1592         set_command_failed_response("EINVAL", "Must not specify both image id and image number");
1593         return finish_command_response(g, false);
1594     }
1595 
1596     switch(g->action) {
1597         case 0:
1598         case 't':
1599         case 'T':
1600         case 'q': {
1601             uint32_t iid = g->id, q_iid = iid;
1602             bool is_query = g->action == 'q';
1603             if (is_query) { iid = 0; if (!q_iid) { REPORT_ERROR("Query graphics command without image id"); break; } }
1604             Image *image = handle_add_command(self, g, payload, is_dirty, iid);
1605             if (!self->currently_loading.loading_for.image_id) free_load_data(&self->currently_loading);
1606             GraphicsCommand *lg = &self->currently_loading.start_command;
1607             if (g->quiet) lg->quiet = g->quiet;
1608             if (is_query) ret = finish_command_response(&(const GraphicsCommand){.id=q_iid, .quiet=g->quiet}, image != NULL);
1609             else ret = finish_command_response(lg, image != NULL);
1610             if (lg->action == 'T' && image && image->root_frame_data_loaded) handle_put_command(self, lg, c, is_dirty, image, cell);
1611             id_type added_image_id = image ? image->internal_id : 0;
1612             if (g->action == 'q') remove_images(self, add_trim_predicate, 0);
1613             if (self->used_storage > self->storage_limit) apply_storage_quota(self, self->storage_limit, added_image_id);
1614             break;
1615         }
1616         case 'a':
1617         case 'f': {
1618             if (!g->id && !g->image_number && !self->currently_loading.loading_for.image_id) {
1619                 REPORT_ERROR("Add frame data command without image id or number");
1620                 break;
1621             }
1622             Image *img;
1623             if (self->currently_loading.loading_for.image_id) img = img_by_internal_id(self, self->currently_loading.loading_for.image_id);
1624             else img = g->id ? img_by_client_id(self, g->id) : img_by_client_number(self, g->image_number);
1625             if (!img) {
1626                 set_command_failed_response("ENOENT", "Animation command refers to non-existent image with id: %u and number: %u", g->id, g->image_number);
1627                 ret = finish_command_response(g, false);
1628             } else {
1629                 GraphicsCommand ag = *g;
1630                 if (ag.action == 'f') {
1631                     img = handle_animation_frame_load_command(self, &ag, img, payload, is_dirty);
1632                     if (!self->currently_loading.loading_for.image_id) free_load_data(&self->currently_loading);
1633                     if (g->quiet) ag.quiet = g->quiet;
1634                     else ag.quiet = self->currently_loading.start_command.quiet;
1635                     ret = finish_command_response(&ag, img != NULL);
1636                 } else if (ag.action == 'a') {
1637                     handle_animation_control_command(self, is_dirty, &ag, img);
1638                 }
1639             }
1640             break;
1641         }
1642         case 'p': {
1643             if (!g->id && !g->image_number) {
1644                 REPORT_ERROR("Put graphics command without image id or number");
1645                 break;
1646             }
1647             uint32_t image_id = handle_put_command(self, g, c, is_dirty, NULL, cell);
1648             GraphicsCommand rg = *g; rg.id = image_id;
1649             ret = finish_command_response(&rg, true);
1650             break;
1651         }
1652         case 'd':
1653             handle_delete_command(self, g, c, is_dirty, cell);
1654             break;
1655         case 'c':
1656             if (!g->id && !g->image_number) {
1657                 REPORT_ERROR("Compose frame data command without image id or number");
1658                 break;
1659             }
1660             Image *img = g->id ? img_by_client_id(self, g->id) : img_by_client_number(self, g->image_number);
1661             if (!img) {
1662                 set_command_failed_response("ENOENT", "Animation command refers to non-existent image with id: %u and number: %u", g->id, g->image_number);
1663                 ret = finish_command_response(g, false);
1664             } else {
1665                 handle_compose_command(self, is_dirty, g, img);
1666                 ret = finish_command_response(g, true);
1667             }
1668             break;
1669         default:
1670             REPORT_ERROR("Unknown graphics command action: %c", g->action);
1671             break;
1672     }
1673     return ret;
1674 }
1675 
1676 
1677 // Boilerplate {{{
1678 static PyObject *
1679 new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
1680     PyObject *ans = (PyObject*)grman_alloc();
1681     if (ans == NULL) PyErr_NoMemory();
1682     return ans;
1683 }
1684 
1685 static PyObject*
1686 image_as_dict(GraphicsManager *self, Image *img) {
1687 #define U(x) #x, (unsigned int)(img->x)
1688 #define B(x) #x, img->x ? Py_True : Py_False
1689     PyObject *frames = PyTuple_New(img->extra_framecnt);
1690     for (unsigned i = 0; i < img->extra_framecnt; i++) {
1691         Frame *f = img->extra_frames + i;
1692         CoalescedFrameData cfd = get_coalesced_frame_data(self, img, f);
1693         if (!cfd.buf) { PyErr_SetString(PyExc_RuntimeError, "Failed to get data for frame"); return NULL; }
1694         PyTuple_SET_ITEM(frames, i, Py_BuildValue(
1695             "{sI sI sy#}",
1696             "gap", f->gap,
1697             "id", f->id,
1698             "data", cfd.buf, (Py_ssize_t)((cfd.is_opaque ? 3 : 4) * img->width * img->height)
1699         ));
1700         free(cfd.buf);
1701         if (PyErr_Occurred()) { Py_CLEAR(frames); return NULL; }
1702     }
1703     CoalescedFrameData cfd = get_coalesced_frame_data(self, img, &img->root_frame);
1704     if (!cfd.buf) { PyErr_SetString(PyExc_RuntimeError, "Failed to get data for root frame"); return NULL; }
1705     PyObject *ans = Py_BuildValue("{sI sI sI sI sI sI sI " "sO sI sO " "sI sI sI " "sI sy# sN}",
1706         U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt), U(client_number),
1707 
1708         B(root_frame_data_loaded), U(animation_state), "is_4byte_aligned", img->root_frame.is_4byte_aligned ? Py_True : Py_False,
1709 
1710         U(current_frame_index), "root_frame_gap", img->root_frame.gap, U(current_frame_index),
1711 
1712         U(animation_duration), "data", cfd.buf, (Py_ssize_t)((cfd.is_opaque ? 3 : 4) * img->width * img->height), "extra_frames", frames
1713     );
1714     free(cfd.buf);
1715     return ans;
1716 #undef B
1717 #undef U
1718 }
1719 
1720 #define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args)
1721 #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
1722 
1723 W(image_for_client_id) {
1724     unsigned long id = PyLong_AsUnsignedLong(args);
1725     bool existing = false;
1726     Image *img = find_or_create_image(self, id, &existing);
1727     if (!existing) { Py_RETURN_NONE; }
1728     return image_as_dict(self, img);
1729 }
1730 
1731 W(image_for_client_number) {
1732     unsigned long num = PyLong_AsUnsignedLong(args);
1733     Image *img = img_by_client_number(self, num);
1734     if (!img) Py_RETURN_NONE;
1735     return image_as_dict(self, img);
1736 }
1737 
1738 W(shm_write) {
1739     const char *name, *data;
1740     Py_ssize_t sz;
1741     PA("ss#", &name, &data, &sz);
1742     int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
1743     if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
1744     int ret = ftruncate(fd, sz);
1745     if (ret != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
1746     void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0);
1747     if (addr == MAP_FAILED) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
1748     memcpy(addr, data, sz);
1749     if (munmap(addr, sz) != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
1750     safe_close(fd, __FILE__, __LINE__);
1751     Py_RETURN_NONE;
1752 }
1753 
1754 W(shm_unlink) {
1755     char *name;
1756     PA("s", &name);
1757     int ret = shm_unlink(name);
1758     if (ret == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
1759     Py_RETURN_NONE;
1760 }
1761 
1762 W(update_layers) {
1763     unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy;
1764     CellPixelSize cell;
1765     PA("IffffIIII", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height);
1766     grman_update_layers(self, scrolled_by, xstart, ystart, dx, dy, sx, sy, cell);
1767     PyObject *ans = PyTuple_New(self->count);
1768     for (size_t i = 0; i < self->count; i++) {
1769         ImageRenderData *r = self->render_data + i;
1770 #define R(offset) Py_BuildValue("{sf sf sf sf}", "left", r->vertices[offset + 8], "top", r->vertices[offset + 1], "right", r->vertices[offset], "bottom", r->vertices[offset + 5])
1771         PyTuple_SET_ITEM(ans, i,
1772             Py_BuildValue("{sN sN sI si sK}", "src_rect", R(0), "dest_rect", R(2), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id)
1773         );
1774 #undef R
1775     }
1776     return ans;
1777 }
1778 
1779 #define M(x, va) {#x, (PyCFunction)py##x, va, ""}
1780 
1781 static PyMethodDef methods[] = {
1782     M(image_for_client_id, METH_O),
1783     M(image_for_client_number, METH_O),
1784     M(update_layers, METH_VARARGS),
1785     {NULL}  /* Sentinel */
1786 };
1787 
1788 static PyMemberDef members[] = {
1789     {"image_count", T_PYSSIZET, offsetof(GraphicsManager, image_count), READONLY, "image_count"},
1790     {"storage_limit", T_PYSSIZET, offsetof(GraphicsManager, storage_limit), 0, "storage_limit"},
1791     {"disk_cache", T_OBJECT_EX, offsetof(GraphicsManager, disk_cache), READONLY, "disk_cache"},
1792     {NULL},
1793 };
1794 
1795 PyTypeObject GraphicsManager_Type = {
1796     PyVarObject_HEAD_INIT(NULL, 0)
1797     .tp_name = "fast_data_types.GraphicsManager",
1798     .tp_basicsize = sizeof(GraphicsManager),
1799     .tp_dealloc = (destructor)dealloc,
1800     .tp_flags = Py_TPFLAGS_DEFAULT,
1801     .tp_doc = "GraphicsManager",
1802     .tp_new = new,
1803     .tp_methods = methods,
1804     .tp_members = members,
1805 };
1806 
1807 static PyObject*
1808 pycreate_canvas(PyObject *self UNUSED, PyObject *args) {
1809     unsigned int bytes_per_pixel;
1810     unsigned int over_width, width, height, x, y;
1811     Py_ssize_t over_sz;
1812     const uint8_t *over_data;
1813     if (!PyArg_ParseTuple(args, "y#IIIIII", &over_data, &over_sz, &over_width, &x, &y, &width, &height, &bytes_per_pixel)) return NULL;
1814     size_t canvas_sz = (size_t)width * height * bytes_per_pixel;
1815     PyObject *ans = PyBytes_FromStringAndSize(NULL, canvas_sz);
1816     if (!ans) return NULL;
1817 
1818     uint8_t* canvas = (uint8_t*)PyBytes_AS_STRING(ans);
1819     memset(canvas, 0, canvas_sz);
1820     ComposeData cd = {
1821         .needs_blending = bytes_per_pixel == 4,
1822         .over_width = over_width, .over_height = over_sz / (bytes_per_pixel * over_width),
1823         .under_width = width, .under_height = height,
1824         .over_px_sz = bytes_per_pixel, .under_px_sz = bytes_per_pixel,
1825         .over_offset_x = x, .over_offset_y = y
1826     };
1827     compose(cd, canvas, over_data);
1828 
1829     return ans;
1830 }
1831 
1832 static PyMethodDef module_methods[] = {
1833     M(shm_write, METH_VARARGS),
1834     M(shm_unlink, METH_VARARGS),
1835     M(create_canvas, METH_VARARGS),
1836     {NULL, NULL, 0, NULL}        /* Sentinel */
1837 };
1838 
1839 
1840 bool
1841 init_graphics(PyObject *module) {
1842     if (PyType_Ready(&GraphicsManager_Type) < 0) return false;
1843     if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false;
1844     if (PyModule_AddFunctions(module, module_methods) != 0) return false;
1845     Py_INCREF(&GraphicsManager_Type);
1846     return true;
1847 }
1848 // }}}
1849