https://github.com/akkartik/mu/blob/main/500fake-screen.mu
  1 # Testable primitives for writing to screen.
  2 #
  3 # Mu mostly uses the screen for text, but it builds it out of pixel graphics
  4 # and a bitmap font. There is no support for a blinking cursor, scrolling and
  5 # so on.
  6 #
  7 # Fake screens are primarily for testing text-mode prints. However, they do
  8 # support some rudimentary pixel operations as well. Caveats:
  9 #
 10 # - Drawing pixels atop text or vice versa is not supported. Results in a fake
 11 #   screen will not mimic real screens in these situations.
 12 # - Fake screens currently also assume a fixed-width 8x16 font.
 13 
 14 type screen {
 15   # text mode
 16   width: int
 17   height: int
 18   data: (handle array screen-cell)
 19   cursor-x: int  # [0..width)
 20   cursor-y: int  # [0..height)
 21   # pixel graphics
 22   pixels: (handle array byte)
 23 }
 24 
 25 type screen-cell {
 26   data: grapheme
 27   color: int
 28   background-color: int
 29 }
 30 
 31 fn initialize-screen _screen: (addr screen), width: int, height: int, pixel-graphics?: boolean {
 32   var screen/esi: (addr screen) <- copy _screen
 33   var tmp/eax: int <- copy 0
 34   var dest/edi: (addr int) <- copy 0
 35   # screen->width = width
 36   dest <- get screen, width
 37   tmp <- copy width
 38   copy-to *dest, tmp
 39   # screen->height = height
 40   dest <- get screen, height
 41   tmp <- copy height
 42   copy-to *dest, tmp
 43   # populate screen->data
 44   {
 45     var data-ah/edi: (addr handle array screen-cell) <- get screen, data
 46     var capacity/eax: int <- copy width
 47     capacity <- multiply height
 48     populate data-ah, capacity
 49   }
 50   # if necessary, populate screen->pixels
 51   {
 52     compare pixel-graphics?, 0/false
 53     break-if-=
 54     var pixels-ah/edi: (addr handle array byte) <- get screen, pixels
 55     var capacity/eax: int <- copy width
 56     capacity <- shift-left 3/log2-font-width
 57     capacity <- multiply height
 58     capacity <- shift-left 4/log2-font-height
 59     populate pixels-ah, capacity
 60   }
 61   # screen->cursor-x = 0
 62   dest <- get screen, cursor-x
 63   copy-to *dest, 0
 64   # screen->cursor-y = 0
 65   dest <- get screen, cursor-y
 66   copy-to *dest, 0
 67 }
 68 
 69 # in graphemes
 70 fn screen-size _screen: (addr screen) -> _/eax: int, _/ecx: int {
 71   var screen/esi: (addr screen) <- copy _screen
 72   var width/eax: int <- copy 0
 73   var height/ecx: int <- copy 0
 74   compare screen, 0
 75   {
 76     break-if-!=
 77     return 0x80/128, 0x30/48
 78   }
 79   # fake screen
 80   var tmp/edx: (addr int) <- get screen, width
 81   width <- copy *tmp
 82   tmp <- get screen, height
 83   height <- copy *tmp
 84   return width, height
 85 }
 86 
 87 # testable screen primitive
 88 fn draw-grapheme _screen: (addr screen), g: grapheme, x: int, y: int, color: int, background-color: int {
 89   var screen/esi: (addr screen) <- copy _screen
 90   {
 91     compare screen, 0
 92     break-if-!=
 93     draw-grapheme-on-real-screen g, x, y, color, background-color
 94     return
 95   }
 96   # fake screen
 97   var idx/ecx: int <- screen-cell-index screen, x, y
 98   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
 99   var data/eax: (addr array screen-cell) <- lookup *data-ah
100   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
101   var dest-cell/ecx: (addr screen-cell) <- index data, offset
102   var dest-grapheme/eax: (addr grapheme) <- get dest-cell, data
103   var g2/edx: grapheme <- copy g
104   copy-to *dest-grapheme, g2
105   var dest-color/eax: (addr int) <- get dest-cell, color
106   var src-color/edx: int <- copy color
107   copy-to *dest-color, src-color
108   dest-color <- get dest-cell, background-color
109   src-color <- copy background-color
110   copy-to *dest-color, src-color
111 }
112 
113 # we can't really render non-ASCII yet, but when we do we'll be ready
114 fn draw-code-point screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
115   var g/eax: grapheme <- copy c
116   draw-grapheme screen, g, x, y, color, background-color
117 }
118 
119 # not really needed for a real screen, though it shouldn't do any harm
120 fn screen-cell-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
121   var screen/esi: (addr screen) <- copy _screen
122   {
123     compare x, 0
124     break-if->=
125     abort "screen-cell-index: negative x"
126   }
127   {
128     var xmax/eax: (addr int) <- get screen, width
129     var xcurr/ecx: int <- copy x
130     compare xcurr, *xmax
131     break-if-<
132     abort "screen-cell-index: x too high"
133   }
134   {
135     compare y, 0
136     break-if->=
137     abort "screen-cell-index: negative y"
138   }
139   {
140     var ymax/eax: (addr int) <- get screen, height
141     var ycurr/ecx: int <- copy y
142     compare ycurr, *ymax
143     break-if-<
144     abort "screen-cell-index: y too high"
145   }
146   var width-addr/eax: (addr int) <- get screen, width
147   var result/ecx: int <- copy y
148   result <- multiply *width-addr
149   result <- add x
150   return result
151 }
152 
153 fn cursor-position _screen: (addr screen) -> _/eax: int, _/ecx: int {
154   var screen/esi: (addr screen) <- copy _screen
155   {
156     compare screen, 0
157     break-if-!=
158     var x/eax: int <- copy 0
159     var y/ecx: int <- copy 0
160     x, y <- cursor-position-on-real-screen
161     return x, y
162   }
163   # fake screen
164   var cursor-x-addr/eax: (addr int) <- get screen, cursor-x
165   var cursor-y-addr/ecx: (addr int) <- get screen, cursor-y
166   return *cursor-x-addr, *cursor-y-addr
167 }
168 
169 fn set-cursor-position _screen: (addr screen), x: int, y: int {
170   var screen/esi: (addr screen) <- copy _screen
171   {
172     compare screen, 0
173     break-if-!=
174     set-cursor-position-on-real-screen x, y
175     return
176   }
177   # fake screen
178   # ignore x < 0
179   {
180     compare x, 0
181     break-if->=
182     return
183   }
184   # ignore x >= width
185   {
186     var width-addr/eax: (addr int) <- get screen, width
187     var width/eax: int <- copy *width-addr
188     compare x, width
189     break-if-<=
190     return
191   }
192   # ignore y < 0
193   {
194     compare y, 0
195     break-if->=
196     return
197   }
198   # ignore y >= height
199   {
200     var height-addr/eax: (addr int) <- get screen, height
201     var height/eax: int <- copy *height-addr
202     compare y, height
203     break-if-<
204     return
205   }
206   # screen->cursor-x = x
207   var dest/edi: (addr int) <- get screen, cursor-x
208   var src/eax: int <- copy x
209   copy-to *dest, src
210   # screen->cursor-y = y
211   dest <- get screen, cursor-y
212   src <- copy y
213   copy-to *dest, src
214 }
215 
216 fn draw-cursor screen: (addr screen), g: grapheme {
217   {
218     compare screen, 0
219     break-if-!=
220     draw-cursor-on-real-screen g
221     return
222   }
223   # fake screen
224   var cursor-x/eax: int <- copy 0
225   var cursor-y/ecx: int <- copy 0
226   cursor-x, cursor-y <- cursor-position screen
227   draw-grapheme screen, g, cursor-x, cursor-y, 0/fg, 7/bg
228 }
229 
230 fn clear-screen _screen: (addr screen) {
231   var screen/esi: (addr screen) <- copy _screen
232   {
233     compare screen, 0
234     break-if-!=
235     clear-real-screen
236     return
237   }
238   # fake screen
239   set-cursor-position screen, 0, 0
240   var y/eax: int <- copy 0
241   var height/ecx: (addr int) <- get screen, height
242   {
243     compare y, *height
244     break-if->=
245     var x/edx: int <- copy 0
246     var width/ebx: (addr int) <- get screen, width
247     {
248       compare x, *width
249       break-if->=
250       draw-code-point screen, 0x20/space, x, y, 0/fg=black, 0/bg=black
251       x <- increment
252       loop
253     }
254     y <- increment
255     loop
256   }
257   set-cursor-position screen, 0, 0
258   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
259   var pixels/eax: (addr array byte) <- lookup *pixels-ah
260   var i/ecx: int <- copy 0
261   var max/edx: int <- length pixels
262   {
263     compare i, max
264     break-if->=
265     var curr/eax: (addr byte) <- index pixels, i
266     var zero/ebx: byte <- copy 0
267     copy-byte-to *curr, zero
268     i <- increment
269     loop
270   }
271 }
272 
273 fn fake-screen-empty? _screen: (addr screen) -> _/eax: boolean {
274   var screen/esi: (addr screen) <- copy _screen
275   var y/eax: int <- copy 0
276   var height/ecx: (addr int) <- get screen, height
277   {
278     compare y, *height
279     break-if->=
280     var x/edx: int <- copy 0
281     var width/ebx: (addr int) <- get screen, width
282     {
283       compare x, *width
284       break-if->=
285       var g/eax: grapheme <- screen-grapheme-at screen, x, y
286       {
287         compare g, 0
288         break-if-=
289         compare g, 0x20/space
290         break-if-=
291         return 0/false
292       }
293       x <- increment
294       loop
295     }
296     y <- increment
297     loop
298   }
299   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
300   var pixels/eax: (addr array byte) <- lookup *pixels-ah
301   var y/ebx: int <- copy 0
302   var height-addr/edx: (addr int) <- get screen, height
303   var height/edx: int <- copy *height-addr
304   height <- shift-left 4/log2-font-height
305   {
306     compare y, height
307     break-if->=
308     var width-addr/edx: (addr int) <- get screen, width
309     var width/edx: int <- copy *width-addr
310     width <- shift-left 3/log2-font-width
311     var x/edi: int <- copy 0
312     {
313       compare x, width
314       break-if->=
315       var idx/ecx: int <- pixel-index screen, x, y
316       var color-addr/ecx: (addr byte) <- index pixels, idx
317       var color/ecx: byte <- copy-byte *color-addr
318       compare color, 0
319       {
320         break-if-=
321         return 0/false
322       }
323       x <- increment
324       loop
325     }
326     y <- increment
327     loop
328   }
329   return 1/true
330 }
331 
332 fn clear-rect _screen: (addr screen), xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
333   var screen/esi: (addr screen) <- copy _screen
334   {
335     compare screen, 0
336     break-if-!=
337     clear-rect-on-real-screen xmin, ymin, xmax, ymax, background-color
338     return
339   }
340   # fake screen
341   set-cursor-position screen, 0, 0
342   var y/eax: int <- copy ymin
343   var ymax/ecx: int <- copy ymax
344   {
345     compare y, ymax
346     break-if->=
347     var x/edx: int <- copy xmin
348     var xmax/ebx: int <- copy xmax
349     {
350       compare x, xmax
351       break-if->=
352       draw-code-point screen, 0x20/space, x, y, 0/fg, background-color
353       x <- increment
354       loop
355     }
356     y <- increment
357     loop
358   }
359   set-cursor-position screen, 0, 0
360 }
361 
362 # there's no grapheme that guarantees to cover every pixel, so we'll bump down
363 # to pixels for a real screen
364 fn clear-real-screen {
365   var y/eax: int <- copy 0
366   {
367     compare y, 0x300/screen-height=768
368     break-if->=
369     var x/edx: int <- copy 0
370     {
371       compare x, 0x400/screen-width=1024
372       break-if->=
373       pixel-on-real-screen x, y, 0/color=black
374       x <- increment
375       loop
376     }
377     y <- increment
378     loop
379   }
380 }
381 
382 fn clear-rect-on-real-screen xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
383   var y/eax: int <- copy ymin
384   y <- shift-left 4/log2-font-height
385   var ymax/ecx: int <- copy ymax
386   ymax <- shift-left 4/log2-font-height
387   {
388     compare y, ymax
389     break-if->=
390     var x/edx: int <- copy xmin
391     x <- shift-left 3/log2-font-width
392     var xmax/ebx: int <- copy xmax
393     xmax <- shift-left 3/log2-font-width
394     {
395       compare x, xmax
396       break-if->=
397       pixel-on-real-screen x, y, background-color
398       x <- increment
399       loop
400     }
401     y <- increment
402     loop
403   }
404 }
405 
406 fn screen-grapheme-at _screen: (addr screen), x: int, y: int -> _/eax: grapheme {
407   var screen/esi: (addr screen) <- copy _screen
408   var idx/ecx: int <- screen-cell-index screen, x, y
409   var result/eax: grapheme <- screen-grapheme-at-idx screen, idx
410   return result
411 }
412 
413 fn screen-grapheme-at-idx _screen: (addr screen), idx-on-stack: int -> _/eax: grapheme {
414   var screen/esi: (addr screen) <- copy _screen
415   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
416   var data/eax: (addr array screen-cell) <- lookup *data-ah
417   var idx/ecx: int <- copy idx-on-stack
418   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
419   var cell/eax: (addr screen-cell) <- index data, offset
420   var src/eax: (addr grapheme) <- get cell, data
421   return *src
422 }
423 
424 fn screen-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
425   var screen/esi: (addr screen) <- copy _screen
426   var idx/ecx: int <- screen-cell-index screen, x, y
427   var result/eax: int <- screen-color-at-idx screen, idx
428   return result
429 }
430 
431 fn screen-color-at-idx _screen: (addr screen), idx-on-stack: int -> _/eax: int {
432   var screen/esi: (addr screen) <- copy _screen
433   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
434   var data/eax: (addr array screen-cell) <- lookup *data-ah
435   var idx/ecx: int <- copy idx-on-stack
436   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
437   var cell/eax: (addr screen-cell) <- index data, offset
438   var src/eax: (addr int) <- get cell, color
439   var result/eax: int <- copy *src
440   return result
441 }
442 
443 fn screen-background-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
444   var screen/esi: (addr screen) <- copy _screen
445   var idx/ecx: int <- screen-cell-index screen, x, y
446   var result/eax: int <- screen-background-color-at-idx screen, idx
447   return result
448 }
449 
450 fn screen-background-color-at-idx _screen: (addr screen), idx-on-stack: int -> _/eax: int {
451   var screen/esi: (addr screen) <- copy _screen
452   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
453   var data/eax: (addr array screen-cell) <- lookup *data-ah
454   var idx/ecx: int <- copy idx-on-stack
455   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
456   var cell/eax: (addr screen-cell) <- index data, offset
457   var src/eax: (addr int) <- get cell, background-color
458   var result/eax: int <- copy *src
459   return result
460 }
461 
462 fn pixel screen: (addr screen), x: int, y: int, color: int {
463   {
464     compare screen, 0
465     break-if-!=
466     pixel-on-real-screen x, y, color
467     return
468   }
469   # fake screen
470   var screen/esi: (addr screen) <- copy screen
471   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
472   var pixels/eax: (addr array byte) <- lookup *pixels-ah
473   {
474     compare pixels, 0
475     break-if-!=
476     abort "pixel graphics not enabled for this screen"
477   }
478   var idx/ecx: int <- pixel-index screen, x, y
479   var dest/ecx: (addr byte) <- index pixels, idx
480   var src/eax: byte <- copy-byte color
481   copy-byte-to *dest, src
482 }
483 
484 fn pixel-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
485   var screen/esi: (addr screen) <- copy _screen
486   {
487     compare x, 0
488     break-if->=
489     abort "screen-cell-index: negative x"
490   }
491   {
492     var xmax-a/eax: (addr int) <- get screen, width
493     var xmax/eax: int <- copy *xmax-a
494     xmax <- shift-left 3/log2-font-width
495     compare x, xmax
496     break-if-<
497     abort "screen-cell-index: x too high"
498   }
499   {
500     compare y, 0
501     break-if->=
502     abort "screen-cell-index: negative y"
503   }
504   {
505     var ymax-a/eax: (addr int) <- get screen, height
506     var ymax/eax: int <- copy *ymax-a
507     ymax <- shift-left 4/log2-font-height
508     compare y, ymax
509     break-if-<
510     abort "screen-cell-index: y too high"
511   }
512   var width-addr/eax: (addr int) <- get screen, width
513   var result/ecx: int <- copy y
514   result <- multiply *width-addr
515   result <- shift-left 3/log2-font-width
516   result <- add x
517   return result
518 }
519 
520 # double-buffering primitive
521 # 'screen' must be a fake screen. 'target-screen' is usually real.
522 # Both screens must have the same size.
523 fn copy-pixels _screen: (addr screen), target-screen: (addr screen) {
524   var screen/esi: (addr screen) <- copy _screen
525   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
526   var _pixels/eax: (addr array byte) <- lookup *pixels-ah
527   var pixels/edi: (addr array byte) <- copy _pixels
528   var width-a/edx: (addr int) <- get screen, width
529   var width/edx: int <- copy *width-a
530   width <- shift-left 3/log2-font-width
531   var height-a/ebx: (addr int) <- get screen, height
532   var height/ebx: int <- copy *height-a
533   height <- shift-left 4/log2-font-height
534   var i/esi: int <- copy 0
535   var y/ecx: int <- copy 0
536   {
537     # screen top left pixels x y width height
538     compare y, height
539     break-if->=
540     var x/eax: int <- copy 0
541     {
542       compare x, width
543       break-if->=
544       {
545         var color-addr/ebx: (addr byte) <- index pixels, i
546         var color/ebx: byte <- copy-byte *color-addr
547         var color2/ebx: int <- copy color
548         pixel target-screen, x, y, color2
549       }
550       x <- increment
551       i <- increment
552       loop
553     }
554     y <- increment
555     loop
556   }
557 }