appendix a now has info about on-screen debugger

This commit is contained in:
sejo 2021-12-14 18:56:01 -06:00
parent c81deee7ea
commit cd8fb372aa
5 changed files with 644 additions and 635 deletions

View File

@ -74,13 +74,21 @@ this should be a light and calm end of our journey, as it has to do less with pr
=> ./uxn_tutorial_day_7.gmi {uxn tutorial day 7}
# appendix a
# appendices
## appendix a: the on-screen debugger
in this appendix we introduce the use of the on-screen debugger available in uxnemu once we set up the system colors.
=> ./uxn_tutorial_appendix_a.gmi {uxn tutorial appendix a}
## appendix b
in this appendix we generalize the background drawing procedure discussed on day 6, into a draw-tiles subroutine that draws an arbitrary rectangle filled with a given tile.
we detail how to get to two versions of this subroutine, one that relies on heavy stack wrangling, and other one that uses variables. this in order to compare both approaches and give us a broader view of the possibilities within uxntal.
=> ./uxn_tutorial_appendix_a.gmi {uxn tutorial appendix a}
=> ./uxn_tutorial_appendix_b.gmi {uxn tutorial appendix b}
# external resources

View File

@ -1,596 +1,70 @@
# uxn tutorial appendix a: repeating a tile inside a rectangle
# uxn tutorial appendix a: the on-screen debugger
in the first part of {uxn tutorial day 6} we discuss how to cover the background of the screen with a given tile.
once we set the system colors in varvara, as described in {uxn tutorial day 2}, we can use the on-screen debugger to aid us in figuring out what is happening with the stacks and memory during the runtime of our programs.
here we will generalize a similar procedure into a draw-tiles subroutine that draws a rectangle filled with a given tile. it will receive the x,y coordinates of the top left corner of the rectangle, its width and height in pixels, and the address of the tile:
# the debugger
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
```
a reminder that we are using the convention of adding a caret (^) after the name of a value to indicate it's a short, and an asterisk (*) to indicate it's a short working as a pointer (i.e. an address in program memory)
we will detail how to get to two versions of this subroutine, one that relies on heavy stack wrangling, and other one that uses variables. this in order to compare both approaches and give us a broader view of the possibilities within uxntal.
## setting up
let's start with the following program as a template. it includes the data for a 1bpp sprite consisting of diagonal lines.
```
( hello-background.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
( macros )
%RTN { JMP2r }
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
BRK
@tile-background 1122 4488 1122 4488
```
## repeating a tile in a row
what's a procedure we could follow to repeat the drawing of a tile starting from x, and ending at a limit corresponding to x+width?
one way would be something like:
* draw tile in x
* add 8 (the size of the tile) to x
* is x less than the limit? if it is, repeat procedure, otherwise end
### concrete version
before abstracting it away, i recommed we write it with concrete numbers.
let's say our initial x is 0008, our width is 0100, and the tile we are drawing is tile-background.
the limit, x+width, would be 0108.
the first step, drawing the tile in x would be:
```
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/x DEO2 ( set initial x )
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
```
adding 8 to x, we know already:
```
.Screen/x DEI2 #0008 ADD2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
```
checking if x is less than the limit, jumping if it is, would be something like:
```
.Screen/x DEI2
#0108 ( the limit )
LTH2 ,&loop JCN ( jump if x is less than the limit )
```
integrating all of it, we would be able to get:
```
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
#0108 LTH2 ,&loop-x JCN ( jump if x is less than the limit )
```
note the use of DUP2 in order to avoid re-reading the value of x.
### abstracting
now, let's say we want to have the previous code work with any given initial x and width, present in the stack before starting.
we can even think of it as a subroutine by itself with the following signature:
```
@draw-tiles-in-a-row ( x^ width^ -- )
```
let's assume for the moment that the address of the sprite was already set, in order to focus in x and width.
when starting the subroutine, the width is at the top of the stack, followed by initial x.
we can use these two values to calculate the limit, that we can stash in the return stack.
one way of achieving that, noting the state of the working stack after each instruction, could be:
```
( initial state: ws: x^ width^ )
OVR2 ( ws: x^ width^ x^ )
ADD2 ( ws: x^ limit^ )
STH2 ( ws: x^ / rs: limit^ )
```
another one:
```
( initial state: ws: x^ width^ )
ADD2k ( ws: x^ width^ limit^ )
STH2 ( ws: x^ width^ / rs: limit^ )
POP2 ( ws: x^ / rs: limit^ )
```
remember that we are showing the top of the stacks at their right.
after these steps, the initial x is at the top of the stack, so we can send it directly to the screen.
the last change that we would need is to replace our hardcoded limit with a STH2kr instruction (copy limit from the return stack into the working stack), and end our routine with a POP2r (remove limit from return stack).
our subroutine would then look as follows:
```
@draw-tiles-in-a-row ( x^ width^ -- )
( calculate and save limit )
OVR2 ( ws: x^ width^ x^ )
ADD2 ( ws: x^ limit^ )
STH2 ( ws: x^ / rs: limit^ )
.Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
STH2kr ( copy limit from rs into ws )
LTH2 ,&loop-x JCN ( jump if x is less than the limit )
POP2r ( pop limit from rs )
RTN
```
### complete program
the following shows our program in context, completely filling the first row of our screen with our tile:
=> ./img/screenshot_uxn-background-row.png screenshot showing the first row of the varvara screen filled with diagonal lines
```
( hello-background.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
( macros )
%RTN { JMP2r }
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
;tile-background .Screen/addr DEO2 ( set tile address )
#0000 ( initial x )
.Screen/width DEI2 ( get screen width )
;draw-tiles-in-a-row JSR2
BRK
@draw-tiles-in-a-row ( x^ width^ -- )
OVR2 ( ws: x^ limit^ x^ )
ADD2 ( ws: x^ limit^ )
STH2 ( ws: x^ / rs: limit^ )
.Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
STH2kr ( copy limit from rs into ws )
LTH2 ,&loop-x JCN ( jump if x is less than the limit )
POP2r ( pop limit from rs )
RTN
@tile-background 1122 4488 1122 4488
```
## repeating a row
similar to what we just did: what's a procedure we could follow to repeat vertically a row starting from y, and ending at a limit corresponding to y+height?
following the same strategy, we could do
* draw row in y
* add 8 (the size of the tile) to y
* is y less than the limit? if it is, repeat procedure, otherwise end
### concrete version
let's use the same numbers as before, assuming that our initial y is 0008, our height is 0100, and therefore our limit would be 0108.
in the case of x, let's start at 0000 and have a width corresponding to the screen width.
as the address wouldn't change in the process, we can set it up at the top and forget about it.
the following code is based on the previous x loop, but it now draws a row in a given y coordinate, adds 8 to it and then checks if it's less than the limit:
```
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/y ( set initial y )
&loop-y
( prepare and draw row )
#0000 ( initial x )
.Screen/width DEI2 ( get screen width )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
#0108 ( put limit in top of the stack )
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
### abstract version
now, before jumping right into emulating the solution for drawing the row, let's note that in this case it is not that easy.
why? because the idea for our draw-tiles subroutine is that it should be able to receive the initial x and the width of the rectangle, and right now these values are hardcoded inside the loop.
this should be the signature for our subroutine:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
```
we can approach this problem either with some "stack wrangling", or with "variables".
also note that we will do that because we are trying to get to a generalized subroutine.
if we just wanted to cover all the screen with a sprite, we have all the required code already: we would only need to adapt the vertical limit of the loop to correspond to the height of the screen, and that would be it!
we could then just jump to the section regarding the paddles. however, what follows can be interesting as a way of looking at possible approach to write more complex uxntal code :)
### using stack wrangling
if you tried using the F2 key while running your program before today, you would have found that apparently nothing happened.
in principle we could just manipulate the items given in the stack, stashing them when appropriate, in order to adapt our subroutine to its signature.
that was because the on-screen debugger uses the screen device, and therefore needs the system colors to be set.
first of all, the tile address is the value at the top of the stack. we can just consume it and forget about it:
now that you have some system colors set, run your program and press the F2 key: you'll see several elements now!
```
( initial ws: x^ y^ width^ height^ addr* )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
```
thinking about the vertical loop, we need to calculate its limit adding height to y, and we need to set the initial y.
we could do the following:
```
ROT2 ( ws: x^ width^ height^ y^ )
DUP2 ( ws: x^ width^ height^ y^ y^ )
( set initial y: )
.Screen/y DEO2 ( ws: x^ width^ height^ y^ )
( calculate and stash vertical limit )
ADD2 ( ws: x^ width^ limit-y^ )
STH2 ( ws: x^ width^ / rs: limit-y^ )
```
now, we might be able to stash also the width and 'x', as we need them afterwards in their original order (first x, then width )
```
STH2 ( ws: x^ / rs: limit-y^ width^ )
STH2 ( ws: / rs: limit-y^ width^ x^ )
```
in theory, the first part of our subroutine could look like:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
( set tile address )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
ROT2 ( ws: x^ width^ height^ y^ )
DUP2 ( ws: x^ width^ height^ y^ y^ )
( set initial y )
.Screen/y DEO2 ( set initial y, ws: x^ width^ height^ y^ )
( calculate and stash limit-y )
ADD2 ( ws: x^ width^ limit-y^ )
STH2 ( ws: x^ width^ / rs: limit-y^ )
( stash width and x )
STH2 STH2 ( ws: / rs: limit-y^ width^ x^ )
&loop-y
( prepare and draw row )
( retrieve x )
STH2r ( ws: x^ / rs: limit-y^ width^ )
( retrieve width )
STH2r ( ws: x^ width^ / rs: limit-y^ )
;draw-tiles-in-a-row JSR2
```
the problem is that inside the loop, both STH2r instructions retrieve and consume the values for x and width from the return stack. therefore, in the next iteration we wouldn't be able to use them again, as they would be lost.
we can think we could replace these instructions with STH2kr:
```
&loop-y
( prepare and draw row )
( retrieve x )
STH2kr ( ws: x^ / rs: limit-y^ width^ x^ )
```
but then we can't retrieve the width because the x is still at the top of the return stack!
oh, many difficulties, but for the sake of the stack wrangling example, let's continue solving this (?)
how can we put the width at the top of the return stack? maybe with a swap applied to the return stack:
```
SWP2r ( ws: x^ / rs: limit-y^ x^ width^ )
```
then we can then retrieve the width and use it:
```
STH2kr ( ws: x^ width^ / rs: limit-y^ x^ width^ )
;draw-tiles-in-a-row JSR2 ( ws: / rs: limit-y^ x^ width^ )
```
what's next? add 8 to y, and check if it's less than the limit. the first part goes without problems:
```
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y; ws: y^ y^ / rs: limit-y^ x^ width^ )
.Screen/y DEO2 ( store new y; ws: y^ / rs: limit-y^ x^ width^ )
```
in order to get the limit into the working stack for the comparison, we have to rotate the return stack:
```
ROT2r ( ws: y^ / rs: x^ width^ limit-y^ )
STH2kr ( ws: y^ limit-y^ / rs: x^ width^ limit-y^ )
```
but ah, before doing the comparison and jumping, we should rearrange the return stack so that it corresponds to the ordering we had at the beginning of the loop:
```
SWP2r ( ws: y^ limit-y^ / rs: x^ limit-y^ width^ )
ROT2r ( ws: y^ limit-y^ / rs: limit-y^ width^ x^ )
```
now we can do the comparison and jump:
```
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
afterwards we should clear the return stack:
=> ./img/screenshot_uxn-debugger.png screenshot of the on-screen debugger using the assigned system colors
* there are some lines and a crosshair in the center, drawn with color 2
* at the top left, there are four rows of eight bytes each, represented in hexadecimal and drawn with color 1; these 32 bytes show the deeper contents of the stack, with the stack "top" highlighted using color 2
* below, there is a single byte drawn with color 2: it corresponds to the address of the top of the return stack that we discuss on {uxn tutorial day 5}
* finally, there is another set of 32 bytes, drawn with color 3; these show the contents of the first section of the zero page in the main memory, discussed on {uxn tutorial day 4}
```
POP2r POP2r POP2r
```
remember: you can use the F1 key to switch between different zoom levels.
after all this, our subroutine would look like the following:
take a look at the representation of the stack: if you didn't change the values i suggested above, you'll see the following numbers at the top left:
``` 2c e5 0c
[2c] e5 0c
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
( set tile address )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
ROT2 ( ws: x^ width^ height^ y^ )
DUP2 ( ws: x^ width^ height^ y^ y^ )
( set initial y )
.Screen/y DEO2 ( set initial y, ws: x^ width^ height^ y^ )
( calculate and stash limit-y )
ADD2 ( ws: x^ width^ limit-y^ )
STH2 ( ws: x^ width^ / rs: limit-y^ )
( stash width and x )
STH2 STH2 ( ws: / rs: limit-y^ width^ x^ )
what are these numbers?
&loop-y
( prepare and draw row )
( retrieve x )
STH2kr ( ws: x^ / rs: limit-y^ width^ x^ )
2ce5 is the short we assigned to the blue components of the system colors, and 0c is the i/o address of the short corresponding to .System/b ! (can you calculate and enumerate what are the numerical addresses of each of the color components in the system device?)
( retrieve width )
SWP2r ( ws: x^ / rs: limit-y^ x^ width^ )
STH2kr ( ws: x^ width^ / rs: limit-y^ x^ width^ )
;draw-tiles-in-a-row JSR2 ( ws: / rs: limit-y^ x^ width^ )
those values are the leftovers from the last operation on our program, the DEO2 that set the blue component of the system colors.
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
we can think of the highlight in the leftmost 2c, as an arrow pointing leftwards to the "top" of the stack. it current position implies that the stack is empty, as there are no more elements to its left.
note that the stack memory is not erased when taking elements out of it, what changes is the value of the address that points to its top.
( retrieve limit-y )
ROT2r ( ws: y^ / rs: x^ width^ limit-y^ )
STH2kr ( ws: y^ limit-y^ / rs: x^ width^ limit-y^ )
# stack debugging test
( rearrange return stack )
SWP2r ( ws: y^ limit-y^ / rs: x^ limit-y^ width^ )
ROT2r ( ws: y^ limit-y^ / rs: limit-y^ width^ x^ )
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
POP2r POP2r POP2r ( clear return stack )
RTN
```
we can then call it like the following in order to get a 256x256 square filled with tiles:
let's try appending to our program the example code we discussed above, adding it after setting the system colors:
```
#0008 #0008 ( x and y )
#0100 #0100 ( width and height )
;tile-background
;draw-tiles JSR2
#0004 #0008 ADD2
```
=> ./img/screenshot_uxn-background-square.png screenshot showing a big square in the varvara screen composed of diagonal lines
run it, open the debugger, and see the contents of the stack.
### using variables
what does it mean what you see?
let's compare the previous approach with the use of relative variables.
we will go "all in" in a relatively wasteful way, without optimizing for procedures that could benefit from stack manipulation.
we'll declare the following labels for our variables, after the RTN that ends the subroutine:
if everything went alright, you'll see:
```
( variables )
&height $2 &width $2 &y $2 &x $2 &limit-y $2
00 0c [00] 08
```
now, we start the subroutine in the same way as before, setting the address for our sprite:
if we think of the highlight as an arrow pointing left towards the top of the stack, we'll see that its position corresponds with the result that we would have expected!
```
( initial ws: x^ y^ width^ height^ addr* )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
```
then, we just store the next values in relative addresses:
```
,&height STR2
,&width STR2
,&y STR2
,&x STR2
```
note that we go in reverse order.
after these operations the stacks are empty.
we can then set the initial y and calculate the vertical limit, using the values stored in the variables:
```
( set initial y )
,&y LDR2 DUP2 ( ws: y^ y^ )
.Screen/y DEO2 ( ws: y^ )
( calculate limit-y )
,&height LDR2 ( ws: y^ height^ )
ADD2 ( ws: limit-y^ )
,&limit-y STR2 ( ws: )
```
our loop now would look as follows:
00 0c <- top
```
&loop-y
( retrieve x and width )
,&x LDR2
,&width LDR2
( draw row )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
000c is the result of the addition that was performed, that it is now stored in the stack!
( retrieve vertical limit )
,&limit-y LDR2
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
and that's it!
compare this with the "concrete" version we developed above, it's very similar in its structure!
the complete subroutine would look like the following:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
( set tile address )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
( store values )
,&height STR2
,&width STR2
,&y STR2
,&x STR2
( set initial y )
,&y LDR2 DUP2 ( ws: y^ y^ )
.Screen/y DEO2 ( ws: y^ )
( calculate vertical limit )
,&height LDR2 ( ws: y^ height^ )
ADD2 ( ws: limit-y^ )
,&limit-y STR2 ( ws: )
&loop-y
( retrieve x and width )
,&x LDR2
,&width LDR2
( draw row )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
( retrieve vertical limit )
,&limit-y LDR2
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
RTN
( variables )
&height $2 &width $2 &y $2 &x $2 &limit-y $2
```
as i said before, we can find here some opportunities for optimization.
maybe the vertical limit can be stashed in the return stack like in the draw-tiles-in-a-row loop, or maybe the variable for the height and for the initial y are not needed.
i'll let you figure them out :)
note that this subroutine as-is, requires 24 bytes of program memory more than the stack wrangling version.
in our case that's not much of a problem, but it's a good way of evaluating our priorities: super readable but probably inefficient code (like this last subroutine), super optimized but probably unreadable code (write-only code, they say), or something in the middle.
## draw-background
now that we have these nice subroutines, we can just wrap them in another one that will cover the whole background with our chosen tile.
for example:
```
@draw-background ( -- )
#0000 #0010 ( initial x and y )
.Screen/width DEI2
.Screen/height DEI2 #0010 SUB2
;tile-background
;draw-tiles JSR2
RTN
```
that we can simply call from our initialization subroutine:
```
;draw-background JSR2
```
the highlighted 00, and the 08 to its right, correspond to the 0008 of our second operand. they were used by the ADD2 instruction already, but they are left unused in the stack memory. they would stay there until overwritten.
=> ./img/screenshot_uxn-background-full.png screenshot showing the varvara screen covered in diagonal lines.
in general, if our program is functioning alright, we will see the highlight of the top of the stack always at the top left position.
nice! i recommend you take a little break; this was heavy!
otherwise, it means that our operations with the stack were left unbalanced: there were more elements added to it than element removed from it.
# support

View File

@ -0,0 +1,597 @@
# uxn tutorial appendix b: repeating a tile inside a rectangle
in the first part of {uxn tutorial day 6} we discuss how to cover the background of the screen with a given tile.
here we will generalize a similar procedure into a draw-tiles subroutine that draws a rectangle filled with a given tile. it will receive the x,y coordinates of the top left corner of the rectangle, its width and height in pixels, and the address of the tile:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
```
a reminder that we are using the convention of adding a caret (^) after the name of a value to indicate it's a short, and an asterisk (*) to indicate it's a short working as a pointer (i.e. an address in program memory)
we will detail how to get to two versions of this subroutine, one that relies on heavy stack wrangling, and other one that uses variables. this in order to compare both approaches and give us a broader view of the possibilities within uxntal.
## setting up
let's start with the following program as a template. it includes the data for a 1bpp sprite consisting of diagonal lines.
```
( hello-background.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
( macros )
%RTN { JMP2r }
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
BRK
@tile-background 1122 4488 1122 4488
```
## repeating a tile in a row
what's a procedure we could follow to repeat the drawing of a tile starting from x, and ending at a limit corresponding to x+width?
one way would be something like:
* draw tile in x
* add 8 (the size of the tile) to x
* is x less than the limit? if it is, repeat procedure, otherwise end
### concrete version
before abstracting it away, i recommed we write it with concrete numbers.
let's say our initial x is 0008, our width is 0100, and the tile we are drawing is tile-background.
the limit, x+width, would be 0108.
the first step, drawing the tile in x would be:
```
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/x DEO2 ( set initial x )
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
```
adding 8 to x, we know already:
```
.Screen/x DEI2 #0008 ADD2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
```
checking if x is less than the limit, jumping if it is, would be something like:
```
.Screen/x DEI2
#0108 ( the limit )
LTH2 ,&loop JCN ( jump if x is less than the limit )
```
integrating all of it, we would be able to get:
```
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
#0108 LTH2 ,&loop-x JCN ( jump if x is less than the limit )
```
note the use of DUP2 in order to avoid re-reading the value of x.
### abstracting
now, let's say we want to have the previous code work with any given initial x and width, present in the stack before starting.
we can even think of it as a subroutine by itself with the following signature:
```
@draw-tiles-in-a-row ( x^ width^ -- )
```
let's assume for the moment that the address of the sprite was already set, in order to focus in x and width.
when starting the subroutine, the width is at the top of the stack, followed by initial x.
we can use these two values to calculate the limit, that we can stash in the return stack.
one way of achieving that, noting the state of the working stack after each instruction, could be:
```
( initial state: ws: x^ width^ )
OVR2 ( ws: x^ width^ x^ )
ADD2 ( ws: x^ limit^ )
STH2 ( ws: x^ / rs: limit^ )
```
another one:
```
( initial state: ws: x^ width^ )
ADD2k ( ws: x^ width^ limit^ )
STH2 ( ws: x^ width^ / rs: limit^ )
POP2 ( ws: x^ / rs: limit^ )
```
remember that we are showing the top of the stacks at their right.
after these steps, the initial x is at the top of the stack, so we can send it directly to the screen.
the last change that we would need is to replace our hardcoded limit with a STH2kr instruction (copy limit from the return stack into the working stack), and end our routine with a POP2r (remove limit from return stack).
our subroutine would then look as follows:
```
@draw-tiles-in-a-row ( x^ width^ -- )
( calculate and save limit )
OVR2 ( ws: x^ width^ x^ )
ADD2 ( ws: x^ limit^ )
STH2 ( ws: x^ / rs: limit^ )
.Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
STH2kr ( copy limit from rs into ws )
LTH2 ,&loop-x JCN ( jump if x is less than the limit )
POP2r ( pop limit from rs )
RTN
```
### complete program
the following shows our program in context, completely filling the first row of our screen with our tile:
=> ./img/screenshot_uxn-background-row.png screenshot showing the first row of the varvara screen filled with diagonal lines
```
( hello-background.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
( macros )
%RTN { JMP2r }
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
;tile-background .Screen/addr DEO2 ( set tile address )
#0000 ( initial x )
.Screen/width DEI2 ( get screen width )
;draw-tiles-in-a-row JSR2
BRK
@draw-tiles-in-a-row ( x^ width^ -- )
OVR2 ( ws: x^ limit^ x^ )
ADD2 ( ws: x^ limit^ )
STH2 ( ws: x^ / rs: limit^ )
.Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
STH2kr ( copy limit from rs into ws )
LTH2 ,&loop-x JCN ( jump if x is less than the limit )
POP2r ( pop limit from rs )
RTN
@tile-background 1122 4488 1122 4488
```
## repeating a row
similar to what we just did: what's a procedure we could follow to repeat vertically a row starting from y, and ending at a limit corresponding to y+height?
following the same strategy, we could do
* draw row in y
* add 8 (the size of the tile) to y
* is y less than the limit? if it is, repeat procedure, otherwise end
### concrete version
let's use the same numbers as before, assuming that our initial y is 0008, our height is 0100, and therefore our limit would be 0108.
in the case of x, let's start at 0000 and have a width corresponding to the screen width.
as the address wouldn't change in the process, we can set it up at the top and forget about it.
the following code is based on the previous x loop, but it now draws a row in a given y coordinate, adds 8 to it and then checks if it's less than the limit:
```
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/y ( set initial y )
&loop-y
( prepare and draw row )
#0000 ( initial x )
.Screen/width DEI2 ( get screen width )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
#0108 ( put limit in top of the stack )
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
### abstract version
now, before jumping right into emulating the solution for drawing the row, let's note that in this case it is not that easy.
why? because the idea for our draw-tiles subroutine is that it should be able to receive the initial x and the width of the rectangle, and right now these values are hardcoded inside the loop.
this should be the signature for our subroutine:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
```
we can approach this problem either with some "stack wrangling", or with "variables".
also note that we will do that because we are trying to get to a generalized subroutine.
if we just wanted to cover all the screen with a sprite, we have all the required code already: we would only need to adapt the vertical limit of the loop to correspond to the height of the screen, and that would be it!
we could then just jump to the section regarding the paddles. however, what follows can be interesting as a way of looking at possible approach to write more complex uxntal code :)
### using stack wrangling
in principle we could just manipulate the items given in the stack, stashing them when appropriate, in order to adapt our subroutine to its signature.
first of all, the tile address is the value at the top of the stack. we can just consume it and forget about it:
```
( initial ws: x^ y^ width^ height^ addr* )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
```
thinking about the vertical loop, we need to calculate its limit adding height to y, and we need to set the initial y.
we could do the following:
```
ROT2 ( ws: x^ width^ height^ y^ )
DUP2 ( ws: x^ width^ height^ y^ y^ )
( set initial y: )
.Screen/y DEO2 ( ws: x^ width^ height^ y^ )
( calculate and stash vertical limit )
ADD2 ( ws: x^ width^ limit-y^ )
STH2 ( ws: x^ width^ / rs: limit-y^ )
```
now, we might be able to stash also the width and 'x', as we need them afterwards in their original order (first x, then width )
```
STH2 ( ws: x^ / rs: limit-y^ width^ )
STH2 ( ws: / rs: limit-y^ width^ x^ )
```
in theory, the first part of our subroutine could look like:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
( set tile address )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
ROT2 ( ws: x^ width^ height^ y^ )
DUP2 ( ws: x^ width^ height^ y^ y^ )
( set initial y )
.Screen/y DEO2 ( set initial y, ws: x^ width^ height^ y^ )
( calculate and stash limit-y )
ADD2 ( ws: x^ width^ limit-y^ )
STH2 ( ws: x^ width^ / rs: limit-y^ )
( stash width and x )
STH2 STH2 ( ws: / rs: limit-y^ width^ x^ )
&loop-y
( prepare and draw row )
( retrieve x )
STH2r ( ws: x^ / rs: limit-y^ width^ )
( retrieve width )
STH2r ( ws: x^ width^ / rs: limit-y^ )
;draw-tiles-in-a-row JSR2
```
the problem is that inside the loop, both STH2r instructions retrieve and consume the values for x and width from the return stack. therefore, in the next iteration we wouldn't be able to use them again, as they would be lost.
we can think we could replace these instructions with STH2kr:
```
&loop-y
( prepare and draw row )
( retrieve x )
STH2kr ( ws: x^ / rs: limit-y^ width^ x^ )
```
but then we can't retrieve the width because the x is still at the top of the return stack!
oh, many difficulties, but for the sake of the stack wrangling example, let's continue solving this (?)
how can we put the width at the top of the return stack? maybe with a swap applied to the return stack:
```
SWP2r ( ws: x^ / rs: limit-y^ x^ width^ )
```
then we can then retrieve the width and use it:
```
STH2kr ( ws: x^ width^ / rs: limit-y^ x^ width^ )
;draw-tiles-in-a-row JSR2 ( ws: / rs: limit-y^ x^ width^ )
```
what's next? add 8 to y, and check if it's less than the limit. the first part goes without problems:
```
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y; ws: y^ y^ / rs: limit-y^ x^ width^ )
.Screen/y DEO2 ( store new y; ws: y^ / rs: limit-y^ x^ width^ )
```
in order to get the limit into the working stack for the comparison, we have to rotate the return stack:
```
ROT2r ( ws: y^ / rs: x^ width^ limit-y^ )
STH2kr ( ws: y^ limit-y^ / rs: x^ width^ limit-y^ )
```
but ah, before doing the comparison and jumping, we should rearrange the return stack so that it corresponds to the ordering we had at the beginning of the loop:
```
SWP2r ( ws: y^ limit-y^ / rs: x^ limit-y^ width^ )
ROT2r ( ws: y^ limit-y^ / rs: limit-y^ width^ x^ )
```
now we can do the comparison and jump:
```
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
afterwards we should clear the return stack:
```
POP2r POP2r POP2r
```
after all this, our subroutine would look like the following:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
( set tile address )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
ROT2 ( ws: x^ width^ height^ y^ )
DUP2 ( ws: x^ width^ height^ y^ y^ )
( set initial y )
.Screen/y DEO2 ( set initial y, ws: x^ width^ height^ y^ )
( calculate and stash limit-y )
ADD2 ( ws: x^ width^ limit-y^ )
STH2 ( ws: x^ width^ / rs: limit-y^ )
( stash width and x )
STH2 STH2 ( ws: / rs: limit-y^ width^ x^ )
&loop-y
( prepare and draw row )
( retrieve x )
STH2kr ( ws: x^ / rs: limit-y^ width^ x^ )
( retrieve width )
SWP2r ( ws: x^ / rs: limit-y^ x^ width^ )
STH2kr ( ws: x^ width^ / rs: limit-y^ x^ width^ )
;draw-tiles-in-a-row JSR2 ( ws: / rs: limit-y^ x^ width^ )
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
( retrieve limit-y )
ROT2r ( ws: y^ / rs: x^ width^ limit-y^ )
STH2kr ( ws: y^ limit-y^ / rs: x^ width^ limit-y^ )
( rearrange return stack )
SWP2r ( ws: y^ limit-y^ / rs: x^ limit-y^ width^ )
ROT2r ( ws: y^ limit-y^ / rs: limit-y^ width^ x^ )
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
POP2r POP2r POP2r ( clear return stack )
RTN
```
we can then call it like the following in order to get a 256x256 square filled with tiles:
```
#0008 #0008 ( x and y )
#0100 #0100 ( width and height )
;tile-background
;draw-tiles JSR2
```
=> ./img/screenshot_uxn-background-square.png screenshot showing a big square in the varvara screen composed of diagonal lines
### using variables
let's compare the previous approach with the use of relative variables.
we will go "all in" in a relatively wasteful way, without optimizing for procedures that could benefit from stack manipulation.
we'll declare the following labels for our variables, after the RTN that ends the subroutine:
```
( variables )
&height $2 &width $2 &y $2 &x $2 &limit-y $2
```
now, we start the subroutine in the same way as before, setting the address for our sprite:
```
( initial ws: x^ y^ width^ height^ addr* )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
```
then, we just store the next values in relative addresses:
```
,&height STR2
,&width STR2
,&y STR2
,&x STR2
```
note that we go in reverse order.
after these operations the stacks are empty.
we can then set the initial y and calculate the vertical limit, using the values stored in the variables:
```
( set initial y )
,&y LDR2 DUP2 ( ws: y^ y^ )
.Screen/y DEO2 ( ws: y^ )
( calculate limit-y )
,&height LDR2 ( ws: y^ height^ )
ADD2 ( ws: limit-y^ )
,&limit-y STR2 ( ws: )
```
our loop now would look as follows:
```
&loop-y
( retrieve x and width )
,&x LDR2
,&width LDR2
( draw row )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
( retrieve vertical limit )
,&limit-y LDR2
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
and that's it!
compare this with the "concrete" version we developed above, it's very similar in its structure!
the complete subroutine would look like the following:
```
@draw-tiles ( x^ y^ width^ height^ addr* -- )
( set tile address )
.Screen/addr DEO2 ( ws: x^ y^ width^ height^ )
( store values )
,&height STR2
,&width STR2
,&y STR2
,&x STR2
( set initial y )
,&y LDR2 DUP2 ( ws: y^ y^ )
.Screen/y DEO2 ( ws: y^ )
( calculate vertical limit )
,&height LDR2 ( ws: y^ height^ )
ADD2 ( ws: limit-y^ )
,&limit-y STR2 ( ws: )
&loop-y
( retrieve x and width )
,&x LDR2
,&width LDR2
( draw row )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
( retrieve vertical limit )
,&limit-y LDR2
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
RTN
( variables )
&height $2 &width $2 &y $2 &x $2 &limit-y $2
```
as i said before, we can find here some opportunities for optimization.
maybe the vertical limit can be stashed in the return stack like in the draw-tiles-in-a-row loop, or maybe the variable for the height and for the initial y are not needed.
i'll let you figure them out :)
note that this subroutine as-is, requires 24 bytes of program memory more than the stack wrangling version.
in our case that's not much of a problem, but it's a good way of evaluating our priorities: super readable but probably inefficient code (like this last subroutine), super optimized but probably unreadable code (write-only code, they say), or something in the middle.
## draw-background
now that we have these nice subroutines, we can just wrap them in another one that will cover the whole background with our chosen tile.
for example:
```
@draw-background ( -- )
#0000 #0010 ( initial x and y )
.Screen/width DEI2
.Screen/height DEI2 #0010 SUB2
;tile-background
;draw-tiles JSR2
RTN
```
that we can simply call from our initialization subroutine:
```
;draw-background JSR2
```
=> ./img/screenshot_uxn-background-full.png screenshot showing the varvara screen covered in diagonal lines.
nice! i recommend you take a little break; this was heavy!
# support
if you found this tutorial to be helpful, consider sharing it and giving it your {support} :)

View File

@ -201,83 +201,13 @@ if we run the program now we'll see a dark purple screen, instead of black as wh
try changing the values of color 0, i.e. the first column, and see what happens :)
# on-screen debugger
we will take a little detour in order to talk about the on-screen debugger, that we can use now thanks to setting the system colors.
if you prefer to jump right into drawing to the screen, feel free to skip this section :)
## the debugger
if you tried using the F2 key while running your program before today, you would have found that apparently nothing happened.
that was because the on-screen debugger uses the screen device, and therefore needs the system colors to be set.
now that you have some system colors set, run your program and press the F2 key: you'll see several elements now!
=> ./img/screenshot_uxn-debugger.png screenshot of the on-screen debugger using the assigned system colors
* there are some lines and a crosshair in the center, drawn with color 2
* at the top left, there are four rows of eight bytes each, represented in hexadecimal and drawn with color 1; these 32 bytes show the deeper contents of the stack, with the stack "top" highlighted using color 2.
* below, there is a single byte drawn with color 2: it corresponds to the address of the top of the return stack (we'll talk about it on day 5)
* finally, there is another set of 32 bytes, drawn with color 3; these show the contents of the first section of the zero page in the main memory.
remember: you can use the F1 key to switch between different zoom levels.
take a look at the representation of the stack: if you didn't change the values i suggested above, you'll see the following numbers at the top left:
``` 2c e5 0c
[2c] e5 0c
```
what are these numbers?
2ce5 is the short we assigned to the blue components of the system colors, and 0c is the i/o address of the short corresponding to .System/b ! (can you calculate and enumerate what are the numerical addresses of each of the color components in the system device?)
those values are the leftovers from the last operation on our program, the DEO2 that set the blue component of the system colors.
we can think of the highlight in the leftmost 2c, as an arrow pointing leftwards to the "top" of the stack. it current position implies that the stack is empty, as there are no more elements to its left.
note that the stack memory is not erased when taking elements out of it, what changes is the value of the address that points to its top.
## stack debugging test
let's try appending to our program the example code we discussed above, adding it after setting the system colors:
```
#0004 #0008 ADD2
```
run it, open the debugger, and see the contents of the stack.
what does it mean what you see?
if everything went alright, you'll see:
```
00 0c [00] 08
```
if we think of the highlight as an arrow pointing left towards the top of the stack, we'll see that its position corresponds with the result that we would have expected!
```
00 0c <- top
```
000c is the result of the addition that was performed, that it is now stored in the stack!
the highlighted 00, and the 08 to its right, correspond to the 0008 of our second operand. they were used by the ADD2 instruction already, but they are left unused in the stack memory. they would stay there until overwritten.
in general, if our program is functioning alright, we will see the highlight of the top of the stack always at the top left position.
otherwise, it means that our operations with the stack were left unbalanced: there were more elements added to it than element removed from it.
now that we have set the system colors it's possible for us to use the on-screen debugger available in uxnemu! you can read more about it in the {uxn tutorial appendix a}.
# the screen device
as a recap: we mentioned that the screen device can only show four different colors at a given time, and that these colors are numbered from 0 to 3. we set these these colors using the corresponding ports in the system device.
now let's discuss the screen device further and start using it!
now let's discuss the screen device and start using it!
## inputs and outputs

View File

@ -318,7 +318,7 @@ that we can simply call from our initialization subroutine:
nice!
in {uxn tutorial appendix a} you can find a detailed discussion of how to generalize a procedure like this one into a draw-tiles subroutine that draws an arbitrary rectangle filled with a given tile.
in {uxn tutorial appendix b} you can find a detailed discussion of how to generalize a procedure like this one into a draw-tiles subroutine that draws an arbitrary rectangle filled with a given tile.
it goes into several possibilities for using uxntal in that abstract way: i'd say it's very interesting, but probably out of scope for making the game :)