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 }