ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | LICENSE

surface_cache.c (12013B)


      1 /*
      2  * Copyright (c) 2022-2024 lumidify <nobody@lumidify.org>
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 /* FIXME: properly test this once it's actually needed */
     18 
     19 #include <stddef.h>
     20 
     21 #include "array.h"
     22 #include "graphics.h"
     23 #include "surface_cache.h"
     24 #include "memory.h"
     25 #include "util.h"
     26 #include "widget.h"
     27 
     28 /* FIXME: Implement a proper cache that isn't as stupid as this one. */
     29 
     30 #define MAX_CACHE_PIXELS (long)3145728 /* 3*1024*1024 */
     31 
     32 struct ltk_surface_cache_key {
     33 	ltk_surface_cache *parent_cache;
     34 	struct cache_surface *s; /* NULL if invalid */
     35 	int min_w;
     36 	int min_h;
     37 	int is_named;
     38 	int widget_type;
     39 	int id;
     40 	unsigned int refcount;
     41 };
     42 
     43 LTK_ARRAY_INIT_DECL_STATIC(skey, ltk_surface_cache_key *)
     44 LTK_ARRAY_INIT_IMPL_STATIC(skey, ltk_surface_cache_key *)
     45 
     46 /* FIXME: maybe optimization using pixmap sizes so pixmaps aren't constantly resized
     47    -> somehow make sure already large pixmaps are reused by widgets needing large
     48       - possibly add some sort of penalty for reassignment to a widget needing a
     49 	pixmap of drastically different size */
     50 
     51 struct cache_surface {
     52 	ltk_surface *s;
     53 	ltk_surface_cache_key *key; /* NULL if not assigned */
     54 };
     55 
     56 struct ltk_surface_cache {
     57 	ltk_renderwindow *renderwindow;
     58 	ltk_array(skey) *named_keys;
     59 	/* FIXME: use generic array for surfaces */
     60 	struct cache_surface **surfaces;
     61 	size_t surfaces_num; /* total number of stored surfaces */
     62 	size_t surfaces_realnum; /* number of currently assigned surfaces */
     63 	size_t surfaces_alloc;
     64 	size_t clock_pos;
     65 	long free_pixels;
     66 };
     67 
     68 /* Cache structure (cs == cache surface):
     69  * |    assigned cs     | unassigned cs (but with valid ltk_surface) | NULL or cs with NULL ltk_surface |
     70  * |--surfaces_realnum--|
     71  * |--surfaces_num---------------------------------------------------|
     72  * |--surfaces_alloc------------------------------------------------------------------------------------|
     73  */
     74 
     75 ltk_surface_cache *
     76 ltk_surface_cache_create(ltk_renderwindow *renderwindow) {
     77 	ltk_surface_cache *sc = ltk_malloc(sizeof(ltk_surface_cache));
     78 	sc->renderwindow = renderwindow;
     79 	sc->named_keys = NULL;
     80 	sc->surfaces = NULL;
     81 	sc->surfaces_num = sc->surfaces_realnum = sc->surfaces_alloc = 0;
     82 	sc->clock_pos = 0;
     83 	sc->free_pixels = MAX_CACHE_PIXELS;
     84 	return sc;
     85 }
     86 
     87 void
     88 ltk_surface_cache_destroy(ltk_surface_cache *cache) {
     89 	/* FIXME: maybe destroy keys as well in case they haven't been released?
     90 	   That would require to keep track of unnamed keys as well */
     91 	if (cache->named_keys)
     92 		ltk_array_destroy(skey, cache->named_keys);
     93 	for (size_t i = 0; i < cache->surfaces_realnum; i++) {
     94 		ltk_surface_destroy(cache->surfaces[i]->s);
     95 		ltk_free(cache->surfaces[i]);
     96 	}
     97 	for (size_t i = cache->surfaces_realnum; i < cache->surfaces_alloc; i++) {
     98 		if (cache->surfaces[i])
     99 			ltk_free(cache->surfaces[i]);
    100 	}
    101 	ltk_free(cache->surfaces);
    102 	ltk_free(cache);
    103 }
    104 
    105 ltk_surface_cache_key *
    106 ltk_surface_cache_get_named_key(ltk_surface_cache *cache, int widget_type, int id, int min_w, int min_h) {
    107 	ltk_assert(min_w > 0 && min_h > 0);
    108 	/* FIXME: binary search */
    109 	if (!cache->named_keys)
    110 		cache->named_keys = ltk_array_create(skey, 1);
    111 	for (size_t i = 0; i < ltk_array_len(cache->named_keys); i++) {
    112 		ltk_surface_cache_key *key = ltk_array_get(cache->named_keys, i);
    113 		if (key->widget_type == widget_type && key->id == id) {
    114 			/* FIXME: how to protect against overflow? */
    115 			key->refcount++;
    116 			return key;
    117 		}
    118 	}
    119 	ltk_surface_cache_key *key = ltk_malloc(sizeof(ltk_surface_cache_key));
    120 	key->parent_cache = cache;
    121 	key->s = NULL;
    122 	key->min_w = min_w;
    123 	key->min_h = min_h;
    124 	key->is_named = 1;
    125 	key->widget_type = widget_type;
    126 	key->id = id;
    127 	key->refcount = 1;
    128 	ltk_array_append(skey, cache->named_keys, key);
    129 	return key;
    130 }
    131 
    132 ltk_surface_cache_key *
    133 ltk_surface_cache_get_unnamed_key(ltk_surface_cache *cache, int min_w, int min_h) {
    134 	ltk_surface_cache_key *key = ltk_malloc(sizeof(ltk_surface_cache_key));
    135 	key->parent_cache = cache;
    136 	key->s = NULL;
    137 	key->min_w = min_w;
    138 	key->min_h = min_h;
    139 	key->is_named = 0;
    140 	key->widget_type = LTK_WIDGET_UNKNOWN;
    141 	key->id = -1;
    142 	key->refcount = 1;
    143 	return key;
    144 }
    145 
    146 /* -> just sets to invalid and changes min_* so appropriate size is taken next time */
    147 /* -> cannot assume anything about the contents afterwards! (unless still valid) */
    148 void
    149 ltk_surface_cache_request_surface_size(ltk_surface_cache_key *key, int min_w, int min_h) {
    150 	key->min_w = min_w;
    151 	key->min_h = min_h;
    152 	if (key->s) {
    153 		int w, h;
    154 		ltk_surface_get_size(key->s->s, &w, &h);
    155 		if (w >= min_w && h >= min_h)
    156 			return;
    157 		ltk_surface_cache *c = key->parent_cache;
    158 		/* move to place for unused surfaces */
    159 		/* FIXME: any way to avoid searching through the cache? */
    160 		for (size_t i = 0; i < c->surfaces_num; i++) {
    161 			if (c->surfaces[i] == key->s) {
    162 				c->surfaces[i] = c->surfaces[c->surfaces_num - 1];
    163 				c->surfaces[c->surfaces_num - 1] = key->s;
    164 				c->surfaces_num--;
    165 				break;
    166 			}
    167 		}
    168 		key->s->key = NULL;
    169 		key->s = NULL;
    170 	}
    171 }
    172 
    173 
    174 /* returns 1 if key was valid, i.e. surface was already assigned, 0 otherwise */
    175 int
    176 ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_ret) {
    177 	if (key->s) {
    178 		*s_ret = key->s->s;
    179 		return 1;
    180 	}
    181 
    182 	ltk_surface_cache *c = key->parent_cache;
    183 	/* FIXME: use generic array everywhere */
    184 	/* FIXME: make surface bigger than needed to avoid too much resizing */
    185 	if (c->surfaces_alloc == 0) {
    186 		c->surfaces_alloc = 4;
    187 		c->surfaces = ltk_malloc(c->surfaces_alloc * sizeof(struct cache_surface *));
    188 		for (size_t i = 1; i < c->surfaces_alloc; i++) {
    189 			c->surfaces[i] = NULL;
    190 		}
    191 		struct cache_surface *cs = ltk_malloc(sizeof(struct cache_surface));
    192 		cs->s = ltk_surface_create(c->renderwindow, key->min_w, key->min_h);
    193 		cs->key = key;
    194 		key->s = cs;
    195 		c->surfaces[0] = cs;
    196 		c->surfaces_num = 1;
    197 		c->surfaces_realnum = 1;
    198 		c->free_pixels -= (long)key->min_w * key->min_h;
    199 	} else if ((long)key->min_w * key->min_h <= c->free_pixels) {
    200 		if (c->surfaces_num == c->surfaces_realnum) {
    201 			if (c->surfaces_realnum == c->surfaces_alloc) {
    202 				size_t old = c->surfaces_alloc;
    203 				c->surfaces_alloc = ideal_array_size(c->surfaces_alloc, c->surfaces_realnum + 1);
    204 				c->surfaces = ltk_reallocarray(c->surfaces, c->surfaces_alloc, sizeof(struct cache_surface *));
    205 				for (size_t i = old; i < c->surfaces_alloc; i++) {
    206 					c->surfaces[i] = NULL;
    207 				}
    208 			}
    209 			if (c->surfaces[c->surfaces_num] == NULL)
    210 				c->surfaces[c->surfaces_num] = ltk_malloc(sizeof(struct cache_surface));
    211 			struct cache_surface *cs = c->surfaces[c->surfaces_num];
    212 			c->surfaces_num++;
    213 			c->surfaces_realnum++;
    214 			cs->s = ltk_surface_create(c->renderwindow, key->min_w, key->min_h);
    215 			cs->key = key;
    216 			key->s = cs;
    217 			c->free_pixels -= (long)key->min_w * key->min_h;
    218 		} else if (c->surfaces_num < c->surfaces_realnum) {
    219 			struct cache_surface *cs = c->surfaces[c->surfaces_num];
    220 			cs->key = key;
    221 			key->s = cs;
    222 			int w, h;
    223 			ltk_surface_get_size(cs->s, &w, &h);
    224 			if (w < key->min_w  || h < key->min_h) {
    225 				ltk_surface_resize(cs->s, key->min_w, key->min_h);
    226 				c->free_pixels -= (long)key->min_w * key->min_h - (long)w * h;
    227 			}
    228 			c->surfaces_num++;
    229 		} else {
    230 			/* FIXME: ERROR!!! */
    231 		}
    232 	} else {
    233 		int w, h;
    234 		/* First try to delete all currently unused surfaces. */
    235 		/* c->surfaces_num could be 0! */
    236 		for (size_t i = c->surfaces_realnum; i > c->surfaces_num; i--) {
    237 			ltk_surface_get_size(c->surfaces[i-1]->s, &w, &h);
    238 			if ((long)key->min_w * key->min_h <= c->free_pixels + w * h) {
    239 				struct cache_surface *cs = c->surfaces[c->surfaces_num];
    240 				c->surfaces[c->surfaces_num] = c->surfaces[i-1];
    241 				c->surfaces[i-1] = cs;
    242 				cs = c->surfaces[c->surfaces_num];
    243 				cs->key = key;
    244 				key->s = cs;
    245 				if (w < key->min_w  || h < key->min_h) {
    246 					ltk_surface_resize(cs->s, key->min_w, key->min_h);
    247 					c->free_pixels -= (long)key->min_w * key->min_h - (long)w * h;
    248 				}
    249 				c->surfaces_num++;
    250 				break;
    251 			} else {
    252 				ltk_surface_destroy(c->surfaces[i-1]->s);
    253 				/* setting this to NULL isn't actually necessary, but it
    254 				   might help with debugging in certain cases */
    255 				c->surfaces[i-1]->s = NULL;
    256 				c->surfaces_realnum--;
    257 				c->free_pixels += (long)w * h;
    258 			}
    259 		}
    260 
    261 		/* That didn't work, so start deleting or taking over assigned surfaces. */
    262 		if (!key->s) {
    263 			while (c->surfaces_num > 0) {
    264 				c->clock_pos %= c->surfaces_num;
    265 				struct cache_surface *cs = c->surfaces[c->clock_pos];
    266 				ltk_surface_get_size(cs->s, &w, &h);
    267 				if ((long)key->min_w * key->min_h <= c->free_pixels + w * h) {
    268 					cs->key->s = NULL;
    269 					cs->key = key;
    270 					key->s = cs;
    271 					if (w < key->min_w  || h < key->min_h) {
    272 						ltk_surface_resize(cs->s, key->min_w, key->min_h);
    273 						c->free_pixels -= (long)key->min_w * key->min_h - (long)w * h;
    274 					}
    275 					c->clock_pos++;
    276 					break;
    277 				} else {
    278 					/* FIXME: This cache architecture really needs to be changed. The whole
    279 					   purpose of the clock_pos is defeated by switching entries around here.
    280 					   It would be possible to change that with memmove, but that would be
    281 					   more inefficient (although it probably wouldn't be too bad since the
    282 					   cache shouldn't be too big anyways). It's probably stupid to separate
    283 					   the different parts of the cache as is currently done. */
    284 					c->surfaces[c->clock_pos] = c->surfaces[c->surfaces_num - 1];
    285 					c->surfaces[c->surfaces_num - 1] = cs;
    286 					cs->key->s = NULL;
    287 					ltk_surface_destroy(cs->s);
    288 					cs->s = NULL;
    289 					cs->key = NULL;
    290 					c->surfaces_realnum--;
    291 					c->surfaces_num--;
    292 					c->free_pixels += (long)w * h;
    293 				}
    294 			}
    295 		}
    296 
    297 		/* The needed surface contains more pixels than the maximum allowed amount.
    298 		   In this case, just create a surface of that size, but it will be the only
    299 		   surface in the cache. */
    300 		/* c->free_pixels should be the maximum amount again here, otherwise there is a bug! */
    301 		ltk_assert(c->free_pixels == MAX_CACHE_PIXELS);
    302 		if (!key->s) {
    303 			/* this should be impossible */
    304 			if (!c->surfaces[0])
    305 				c->surfaces[0] = ltk_malloc(sizeof(struct cache_surface));
    306 			struct cache_surface *cs = c->surfaces[0];
    307 			cs->s = ltk_surface_create(c->renderwindow, key->min_w, key->min_h);
    308 			cs->key = key;
    309 			key->s = cs;
    310 			c->surfaces_num = 1;
    311 			c->surfaces_realnum = 1;
    312 			c->free_pixels -= (long)key->min_w * key->min_h;
    313 		}
    314 	}
    315 	*s_ret = key->s->s;
    316 	return 0;
    317 }
    318 
    319 void
    320 ltk_surface_cache_release_key(ltk_surface_cache_key *key) {
    321 	int destroy = 0;
    322 	if (key->is_named) {
    323 		if (key->refcount > 0)
    324 			key->refcount--;
    325 		if (key->refcount == 0) {
    326 			/* if the key is valid, this must be true */
    327 			ltk_assert(key->parent_cache->named_keys != NULL);
    328 			for (size_t i = 0; i < ltk_array_len(key->parent_cache->named_keys); i++) {
    329 				ltk_surface_cache_key *e = ltk_array_get(key->parent_cache->named_keys, i);
    330 				if (e == key) {
    331 					ltk_array_delete(skey, key->parent_cache->named_keys, i, 1);
    332 					break;
    333 				}
    334 			}
    335 			destroy = 1;
    336 		}
    337 	}
    338 	if (!key->is_named || destroy) {
    339 		if (key->s) {
    340 			key->s->key = NULL;
    341 			ltk_surface_cache *c = key->parent_cache;
    342 			/* move to place for unused surfaces */
    343 			/* FIXME: any way to avoid searching through the cache? */
    344 			for (size_t i = 0; i < c->surfaces_num; i++) {
    345 				if (c->surfaces[i] == key->s) {
    346 					c->surfaces[i] = c->surfaces[c->surfaces_num - 1];
    347 					c->surfaces[c->surfaces_num - 1] = key->s;
    348 					c->surfaces_num--;
    349 					break;
    350 				}
    351 			}
    352 		}
    353 
    354 		ltk_free(key);
    355 	}
    356 }