diff --git a/Docs/Tutorials/BigBlue/TaleOfBigBlue.md b/Docs/Tutorials/BigBlue/TaleOfBigBlue.md index 76e50c6d..8257b7a8 100644 --- a/Docs/Tutorials/BigBlue/TaleOfBigBlue.md +++ b/Docs/Tutorials/BigBlue/TaleOfBigBlue.md @@ -8,7 +8,7 @@ Part 1: Outlaw Editor I'm Martin Haye, and I'm gonnaa to tell you the story of a pixel called Big Blue, and his journey from Seth's mind to an Apple II screen. Along the way we'll be taking a detailed tour of the code and data behind the scenes so you can get a feel for how it all works. -[TODO: back-fill text and links] +[TODO: back-fill text and links] TK Part 2: Casting Rays -------------------- @@ -102,3 +102,218 @@ The results of all this math for a given horizontal coordinate are: (1) the wall Next time we'll see this code on the Apple II, and take a look at how the results get drawn on the hi-res graphics screen. + +Part 3: ...and calc! and blend! and calc! +----------------------------------------- + +*Hey!* Check out the video screencast of part 3: TK + +Let's do some pixel calisthenics! In part 3 we're going to see how all that ray-casting logic works on the Apple II, and then delve into the mysteries of blending color pixels onto the hi-res screen. The final part next time will cover the secret sauce, fast scaling of Big Blue's texture image to the proper size. + +So I showed you a bunch of ray casting code in Javascript. Let's take a quick look at the 6502 assembly language code that does the same stuff. You don't have to understand it all, but it's good to know where it is and roughly what it does. + +First, we set up the player's position and direction. We store each coordinate in two bytes: the low byte is the fractional part (0..255, so $80 = 128 = 0.5), and the high byte is the whole part (1, 2, etc.). +[BigBlue3_10](https://github.com/badvision/lawless-legends/search?q=BigBlue3_10) + +```Assembly +; Establish the initial player position and direction [ref BigBlue3_10] +setPlayerPos: + ; X=1.5 + lda #1 + sta playerX+1 + lda #$80 + sta playerX + ; Y=2.5 + lda #2 + sta playerY+1 + lda #$80 + sta playerY + ; direction=0 + lda #0 + sta playerDir + rts +``` + +Remember those logarithm tables we created in the Javascript code? And the table of vectors for each possible angle? They're just encoded directly here rather than computed on the 6502. [BigBlue3_20](https://github.com/badvision/lawless-legends/search?q=BigBlue3_20) + +```Assembly +; Table to translate an unsigned byte to 3+5 bit fixed point log2 [ref BigBlue3_20] +tbl_log2_b_b: + .byte $00,$00,$00,$00,$00,$07,$0C,$11,$15,$19,$1C,$1F,$22,$24,$27,$29 + .byte $2B,$2D,$2E,$30,$32,$33,$34,$36,$37,$38,$3A,$3B,$3C,$3D,$3E,$3F + .byte $40,$41,$42,$43,$44,$44,$45,$46,$47,$48,$48,$49,$4A,$4A,$4B,$4C + ;...etc... +``` + +[BigBlue3_30](https://github.com/badvision/lawless-legends/search?q=BigBlue3_30) + +```Assembly +; Precalculated ray initialization parameters. One table for each of the 16 angles. +; Each angle has 63 rays, and each ray is provided with 4 parameters (one byte each param): +; dirX, dirY, deltaX, deltaY. [ref BigBlue3_30] +precast_0: + .byte $72,$C7,$3E,$7C + .byte $72,$C9,$3D,$7E + .byte $72,$CB,$2C,$5E + .byte $72,$CD,$39,$7E + ;...etc... +precast_1: + .byte $7F,$F7,$09,$7F + .byte $7E,$F9,$05,$56 + .byte $7E,$FA,$05,$6F + .byte $7D,$FC,$04,$7D + ;...etc... +``` + +Here's the code to process a keypress from the player. [BigBlue3_40](https://github.com/badvision/lawless-legends/search?q=BigBlue3_40) + +```Assembly + ; Dispatch the keypress [ref BigBlue3_40] +: cmp #'W' ; 'W' for forward + bne :+ + jsr moveForward + jmp @nextFrame +: cmp #'X' ; 'X' alternative for 'S' + bne :+ + lda #'S' +: cmp #'S' ; 'S' for backward + bne :+ + jsr moveBackward + jmp @nextFrame +: cmp #'A' ; 'A' for left + bne :+ + ; ...etc... +``` + +When we need to re-draw, this code steps through each ray, calculating the texture number, coordinate, and height, then drawing it. +[BigBlue3_50](https://github.com/badvision/lawless-legends/search?q=BigBlue3_50) + +```Assembly + ; Calculate the height, texture number, and texture column for one ray + ; [ref BigBlue3_50] +@oneCol: + stx pMap ; set initial map pointer for the ray + sty pMap+1 + phy ; save map row ptr + phx + pha ; save ray offset + tay ; ray offset where it needs to be + jsr castRay ; cast the ray across the map + lda pixNum + bne :+ + jsr clearBlit ; clear blit on the first pixel +: jsr drawRay ; and draw the ray +``` + +And finally there's a whole bunch of code that does all that complicated math we don't understand. I'm not going to explain all the code in depth, other than to say it does the same thing the Javascript code did... just using a lot more lines! +[BigBlue3_60](https://github.com/badvision/lawless-legends/search?q=BigBlue3_60) + +```Assembly +;------------------------------------------------------------------------------- +; Cast a ray [ref BigBlue3_60] +; Input: pRayData, plus Y reg: precalculated ray data (4 bytes) +; playerX, playerY (integral and fractional bytes of course) +; pMap: pointer to current row on the map (mapBase + playerY{>}*height) +; Output: lineCt - height to draw in double-lines +; txColumn - column in the texture to draw +castRay: + ; First, grab the precalculated ray data from the table. + ldx #1 ; default X step: forward one column of the map + lda (pRayData),y ; rayDirX + ; ...and lots more code after this... +``` + +Okay, we're done covering ground we've seen before. Let's move on to a weighty subject: getting pixels on the screen. The Apple II's hi-res graphics memory is organized very strangely. The easy part is that each line is 40 consecutive bytes. However, the address for line 2 is not right after the address for line 1, and in general a weird formula is required to determine the starting address of a line. + +| Line number | Start Address | End Address | +| ----------- | ------------- | ----------- | +| 0 | $2000 | $2027 | +| 1 | $2400 | $2427 | +| 2 | $2800 | $2827 | +| 3 | $2C00 | $2C27 | +| ... | ... | ... | +| 8 | $2080 | $20A7 | +| 3 | $2180 | $21A7 | + +It gets even weirder. Each byte stores 7 black-and-white pixels. What about color? In color mode each *pair* of pixels is taken in turn to mean a single color pixel. That means each byte stores *three and a half* pixels! Sounds crazy, yes? Sounds like it would lead to a very complicated program, and make it very time-consuming to put one pixel onto the screen. + +You may have watched an image being loaded onto the Apple II hi-res screen. You'll have noticed that it loads in bands -- that's due to the weirdness we're talking about. + +![Partly loaded hi-res screen](partScreen.png) + +We don't want weird and complex, we need simple and fast. So our we throw some smarts at the problem to isolate all the complexity to a small part of our program, so the rest of the program doesn't have to worry about it. We use a technique called "unrolling". That's a little program that writes a big repetetive program into memory. In our case the unrolled routine has a block of code for every line on the screen, and each block contains an instruction or two for each kind of bit pair on that line. Code outside simply sticks the pixels in very regular and easy-to-calculate places, then calls the unrolled loop to blast everything onto the screen at high speed. In programming circles we call that blasting process a "bit blit" (stands for Bit-Level Block Transfer). + +Here's the template for one block of the blit. This template gets copied and repeated for each line on the screen, substituting different addresses for the screen lines. [BigBlue3_70](https://github.com/badvision/lawless-legends/search?q=BigBlue3_70) + +```Assembly +; Template for blitting code [ref BigBlue3_70] +blitTemplate: ; comments show byte offset + lda decodeTo57 ; 0: pixel 3 + asl ; 3: save half of pix 3 in carry + ora decodeTo01 ; 4: pixel 0 + ora decodeTo23 ; 7: pixel 1 + ora decodeTo45 ; 10: pixel 2 + sta (0),y ; 13: even column + iny ; 15: prep for odd + lda decodeTo01b ; 16: pixel 4 + ora decodeTo23b ; 19: pixel 5 + rol ; 22: recover half of pix 3 + ora decodeTo56 ; 23: pixel 6 - after rol to ensure right hi bit + sta (0),y ; 26: odd column + dey ; 28: prep for even + ; 29 bytes total +``` + +All those ``lda`` and ``ora`` instructions are actually performing table lookups. The tables are aligned so that the low byte of the address is the actual value to look up. + +So as you can see, the code takes 7 color pixels from separate bytes and, using these fancy table lookups to quickly shift the bits into their proper place, combines them with binary math into 2 output bytes. + +The next block will be the same as the first, but instead of ``sta (0),y`` we'll use ``sta (2),y``. And so on for the third block. Then just before calling the blit for the first time, we have code that puts all the screen line addresses into locations 0, 2, 4, etc. so the blitting code will store to the right places on the screen. + +Here's what it the actual unrolled code looks like when we disassemble it on an Apple II after doing some rendering: + +```Assembly +B000- AD 11 AD LDA $AD11 +B003- 0A ASL +B004- 0D 11 A7 ORA $A711 +B007- 0D 11 A9 ORA $A922 +B00A- 0D 11 AB ORA $AB22 +B00D- 91 00 STA ($00),Y +B00F- C8 INY +B010- AD 11 A8 LDA $A811 +B013- 0D 11 AA ORA $AA22 +B016- 2A ROL +B017- 0D 11 AC ORA $AC11 +B01A- 91 00 STA ($00),Y +B01C- 88 DEY +B01D- AD 11 AD LDA $AD11 +B020- 0A ASL +B021- 0D 11 A7 ORA $A711 +B024- 0D 11 A9 ORA $A922 +B027- 0D 11 AB ORA $AB22 +B02A- 91 02 STA ($02),Y +B02C- C8 INY +B02D- AD 11 A8 LDA $A811 +B030- 0D 11 AA ORA $AA01 +B033- 2A ROL +B034- 0D 11 AC ORA $AC11 +B037- 91 02 STA ($02),Y +B039- 88 DEY +B03A- AD 11 AD LDA $AD30 +B03D- 0A ASL +B03E- 0D 11 A7 ORA $A711 +B041- 0D 11 A9 ORA $A911 +B044- 0D 11 AB ORA $AB12 +B047- 91 04 STA ($04),Y +B049- C8 INY +B04A- AD 11 A8 LDA $A830 +B04D- 0D 11 AA ORA $AA32 +B050- 2A ROL +B051- 0D 11 AC ORA $AC22 +B054- 91 04 STA ($04),Y +B056- 88 DEY +B057- AD 11 AD LDA $AD11 +... +``` + +See all those 11's, 22's and other numbers at the end of these lines? Believe it or not those are actual pixel values! Originally in the template they were 00. So some code somewhere has filled them in. What code? We'll fill in that last missing piece next time, when we talk about texture scaling in the final chapter of Big Blue's biography. diff --git a/Docs/Tutorials/BigBlue/partScreen.png b/Docs/Tutorials/BigBlue/partScreen.png new file mode 100644 index 00000000..8bc27fcf Binary files /dev/null and b/Docs/Tutorials/BigBlue/partScreen.png differ diff --git a/Platform/Apple/virtual/src/raycast/render.s b/Platform/Apple/virtual/src/raycast/render.s index 6cf3b45a..40f6c1f1 100644 --- a/Platform/Apple/virtual/src/raycast/render.s +++ b/Platform/Apple/virtual/src/raycast/render.s @@ -285,7 +285,7 @@ pow2_w_w: rts ;------------------------------------------------------------------------------- -; Cast a ray +; Cast a ray [ref BigBlue3_60] ; Input: pRayData, plus Y reg: precalculated ray data (4 bytes) ; playerX, playerY (integral and fractional bytes of course) ; pMap: pointer to current row on the map (mapBase + playerY{>}*height) @@ -643,7 +643,7 @@ drawRay: DEBUG_STR "Calling expansion code." jmp $100 ; was copied here earlier from @callIt -; Template for blitting code +; Template for blitting code [ref BigBlue3_70] blitTemplate: ; comments show byte offset lda decodeTo57 ; 0: pixel 3 asl ; 3: save half of pix 3 in carry @@ -1052,23 +1052,8 @@ initMem: jmp (expandVec,x) ;------------------------------------------------------------------------------- -; Establish the initial player position and direction +; Establish the initial player position and direction [ref BigBlue3_10] setPlayerPos: - .if 1 ; for testing only - ; X=blah - lda #$C - sta playerX+1 - lda #$F4 - sta playerX - ; Y=blah - lda #8 - sta playerY+1 - lda #$67 - sta playerY - ; direction=blah - lda #$7 - sta playerDir - .else ; X=1.5 lda #1 sta playerX+1 @@ -1082,7 +1067,6 @@ setPlayerPos: ; direction=0 lda #0 sta playerDir - .endif rts ;------------------------------------------------------------------------------- @@ -1286,6 +1270,7 @@ renderFrame: sta byteNum ; A-reg needs to be zero at this point -- it is the ray offset. ; Calculate the height, texture number, and texture column for one ray + ; [ref BigBlue3_50] @oneCol: stx pMap ; set initial map pointer for the ray sty pMap+1 @@ -1449,7 +1434,7 @@ main: bcc :+ ; no sec sbc #$20 ; yes, convert to upper case - ; Dispatch the keypress + ; Dispatch the keypress [ref BigBlue3_40] : cmp #'W' ; 'W' for forward bne :+ jsr moveForward @@ -1484,7 +1469,7 @@ main: ; Following are log/pow lookup tables. For speed, align them on a page boundary. .align 256 -; Table to translate an unsigned byte to 3+5 bit fixed point log2 +; Table to translate an unsigned byte to 3+5 bit fixed point log2 [ref BigBlue3_20] tbl_log2_b_b: .byte $00,$00,$00,$00,$00,$07,$0C,$11,$15,$19,$1C,$1F,$22,$24,$27,$29 .byte $2B,$2D,$2E,$30,$32,$33,$34,$36,$37,$38,$3A,$3B,$3C,$3D,$3E,$3F @@ -1562,7 +1547,7 @@ tbl_pow2_w_w: ; Precalculated ray initialization parameters. One table for each of the 16 angles. ; Each angle has 63 rays, and each ray is provided with 4 parameters (one byte each param): -; dirX, dirY, deltaX, deltaY +; dirX, dirY, deltaX, deltaY. [ref BigBlue3_30] precast_0: .byte $72,$C7,$3E,$7C .byte $72,$C9,$3D,$7E