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