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