https://github.com/akkartik/mu/blob/main/baremetal/103grapheme.subx
  1 # Use the built-in font to draw a grapheme to real screen.
  2 #
  3 # We need to do this in machine code because Mu doesn't have global variables
  4 # yet (for the start of video memory).
  5 #
  6 # There are uncomfortable assumptions baked in here about english/latin
  7 # script. We convert the grid of pixels into a fixed-width grid of graphemes,
  8 # which may not work well with other language families.
  9 
 10 == code
 11 
 12 # The Mu computer's screen is 1024px wide and 768px tall.
 13 # The Mu computer's font is 8px wide and 16px tall.
 14 # Therefore 'x' here is in [0, 128), and 'y' is in [0, 48)
 15 draw-grapheme-on-real-screen:  # g: grapheme, x: int, y: int, color: int, background-color: int
 16     # . prologue
 17     55/push-ebp
 18     89/<- %ebp 4/r32/esp
 19     # . save registers
 20     50/push-eax
 21     51/push-ecx
 22     52/push-edx
 23     53/push-ebx
 24     56/push-esi
 25     # var letter-bitmap/esi = font[g]
 26     8b/-> *(ebp+8) 6/r32/esi
 27     c1 4/subop/shift-left %esi 4/imm8
 28     8d/copy-address *(esi+0x8c00) 6/r32/esi  # font-start
 29     # if (letter-bitmap >= 0x9400) return  # characters beyond ASCII currently not supported
 30     81 7/subop/compare %esi 0x9400/imm32
 31     7d/jump-if->= $draw-grapheme-on-real-screen:end/disp8
 32     # var ycurr/edx: int = y*16
 33     8b/-> *(ebp+0x10) 2/r32/edx
 34     c1 4/subop/shift-left %edx 4/imm8
 35     # var ymax/ebx: int = ycurr + 16
 36     8b/-> *(ebp+0x10) 3/r32/ebx
 37     c1 4/subop/shift-left %ebx 4/imm8
 38     81 0/subop/add %ebx 0x10/imm32
 39     {
 40       # if (ycurr >= ymax) break
 41       39/compare %edx 3/r32/ebx
 42       7d/jump-if->= break/disp8
 43       # var xcurr/eax: int = x*8 + 7
 44       8b/-> *(ebp+0xc) 0/r32/eax  # font-width - 1
 45       c1 4/subop/shift-left %eax 3/imm8
 46       81 0/subop/add %eax 7/imm32
 47       # var xmin/ecx: int = x*8
 48       8b/-> *(ebp+0xc) 1/r32/ecx
 49       c1 4/subop/shift-left %ecx 3/imm8
 50       # var row-bitmap/ebx: int = *letter-bitmap
 51       53/push-ebx
 52       8b/-> *esi 3/r32/ebx
 53       {
 54         # if (xcurr < xmin) break
 55         39/compare %eax 1/r32/ecx
 56         7c/jump-if-< break/disp8
 57         # shift LSB from row-bitmap into carry flag (CF)
 58         c1 5/subop/shift-right-logical %ebx 1/imm8
 59         # if LSB, draw a pixel in the given color
 60         {
 61           73/jump-if-not-CF break/disp8
 62           (pixel-on-real-screen %eax %edx *(ebp+0x14))
 63           eb/jump $draw-grapheme-on-real-screen:continue/disp8
 64         }
 65         # otherwise use the background color
 66         (pixel-on-real-screen %eax %edx *(ebp+0x18))
 67 $draw-grapheme-on-real-screen:continue:
 68         # --x
 69         48/decrement-eax
 70         #
 71         eb/jump loop/disp8
 72       }
 73       # reclaim row-bitmap
 74       5b/pop-to-ebx
 75       # ++y
 76       42/increment-edx
 77       # next bitmap row
 78       46/increment-esi
 79       #
 80       eb/jump loop/disp8
 81     }
 82 $draw-grapheme-on-real-screen:end:
 83     # . restore registers
 84     5e/pop-to-esi
 85     5b/pop-to-ebx
 86     5a/pop-to-edx
 87     59/pop-to-ecx
 88     58/pop-to-eax
 89     # . epilogue
 90     89/<- %esp 5/r32/ebp
 91     5d/pop-to-ebp
 92     c3/return
 93 
 94 cursor-position-on-real-screen:  # -> _/eax: int, _/ecx: int
 95     # . prologue
 96     55/push-ebp
 97     89/<- %ebp 4/r32/esp
 98     # TODO: support fake screen; we currently assume 'screen' is always 0 (real)
 99     8b/-> *Real-screen-cursor-x 0/r32/eax
100     8b/-> *Real-screen-cursor-y 1/r32/ecx
101 $cursor-position-on-real-screen:end:
102     # . epilogue
103     89/<- %esp 5/r32/ebp
104     5d/pop-to-ebp
105     c3/return
106 
107 set-cursor-position-on-real-screen:  # x: int, y: int
108     # . prologue
109     55/push-ebp
110     89/<- %ebp 4/r32/esp
111     # . save registers
112     50/push-eax
113     #
114     8b/-> *(ebp+8) 0/r32/eax
115     89/<- *Real-screen-cursor-x 0/r32/eax
116     8b/-> *(ebp+0xc) 0/r32/eax
117     89/<- *Real-screen-cursor-y 0/r32/eax
118 $set-cursor-position-on-real-screen:end:
119     # . restore registers
120     58/pop-to-eax
121     # . epilogue
122     89/<- %esp 5/r32/ebp
123     5d/pop-to-ebp
124     c3/return
125 
126 # Draw cursor at current location. But this is rickety:
127 #   - does not clear previous location cursor was shown at.
128 #   - does not preserve what was at the cursor. Caller is responsible for
129 #     tracking what was on the screen at this position before and passing it
130 #     in again.
131 show-cursor-on-real-screen:  # g: grapheme
132     # . prologue
133     55/push-ebp
134     89/<- %ebp 4/r32/esp
135     # . save registers
136     50/push-eax
137     51/push-ecx
138     #
139     (cursor-position-on-real-screen)  # => eax, ecx
140     (draw-grapheme-on-real-screen *(ebp+8) %eax %ecx 0 7)
141 $show-cursor-on-real-screen:end:
142     # . restore registers
143     59/pop-to-ecx
144     58/pop-to-eax
145     # . epilogue
146     89/<- %esp 5/r32/ebp
147     5d/pop-to-ebp
148     c3/return
149 
150 == data
151 
152 # The cursor is where certain Mu functions (usually of the form
153 # 'draw*cursor*') print to by default.
154 #
155 # We don't bother displaying the cursor when drawing. It only becomes visible
156 # on show-cursor, which is quite rickety (see above)
157 #
158 # It's up to applications to manage cursor display:
159 #   - clean up where it used to be
160 #   - display the cursor before waiting for a key
161 #   - ensure its location appropriately suggests the effect keystrokes will have
162 #   - ensure its contents (and colors) appropriately reflect the state of the
163 #     screen
164 #
165 # There's no blinking, etc. We aren't using any hardware-supported text mode
166 # here.
167 Real-screen-cursor-x:
168   0/imm32
169 Real-screen-cursor-y:
170   0/imm32