This lab is a lead-in to project 1. Project 1 will assume that you have done this lab. Skipping this lab would be a very, very bad idea.

In this lab, you will be getting vengeance for lab 2 and making a real drawing program that uses the mouse (or trackpad, or stylus, or whatever) to draw on the screen.

A drawing program is conceptually not very complicated. It can be as simple as:

  1. get the current position of the mouse.
  2. if the user is holding down the mouse button, draw a pixel at that position.
  3. repeat.

But ours will be a little better than that.


0. Getting started

Here are the include files for this lab, as a zip file..

# it is very important that you do not put any code or variables
# before this .include line. you can put comments, but nothing else.
.include "display_2244_0203.asm"
.include "lab4_graphics.asm"

.eqv MODE_NOTHING 0
.eqv MODE_BRUSH   1
.eqv MODE_COLOR   2

.eqv PALETTE_X  32
.eqv PALETTE_Y  56
.eqv PALETTE_X2 96
.eqv PALETTE_Y2 72

.data
	drawmode: .word MODE_NOTHING
	last_x:   .word -1
	last_y:   .word -1
	color:    .word 0b111111 # 3,3,3 (white)
.text

.global main
main:

Good lord, what IS all this stuff?

On labs 2 and 3, you were given some very simplified include files that had only what was needed to do those labs. This time, you have been given the entire display driver, or at least, everything I’ve written to this point.

Don’t get too overwhelmed. Again, most of the functions you’ve been given, you won’t need to use. (But you’re free to look at them… :) )


1. main and load_graphics

This starts very similarly to lab 3, so refer back to what you did there.

  1. main needs to:
    • call display_init(15, 1, 0)
      • Yes, this time display_init has arguments!
      • The first argument is the number of milliseconds per frame. 15 ms is approximately 60 frames/second.
      • The second is a boolean to enable the framebuffer, the thing you used on lab 2.
      • The third is a boolean to disable the tilemap, the thing you used on lab 3.
    • call load_graphics which you will be writing shortly
    • loop infinitely this time, and in the loop:
      • call check_input
      • call draw_cursor
      • call display_finish_frame
  2. load_graphics needs to:
    • call display_load_sprite_gfx(cursor_gfx, CURSOR_TILE, N_CURSOR_TILES)
      • this time, I gave you constants for the second and third arguments
      • but it works just like lab 3.
    • call display_load_sprite_gfx(palette_sprite_gfx, PALETTE_TILE, N_PALETTE_TILES)
      • people mess up this call a lot. be careful about your arguments.
  3. Finally, stub out the check_input and draw_cursor functions like you learned to do on lab 3, and check that your program assembles and runs. (You’ll have to hit the stop button to end it.)

drawmode and the MODE_ constants

(This is a high-level explanation of what you’ll be building. You aren’t expected to be able to write all the code by only reading this. hashtag keep reading)

The drawmode variable is going to affect the behavior of our program in a big way. Depending on what drawmode is, the program will process input differently (i.e. do different things in check_input).

It starts off as MODE_NOTHING. This is the “default” mode that means we’re not doing anything. In this mode we can:

In MODE_BRUSH, we are holding down the mouse button and drawing. In this mode we just:

Finally in MODE_COLOR, we:

If this isn’t making a ton of sense, let’s look at something a bit more visual.


The shape of the program

This time, I am leaving it more up to you to split up your program into functions that make sense. But I’m not leaving you completely on your own. Here is a call graph of my solution. All the functions outside the yellow rectangle are my lab implementation. (display_init and display_finish_frame are not shown to make the diagram smaller):

You can see there are some functions that call several other functions (e.g. main, check_input) and one that is called by multiple other functions (display_draw_line).

You are free to follow my program structure or do your own. Putting everything in 2 or 3 enormous functions will lose you points, and it will make it way harder for you to write, too.

You can see that check_input calls one of my three drawmode_ functions, based on the current value of drawmode. You can also see the various things that drawmode_nothing calls, and can probably start to get an idea for what will happen in each function.


2. draw_cursor

Just like lab 3, you need to draw a cursor on the screen. Unlike lab 3, you’ll be using the mouse position as the cursor’s position instead of using your own variables and keyboard arrows to move it.

The MMIO variables for the mouse’s position are described in detail below. Your draw_cursor function will be almost exactly the same as lab 3’s, with a couple changes:

Done right, you should be able to see a crosshair cursor like in the image when you move your mouse over the display, and it should “stick to” your mouse cursor.

Also, when you move your mouse cursor off the display, it should disappear.

If you see nothing:


How to get mouse input in the display

This section is for reference. This isn’t a step in the lab.

There are two variables for getting the mouse position:

If you lw from them, you will get the coordinates of the mouse measured from the top-left of the display. Both X and Y range [0, 127]. However, if the mouse cursor is outside of the display, both variables will instead be -1. You only have to check one: either they’re both -1 or neither is.

For getting the mouse buttons, it works a little differently from keyboard input. Since there are only three buttons, you can get their state all at once, and use bitwise AND to extract the state of just one. For example:

    lw t0, display_mouse_pressed # get the state of *all* buttons
    and t0, t0, MOUSE_LBUTTON    # use bitwise AND to extract just the left button
    beq t0, 0, _endif_lbutton    # 0 means not pressed, not-0 means pressed
        # here, the left mouse button is pressed. respond to it!
    _endif_lbutton:

The mouse button variables are:

This is what the variables keep track of (and this is also true for display_key_pressed etc.):

And the constants you use in the bitwise AND are:


3. check_input and the drawmode_ functions

Although it’s called check_input it kinda ends up doing… most stuff. Lol. (Hey, there is a “more elegant” way to do this but it ends up being a bunch of extra complexity for not a lot of benefit in this case.)

This is what check_input should do:

  1. If they pressed KEY_ESCAPE, use syscall 10 to exit.
    • This is very much like lab 3 but even simpler.
    • Actually, let me make it even simpler. In lab 3 you learned to do it like:
         li t0, KEY_ESCAPE
         sw t0, display_key_pressed
         lw t0, display_key_pressed
         # now t0 is 1 if it's pressed
    
    • But this is starting to get repetitive, huh. So there are some macros in the display driver that shorten this to:
         display_is_key_pressed t0, KEY_ESCAPE
         # now t0 is 1 if it's pressed
    
    • The macro does the exact same thing, just on one line. (go look for it in display_2244_0203.asm.) There are also display_is_key_held and display_is_key_released
  2. Otherwise, switch on the drawmode variable and based on which value it is, call one of drawmode_nothing, drawmode_brush, or drawmode_color.

You already know enough to do this. You know how to make a switch-case, you know how to call functions, you know how to stub them out. Go do it. But,

STOP DOING THIS:

    li t1, MODE_NOTHING       # WHY????????
    beq t0, t1, _case_nothing # SO CONFUSINGGGGGGGGG

START DOING THIS:

    beq t0, MODE_NOTHING, _case_nothing

IT’S SHORTER AND EASIER TO READ AAAAAAAAAAAAA

Okay I’m normal now. Go test your program and make sure it doesn’t crash or something. Maybe put a test print in each of the three drawmode_ functions so you know which one is running, change the drawmode variable’s initializer to make sure all three modes work, that kinda thing. Test your foundation before building anything else on top of it.


4. The brush

The brush is the most basic drawing tool. You click and drag to draw with a 1-pixel-wide “brush.” When you let go, it stops drawing.

Here’s what you need to do:

display_draw_line

This is one of the functions in the display driver. It draws a line of any color, at any angle, between two points. It’s easy to use but it is a little weird, because it takes five arguments.

In order to pass the fifth argument, I did something a little bad: I used v1 to pass the fifth argument. What! We’re not using it for anything else!!

So the arguments to this function are:

In this case you are going to use the value of the variable color for that last argument. It is initialized to 0b111111 - decimal 63 - which is white.

Logical OR ||

In Java we can just write two conditions with || to check them both and do something if either one is true. We don’t have that in asm. We have to build it out of branches.

Let’s say I want to do something like if(x == 5 || x == 10). I’d write it like a weird switch-case:

    lw t0, x
    # it's like you're checking for two cases...
    beq t0, 5, _then
    beq t0, 10, _then
    j _endif # and then you have a default that goes to the endif.
    _then:
        print_str "it's either 5 or 10!\n"

        # now when you get here, think to yourself: do you want to
        # leave this if and *run the next one?* or do you want to JUMP
        # to *after* all the ifs? look at the description above.
    _endif:

Offscreen coordinates

When the mouse goes offscreen (i.e. off the edges of the display), display_mouse_x/y will be -1, but you only need to check for one of them. So the if in the above code is something like:

if(user_released_lbutton ||  display_mouse_x < 0) {
    drawmode = MODE_NOTHING;
} else { ...

So you’ve got 2 things to check for. 2 branches. ez.

Once you’ve got it implemented correctly, the brush should work like so:

Important things to test that this animation shows:


5. Changing colors

White is boring! We actually have a palette of 64 colors that we can draw with. Let’s make that happen. Here’s what you need to do:

Showing the palette sprites

There are 16 palette tiles. You will be drawing those 16 tiles in an 8x2 arrangement like the diagram to the right. Gee, with an 8x2 rectangle, what kind of looping structure do you think you’ll have? (Remember load_map on lab 3?)

So just to get you started,

la t9, display_spr_table
add t9, t9, 4
# now t9 is pointing at sprite 1, so we don't
# overwrite the cursor sprite

# inside your...
    # nested for loop...
        # t0 = blah blah calculate the X coordinate somehow
        sb t0, 0(t9)

        # then calculate the Y
        sb t0, 1(t9)

        # then calculate which tile to use
        sb t0, 2(t9)

        # and finally the flags are just 1
        li t0, 1
        sb t0, 3(t9)

        # finally, add 4 to t9 to move to the next sprite.
        add t9, t9, 4
    # inner loop increment and branch here
# outer loop increment and branch here

To test it out, just click on the display and hit the C key. The palette should then appear:

If the palette doesn’t appear, go back to the load_graphics step and make sure you are loading the palette graphics correctly. If you are, then maybe you are setting the palette sprite flags to 0x41 instead of 1.

If the palette appears, but it looks wrong (e.g. repeated colors, all blue or something), you are probably just not calculating the tile number correctly for each sprite.

Clicking on the palette and logical AND &&

PALETTE_X/Y and PALETTE_X2/Y2 define a rectangle where the user can click inside to pick the color. In Java we’d write this check as:

if(x >= PALETTE_X && x < PALETTE_X2 && y >= PALETTE_Y && y < PALETTE_X2) {
    // (x,y) is inside the palette!
}

You can think of && as a nested if:

if(x >= PALETTE_X) {
    if(x < PALETTE_X2) {
        if(y >= PALETTE_Y) {
            if(y < PALETTE_X2) {
                // (x,y) is inside the palette!
} } } } // I'm putting them all on one line for a reason

So in MIPS this would be implemented as 4 branches in a row, which all branch to the endif, and you only need one endif label for all 4. And of course, you need to invert the conditions. (It might make more sense if you think of it like, “if the mouse x is less than PALETTE_X, then it’s to the left of the rectangle and can’t be inside, so skip the if.” and so on.)

Converting from mouse coordinates to a color value

This is a little weird, but just trust me. Here is some pseudocode.

// x and y can be registers, I'm just using names here for clarity.
x = display_mouse_x - PALETTE_X;
y = display_mouse_y - PALETTE_Y;
color = (x / 4 + (y / 4) * 16); // do NOT simplify!

You might see (y / 4) * 16 above and want to simplify it to y * 4, but remember that we are using integer division, so the result of dividing then multiplying is not the same as just multiplying. What is this code doing? Well… let’s track the range of the y coordinate as we calculate:

y = display_mouse_y; // [56, 71] (since the conditions were met)
y = y - PALETTE_Y;   // [0, 15]  (PALETTE_Y == 56)
y = y / 4;           // [0, 3]
y = y * 16;          // can only be one of 4 values: { 0, 16, 32, 48 }

Try tracking the range of x. You’ll see that x + y will always be in the range [0, 63].

Now, you should be able to change the drawing color:

Important things to test that this animation shows:


That’s it!

It’s not a very full-featured drawing program……………

yet.

Guess what project 1 will be?


Submitting

Submit only your _lab4.asm file. Do not submit the provided files.

To submit:

  1. On Canvas, go to “Assignments” and click on this lab.
  2. Click “Start Assignment.”
  3. Under “File Upload,” click the “Browse” button and choose your .asm file.
  4. Click “Submit Assignment.”

If you need to resubmit, that’s fine, just click “New Attempt” on the assignment page and upload it again.