URI:
       tsurface_cache.c - ltk - Socket-based GUI 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)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tsurface_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 }