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 }