Drawing shapes on the screen

A house, a sun and a sky drawn onto the bitmap display with sw and nothing else, by two subroutines that work out which word each pixel is.

A picture in a handful of shapes: two rectangles for the sky and the ground, a disc for the sun, a rectangle for the house, six rows of decreasing width for its roof and one more rectangle for the door. Press Run and watch the Screen panel next to the program.

Print a string asked the environment for a line of text. The screen asks nothing of anybody. It is a block of memory, one word per pixel, and every shape on it is a loop of sw instructions that your program writes.

You need to know: the "The bitmap display and the keyboard registers" lecture and the "A 2D array" Example. What is new here is a shape as a subroutine: fill_rect and fill_disc are the two the rest of the program calls, because nothing in the machine draws anything.

The # @screen line is read by every Build, before the first instruction runs. unit=8 draws one word as an 8 by 8 block, width=256 height=256 is the display area, and base=display names a label your own program defines, so the grid starts wherever the assembler put it. 256 divided by 8 is 32, which is why SIDE is 32 and why .space 4096 is exactly the right amount of room: 32 by 32 words of four bytes each.

A colour is the low 24 bits of a word, red in bits 23 to 16, green in 15 to 8 and blue in 7 to 0. So 0x0070B0E0 is rgb(112, 176, 224), a pale blue, and the order is the #RRGGBB you write in CSS with a 0x on the front. The M68K's screen takes the same three bytes the other way round.

The address of the pixel at column x and row y is base + (y * SIDE + x) * 4, which is the two dimensional array of the Example before this one with an element size of four. Both subroutines here work that out the same way: slli by 5 for the y * 32, an add for the x, slli by 2 for the four bytes, and an add for the base.

fill_rect computes that address once per row and then walks along the row with addi t2, t2, 4, because the pixels of a row sit next to each other in memory. fill_disc computes it per pixel, because it only writes the ones it keeps. A cell is inside the disc when dx * dx + dy * dy is under r * r, which is Pythagoras with the square root left off both sides.

fill_disc writes the address into t4, the register that was holding the squared distance a line earlier, and it has to: RISC-V has seven temporaries, t0 to t6, and the loop is already using all seven. MIPS has ten of them, so its version of this subroutine gives the address one of its own. Once the bge has read t4 the distance is finished with, so reusing it costs nothing but a comment.

The grid's address is in s0 and the colour in s1, and neither is an argument. The M68K's screen has a pen colour and a fill colour of its own that a task sets; here the hardware has no such thing, so this program keeps its own current colour in a saved register and every drawing subroutine reads it from there. s0 to s11 are the registers a subroutine has to give back, so fill_rect writing only t registers is what makes that work.

The whole picture is 6266 instructions out of the two million a Playground gets, and 1251 of those are the sw instructions themselves: a 32 by 32 grid is 1024 words, and the sky and the ground between them cover every one of those before anything else is drawn on top.

There is no text in the picture. The M68K has a task that draws a string at a pixel position; the bitmap display has nothing of the kind, and a caption under a house has to be either drawn letter by letter out of pixels or printed to the console instead.

Try changing the sun's li a0, 26 to li a0, 29, which moves its centre three cells right. Its right hand edge is cut off at column 31, and the cells that fell off it appear at the left of the next row down, because nothing between the coordinates and the sw checks that the column is still on the screen: a grid is one line of memory and column 32 of a row is column 0 of the next.