# Mu - 500fake-screen.mu

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 # fake screens only
    120 fn screen-cell-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
    121   var screen/esi: (addr screen) <- copy _screen
    122   # some bounds checks that aren't needed for a real screen, but might help catch problems
    123   {
    124     compare x, 0
    125     break-if->=
    126     abort "screen-cell-index: negative x"
    127   }
    128   {
    129     var xmax/eax: (addr int) <- get screen, width
    130     var xcurr/ecx: int <- copy x
    131     compare xcurr, *xmax
    132     break-if-<
    133     abort "screen-cell-index: x too high"
    134   }
    135   {
    136     compare y, 0
    137     break-if->=
    138     abort "screen-cell-index: negative y"
    139   }
    140   {
    141     var ymax/eax: (addr int) <- get screen, height
    142     var ycurr/ecx: int <- copy y
    143     compare ycurr, *ymax
    144     break-if-<
    145     abort "screen-cell-index: y too high"
    146   }
    147   var width-addr/eax: (addr int) <- get screen, width
    148   var result/ecx: int <- copy y
    149   result <- multiply *width-addr
    150   result <- add x
    151   return result
    152 }
    153
    154 fn cursor-position _screen: (addr screen) -> _/eax: int, _/ecx: int {
    155   var screen/esi: (addr screen) <- copy _screen
    156   {
    157     compare screen, 0
    158     break-if-!=
    159     var x/eax: int <- copy 0
    160     var y/ecx: int <- copy 0
    161     x, y <- cursor-position-on-real-screen
    162     return x, y
    163   }
    164   # fake screen
    165   var cursor-x-addr/eax: (addr int) <- get screen, cursor-x
    166   var cursor-y-addr/ecx: (addr int) <- get screen, cursor-y
    167   return *cursor-x-addr, *cursor-y-addr
    168 }
    169
    170 fn set-cursor-position _screen: (addr screen), x: int, y: int {
    171   var screen/esi: (addr screen) <- copy _screen
    172   {
    173     compare screen, 0
    174     break-if-!=
    175     set-cursor-position-on-real-screen x, y
    176     return
    177   }
    178   # fake screen
    179   # ignore x < 0
    180   {
    181     compare x, 0
    182     break-if->=
    183     return
    184   }
    185   # ignore x >= width
    186   {
    187     var width-addr/eax: (addr int) <- get screen, width
    188     var width/eax: int <- copy *width-addr
    189     compare x, width
    190     break-if-<=
    191     return
    192   }
    193   # ignore y < 0
    194   {
    195     compare y, 0
    196     break-if->=
    197     return
    198   }
    199   # ignore y >= height
    200   {
    201     var height-addr/eax: (addr int) <- get screen, height
    202     var height/eax: int <- copy *height-addr
    203     compare y, height
    204     break-if-<
    205     return
    206   }
    207   # screen->cursor-x = x
    208   var dest/edi: (addr int) <- get screen, cursor-x
    209   var src/eax: int <- copy x
    210   copy-to *dest, src
    211   # screen->cursor-y = y
    212   dest <- get screen, cursor-y
    213   src <- copy y
    214   copy-to *dest, src
    215 }
    216
    217 fn draw-cursor screen: (addr screen), g: grapheme {
    218   {
    219     compare screen, 0
    220     break-if-!=
    221     draw-cursor-on-real-screen g
    222     return
    223   }
    224   # fake screen
    225   var cursor-x/eax: int <- copy 0
    226   var cursor-y/ecx: int <- copy 0
    227   cursor-x, cursor-y <- cursor-position screen
    228   draw-grapheme screen, g, cursor-x, cursor-y, 0/fg, 7/bg
    229 }
    230
    231 fn clear-screen _screen: (addr screen) {
    232   var screen/esi: (addr screen) <- copy _screen
    233   {
    234     compare screen, 0
    235     break-if-!=
    236     clear-real-screen
    237     return
    238   }
    239   # fake screen
    240   set-cursor-position screen, 0, 0
    241   var y/eax: int <- copy 0
    242   var height/ecx: (addr int) <- get screen, height
    243   {
    244     compare y, *height
    245     break-if->=
    246     var x/edx: int <- copy 0
    247     var width/ebx: (addr int) <- get screen, width
    248     {
    249       compare x, *width
    250       break-if->=
    251       draw-code-point screen, 0x20/space, x, y, 0/fg=black, 0/bg=black
    252       x <- increment
    253       loop
    254     }
    255     y <- increment
    256     loop
    257   }
    258   set-cursor-position screen, 0, 0
    259   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
    260   var pixels/eax: (addr array byte) <- lookup *pixels-ah
    261   var i/ecx: int <- copy 0
    262   var max/edx: int <- length pixels
    263   {
    264     compare i, max
    265     break-if->=
    266     var curr/eax: (addr byte) <- index pixels, i
    267     var zero/ebx: byte <- copy 0
    268     copy-byte-to *curr, zero
    269     i <- increment
    270     loop
    271   }
    272 }
    273
    274 fn fake-screen-empty? _screen: (addr screen) -> _/eax: boolean {
    275   var screen/esi: (addr screen) <- copy _screen
    276   var y/eax: int <- copy 0
    277   var height/ecx: (addr int) <- get screen, height
    278   {
    279     compare y, *height
    280     break-if->=
    281     var x/edx: int <- copy 0
    282     var width/ebx: (addr int) <- get screen, width
    283     {
    284       compare x, *width
    285       break-if->=
    286       var g/eax: grapheme <- screen-grapheme-at screen, x, y
    287       {
    288         compare g, 0
    289         break-if-=
    290         compare g, 0x20/space
    291         break-if-=
    292         return 0/false
    293       }
    294       x <- increment
    295       loop
    296     }
    297     y <- increment
    298     loop
    299   }
    300   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
    301   var pixels/eax: (addr array byte) <- lookup *pixels-ah
    302   var y/ebx: int <- copy 0
    303   var height-addr/edx: (addr int) <- get screen, height
    304   var height/edx: int <- copy *height-addr
    305   height <- shift-left 4/log2-font-height
    306   {
    307     compare y, height
    308     break-if->=
    309     var width-addr/edx: (addr int) <- get screen, width
    310     var width/edx: int <- copy *width-addr
    311     width <- shift-left 3/log2-font-width
    312     var x/edi: int <- copy 0
    313     {
    314       compare x, width
    315       break-if->=
    316       var idx/ecx: int <- pixel-index screen, x, y
    317       var color-addr/ecx: (addr byte) <- index pixels, idx
    318       var color/ecx: byte <- copy-byte *color-addr
    319       compare color, 0
    320       {
    321         break-if-=
    322         return 0/false
    323       }
    324       x <- increment
    325       loop
    326     }
    327     y <- increment
    328     loop
    329   }
    330   return 1/true
    331 }
    332
    333 fn clear-rect _screen: (addr screen), xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
    334   var screen/esi: (addr screen) <- copy _screen
    335   {
    336     compare screen, 0
    337     break-if-!=
    338     clear-rect-on-real-screen xmin, ymin, xmax, ymax, background-color
    339     return
    340   }
    341   # fake screen
    342   set-cursor-position screen, 0, 0
    343   var y/eax: int <- copy ymin
    344   var ymax/ecx: int <- copy ymax
    345   {
    346     compare y, ymax
    347     break-if->=
    348     var x/edx: int <- copy xmin
    349     var xmax/ebx: int <- copy xmax
    350     {
    351       compare x, xmax
    352       break-if->=
    353       draw-code-point screen, 0x20/space, x, y, 0/fg, background-color
    354       x <- increment
    355       loop
    356     }
    357     y <- increment
    358     loop
    359   }
    360   set-cursor-position screen, 0, 0
    361 }
    362
    363 # there's no grapheme that guarantees to cover every pixel, so we'll bump down
    364 # to pixels for a real screen
    365 fn clear-real-screen {
    366   var y/eax: int <- copy 0
    367   {
    368     compare y, 0x300/screen-height=768
    369     break-if->=
    370     var x/edx: int <- copy 0
    371     {
    372       compare x, 0x400/screen-width=1024
    373       break-if->=
    374       pixel-on-real-screen x, y, 0/color=black
    375       x <- increment
    376       loop
    377     }
    378     y <- increment
    379     loop
    380   }
    381 }
    382
    383 fn clear-rect-on-real-screen xmin: int, ymin: int, xmax: int, ymax: int, background-color: int {
    384   var y/eax: int <- copy ymin
    385   y <- shift-left 4/log2-font-height
    386   var ymax/ecx: int <- copy ymax
    387   ymax <- shift-left 4/log2-font-height
    388   {
    389     compare y, ymax
    390     break-if->=
    391     var x/edx: int <- copy xmin
    392     x <- shift-left 3/log2-font-width
    393     var xmax/ebx: int <- copy xmax
    394     xmax <- shift-left 3/log2-font-width
    395     {
    396       compare x, xmax
    397       break-if->=
    398       pixel-on-real-screen x, y, background-color
    399       x <- increment
    400       loop
    401     }
    402     y <- increment
    403     loop
    404   }
    405 }
    406
    407 fn screen-grapheme-at _screen: (addr screen), x: int, y: int -> _/eax: grapheme {
    408   var screen/esi: (addr screen) <- copy _screen
    409   var idx/ecx: int <- screen-cell-index screen, x, y
    410   var result/eax: grapheme <- screen-grapheme-at-idx screen, idx
    411   return result
    412 }
    413
    414 fn screen-grapheme-at-idx _screen: (addr screen), idx-on-stack: int -> _/eax: grapheme {
    415   var screen/esi: (addr screen) <- copy _screen
    416   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
    417   var data/eax: (addr array screen-cell) <- lookup *data-ah
    418   var idx/ecx: int <- copy idx-on-stack
    419   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
    420   var cell/eax: (addr screen-cell) <- index data, offset
    421   var src/eax: (addr grapheme) <- get cell, data
    422   return *src
    423 }
    424
    425 fn screen-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
    426   var screen/esi: (addr screen) <- copy _screen
    427   var idx/ecx: int <- screen-cell-index screen, x, y
    428   var result/eax: int <- screen-color-at-idx screen, idx
    429   return result
    430 }
    431
    432 fn screen-color-at-idx _screen: (addr screen), idx-on-stack: int -> _/eax: int {
    433   var screen/esi: (addr screen) <- copy _screen
    434   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
    435   var data/eax: (addr array screen-cell) <- lookup *data-ah
    436   var idx/ecx: int <- copy idx-on-stack
    437   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
    438   var cell/eax: (addr screen-cell) <- index data, offset
    439   var src/eax: (addr int) <- get cell, color
    440   var result/eax: int <- copy *src
    441   return result
    442 }
    443
    444 fn screen-background-color-at _screen: (addr screen), x: int, y: int -> _/eax: int {
    445   var screen/esi: (addr screen) <- copy _screen
    446   var idx/ecx: int <- screen-cell-index screen, x, y
    447   var result/eax: int <- screen-background-color-at-idx screen, idx
    448   return result
    449 }
    450
    451 fn screen-background-color-at-idx _screen: (addr screen), idx-on-stack: int -> _/eax: int {
    452   var screen/esi: (addr screen) <- copy _screen
    453   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
    454   var data/eax: (addr array screen-cell) <- lookup *data-ah
    455   var idx/ecx: int <- copy idx-on-stack
    456   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
    457   var cell/eax: (addr screen-cell) <- index data, offset
    458   var src/eax: (addr int) <- get cell, background-color
    459   var result/eax: int <- copy *src
    460   return result
    461 }
    462
    463 fn pixel screen: (addr screen), x: int, y: int, color: int {
    464   {
    465     compare screen, 0
    466     break-if-!=
    467     pixel-on-real-screen x, y, color
    468     return
    469   }
    470   # fake screen
    471   var screen/esi: (addr screen) <- copy screen
    472   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
    473   var pixels/eax: (addr array byte) <- lookup *pixels-ah
    474   {
    475     compare pixels, 0
    476     break-if-!=
    477     abort "pixel graphics not enabled for this screen"
    478   }
    479   var idx/ecx: int <- pixel-index screen, x, y
    480   var dest/ecx: (addr byte) <- index pixels, idx
    481   var src/eax: byte <- copy-byte color
    482   copy-byte-to *dest, src
    483 }
    484
    485 fn pixel-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
    486   var screen/esi: (addr screen) <- copy _screen
    487   {
    488     compare x, 0
    489     break-if->=
    490     abort "screen-cell-index: negative x"
    491   }
    492   {
    493     var xmax-a/eax: (addr int) <- get screen, width
    494     var xmax/eax: int <- copy *xmax-a
    495     xmax <- shift-left 3/log2-font-width
    496     compare x, xmax
    497     break-if-<
    498     abort "screen-cell-index: x too high"
    499   }
    500   {
    501     compare y, 0
    502     break-if->=
    503     abort "screen-cell-index: negative y"
    504   }
    505   {
    506     var ymax-a/eax: (addr int) <- get screen, height
    507     var ymax/eax: int <- copy *ymax-a
    508     ymax <- shift-left 4/log2-font-height
    509     compare y, ymax
    510     break-if-<
    511     abort "screen-cell-index: y too high"
    512   }
    513   var width-addr/eax: (addr int) <- get screen, width
    514   var result/ecx: int <- copy y
    515   result <- multiply *width-addr
    516   result <- shift-left 3/log2-font-width
    517   result <- add x
    518   return result
    519 }
    520
    521 # double-buffering primitive
    522 # 'screen' must be a fake screen. 'target-screen' is usually real.
    523 # Both screens must have the same size.
    524 fn copy-pixels _screen: (addr screen), target-screen: (addr screen) {
    525   var screen/esi: (addr screen) <- copy _screen
    526   var pixels-ah/eax: (addr handle array byte) <- get screen, pixels
    527   var _pixels/eax: (addr array byte) <- lookup *pixels-ah
    528   var pixels/edi: (addr array byte) <- copy _pixels
    529   var width-a/edx: (addr int) <- get screen, width
    530   var width/edx: int <- copy *width-a
    531   width <- shift-left 3/log2-font-width
    532   var height-a/ebx: (addr int) <- get screen, height
    533   var height/ebx: int <- copy *height-a
    534   height <- shift-left 4/log2-font-height
    535   var i/esi: int <- copy 0
    536   var y/ecx: int <- copy 0
    537   {
    538     # screen top left pixels x y width height
    539     compare y, height
    540     break-if->=
    541     var x/eax: int <- copy 0
    542     {
    543       compare x, width
    544       break-if->=
    545       {
    546         var color-addr/ebx: (addr byte) <- index pixels, i
    547         var color/ebx: byte <- copy-byte *color-addr
    548         var color2/ebx: int <- copy color
    549         pixel target-screen, x, y, color2
    550       }
    551       x <- increment
    552       i <- increment
    553       loop
    554     }
    555     y <- increment
    556     loop
    557   }
    558 }