URI:
       surface_cache.c - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }