ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

surface_cache.c (12222B)


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