xref: /netbsd-src/sys/dev/wscons/wsdisplay_glyphcache.c (revision 08eb92781f914973e4424deeaa66f9f4b384e0c9)
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