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 }