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 }