1*08eb9278Smacallan /* $NetBSD: wsdisplay_glyphcache.c,v 1.14 2024/12/06 11:46:11 macallan Exp $ */ 2e297db4bSmacallan 3e297db4bSmacallan /* 4e297db4bSmacallan * Copyright (c) 2012 Michael Lorenz 5e297db4bSmacallan * All rights reserved. 6e297db4bSmacallan * 7e297db4bSmacallan * Redistribution and use in source and binary forms, with or without 8e297db4bSmacallan * modification, are permitted provided that the following conditions 9e297db4bSmacallan * are met: 10e297db4bSmacallan * 1. Redistributions of source code must retain the above copyright 11e297db4bSmacallan * notice, this list of conditions and the following disclaimer. 12e297db4bSmacallan * 2. Redistributions in binary form must reproduce the above copyright 13e297db4bSmacallan * notice, this list of conditions and the following disclaimer in the 14e297db4bSmacallan * documentation and/or other materials provided with the distribution. 15e297db4bSmacallan * 16e297db4bSmacallan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17e297db4bSmacallan * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18e297db4bSmacallan * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19e297db4bSmacallan * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20e297db4bSmacallan * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21e297db4bSmacallan * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22e297db4bSmacallan * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23e297db4bSmacallan * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24e297db4bSmacallan * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25e297db4bSmacallan * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26e297db4bSmacallan */ 27e297db4bSmacallan 28e297db4bSmacallan /* 29e297db4bSmacallan * a simple glyph cache in offscreen memory 30e297db4bSmacallan */ 31e297db4bSmacallan 32d8e04c90Spooka #ifdef _KERNEL_OPT 33d8e04c90Spooka #include "opt_glyphcache.h" 34d8e04c90Spooka #endif 35d8e04c90Spooka 36f9528943Smacallan #include <sys/systm.h> 37e297db4bSmacallan #include <sys/atomic.h> 38e297db4bSmacallan #include <sys/errno.h> 39f9528943Smacallan #include <sys/kmem.h> 406743ac6bSmacallan #include <dev/wscons/wsdisplayvar.h> 416743ac6bSmacallan #include <dev/rasops/rasops.h> 426743ac6bSmacallan #include <dev/wscons/wsdisplay_vconsvar.h> 43e297db4bSmacallan #include <dev/wscons/wsdisplay_glyphcachevar.h> 44f9528943Smacallan 45f9528943Smacallan #ifdef GLYPHCACHE_DEBUG 46f9528943Smacallan #define DPRINTF aprint_normal 47f9528943Smacallan #else 48f9528943Smacallan #define DPRINTF while (0) printf 49f9528943Smacallan #endif 50f9528943Smacallan 516743ac6bSmacallan #define NBUCKETS 32 526743ac6bSmacallan 53f9528943Smacallan static inline int 54f9528943Smacallan attr2idx(long attr) 55f9528943Smacallan { 56f9528943Smacallan return (((attr >> 16) & 0x0f) | ((attr >> 20) & 0xf0)); 57f9528943Smacallan } 58e297db4bSmacallan 59e297db4bSmacallan int 60e297db4bSmacallan glyphcache_init(glyphcache *gc, int first, int lines, int width, 61e297db4bSmacallan int cellwidth, int cellheight, long attr) 62e297db4bSmacallan { 63be0712f5Smacallan return glyphcache_init_align(gc, first, lines, width, cellwidth, cellheight, 64be0712f5Smacallan attr, 0); 65be0712f5Smacallan } 66be0712f5Smacallan 67be0712f5Smacallan int 68be0712f5Smacallan glyphcache_init_align(glyphcache *gc, int first, int lines, int width, 69be0712f5Smacallan int cellwidth, int cellheight, long attr, int alignment) 70be0712f5Smacallan { 716743ac6bSmacallan 726743ac6bSmacallan /* first the geometry stuff */ 736743ac6bSmacallan if (lines < 0) lines = 0; 746743ac6bSmacallan gc->gc_width = width; 756743ac6bSmacallan gc->gc_cellwidth = -1; 766743ac6bSmacallan gc->gc_cellheight = -1; 776743ac6bSmacallan gc->gc_firstline = first; 78*08eb9278Smacallan gc->gc_firstcol = 0; 796743ac6bSmacallan gc->gc_lines = lines; 80be0712f5Smacallan gc->gc_cellalign = alignment; 816743ac6bSmacallan gc->gc_buckets = NULL; 826743ac6bSmacallan gc->gc_numbuckets = 0; 83e5e66fc5Schristos // XXX: Never free? 84e5e66fc5Schristos gc->gc_buckets = kmem_alloc(sizeof(*gc->gc_buckets) * NBUCKETS, 85e5e66fc5Schristos KM_SLEEP); 866743ac6bSmacallan gc->gc_nbuckets = NBUCKETS; 876743ac6bSmacallan return glyphcache_reconfig(gc, cellwidth, cellheight, attr); 886743ac6bSmacallan 896743ac6bSmacallan } 906743ac6bSmacallan 916743ac6bSmacallan int 92*08eb9278Smacallan glyphcache_init_x(glyphcache *gc, int x, int y, int lines, int width, 93*08eb9278Smacallan int cellwidth, int cellheight, long attr) 94*08eb9278Smacallan { 95*08eb9278Smacallan 96*08eb9278Smacallan /* first the geometry stuff */ 97*08eb9278Smacallan if (lines < 0) lines = 0; 98*08eb9278Smacallan gc->gc_width = width; 99*08eb9278Smacallan gc->gc_cellwidth = -1; 100*08eb9278Smacallan gc->gc_cellheight = -1; 101*08eb9278Smacallan gc->gc_firstline = y; 102*08eb9278Smacallan gc->gc_firstcol = x; 103*08eb9278Smacallan gc->gc_lines = lines; 104*08eb9278Smacallan gc->gc_cellalign = 0; 105*08eb9278Smacallan gc->gc_buckets = NULL; 106*08eb9278Smacallan gc->gc_numbuckets = 0; 107*08eb9278Smacallan // XXX: Never free? 108*08eb9278Smacallan gc->gc_buckets = kmem_alloc(sizeof(*gc->gc_buckets) * NBUCKETS, 109*08eb9278Smacallan KM_SLEEP); 110*08eb9278Smacallan gc->gc_nbuckets = NBUCKETS; 111*08eb9278Smacallan return glyphcache_reconfig(gc, cellwidth, cellheight, attr); 112*08eb9278Smacallan 113*08eb9278Smacallan } 114*08eb9278Smacallan 115*08eb9278Smacallan int 1166743ac6bSmacallan glyphcache_reconfig(glyphcache *gc, int cellwidth, int cellheight, long attr) 1176743ac6bSmacallan { 118f9528943Smacallan int cache_lines, buckets, i, usedcells = 0, idx; 119f9528943Smacallan gc_bucket *b; 120e297db4bSmacallan 1216743ac6bSmacallan /* see if we actually need to reconfigure anything */ 1226743ac6bSmacallan if ((gc->gc_cellwidth == cellwidth) && 1236743ac6bSmacallan (gc->gc_cellheight == cellheight) && 1246743ac6bSmacallan ((gc->gc_buckets != NULL) && 1256743ac6bSmacallan (gc->gc_buckets[0].gb_index == attr2idx(attr)))) { 1266743ac6bSmacallan return 0; 1276743ac6bSmacallan } 1286743ac6bSmacallan 129e297db4bSmacallan gc->gc_cellwidth = cellwidth; 130be0712f5Smacallan if (gc->gc_cellalign != 0) { 131be0712f5Smacallan /* alignment in bytes */ 132be0712f5Smacallan gc->gc_cellstride = 133be0712f5Smacallan (gc->gc_cellwidth + gc->gc_cellalign - 1) & 134be0712f5Smacallan ~(gc->gc_cellalign - 1); 135be0712f5Smacallan } else 136be0712f5Smacallan gc->gc_cellstride = cellwidth; 137e297db4bSmacallan gc->gc_cellheight = cellheight; 1386743ac6bSmacallan 139be0712f5Smacallan gc->gc_cellsperline = gc->gc_width / gc->gc_cellstride; 1406743ac6bSmacallan 1416743ac6bSmacallan cache_lines = gc->gc_lines / cellheight; 142e297db4bSmacallan gc->gc_numcells = cache_lines * gc->gc_cellsperline; 143f9528943Smacallan 144f9528943Smacallan /* now allocate buckets */ 145f9528943Smacallan buckets = (gc->gc_numcells / 223); 146f9528943Smacallan if ((buckets * 223) < gc->gc_numcells) 147f9528943Smacallan buckets++; 1489622d8acSmacallan 1499622d8acSmacallan /* 1509622d8acSmacallan * if we don't have enough video memory to cache at least a few glyphs 1519622d8acSmacallan * we stop right here 1529622d8acSmacallan */ 1539622d8acSmacallan if (buckets < 1) 1549622d8acSmacallan return ENOMEM; 1559622d8acSmacallan 156d1579b2dSriastradh buckets = uimin(buckets, gc->gc_nbuckets); 157f9528943Smacallan gc->gc_numbuckets = buckets; 158f9528943Smacallan 159f9528943Smacallan DPRINTF("%s: using %d buckets\n", __func__, buckets); 160f9528943Smacallan for (i = 0; i < buckets; i++) { 161f9528943Smacallan b = &gc->gc_buckets[i]; 162f9528943Smacallan b->gb_firstcell = usedcells; 163d1579b2dSriastradh b->gb_numcells = uimin(223, gc->gc_numcells - usedcells); 164f9528943Smacallan usedcells += 223; 165f9528943Smacallan b->gb_usedcells = 0; 166f9528943Smacallan b->gb_index = -1; 167f9528943Smacallan } 168f9528943Smacallan 169f9528943Smacallan /* initialize the attribute map... */ 170f9528943Smacallan for (i = 0; i < 256; i++) { 171f9528943Smacallan gc->gc_attrmap[i] = -1; 172f9528943Smacallan } 173f9528943Smacallan 174f9528943Smacallan /* first bucket goes to default attr */ 175f9528943Smacallan idx = attr2idx(attr); 176f9528943Smacallan if (idx >= 0) { 177f9528943Smacallan gc->gc_attrmap[idx] = 0; 178f9528943Smacallan gc->gc_buckets[0].gb_index = idx; 179f9528943Smacallan } 180f9528943Smacallan 181e297db4bSmacallan glyphcache_wipe(gc); 182f9528943Smacallan DPRINTF("%s: using %d cells total, from %d width %d\n", __func__, 183f9528943Smacallan gc->gc_numcells, gc->gc_firstline, gc->gc_cellsperline); 184be0712f5Smacallan DPRINTF("%s: cell size %d x %d, stride %d\n", __func__, 185be0712f5Smacallan gc->gc_cellwidth, gc->gc_cellheight, gc->gc_cellstride); 186e297db4bSmacallan return 0; 187e297db4bSmacallan } 188e297db4bSmacallan 189e297db4bSmacallan void 1906743ac6bSmacallan glyphcache_adapt(struct vcons_screen *scr, void *cookie) 1916743ac6bSmacallan { 1926743ac6bSmacallan glyphcache *gc = cookie; 1936743ac6bSmacallan struct rasops_info *ri = &scr->scr_ri; 1946743ac6bSmacallan 1956743ac6bSmacallan if (ri->ri_wsfcookie != gc->gc_fontcookie) { 1966743ac6bSmacallan glyphcache_wipe(gc); 1976743ac6bSmacallan gc->gc_fontcookie = ri->ri_wsfcookie; 1986743ac6bSmacallan } 1996743ac6bSmacallan 2006743ac6bSmacallan glyphcache_reconfig(gc, ri->ri_font->fontwidth, 2016743ac6bSmacallan ri->ri_font->fontheight, scr->scr_defattr); 2026743ac6bSmacallan } 2036743ac6bSmacallan 2046743ac6bSmacallan void 205e297db4bSmacallan glyphcache_wipe(glyphcache *gc) 206e297db4bSmacallan { 207f9528943Smacallan gc_bucket *b; 208f9528943Smacallan int i, j, idx; 209e297db4bSmacallan 21086e9be6aSmacallan if ((gc->gc_buckets == NULL) || (gc->gc_numbuckets < 1)) 21186e9be6aSmacallan return; 21286e9be6aSmacallan 213f9528943Smacallan idx = gc->gc_buckets[0].gb_index; 214f9528943Smacallan 215f9528943Smacallan /* empty all the buckets */ 216f9528943Smacallan for (i = 0; i < gc->gc_numbuckets; i++) { 217f9528943Smacallan b = &gc->gc_buckets[i]; 218f9528943Smacallan b->gb_usedcells = 0; 219f9528943Smacallan b->gb_index = -1; 220f9528943Smacallan for (j = 0; j < b->gb_numcells; j++) 221f9528943Smacallan b->gb_map[j] = -1; 222f9528943Smacallan } 223f9528943Smacallan 224f9528943Smacallan for (i = 0; i < 256; i++) { 225f9528943Smacallan gc->gc_attrmap[i] = -1; 226f9528943Smacallan } 227f9528943Smacallan 228f9528943Smacallan /* now put the first bucket back where it was */ 229f9528943Smacallan gc->gc_attrmap[idx] = 0; 230f9528943Smacallan gc->gc_buckets[0].gb_index = idx; 231e297db4bSmacallan } 232e297db4bSmacallan 233e297db4bSmacallan /* 234e297db4bSmacallan * add a glyph drawn at (x,y) to the cache as (c) 235e297db4bSmacallan * call this only if glyphcache_try() returned GC_ADD 236e297db4bSmacallan * caller or gc_bitblt must make sure the glyph is actually completely drawn 237e297db4bSmacallan */ 238e297db4bSmacallan int 239e297db4bSmacallan glyphcache_add(glyphcache *gc, int c, int x, int y) 240e297db4bSmacallan { 241f9528943Smacallan gc_bucket *b = gc->gc_next; 242e297db4bSmacallan int cell; 243e297db4bSmacallan int cx, cy; 244e297db4bSmacallan 245f9528943Smacallan if (b->gb_usedcells >= b->gb_numcells) 246e297db4bSmacallan return ENOMEM; 247f9528943Smacallan cell = atomic_add_int_nv(&b->gb_usedcells, 1) - 1; 248f9528943Smacallan cell += b->gb_firstcell; 249e297db4bSmacallan cy = gc->gc_firstline + 250e297db4bSmacallan (cell / gc->gc_cellsperline) * gc->gc_cellheight; 251*08eb9278Smacallan cx = gc->gc_firstcol + 252*08eb9278Smacallan (cell % gc->gc_cellsperline) * gc->gc_cellstride; 253f9528943Smacallan b->gb_map[c - 33] = (cx << 16) | cy; 254e297db4bSmacallan gc->gc_bitblt(gc->gc_blitcookie, x, y, cx, cy, 255e297db4bSmacallan gc->gc_cellwidth, gc->gc_cellheight, gc->gc_rop); 256f9528943Smacallan if (gc->gc_underline & 1) { 257f9528943Smacallan glyphcache_underline(gc, x, y, gc->gc_underline); 258f9528943Smacallan } 259e297db4bSmacallan return 0; 260e297db4bSmacallan } 261e297db4bSmacallan 262f9528943Smacallan void 263f9528943Smacallan glyphcache_underline(glyphcache *gc, int x, int y, long attr) 264f9528943Smacallan { 265f9528943Smacallan if (gc->gc_rectfill == NULL) 266f9528943Smacallan return; 267f9528943Smacallan 268f9528943Smacallan gc->gc_rectfill(gc->gc_blitcookie, x, y + gc->gc_cellheight - 2, 269f9528943Smacallan gc->gc_cellwidth, 1, attr); 270f9528943Smacallan } 271e297db4bSmacallan /* 272e297db4bSmacallan * check if (c) is in the cache, if so draw it at (x,y) 273e297db4bSmacallan * return: 274e297db4bSmacallan * - GC_OK when the glyph was found 275e297db4bSmacallan * - GC_ADD when the glyph wasn't found but can be added 276e297db4bSmacallan * - GC_NOPE when the glyph can't be cached 277e297db4bSmacallan */ 278e297db4bSmacallan int 279e297db4bSmacallan glyphcache_try(glyphcache *gc, int c, int x, int y, long attr) 280e297db4bSmacallan { 281f9528943Smacallan int cell, cx, cy, idx, bi; 282f9528943Smacallan gc_bucket *b; 283f9528943Smacallan 284f9528943Smacallan idx = attr2idx(attr); 285f9528943Smacallan /* see if we're in range */ 286f9528943Smacallan if ((c < 33) || (c > 255) || (idx < 0)) 287e297db4bSmacallan return GC_NOPE; 288f9528943Smacallan /* see if there's already a bucket for this attribute */ 289f9528943Smacallan bi = gc->gc_attrmap[idx]; 290f9528943Smacallan if (bi == -1) { 291f9528943Smacallan /* nope, see if there's an empty one left */ 292f9528943Smacallan bi = 1; 293f9528943Smacallan while ((bi < gc->gc_numbuckets) && 294f9528943Smacallan (gc->gc_buckets[bi].gb_index != -1)) { 295f9528943Smacallan bi++; 296f9528943Smacallan } 297f9528943Smacallan if (bi < gc->gc_numbuckets) { 298f9528943Smacallan /* found one -> grab it */ 299f9528943Smacallan gc->gc_attrmap[idx] = bi; 300f9528943Smacallan b = &gc->gc_buckets[bi]; 301f9528943Smacallan b->gb_index = idx; 302f9528943Smacallan b->gb_usedcells = 0; 303f9528943Smacallan /* make sure this doesn't get evicted right away */ 304f9528943Smacallan b->gb_lastread = time_uptime; 305f9528943Smacallan } else { 306f9528943Smacallan /* 307f9528943Smacallan * still nothing 308f9528943Smacallan * steal the least recently read bucket 309f9528943Smacallan */ 310f9528943Smacallan time_t moo = time_uptime; 311f9528943Smacallan int i, oldest = 1; 312f9528943Smacallan 313f9528943Smacallan for (i = 1; i < gc->gc_numbuckets; i++) { 314f9528943Smacallan if (gc->gc_buckets[i].gb_lastread < moo) { 315f9528943Smacallan oldest = i; 316f9528943Smacallan moo = gc->gc_buckets[i].gb_lastread; 317f9528943Smacallan } 318f9528943Smacallan } 319f9528943Smacallan 320f9528943Smacallan /* if we end up here all buckets must be in use */ 321f9528943Smacallan b = &gc->gc_buckets[oldest]; 322f9528943Smacallan gc->gc_attrmap[b->gb_index] = -1; 323f9528943Smacallan b->gb_index = idx; 324f9528943Smacallan b->gb_usedcells = 0; 325f9528943Smacallan gc->gc_attrmap[idx] = oldest; 326f9528943Smacallan /* now scrub it */ 327f9528943Smacallan for (i = 0; i < b->gb_numcells; i++) 328f9528943Smacallan b->gb_map[i] = -1; 329f9528943Smacallan /* and set the time stamp */ 330f9528943Smacallan b->gb_lastread = time_uptime; 331f9528943Smacallan } 332f9528943Smacallan } else { 333f9528943Smacallan /* found one */ 334f9528943Smacallan b = &gc->gc_buckets[bi]; 335f9528943Smacallan } 336f9528943Smacallan 337f9528943Smacallan /* see if there's room in the bucket */ 338f9528943Smacallan if (b->gb_usedcells >= b->gb_numcells) 339e297db4bSmacallan return GC_NOPE; 340f9528943Smacallan 341f9528943Smacallan cell = b->gb_map[c - 33]; 342f9528943Smacallan if (cell == -1) { 343f9528943Smacallan gc->gc_next = b; 344f9528943Smacallan gc->gc_underline = attr; 345e297db4bSmacallan return GC_ADD; 346f9528943Smacallan } 347f9528943Smacallan 348f9528943Smacallan /* it's in the cache - draw it */ 3494d3aa564Smacallan cy = cell & 0xffff; 3504d3aa564Smacallan cx = (cell >> 16) & 0xffff; 351e297db4bSmacallan gc->gc_bitblt(gc->gc_blitcookie, cx, cy, x, y, 352e297db4bSmacallan gc->gc_cellwidth, gc->gc_cellheight, gc->gc_rop); 353f9528943Smacallan /* and underline it if needed */ 354f9528943Smacallan if (attr & 1) 355f9528943Smacallan glyphcache_underline(gc, x, y, attr); 356f9528943Smacallan /* update bucket's time stamp */ 357f9528943Smacallan b->gb_lastread = time_uptime; 358e297db4bSmacallan return GC_OK; 359e297db4bSmacallan } 360