summaryrefslogtreecommitdiff
path: root/sys/src/9/omap/screen.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/omap/screen.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/omap/screen.c')
-rwxr-xr-xsys/src/9/omap/screen.c791
1 files changed, 791 insertions, 0 deletions
diff --git a/sys/src/9/omap/screen.c b/sys/src/9/omap/screen.c
new file mode 100755
index 000000000..e47fd2821
--- /dev/null
+++ b/sys/src/9/omap/screen.c
@@ -0,0 +1,791 @@
+/*
+ * ti omap35 display subsystem (dss)
+ *
+ * can handle 2ⁿ bits per pixel for 0 < n ≤ 4, and 12 and 24 bits.
+ * can handle 1024×768 at 60 Hz with pixel clock of 63.5 MHz
+ * 1280×800 at 59.91 Hz with pixel clock of 71 MHz
+ * 1400×1050 lcd at 50 MHz with pixel clock of 75 MHz
+ * has 256 24-bit entries in RGB palette
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "../port/error.h"
+
+#define Image IMAGE
+#include <draw.h>
+#include <memdraw.h>
+#include <cursor.h>
+#include "screen.h"
+// #include "gamma.h"
+
+enum {
+ Tabstop = 4, /* should be 8 */
+ Scroll = 8, /* lines to scroll at one time */
+ /*
+ * screen settings for Wid and Ht, should a bit more dynamic?
+ * http://www.epanorama.net/faq/vga2rgb/calc.html
+ * used to calculate settings.
+ */
+
+// Hbp = (248-1) << 20,
+// Hfp = (48-1) << 8,
+// Hsw = 112-1,
+
+// Vbp = 38 << 20,
+// Vfp = 1 << 8,
+// Vsw = 3,
+
+ Tft = 0x60,
+
+ Loadmode = 2 << 1,
+ Fifosize = 0x400,
+
+ /* dispc sysconfig */
+ Midlemode = 2 << 12,
+ Sidlemode = 2 << 3,
+ EnableWakeup = 1 << 2,
+ Autoidle = 1 << 0,
+
+ /* dispc pool_freq */
+ Ipc = 1 << 14,
+ Ihs = 1 << 13,
+ Ivs = 1 << 12,
+ Acb = 0x28,
+
+ /* gfx attribs */
+ Burstsize = 2 << 6,
+ Format = 6 << 1,
+ Gfxenable = 1 << 0,
+
+ /* dispc control */
+ Gpout1 = 1 << 16,
+ Gpout0 = 1 << 15,
+ Tftdata = 3 << 8,
+ Digital = 1 << 6,
+ Lcd = 1 << 5,
+ Stntft = 1 << 3,
+ Digitalen = 1 << 1,
+// Lcden = 1 << 0, /* unused */
+};
+
+typedef struct Dispcregs Dispc;
+typedef struct Dssregs Dss;
+typedef struct Ioregs Ioregs;
+
+struct Ioregs { /* common registers, 68 (0x44) bytes */
+ ulong rev;
+ uchar _pad0[0x10-0x4];
+ ulong sysconf;
+ ulong sysstat;
+ ulong irqstat1;
+
+ /* Dispc only regs */
+ ulong irqen1;
+ ulong wkupen;
+ ulong _pad1;
+ ulong irqsts2;
+ ulong irqen2;
+ ulong _pad2[4];
+
+ ulong ctrl;
+};
+
+struct Dssregs { /* display subsys at 0x48050000 */
+ Ioregs;
+ ulong sdicrtl;
+ ulong pllcrtl;
+ uchar _pad3[0x5c-0x4c];
+ ulong sdistat;
+};
+
+struct Dispcregs { /* display ctlr at 0x48050400 */
+ Ioregs;
+ ulong config;
+ ulong _pad3;
+ ulong defaultcolor[2];
+ ulong transcolor[2];
+ ulong linestat;
+ ulong linenum;
+ ulong timing_h;
+ ulong timing_v;
+ ulong pol_req;
+ ulong divisor;
+ ulong alpha;
+ ulong digsize;
+ ulong lcdsize;
+
+ ulong base[2]; /* should allocate both to avoid dithering */
+ ulong pos;
+ ulong size;
+ ulong _pad4[4];
+ ulong attrib;
+ ulong fifothr;
+ ulong fifosize;
+ ulong rowinc;
+ ulong pixelinc;
+ ulong winskip;
+ ulong palette; /* gfx_table_ba */
+ uchar _pad5[0x5d4 - 0x4bc];
+
+ ulong datacycle[3];
+ uchar _pad5[0x620 - 0x5e0];
+
+ ulong cprcoefr;
+ ulong cprcoefg;
+ ulong cprcoefb;
+ ulong preload;
+};
+
+int drawdebug;
+Point ZP = {0, 0};
+Cursor arrow = {
+ { -1, -1 },
+ { 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+ 0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+ 0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+ 0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+ },
+ { 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+ 0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+ 0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+ 0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+ },
+};
+
+OScreen oscreen;
+Settings settings[] = {
+[Res800x600] { 800, 600, 60, RGB16, 40000, 88, 40, 128, 23, 1, 5, },
+[Res1024x768] { 1024, 768, 60, RGB16, 65000, 160, 24, 136, 29, 3, 7, },
+[Res1280x1024] { 1280, 1024, 60, RGB16, 108000, 248, 48, 112, 38, 1, 4, },
+[Res1400x1050] { 1400, 1050, 50, RGB16, 108000, 248, 48, 112, 38, 1, 4, }, // TODO
+};
+Omap3fb *framebuf;
+Memimage *gscreen;
+
+static Memdata xgdata;
+
+static Memimage xgscreen =
+{
+ { 0, 0, Wid, Ht }, /* r */
+ { 0, 0, Wid, Ht }, /* clipr */
+ Depth, /* depth */
+ 3, /* nchan */
+ RGB16, /* chan */
+ nil, /* cmap */
+ &xgdata, /* data */
+ 0, /* zero */
+ Wid*(Depth/BI2BY)/BY2WD, /* width in words of a single scan line */
+ 0, /* layer */
+ 0, /* flags */
+};
+
+static Memimage *conscol;
+static Memimage *back;
+
+static Memsubfont *memdefont;
+
+static Lock screenlock;
+
+static Point curpos;
+static int h, w;
+static int landscape = 0; /* screen orientation, default is 0: portrait */
+static ushort *vscreen; /* virtual screen */
+static Rectangle window;
+
+static Dispc *dispc = (Dispc *)PHYSDISPC;
+static Dss *dss = (Dss *)PHYSDSS;
+
+static void omapscreenputs(char *s, int n);
+static ulong rep(ulong, int);
+static void screenputc(char *buf);
+static void screenwin(void);
+
+/*
+ * Software cursor.
+ */
+int swvisible; /* is the cursor visible? */
+int swenabled; /* is the cursor supposed to be on the screen? */
+Memimage* swback; /* screen under cursor */
+Memimage* swimg; /* cursor image */
+Memimage* swmask; /* cursor mask */
+Memimage* swimg1;
+Memimage* swmask1;
+
+Point swoffset;
+Rectangle swrect; /* screen rectangle in swback */
+Point swpt; /* desired cursor location */
+Point swvispt; /* actual cursor location */
+int swvers; /* incremented each time cursor image changes */
+int swvisvers; /* the version on the screen */
+
+static void
+lcdoff(void)
+{
+ dispc->ctrl &= ~1; /* disable the lcd */
+ coherence();
+
+ dispc->irqstat1 |= 1; /* set framedone */
+ coherence();
+
+ /* the lcd never comes ready, so don't bother with this */
+#ifdef notdef
+ /* spin until the frame is complete, but not forever */
+ for(cnt = 50; !(dispc->irqstat1 & 1) && cnt-- > 0; )
+ delay(10);
+#endif
+ delay(20); /* worst case for 1 frame, 50Hz */
+}
+
+static void
+dssstart(void)
+{
+ /* should reset the dss system */
+ dss->sysconf |= 1;
+ coherence();
+}
+
+/* see spruf98i §15.6.7.4.2 */
+static void
+configdispc(void)
+{
+ Settings *sp;
+
+ sp = oscreen.settings;
+ dss->ctrl &= 0x78; /* choose dss clock */
+ dispc->sysconf = Midlemode | Sidlemode | EnableWakeup | Autoidle;
+ dispc->config = Loadmode;
+ coherence();
+
+ /* pll */
+ dispc->defaultcolor[0] = 0; /* set background color to black? */
+ dispc->defaultcolor[1] = 0;
+ dispc->transcolor[0] = 0; /* set transparency to full */
+ dispc->transcolor[1] = 0;
+
+ dispc->timing_h = (sp->hbp-1) << 20 | (sp->hfp-1) << 8 |
+ (sp->hsw-1);
+ dispc->timing_v = sp->vbp << 20 | sp->vfp << 8 |
+ (sp->vsw-1);
+
+ dispc->pol_req = Ipc | Ihs | Ivs | Acb;
+ dispc->divisor = 1 << 16 | HOWMANY(432000, sp->pixelclock);
+
+ dispc->lcdsize = (sp->ht - 1) << 16 | (sp->wid - 1);
+ coherence();
+
+ dispc->base[0] = PADDR(framebuf->pixel);
+ dispc->base[1] = PADDR(framebuf->pixel);
+
+ dispc->pos = 0; /* place screen in the left corner */
+ /* use the whole screen */
+ dispc->size = (sp->ht - 1) << 16 | (sp->wid - 1);
+
+ /* what mode does plan 9 use for fb? */
+ dispc->attrib = Burstsize | Format | Gfxenable;
+
+ dispc->preload = Tft;
+ dispc->fifosize = Fifosize;
+ /* 1008 is max for our Burstsize */
+ dispc->fifothr = (Fifosize - 1) << 16 | (1008 - 1);
+
+ /* 1 byte is one pixel (not true, we use 2 bytes per pixel) */
+ dispc->rowinc = 1;
+ dispc->pixelinc = 1;
+ dispc->winskip = 0; /* don't skip anything */
+ coherence();
+
+ // dispc->palette = PADDR(framebuf->palette);
+}
+
+static void
+lcdon(int enable)
+{
+ dispc->ctrl = Gpout1 | Gpout0 | Tftdata | Digital | Lcd | Stntft |
+ Digitalen | enable;
+ coherence();
+ delay(10);
+}
+
+static void
+lcdstop(void)
+{
+ configscreengpio();
+ screenclockson();
+
+ lcdoff();
+}
+
+static void
+lcdinit(void)
+{
+ lcdstop();
+
+ dssstart();
+ configdispc();
+}
+
+/* Paint the image data with blue pixels */
+void
+screentest(void)
+{
+ int i;
+
+ for (i = nelem(framebuf->pixel) - 1; i >= 0; i--)
+ framebuf->pixel[i] = 0x1f; /* blue */
+// memset(framebuf->pixel, ~0, sizeof framebuf->pixel); /* white */
+}
+
+void
+screenpower(int on)
+{
+ blankscreen(on == 0);
+}
+
+/*
+ * called with drawlock locked for us, most of the time.
+ * kernel prints at inopportune times might mean we don't
+ * hold the lock, but memimagedraw is now reentrant so
+ * that should be okay: worst case we get cursor droppings.
+ */
+void
+swcursorhide(void)
+{
+ if(swvisible == 0)
+ return;
+ if(swback == nil)
+ return;
+ swvisible = 0;
+ memimagedraw(gscreen, swrect, swback, ZP, memopaque, ZP, S);
+ flushmemscreen(swrect);
+}
+
+void
+swcursoravoid(Rectangle r)
+{
+ if(swvisible && rectXrect(r, swrect))
+ swcursorhide();
+}
+
+void
+swcursordraw(void)
+{
+ if(swvisible)
+ return;
+ if(swenabled == 0)
+ return;
+ if(swback == nil || swimg1 == nil || swmask1 == nil)
+ return;
+// assert(!canqlock(&drawlock)); // assertion fails on omap
+ swvispt = swpt;
+ swvisvers = swvers;
+ swrect = rectaddpt(Rect(0,0,16,16), swvispt);
+ memimagedraw(swback, swback->r, gscreen, swpt, memopaque, ZP, S);
+ memimagedraw(gscreen, swrect, swimg1, ZP, swmask1, ZP, SoverD);
+ flushmemscreen(swrect);
+ swvisible = 1;
+}
+
+int
+cursoron(int dolock)
+{
+ if (dolock)
+ lock(&oscreen);
+ cursoroff(0);
+ swcursordraw();
+ if (dolock)
+ unlock(&oscreen);
+ return 0;
+}
+
+void
+cursoroff(int dolock)
+{
+ if (dolock)
+ lock(&oscreen);
+ swcursorhide();
+ if (dolock)
+ unlock(&oscreen);
+}
+
+void
+swload(Cursor *curs)
+{
+ uchar *ip, *mp;
+ int i, j, set, clr;
+
+ if(!swimg || !swmask || !swimg1 || !swmask1)
+ return;
+ /*
+ * Build cursor image and mask.
+ * Image is just the usual cursor image
+ * but mask is a transparent alpha mask.
+ *
+ * The 16x16x8 memimages do not have
+ * padding at the end of their scan lines.
+ */
+ ip = byteaddr(swimg, ZP);
+ mp = byteaddr(swmask, ZP);
+ for(i=0; i<32; i++){
+ set = curs->set[i];
+ clr = curs->clr[i];
+ for(j=0x80; j; j>>=1){
+ *ip++ = set&j ? 0x00 : 0xFF;
+ *mp++ = (clr|set)&j ? 0xFF : 0x00;
+ }
+ }
+ swoffset = curs->offset;
+ swvers++;
+ memimagedraw(swimg1, swimg1->r, swimg, ZP, memopaque, ZP, S);
+ memimagedraw(swmask1, swmask1->r, swmask, ZP, memopaque, ZP, S);
+}
+
+/* called from devmouse */
+void
+setcursor(Cursor* curs)
+{
+ cursoroff(1);
+ oscreen.Cursor = *curs;
+ swload(curs);
+ cursoron(1);
+}
+
+int
+swmove(Point p)
+{
+ swpt = addpt(p, swoffset);
+ return 0;
+}
+
+void
+swcursorclock(void)
+{
+ int x;
+
+ if(!swenabled)
+ return;
+ swmove(mousexy());
+ if(swvisible && eqpt(swpt, swvispt) && swvers==swvisvers)
+ return;
+
+ x = splhi();
+ if(swenabled)
+ if(!swvisible || !eqpt(swpt, swvispt) || swvers!=swvisvers)
+ if(canqlock(&drawlock)){
+ swcursorhide();
+ swcursordraw();
+ qunlock(&drawlock);
+ }
+ splx(x);
+}
+
+void
+swcursorinit(void)
+{
+ static int init;
+
+ if(!init){
+ init = 1;
+ addclock0link(swcursorclock, 10);
+ }
+ if(swback){
+ freememimage(swback);
+ freememimage(swmask);
+ freememimage(swmask1);
+ freememimage(swimg);
+ freememimage(swimg1);
+ }
+
+ swback = allocmemimage(Rect(0,0,32,32), gscreen->chan);
+ swmask = allocmemimage(Rect(0,0,16,16), GREY8);
+ swmask1 = allocmemimage(Rect(0,0,16,16), GREY1);
+ swimg = allocmemimage(Rect(0,0,16,16), GREY8);
+ swimg1 = allocmemimage(Rect(0,0,16,16), GREY1);
+ if(swback==nil || swmask==nil || swmask1==nil || swimg==nil || swimg1 == nil){
+ print("software cursor: allocmemimage fails\n");
+ return;
+ }
+
+ memfillcolor(swmask, DOpaque);
+ memfillcolor(swmask1, DOpaque);
+ memfillcolor(swimg, DBlack);
+ memfillcolor(swimg1, DBlack);
+}
+
+/* called from main and possibly later from devdss to change resolution */
+void
+screeninit(void)
+{
+ static int first = 1;
+
+ if (first) {
+ iprint("screeninit...");
+ oscreen.settings = &settings[Res1280x1024];
+
+ lcdstop();
+ if (framebuf)
+ free(framebuf);
+ /* mode is 16*32 = 512 */
+ framebuf = xspanalloc(sizeof *framebuf, 16*32, 0);
+ }
+
+ lcdinit();
+ lcdon(1);
+ if (first) {
+ memimageinit();
+ memdefont = getmemdefont();
+ screentest();
+ }
+
+ xgdata.ref = 1;
+ xgdata.bdata = (uchar *)framebuf->pixel;
+
+ gscreen = &xgscreen;
+ gscreen->r = Rect(0, 0, Wid, Ht);
+ gscreen->clipr = gscreen->r;
+ /* width, in words, of a single scan line */
+ gscreen->width = Wid * (Depth / BI2BY) / BY2WD;
+ flushmemscreen(gscreen->r);
+
+ blanktime = 3; /* minutes */
+
+ if (first) {
+ iprint("on: blue for 3 seconds...");
+ delay(3*1000);
+ iprint("\n");
+
+ screenwin(); /* draw border & top orange bar */
+ screenputs = omapscreenputs;
+ iprint("screen: frame buffer at %#p for %dx%d\n",
+ framebuf, oscreen.settings->wid, oscreen.settings->ht);
+
+ swenabled = 1;
+ swcursorinit(); /* needs gscreen set */
+ setcursor(&arrow);
+
+ first = 0;
+ }
+}
+
+/* flushmemscreen should change buffer? */
+void
+flushmemscreen(Rectangle r)
+{
+ ulong start, end;
+
+ if (r.min.x < 0)
+ r.min.x = 0;
+ if (r.max.x > Wid)
+ r.max.x = Wid;
+ if (r.min.y < 0)
+ r.min.y = 0;
+ if (r.max.y > Ht)
+ r.max.y = Ht;
+ if (rectclip(&r, gscreen->r) == 0)
+ return;
+ start = (ulong)&framebuf->pixel[r.min.y*Wid + r.min.x];
+ end = (ulong)&framebuf->pixel[(r.max.y - 1)*Wid + r.max.x -1];
+ cachedwbse((ulong *)start, end - start);
+}
+
+/*
+ * export screen to devdraw
+ */
+uchar*
+attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
+{
+ *r = gscreen->r;
+ *d = gscreen->depth;
+ *chan = gscreen->chan;
+ *width = gscreen->width;
+ *softscreen = (landscape == 0);
+ return (uchar *)gscreen->data->bdata;
+}
+
+void
+getcolor(ulong p, ulong *pr, ulong *pg, ulong *pb)
+{
+ USED(p, pr, pg, pb);
+}
+
+int
+setcolor(ulong p, ulong r, ulong g, ulong b)
+{
+ USED(p, r, g, b);
+ return 0;
+}
+
+void
+blankscreen(int blank)
+{
+ if (blank)
+ lcdon(0);
+ else {
+ lcdinit();
+ lcdon(1);
+ }
+}
+
+static void
+omapscreenputs(char *s, int n)
+{
+ int i;
+ Rune r;
+ char buf[4];
+
+ if (!islo()) {
+ /* don't deadlock trying to print in interrupt */
+ if (!canlock(&screenlock))
+ return; /* discard s */
+ } else
+ lock(&screenlock);
+
+ while (n > 0) {
+ i = chartorune(&r, s);
+ if (i == 0) {
+ s++;
+ --n;
+ continue;
+ }
+ memmove(buf, s, i);
+ buf[i] = 0;
+ n -= i;
+ s += i;
+ screenputc(buf);
+ }
+ unlock(&screenlock);
+}
+
+static void
+screenwin(void)
+{
+ char *greet;
+ Memimage *orange;
+ Point p, q;
+ Rectangle r;
+
+ memsetchan(gscreen, RGB16);
+
+ back = memwhite;
+ conscol = memblack;
+
+ orange = allocmemimage(Rect(0, 0, 1, 1), RGB16);
+ orange->flags |= Frepl;
+ orange->clipr = gscreen->r;
+ orange->data->bdata[0] = 0x40; /* magic: colour? */
+ orange->data->bdata[1] = 0xfd; /* magic: colour? */
+
+ w = memdefont->info[' '].width;
+ h = memdefont->height;
+
+ r = insetrect(gscreen->r, 4);
+
+ memimagedraw(gscreen, r, memblack, ZP, memopaque, ZP, S);
+ window = insetrect(r, 4);
+ memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);
+
+ memimagedraw(gscreen, Rect(window.min.x, window.min.y,
+ window.max.x, window.min.y + h + 5 + 6), orange, ZP, nil, ZP, S);
+ freememimage(orange);
+ window = insetrect(window, 5);
+
+ greet = " Plan 9 Console ";
+ p = addpt(window.min, Pt(10, 0));
+ q = memsubfontwidth(memdefont, greet);
+ memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
+ flushmemscreen(r);
+ window.min.y += h + 6;
+ curpos = window.min;
+ window.max.y = window.min.y + ((window.max.y - window.min.y) / h) * h;
+}
+
+static void
+scroll(void)
+{
+ int o;
+ Point p;
+ Rectangle r;
+
+ /* move window contents up Scroll text lines */
+ o = Scroll * h;
+ r = Rpt(window.min, Pt(window.max.x, window.max.y - o));
+ p = Pt(window.min.x, window.min.y + o);
+ memimagedraw(gscreen, r, gscreen, p, nil, p, S);
+ flushmemscreen(r);
+
+ /* clear the bottom Scroll text lines */
+ r = Rpt(Pt(window.min.x, window.max.y - o), window.max);
+ memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
+ flushmemscreen(r);
+
+ curpos.y -= o;
+}
+
+static void
+screenputc(char *buf)
+{
+ int w;
+ uint pos;
+ Point p;
+ Rectangle r;
+ static int *xp;
+ static int xbuf[256];
+
+ if (xp < xbuf || xp >= &xbuf[sizeof(xbuf)])
+ xp = xbuf;
+
+ switch (buf[0]) {
+ case '\n':
+ if (curpos.y + h >= window.max.y)
+ scroll();
+ curpos.y += h;
+ screenputc("\r");
+ break;
+ case '\r':
+ xp = xbuf;
+ curpos.x = window.min.x;
+ break;
+ case '\t':
+ p = memsubfontwidth(memdefont, " ");
+ w = p.x;
+ if (curpos.x >= window.max.x - Tabstop * w)
+ screenputc("\n");
+
+ pos = (curpos.x - window.min.x) / w;
+ pos = Tabstop - pos % Tabstop;
+ *xp++ = curpos.x;
+ r = Rect(curpos.x, curpos.y, curpos.x + pos * w, curpos.y + h);
+ memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+ flushmemscreen(r);
+ curpos.x += pos * w;
+ break;
+ case '\b':
+ if (xp <= xbuf)
+ break;
+ xp--;
+ r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
+ memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+ flushmemscreen(r);
+ curpos.x = *xp;
+ break;
+ case '\0':
+ break;
+ default:
+ p = memsubfontwidth(memdefont, buf);
+ w = p.x;
+
+ if (curpos.x >= window.max.x - w)
+ screenputc("\n");
+
+ *xp++ = curpos.x;
+ r = Rect(curpos.x, curpos.y, curpos.x + w, curpos.y + h);
+ memimagedraw(gscreen, r, back, back->r.min, nil, back->r.min, S);
+ memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
+ flushmemscreen(r);
+ curpos.x += w;
+ }
+}