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: code-point
 27   color: int
 28   background-color: int
 29   unused?: boolean
 30 }
 31 
 32 fn initialize-screen _screen: (addr screen), width: int, height: int, pixel-graphics?: boolean {
 33   var screen/esi: (addr screen) <- copy _screen
 34   var tmp/eax: int <- copy 0
 35   var dest/edi: (addr int) <- copy 0
 36   # screen->width = width
 37   dest <- get screen, width
 38   tmp <- copy width
 39   copy-to *dest, tmp
 40   # screen->height = height
 41   dest <- get screen, height
 42   tmp <- copy height
 43   copy-to *dest, tmp
 44   # populate screen->data
 45   {
 46     var data-ah/edi: (addr handle array screen-cell) <- get screen, data
 47     var capacity/eax: int <- copy width
 48     capacity <- multiply height
 49     #
 50     populate data-ah, capacity
 51   }
 52   # if necessary, populate screen->pixels
 53   {
 54     compare pixel-graphics?, 0/false
 55     break-if-=
 56     var pixels-ah/edi: (addr handle array byte) <- get screen, pixels
 57     var capacity/eax: int <- copy width
 58     capacity <- shift-left 3/log2-font-width
 59     capacity <- multiply height
 60     capacity <- shift-left 4/log2-font-height
 61     #
 62     populate pixels-ah, capacity
 63   }
 64   # screen->cursor-x = 0
 65   dest <- get screen, cursor-x
 66   copy-to *dest, 0
 67   # screen->cursor-y = 0
 68   dest <- get screen, cursor-y
 69   copy-to *dest, 0
 70 }
 71 
 72 # in graphemes
 73 fn screen-size _screen: (addr screen) -> _/eax: int, _/ecx: int {
 74   var screen/esi: (addr screen) <- copy _screen
 75   var width/eax: int <- copy 0
 76   var height/ecx: int <- copy 0
 77   compare screen, 0
 78   {
 79     break-if-!=
 80     return 0x80/128, 0x30/48
 81   }
 82   # fake screen
 83   var tmp/edx: (addr int) <- get screen, width
 84   width <- copy *tmp
 85   tmp <- get screen, height
 86   height <- copy *tmp
 87   return width, height
 88 }
 89 
 90 # testable screen primitive
 91 # return number of 8x16 units drawn
 92 fn draw-code-point _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int -> _/eax: int {
 93   var screen/esi: (addr screen) <- copy _screen
 94   {
 95     compare screen, 0
 96     break-if-!=
 97     var result/eax: int <- draw-code-point-on-real-screen c, x, y, color, background-color
 98     return result
 99   }
100   # fake screen
101   var wide?/eax: boolean <- wide-code-point? c
102   compare wide?, 0/false
103   {
104     break-if-=
105     draw-wide-code-point-on-fake-screen screen, c, x, y, color, background-color
106     return 2
107   }
108   draw-narrow-code-point-on-fake-screen screen, c, x, y, color, background-color
109   return 1
110 }
111 
112 fn draw-narrow-code-point-on-fake-screen _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
113   var screen/esi: (addr screen) <- copy _screen
114   # ignore if out of bounds
115   {
116     compare x, 0
117     break-if->=
118     return
119   }
120   {
121     var xmax-addr/eax: (addr int) <- get screen, width
122     var xmax/eax: int <- copy *xmax-addr
123     compare x, xmax
124     break-if-<
125     {
126       loop
127     }
128     return
129   }
130   {
131     compare y, 0
132     break-if->=
133     return
134   }
135   {
136     var ymax-addr/eax: (addr int) <- get screen, height
137     var ymax/eax: int <- copy *ymax-addr
138     compare y, ymax
139     break-if-<
140     return
141   }
142   #
143   var index/ecx: int <- screen-cell-index screen, x, y
144   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
145   var data/eax: (addr array screen-cell) <- lookup *data-ah
146   var offset/ecx: (offset screen-cell) <- compute-offset data, index
147   var dest-cell/ecx: (addr screen-cell) <- index data, offset
148   var dest-code-point/eax: (addr code-point) <- get dest-cell, data
149   var c2/edx: code-point <- copy c
150   copy-to *dest-code-point, c2
151   var dest-color/eax: (addr int) <- get dest-cell, color
152   var src-color/edx: int <- copy color
153   copy-to *dest-color, src-color
154   dest-color <- get dest-cell, background-color
155   src-color <- copy background-color
156   copy-to *dest-color, src-color
157   var dest/eax: (addr boolean) <- get dest-cell, unused?
158   copy-to *dest, 0/false
159 }
160 
161 fn draw-wide-code-point-on-fake-screen _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
162   var screen/esi: (addr screen) <- copy _screen
163   # ignore if out of bounds
164   {
165     compare x, 0
166     break-if->=
167     return
168   }
169   {
170     var xmax-addr/eax: (addr int) <- get screen, width
171     var xmax/eax: int <- copy *xmax-addr
172     xmax <- decrement  # wide code-points need an extra unit
173     compare x, xmax
174     break-if-<
175     return
176   }
177   {
178     compare y, 0
179     break-if->=
180     return
181   }
182   {
183     var ymax-addr/eax: (addr int) <- get screen, height
184     var ymax/eax: int <- copy *ymax-addr
185     compare y, ymax
186     break-if-<
187     return
188   }
189   #
190   var index/ecx: int <- screen-cell-index screen, x, y
191   {
192     var data-ah/eax: (addr handle array screen-cell) <- get screen, data
193     var data/eax: (addr array screen-cell) <- lookup *data-ah
194     var offset/ecx: (offset screen-cell) <- compute-offset data, index
195     var dest-cell/ecx: (addr screen-cell) <- index data, offset
196     var dest-code-point/eax: (addr code-point) <- get dest-cell, data
197     var c2/edx: code-point <- copy c
198     copy-to *dest-code-point, c2
199     var dest-color/eax: (addr int) <- get dest-cell, color
200     var src-color/edx: int <- copy color
201     copy-to *dest-color, src-color
202     dest-color <- get dest-cell, background-color
203     src-color <- copy background-color
204     copy-to *dest-color, src-color
205     var dest/eax: (addr boolean) <- get dest-cell, unused?
206     copy-to *dest, 0/false
207   }
208   # set next screen-cell to unused
209   index <- increment
210   {
211     var data-ah/eax: (addr handle array screen-cell) <- get screen, data
212     var data/eax: (addr array screen-cell) <- lookup *data-ah
213     var offset/ecx: (offset screen-cell) <- compute-offset data, index
214     var dest-cell/ecx: (addr screen-cell) <- index data, offset
215     var dest/eax: (addr boolean) <- get dest-cell, unused?
216     copy-to *dest, 1/true
217   }
218 }
219 
220 # fake screens only
221 fn screen-cell-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
222   var screen/esi: (addr screen) <- copy _screen
223   var width-addr/eax: (addr int) <- get screen, width
224   var result/ecx: int <- copy y
225   result <- multiply *width-addr
226   result <- add x
227   return result
228 }
229 
230 fn cursor-position _screen: (addr screen) -> _/eax: int, _/ecx: int {
231   var screen/esi: (addr screen) <- copy _screen
232   {
233     compare screen, 0
234     break-if-!=
235     var x/eax: int <- copy 0
236     var y/ecx: int <- copy 0
237     x, y <- cursor-position-on-real-screen
238     return x, y
239   }
240   # fake screen
241   var cursor-x-addr/eax: (addr int) <- get screen, cursor-x
242   var cursor-y-addr/ecx: (addr int) <- get screen, cursor-y
243   return *cursor-x-addr, *cursor-y-addr
244 }
245 
246 fn set-cursor-position _screen: (addr screen), x: int, y: int {
247   var screen/esi: (addr screen) <- copy _screen
248   {
249     compare screen, 0
250     break-if-!=
251     set-cursor-position-on-real-screen x, y
252     return
253   }
254   # fake screen
255   # ignore x < 0
256   {
257     compare x, 0
258     break-if->=
259     return
260   }
261   # ignore x >= width
262   {
263     var width-addr/eax: (addr int) <- get screen, width
264     var width/eax: int <- copy *width-addr
265     compare x, width
266     break-if-<=
267     return
268   }
269   # ignore y < 0
270   {
271     compare y, 0
272     break-if->=
273     return
274   }
275   # ignore y >= height
276   {
277     var height-addr/eax: (addr int) <- get screen, height
278     var height/eax: int <- copy *height-addr
279     compare y, height
280     break-if-<
281     return
282   }
283   # screen->cursor-x = x
284   var dest/edi: (addr int) <- get screen, cursor-x
285   var src/eax: int <- copy x
286   copy-to *dest, src
287   # screen->cursor-y = y
288   dest <- get screen, cursor-y
289   src <- copy y
290   copy-to *dest, src
291 }
292 
293 fn draw-cursor screen: (addr screen), c: code-point {
294   {
295     compare screen, 0
296     break-if-!=
297     draw-cursor-on-real-screen c
298     return
299   }
300   # fake screen
301   var cursor-x/eax: int <- copy 0
302   var cursor-y/ecx: int <- copy 0
303   cursor-x, cursor-y <- cursor-position screen
304   var dummy/eax: int <- draw-code-point screen, c, cursor-x, cursor-y, 0/fg, 7/bg
305 }
306 
307 fn clear-screen _screen: (addr screen) {
308   var screen/esi: (addr screen) <- copy _screen
309   {
310     compare screen, 0
311     break-if-!=
312     clear-real-screen
313     return
314   }
315   # fake screen
316   set-cursor-position screen, 0, 0
317   var y/eax: int <- copy 0
318   var height/ecx: (addr int) <- get screen, height
319   {
320     compare y, *height
321     break-if->=
322     var x/edx: int <- copy 0
323     var width/ebx: (addr int) <- get screen, width
324     {
325       compare x, *width
326       break-if->=
327       var dummy/eax: int <- draw-code-point screen, 0/nul, x, y, 0/fg=black, 0/bg=black
328       x <- increment
329       loop
330     }
331     y <- increment
332     loop
333   }
334   set-cursor-position screen, 0, 0
335   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
336   var pixels/eax: (addr array byte) <- lookup *pixels-ah
337   var i/ecx: int <- copy 0
338   var max/edx: int <- length pixels
339   {
340     compare i, max
341     break-if->=
342     var curr/eax: (addr byte) <- index pixels, i
343     var zero/ebx: byte <- copy 0
344     copy-byte-to *curr, zero
345     i <- increment
346     loop
347   }
348 }
349 
350 fn fake-screen-empty? _screen: (addr screen) -> _/eax: boolean {
351   var screen/esi: (addr screen) <- copy _screen
352   var y/eax: int <- copy 0
353   var height/ecx: (addr int) <- get screen, height
354   {
355     compare y, *height
356     break-if->=
357     var x/edx: int <- copy 0
358     var width/ebx: (addr int) <- get screen, width
359     {
360       compare x, *width
361       break-if->=
362       var c/eax: code-point <- screen-code-point-at screen, x, y
363       {
364         compare c, 0
365         break-if-=
366         compare c, 0x20/space
367         break-if-=
368         return 0/false
369       }
370       x <- increment
371       loop
372     }
373     y <- increment
374     loop
375   }
376   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
377   var pixels/eax: (addr array byte) <- lookup *pixels-ah
378   var y/ebx: int <- copy 0
379   var height-addr/edx: (addr int) <- get screen, height
380   var height/edx: int <- copy *height-addr
381   height <- shift-left 4/log2-font-height
382   {
383     compare y, height
384     break-if->=
385     var width-addr/edx: (addr int) <- get screen, width
386     var width/edx: int <- copy *width-addr
387     width <- shift-left 3/log2-font-width
388     var x/edi: int <- copy 0
389     {
390       compare x, width
391       break-if->=
392       var index/ecx: int <- pixel-index screen, x, y
393       var color-addr/ecx: (addr byte) <- index pixels, index
394       var color/ecx: byte <- copy-byte *color-addr
395       compare color, 0
396       {
397         break-if-=
398         return 0/false
399       }
400       x <- increment
401       loop
402     }
403     y <- increment
404     loop
405   }
406   return 1/true
407 }
408 
409 fn clear-rect _screen: (addr screen), xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
410   var screen/esi: (addr screen) <- copy _screen
411   {
412     compare screen, 0
413     break-if-!=
414     clear-rect-on-real-screen xmin, ymin, xmax, ymax, background-color
415     return
416   }
417   # fake screen
418   set-cursor-position screen, 0, 0
419   var y/eax: int <- copy ymin
420   var ymax/ecx: int <- copy ymax
421   {
422     compare y, ymax
423     break-if->=
424     var x/edx: int <- copy xmin
425     var xmax/ebx: int <- copy xmax
426     {
427       compare x, xmax
428       break-if->=
429       var dummy/eax: int <- draw-code-point screen, 0x20/space, x, y, 0/fg, background-color
430       x <- increment
431       loop
432     }
433     y <- increment
434     loop
435   }
436   set-cursor-position screen, 0, 0
437 }
438 
439 # there's no grapheme that guarantees to cover every pixel, so we'll bump down
440 # to pixels for a real screen
441 fn clear-real-screen {
442   var y/eax: int <- copy 0
443   {
444     compare y, 0x300/screen-height=768
445     break-if->=
446     var x/edx: int <- copy 0
447     {
448       compare x, 0x400/screen-width=1024
449       break-if->=
450       pixel-on-real-screen x, y, 0/color=black
451       x <- increment
452       loop
453     }
454     y <- increment
455     loop
456   }
457 }
458 
459 fn clear-rect-on-real-screen xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
460   var y/eax: int <- copy ymin
461   y <- shift-left 4/log2-font-height
462   var ymax/ecx: int <- copy ymax
463   ymax <- shift-left 4/log2-font-height
464   {
465     compare y, ymax
466     break-if->=
467     var x/edx: int <- copy xmin
468     x <- shift-left 3/log2-font-width
469     var xmax/ebx: int <- copy xmax
470     xmax <- shift-left 3/log2-font-width
471     {
472       compare x, xmax
473       break-if->=
474       pixel-on-real-screen x, y, background-color
475       x <- increment
476       loop
477     }
478     y <- increment
479     loop
480   }
481 }
482 
483 fn screen-cell-unused-at? _screen: (addr screen), x: int, y: int -> _/eax: boolean {
484   var screen/esi: (addr screen) <- copy _screen
485   var index/ecx: int <- screen-cell-index screen, x, y
486   var result/eax: boolean <- screen-cell-unused-at-index? screen, index
487   return result
488 }
489 
490 fn screen-cell-unused-at-index? _screen: (addr screen), _index: int -> _/eax: boolean {
491   var screen/esi: (addr screen) <- copy _screen
492   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
493   var data/eax: (addr array screen-cell) <- lookup *data-ah
494   var index/ecx: int <- copy _index
495   var offset/ecx: (offset screen-cell) <- compute-offset data, index
496   var cell/eax: (addr screen-cell) <- index data, offset
497   var src/eax: (addr boolean) <- get cell, unused?
498   return *src
499 }
500 
501 fn screen-code-point-at _screen: (addr screen), x: int, y: int -> _/eax: code-point {
502   var screen/esi: (addr screen) <- copy _screen
503   var index/ecx: int <- screen-cell-index screen, x, y
504   var result/eax: code-point <- screen-code-point-at-index screen, index
505   return result
506 }
507 
508 fn screen-code-point-at-index _screen: (addr screen), _index: int -> _/eax: code-point {
509   var screen/esi: (addr screen) <- copy _screen
510   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
511   var data/eax: (addr array screen-cell) <- lookup *data-ah
512   var index/ecx: int <- copy _index
513   var offset/ecx: (offset screen-cell) <- compute-offset data, index
514   var cell/eax: (addr screen-cell) <- index data, offset
515   var src/eax: (addr code-point) <- get cell, data
516   return *src
517 }
518 
519 fn screen-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
520   var screen/esi: (addr screen) <- copy _screen
521   var index/ecx: int <- screen-cell-index screen, x, y
522   var result/eax: int <- screen-color-at-index screen, index
523   return result
524 }
525 
526 fn screen-color-at-index _screen: (addr screen), _index: int -> _/eax: int {
527   var screen/esi: (addr screen) <- copy _screen
528   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
529   var data/eax: (addr array screen-cell) <- lookup *data-ah
530   var index/ecx: int <- copy _index
531   var offset/ecx: (offset screen-cell) <- compute-offset data, index
532   var cell/eax: (addr screen-cell) <- index data, offset
533   var src/eax: (addr int) <- get cell, color
534   var result/eax: int <- copy *src
535   return result
536 }
537 
538 fn screen-background-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
539   var screen/esi: (addr screen) <- copy _screen
540   var index/ecx: int <- screen-cell-index screen, x, y
541   var result/eax: int <- screen-background-color-at-index screen, index
542   return result
543 }
544 
545 fn screen-background-color-at-index _screen: (addr screen), _index: int -> _/eax: int {
546   var screen/esi: (addr screen) <- copy _screen
547   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
548   var data/eax: (addr array screen-cell) <- lookup *data-ah
549   var index/ecx: int <- copy _index
550   var offset/ecx: (offset screen-cell) <- compute-offset data, index
551   var cell/eax: (addr screen-cell) <- index data, offset
552   var src/eax: (addr int) <- get cell, background-color
553   var result/eax: int <- copy *src
554   return result
555 }
556 
557 fn pixel screen: (addr screen), x: int, y: int, color: int {
558   {
559     compare screen, 0
560     break-if-!=
561     pixel-on-real-screen x, y, color
562     return
563   }
564   # fake screen
565   var screen/esi: (addr screen) <- copy screen
566   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
567   var pixels/eax: (addr array byte) <- lookup *pixels-ah
568   {
569     compare pixels, 0
570     break-if-!=
571     abort "pixel graphics not enabled for this screen"
572   }
573   # ignore if out of bounds
574   {
575     compare x, 0
576     break-if->=
577     return
578   }
579   {
580     var xmax-addr/eax: (addr int) <- get screen, width
581     var xmax/eax: int <- copy *xmax-addr
582     xmax <- shift-left 3/log2-font-width
583     compare x, xmax
584     break-if-<
585     return
586   }
587   {
588     compare y, 0
589     break-if->=
590     return
591   }
592   {
593     var ymax-addr/eax: (addr int) <- get screen, height
594     var ymax/eax: int <- copy *ymax-addr
595     ymax <- shift-left 4/log2-font-height
596     compare y, ymax
597     break-if-<
598     return
599   }
600   #
601   var index/ecx: int <- pixel-index screen, x, y
602   var dest/ecx: (addr byte) <- index pixels, index
603   var src/eax: byte <- copy-byte color
604   copy-byte-to *dest, src
605 }
606 
607 fn pixel-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
608   var screen/esi: (addr screen) <- copy _screen
609   var width-addr/eax: (addr int) <- get screen, width
610   var result/ecx: int <- copy y
611   result <- multiply *width-addr
612   result <- shift-left 3/log2-font-width
613   result <- add x
614   return result
615 }
616 
617 # double-buffering primitive
618 # 'screen' must be a fake screen. 'target-screen' is usually real.
619 # Both screens must have the same size.
620 fn copy-pixels _screen: (addr screen), target-screen: (addr screen) {
621   var screen/esi: (addr screen) <- copy _screen
622   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
623   var _pixels/eax: (addr array byte) <- lookup *pixels-ah
624   var pixels/edi: (addr array byte) <- copy _pixels
625   var width-a/edx: (addr int) <- get screen, width
626   var width/edx: int <- copy *width-a
627   width <- shift-left 3/log2-font-width
628   var height-a/ebx: (addr int) <- get screen, height
629   var height/ebx: int <- copy *height-a
630   height <- shift-left 4/log2-font-height
631   var i/esi: int <- copy 0
632   var y/ecx: int <- copy 0
633   {
634     # screen top left pixels x y width height
635     compare y, height
636     break-if->=
637     var x/eax: int <- copy 0
638     {
639       compare x, width
640       break-if->=
641       {
642         var color-addr/ebx: (addr byte) <- index pixels, i
643         var color/ebx: byte <- copy-byte *color-addr
644         var color2/ebx: int <- copy color
645         pixel target-screen, x, y, color2
646       }
647       x <- increment
648       i <- increment
649       loop
650     }
651     y <- increment
652     loop
653   }
654 }
655 
656 # It turns out double-buffering screen-cells is useless because rendering fonts
657 # takes too long. (At least under Qemu.)
658 # So we'll instead convert screen-cells to pixels when double-buffering.
659 # 'screen' must be a fake screen.
660 fn convert-screen-cells-to-pixels _screen: (addr screen) {
661   var screen/esi: (addr screen) <- copy _screen
662   var width-a/ebx: (addr int) <- get screen, width
663   var height-a/edx: (addr int) <- get screen, height
664   var data-ah/eax: (addr handle array byte) <- get screen, pixels
665   var _data/eax: (addr array byte) <- lookup *data-ah
666   var data: (addr array byte)
667   copy-to data, _data
668   var y/ecx: int <- copy 0
669   {
670     compare y, *height-a
671     break-if->=
672     var x/edi: int <- copy 0
673     $convert-screen-cells-to-pixels:loop-x: {
674       compare x, *width-a
675       break-if->=
676       {
677         var tmp/eax: code-point <- screen-code-point-at screen, x, y
678         # skip null code-points that only get created when clearing screen
679         # there may be other pixels drawn there, and we don't want to clobber them
680         # this is a situation where fake screens aren't faithful to real screens; we don't support overlap between screen-cells and raw pixels
681         compare tmp, 0
682         break-if-=
683         var c: code-point
684         copy-to c, tmp
685         var tmp/eax: int <- screen-color-at screen, x, y
686         var fg: int
687         copy-to fg, tmp
688         var bg/eax: int <- screen-background-color-at screen, x, y
689         var offset/eax: int <- draw-code-point-on-screen-array data, c, x, y, fg, bg, *width-a, *height-a
690         x <- add offset
691         loop $convert-screen-cells-to-pixels:loop-x
692       }
693       x <- increment
694       loop
695     }
696     y <- increment
697     loop
698   }
699 }