]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/omap/screen.c
kernel: add portable uncached memory allocator (ucalloc) (from sources)
[plan9front.git] / sys / src / 9 / omap / screen.c
1 /*
2  * ti omap35 display subsystem (dss)
3  *
4  * can handle 2ⁿ bits per pixel for 0 < n ≤ 4, and 12 and 24 bits.
5  * can handle   1024×768 at 60 Hz with pixel clock of 63.5 MHz
6  *              1280×800 at 59.91 Hz with pixel clock of 71 MHz
7  *              1400×1050 lcd at 50 MHz with pixel clock of 75 MHz
8  * has 256 24-bit entries in RGB palette
9  */
10 #include "u.h"
11 #include "../port/lib.h"
12 #include "mem.h"
13 #include "dat.h"
14 #include "fns.h"
15 #include "io.h"
16 #include "ureg.h"
17 #include "../port/error.h"
18
19 #define Image   IMAGE
20 #include <draw.h>
21 #include <memdraw.h>
22 #include <cursor.h>
23 #include "screen.h"
24 // #include "gamma.h"
25
26 enum {
27         Tabstop = 4,            /* should be 8 */
28         Scroll  = 8,            /* lines to scroll at one time */
29         /*
30          * screen settings for Wid and Ht, should a bit more dynamic?
31          * http://www.epanorama.net/faq/vga2rgb/calc.html
32          * used to calculate settings.
33          */
34
35 //      Hbp     = (248-1) << 20,
36 //      Hfp     = (48-1) << 8,
37 //      Hsw     = 112-1,
38
39 //      Vbp     = 38 << 20,
40 //      Vfp     = 1 << 8,
41 //      Vsw     = 3,
42
43         Tft     = 0x60,
44
45         Loadmode = 2 << 1,
46         Fifosize = 0x400,
47
48         /* dispc sysconfig */
49         Midlemode       = 2 << 12,
50         Sidlemode       = 2 << 3,
51         EnableWakeup    = 1 << 2,
52         Autoidle        = 1 << 0,
53
54         /* dispc pool_freq */
55         Ipc             = 1 << 14,
56         Ihs             = 1 << 13,
57         Ivs             = 1 << 12,
58         Acb             = 0x28,
59
60         /* gfx attribs */
61         Burstsize       = 2 << 6,
62         Format          = 6 << 1,
63         Gfxenable       = 1 << 0,
64
65         /* dispc control */
66         Gpout1          = 1 << 16,
67         Gpout0          = 1 << 15,
68         Tftdata         = 3 << 8,
69         Digital         = 1 << 6,
70         Lcd             = 1 << 5,
71         Stntft          = 1 << 3,
72         Digitalen       = 1 << 1,
73 //      Lcden           = 1 << 0,       /* unused */
74 };
75
76 typedef struct Dispcregs Dispc;
77 typedef struct Dssregs Dss;
78 typedef struct Ioregs Ioregs;
79
80 struct Ioregs {                         /* common registers, 68 (0x44) bytes */
81         ulong   rev;
82         uchar   _pad0[0x10-0x4];
83         ulong   sysconf;
84         ulong   sysstat;
85         ulong   irqstat1;
86
87         /* Dispc only regs */
88         ulong   irqen1;
89         ulong   wkupen;
90         ulong   _pad1;
91         ulong   irqsts2;
92         ulong   irqen2;
93         ulong   _pad2[4];
94
95         ulong   ctrl;
96 };
97
98 struct Dssregs {                        /* display subsys at 0x48050000 */
99         Ioregs;
100         ulong   sdicrtl;
101         ulong   pllcrtl;
102         uchar   _pad3[0x5c-0x4c];
103         ulong   sdistat;
104 };
105
106 struct Dispcregs {                      /* display ctlr at 0x48050400 */
107         Ioregs;
108         ulong   config;
109         ulong   _pad3;
110         ulong   defaultcolor[2];
111         ulong   transcolor[2];
112         ulong   linestat;
113         ulong   linenum;
114         ulong   timing_h;
115         ulong   timing_v;
116         ulong   pol_req;
117         ulong   divisor;
118         ulong   alpha;
119         ulong   digsize;
120         ulong   lcdsize;
121
122         ulong   base[2];        /* should allocate both to avoid dithering */
123         ulong   pos;
124         ulong   size;
125         ulong   _pad4[4];
126         ulong   attrib;
127         ulong   fifothr;
128         ulong   fifosize;
129         ulong   rowinc;
130         ulong   pixelinc;
131         ulong   winskip;
132         ulong   palette;                /* gfx_table_ba */
133         uchar   _pad5[0x5d4 - 0x4bc];
134
135         ulong   datacycle[3];
136         uchar   _pad5[0x620 - 0x5e0];
137
138         ulong   cprcoefr;
139         ulong   cprcoefg;
140         ulong   cprcoefb;
141         ulong   preload;
142 };
143
144 int     drawdebug;
145 Point   ZP = {0, 0};
146 Cursor  arrow = {
147         { -1, -1 },
148         { 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
149           0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
150           0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
151           0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
152         },
153         { 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
154           0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
155           0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
156           0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
157         },
158 };
159
160 OScreen oscreen;
161 Settings settings[] = {
162 [Res800x600]   {  800,  600, 60, RGB16,  40000,  88, 40, 128,   23, 1, 5, },
163 [Res1024x768]  { 1024,  768, 60, RGB16,  65000, 160, 24, 136,   29, 3, 7, },
164 [Res1280x1024] { 1280, 1024, 60, RGB16, 108000, 248, 48, 112,   38, 1, 4, },
165 [Res1400x1050] { 1400, 1050, 50, RGB16, 108000, 248, 48, 112,   38, 1, 4, }, // TODO
166 };
167 Omap3fb *framebuf;
168 Memimage *gscreen;
169
170 static Memdata xgdata;
171
172 static Memimage xgscreen =
173 {
174         { 0, 0, Wid, Ht },      /* r */
175         { 0, 0, Wid, Ht },      /* clipr */
176         Depth,                  /* depth */
177         3,                      /* nchan */
178         RGB16,                  /* chan */
179         nil,                    /* cmap */
180         &xgdata,                /* data */
181         0,                      /* zero */
182         Wid*(Depth/BI2BY)/BY2WD, /* width in words of a single scan line */
183         0,                      /* layer */
184         0,                      /* flags */
185 };
186
187 static Memimage *conscol;
188 static Memimage *back;
189
190 static Memsubfont *memdefont;
191
192 static Lock screenlock;
193
194 static Point    curpos;
195 static int      h, w;
196 static int      landscape = 0;  /* screen orientation, default is 0: portrait */
197 static ushort   *vscreen;       /* virtual screen */
198 static Rectangle window;
199
200 static Dispc *dispc = (Dispc *)PHYSDISPC;
201 static Dss *dss = (Dss *)PHYSDSS;
202
203 static  void    omapscreenputs(char *s, int n);
204 static  ulong   rep(ulong, int);
205 static  void    screenputc(char *buf);
206 static  void    screenwin(void);
207
208 /*
209  * Software cursor. 
210  */
211 int     swvisible;      /* is the cursor visible? */
212 int     swenabled;      /* is the cursor supposed to be on the screen? */
213 Memimage*       swback; /* screen under cursor */
214 Memimage*       swimg;  /* cursor image */
215 Memimage*       swmask; /* cursor mask */
216 Memimage*       swimg1;
217 Memimage*       swmask1;
218
219 Point   swoffset;
220 Rectangle       swrect; /* screen rectangle in swback */
221 Point   swpt;   /* desired cursor location */
222 Point   swvispt;        /* actual cursor location */
223 int     swvers; /* incremented each time cursor image changes */
224 int     swvisvers;      /* the version on the screen */
225
226 static void
227 lcdoff(void)
228 {
229         dispc->ctrl &= ~1;              /* disable the lcd */
230         coherence();
231
232         dispc->irqstat1 |= 1;           /* set framedone */
233         coherence();
234
235         /* the lcd never comes ready, so don't bother with this */
236 #ifdef notdef
237         /* spin until the frame is complete, but not forever */
238         for(cnt = 50; !(dispc->irqstat1 & 1) && cnt-- > 0; )
239                 delay(10);
240 #endif
241         delay(20);                      /* worst case for 1 frame, 50Hz */
242 }
243
244 static void
245 dssstart(void)
246 {
247         /* should reset the dss system */
248         dss->sysconf |= 1;
249         coherence();
250 }
251
252 /* see spruf98i §15.6.7.4.2 */
253 static void
254 configdispc(void)
255 {
256         Settings *sp;
257
258         sp = oscreen.settings;
259         dss->ctrl &= 0x78;              /* choose dss clock */
260         dispc->sysconf = Midlemode | Sidlemode | EnableWakeup | Autoidle;
261         dispc->config = Loadmode;
262         coherence();
263
264         /* pll */
265         dispc->defaultcolor[0] = 0;     /* set background color to black? */
266         dispc->defaultcolor[1] = 0;
267         dispc->transcolor[0] = 0;       /* set transparency to full */
268         dispc->transcolor[1] = 0;
269
270         dispc->timing_h = (sp->hbp-1) << 20 | (sp->hfp-1) << 8 |
271                         (sp->hsw-1);
272         dispc->timing_v = sp->vbp << 20 | sp->vfp << 8 |
273                         (sp->vsw-1);
274
275         dispc->pol_req = Ipc | Ihs | Ivs | Acb;
276         dispc->divisor = 1 << 16 | HOWMANY(432000, sp->pixelclock);
277
278         dispc->lcdsize = (sp->ht - 1) << 16 | (sp->wid - 1);
279         coherence();
280
281         dispc->base[0] = PADDR(framebuf->pixel);
282         dispc->base[1] = PADDR(framebuf->pixel);
283
284         dispc->pos = 0;                 /* place screen in the left corner */
285         /* use the whole screen */
286         dispc->size = (sp->ht - 1) << 16 | (sp->wid - 1);
287
288         /* what mode does plan 9 use for fb? */
289         dispc->attrib = Burstsize | Format | Gfxenable;
290
291         dispc->preload = Tft;
292         dispc->fifosize = Fifosize;
293         /* 1008 is max for our Burstsize */
294         dispc->fifothr = (Fifosize - 1) << 16 | (1008 - 1);
295
296         /* 1 byte is one pixel (not true, we use 2 bytes per pixel) */
297         dispc->rowinc = 1;
298         dispc->pixelinc = 1;
299         dispc->winskip = 0;             /* don't skip anything */
300         coherence();
301
302         // dispc->palette = PADDR(framebuf->palette);
303 }
304
305 static void
306 lcdon(int enable)
307 {
308         dispc->ctrl = Gpout1 | Gpout0 | Tftdata | Digital | Lcd | Stntft |
309                 Digitalen | enable;
310         coherence();
311         delay(10);
312 }
313
314 static void
315 lcdstop(void)
316 {
317         configscreengpio();
318         screenclockson();
319
320         lcdoff();
321 }
322
323 static void
324 lcdinit(void)
325 {
326         lcdstop();
327
328         dssstart();
329         configdispc();
330 }
331
332 /* Paint the image data with blue pixels */
333 void
334 screentest(void)
335 {
336         int i;
337
338         for (i = nelem(framebuf->pixel) - 1; i >= 0; i--)
339                 framebuf->pixel[i] = 0x1f;                      /* blue */
340 //      memset(framebuf->pixel, ~0, sizeof framebuf->pixel);    /* white */
341 }
342
343 void
344 screenpower(int on)
345 {
346         blankscreen(on == 0);
347 }
348
349 /*
350  * called with drawlock locked for us, most of the time.
351  * kernel prints at inopportune times might mean we don't
352  * hold the lock, but memimagedraw is now reentrant so
353  * that should be okay: worst case we get cursor droppings.
354  */
355 void
356 swcursorhide(void)
357 {
358         if(swvisible == 0)
359                 return;
360         if(swback == nil)
361                 return;
362         swvisible = 0;
363         memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S);
364         flushmemscreen(swrect);
365 }
366
367 void
368 swcursoravoid(Rectangle r)
369 {
370         if(swvisible && rectXrect(r, swrect))
371                 swcursorhide();
372 }
373
374 void
375 swcursordraw(void)
376 {
377         if(swvisible)
378                 return;
379         if(swenabled == 0)
380                 return;
381         if(swback == nil || swimg1 == nil || swmask1 == nil)
382                 return;
383 //      assert(!canqlock(&drawlock));           // assertion fails on omap
384         swvispt = swpt;
385         swvisvers = swvers;
386         swrect = rectaddpt(Rect(0,0,16,16), swvispt);
387         memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S);
388         memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD);
389         flushmemscreen(swrect);
390         swvisible = 1;
391 }
392
393 int
394 cursoron(int dolock)
395 {
396         if (dolock)
397                 lock(&oscreen);
398         cursoroff(0);
399         swcursordraw();
400         if (dolock)
401                 unlock(&oscreen);
402         return 0;
403 }
404
405 void
406 cursoroff(int dolock)
407 {
408         if (dolock)
409                 lock(&oscreen);
410         swcursorhide();
411         if (dolock)
412                 unlock(&oscreen);
413 }
414
415 void
416 swload(Cursor *curs)
417 {
418         uchar *ip, *mp;
419         int i, j, set, clr;
420
421         if(!swimg || !swmask || !swimg1 || !swmask1)
422                 return;
423         /*
424          * Build cursor image and mask.
425          * Image is just the usual cursor image
426          * but mask is a transparent alpha mask.
427          * 
428          * The 16x16x8 memimages do not have
429          * padding at the end of their scan lines.
430          */
431         ip = byteaddr(swimg, ZP);
432         mp = byteaddr(swmask, ZP);
433         for(i=0; i<32; i++){
434                 set = curs->set[i];
435                 clr = curs->clr[i];
436                 for(j=0x80; j; j>>=1){
437                         *ip++ = set&j ? 0x00 : 0xFF;
438                         *mp++ = (clr|set)&j ? 0xFF : 0x00;
439                 }
440         }
441         swoffset = curs->offset;
442         swvers++;
443         memimagedraw(swimg1,  swimg1->r,  swimg,  ZP, memopaque, ZP, S);
444         memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S);
445 }
446
447 /* called from devmouse */
448 void
449 setcursor(Cursor* curs)
450 {
451         cursoroff(1);
452         oscreen.Cursor = *curs;
453         swload(curs);
454         cursoron(1);
455 }
456
457 int
458 swmove(Point p)
459 {
460         swpt = addpt(p, swoffset);
461         return 0;
462 }
463
464 void
465 swcursorclock(void)
466 {
467         int x;
468
469         if(!swenabled)
470                 return;
471         swmove(mousexy());
472         if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers)
473                 return;
474
475         x = splhi();
476         if(swenabled)
477         if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers)
478         if(canqlock(&drawlock)){
479                 swcursorhide();
480                 swcursordraw();
481                 qunlock(&drawlock);
482         }
483         splx(x);
484 }
485
486 void
487 swcursorinit(void)
488 {
489         static int init;
490
491         if(!init){
492                 init = 1;
493                 addclock0link(swcursorclock, 10);
494         }
495         if(swback){
496                 freememimage(swback);
497                 freememimage(swmask);
498                 freememimage(swmask1);
499                 freememimage(swimg);
500                 freememimage(swimg1);
501         }
502
503         swback  = allocmemimage(Rect(0,0,32,32), gscreen->chan);
504         swmask  = allocmemimage(Rect(0,0,16,16), GREY8);
505         swmask1 = allocmemimage(Rect(0,0,16,16), GREY1);
506         swimg   = allocmemimage(Rect(0,0,16,16), GREY8);
507         swimg1  = allocmemimage(Rect(0,0,16,16), GREY1);
508         if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){
509                 print("software cursor: allocmemimage fails\n");
510                 return;
511         }
512
513         memfillcolor(swmask, DOpaque);
514         memfillcolor(swmask1, DOpaque);
515         memfillcolor(swimg, DBlack);
516         memfillcolor(swimg1, DBlack);
517 }
518
519 /* called from main and possibly later from devdss to change resolution */
520 void
521 screeninit(void)
522 {
523         static int first = 1;
524
525         if (first) {
526                 iprint("screeninit...");
527                 oscreen.settings = &settings[Res1280x1024];
528
529                 lcdstop();
530                 if (framebuf)
531                         free(framebuf);
532                 /* mode is 16*32 = 512 */
533                 framebuf = xspanalloc(sizeof *framebuf, 16*32, 0);
534         }
535
536         lcdinit();
537         lcdon(1);
538         if (first) {
539                 memimageinit();
540                 memdefont = getmemdefont();
541                 screentest();
542         }
543
544         xgdata.ref = 1;
545         xgdata.bdata = (uchar *)framebuf->pixel;
546
547         gscreen = &xgscreen;
548         gscreen->r = Rect(0, 0, Wid, Ht);
549         gscreen->clipr = gscreen->r;
550         /* width, in words, of a single scan line */
551         gscreen->width = Wid * (Depth / BI2BY) / BY2WD;
552         flushmemscreen(gscreen->r);
553
554         blanktime = 3;                          /* minutes */
555
556         if (first) {
557                 iprint("on: blue for 3 seconds...");
558                 delay(3*1000);
559                 iprint("\n");
560
561                 screenwin();            /* draw border & top orange bar */
562                 screenputs = omapscreenputs;
563                 iprint("screen: frame buffer at %#p for %dx%d\n",
564                         framebuf, oscreen.settings->wid, oscreen.settings->ht);
565
566                 swenabled = 1;
567                 swcursorinit();         /* needs gscreen set */
568                 setcursor(&arrow);
569
570                 first = 0;
571         }
572 }
573
574 /* flushmemscreen should change buffer? */
575 void
576 flushmemscreen(Rectangle r)
577 {
578         ulong start, end;
579
580         if (r.min.x < 0)
581                 r.min.x = 0;
582         if (r.max.x > Wid)
583                 r.max.x = Wid;
584         if (r.min.y < 0)
585                 r.min.y = 0;
586         if (r.max.y > Ht)
587                 r.max.y = Ht;
588         if (rectclip(&r, gscreen->r) == 0)
589                 return;
590         start = (ulong)&framebuf->pixel[r.min.y*Wid + r.min.x];
591         end   = (ulong)&framebuf->pixel[(r.max.y - 1)*Wid + r.max.x -1];
592         cachedwbse((ulong *)start, end - start);
593 }
594
595 /*
596  * export screen to devdraw
597  */
598 uchar*
599 attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
600 {
601         *r = gscreen->r;
602         *d = gscreen->depth;
603         *chan = gscreen->chan;
604         *width = gscreen->width;
605         *softscreen = (landscape == 0);
606         return (uchar *)gscreen->data->bdata;
607 }
608
609 void
610 getcolor(ulong p, ulong *pr, ulong *pg, ulong *pb)
611 {
612         USED(p, pr, pg, pb);
613 }
614
615 int
616 setcolor(ulong p, ulong r, ulong g, ulong b)
617 {
618         USED(p, r, g, b);
619         return 0;
620 }
621
622 void
623 blankscreen(int blank)
624 {
625         if (blank)
626                 lcdon(0);
627         else {
628                 lcdinit();
629                 lcdon(1);
630         }
631 }
632
633 static void
634 omapscreenputs(char *s, int n)
635 {
636         int i;
637         Rune r;
638         char buf[4];
639
640         if (!islo()) {
641                 /* don't deadlock trying to print in interrupt */
642                 if (!canlock(&screenlock))
643                         return;                 /* discard s */
644         } else
645                 lock(&screenlock);
646
647         while (n > 0) {
648                 i = chartorune(&r, s);
649                 if (i == 0) {
650                         s++;
651                         --n;
652                         continue;
653                 }
654                 memmove(buf, s, i);
655                 buf[i] = 0;
656                 n -= i;
657                 s += i;
658                 screenputc(buf);
659         }
660         unlock(&screenlock);
661 }
662
663 static void
664 screenwin(void)
665 {
666         char *greet;
667         Memimage *orange;
668         Point p, q;
669         Rectangle r;
670
671         memsetchan(gscreen, RGB16);
672
673         back = memwhite;
674         conscol = memblack;
675
676         orange = allocmemimage(Rect(0, 0, 1, 1), RGB16);
677         orange->flags |= Frepl;
678         orange->clipr = gscreen->r;
679         orange->data->bdata[0] = 0x40;          /* magic: colour? */
680         orange->data->bdata[1] = 0xfd;          /* magic: colour? */
681
682         w = memdefont->info[' '].width;
683         h = memdefont->height;
684
685         r = insetrect(gscreen->r, 4);
686
687         memimagedraw(gscreen, r, memblack, ZP, memopaque, ZP, S);
688         window = insetrect(r, 4);
689         memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);
690
691         memimagedraw(gscreen, Rect(window.min.x, window.min.y,
692                 window.max.x, window.min.y + h + 5 + 6), orange, ZP, nil, ZP, S);
693         freememimage(orange);
694         window = insetrect(window, 5);
695
696         greet = " Plan 9 Console ";
697         p = addpt(window.min, Pt(10, 0));
698         q = memsubfontwidth(memdefont, greet);
699         memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
700         flushmemscreen(r);
701         window.min.y += h + 6;
702         curpos = window.min;
703         window.max.y = window.min.y + ((window.max.y - window.min.y) / h) * h;
704 }
705
706 static void
707 scroll(void)
708 {
709         int o;
710         Point p;
711         Rectangle r;
712
713         /* move window contents up Scroll text lines */
714         o = Scroll * h;
715         r = Rpt(window.min, Pt(window.max.x, window.max.y - o));
716         p = Pt(window.min.x, window.min.y + o);
717         memimagedraw(gscreen, r, gscreen, p, nil, p, S);
718         flushmemscreen(r);
719
720         /* clear the bottom Scroll text lines */
721         r = Rpt(Pt(window.min.x, window.max.y - o), window.max);
722         memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
723         flushmemscreen(r);
724
725         curpos.y -= o;
726 }
727
728 static void
729 screenputc(char *buf)
730 {
731         int w;
732         uint pos;
733         Point p;
734         Rectangle r;
735         static int *xp;
736         static int xbuf[256];
737
738         if (xp < xbuf || xp >= &xbuf[sizeof(xbuf)])
739                 xp = xbuf;
740
741         switch (buf[0]) {
742         case '\n':
743                 if (curpos.y + h >= window.max.y)
744                         scroll();
745                 curpos.y += h;
746                 screenputc("\r");
747                 break;
748         case '\r':
749                 xp = xbuf;
750                 curpos.x = window.min.x;
751                 break;
752         case '\t':
753                 p = memsubfontwidth(memdefont, " ");
754                 w = p.x;
755                 if (curpos.x >= window.max.x - Tabstop * w)
756                         screenputc("\n");
757
758                 pos = (curpos.x - window.min.x) / w;
759                 pos = Tabstop - pos % Tabstop;
760                 *xp++ = curpos.x;
761                 r = Rect(curpos.x, curpos.y, curpos.x + pos * w, curpos.y + h);
762                 memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
763                 flushmemscreen(r);
764                 curpos.x += pos * w;
765                 break;
766         case '\b':
767                 if (xp <= xbuf)
768                         break;
769                 xp--;
770                 r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
771                 memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
772                 flushmemscreen(r);
773                 curpos.x = *xp;
774                 break;
775         case '\0':
776                 break;
777         default:
778                 p = memsubfontwidth(memdefont, buf);
779                 w = p.x;
780
781                 if (curpos.x >= window.max.x - w)
782                         screenputc("\n");
783
784                 *xp++ = curpos.x;
785                 r = Rect(curpos.x, curpos.y, curpos.x + w, curpos.y + h);
786                 memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
787                 memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
788                 flushmemscreen(r);
789                 curpos.x += w;
790         }
791 }