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 }