Skip to navigation

Screen buffer: DitherScreenBuffer

Name: DitherScreenBuffer [Show more] Type: Subroutine Category: Screen buffer Summary: Dither the contents of the screen buffer onto the screen
Context: See this subroutine in context in the source code References: This subroutine is called as follows: * DrawUpdatedObject calls DitherScreenBuffer

Arguments: ditherOuterLoop The number of outer loops to implement for the dithered effect, where each inner loop dithers 255 pixels to the screen
.DitherScreenBuffer LDA #255 \ Each inner loop of the dithering process updates 255 STA ditherInnerLoop \ random pixels on-screen, so set the inner loop counter \ in ditherInnerLoop accordingly LDA bufferColumns \ Set bufferWidthBytes = bufferColumns * 8 ASL A \ ASL A \ This converts the number of character columns into ASL A \ pixel bytes, as each character block contains eight STA bufferWidthBytes \ bytes SEC \ Set A = bufferWidthBytes - 1 SBC #1 \ We now set bitMask to a bit mask that covers all the \ non-zero bits in the list length in A, so if A is of \ the form %001xxxxx, for example, then bitMask will \ contain %00111111, while A being like %000001xx will \ give a bitMask of %00000111 \ \ To do this we count the number of continuous clear \ bits at the top of A, and then use this as an index \ into the leadingBitMask table \ \ So we count zeroes from bit 7 down until we hit a 1, \ and put the result into Y BEQ dith2 \ If A = 0 then jump to dith2 to skip the following \ process and set bitMaskDither to %00000000, which has \ the same number of leading zeroes as A LDY #&FF \ Set Y = -1 so the following loop counts the number of \ zeroes correctly .dith1 ASL A \ Shift A to the left, moving the top bit into the C \ flag INY \ Increment the zero counter in Y BCC dith1 \ Loop back to keep shifting and counting zeroes until \ we shift a 1 out of bit 7, at which point Y contains \ the length of the run of zeroes in bits 7 to 0 of the \ buffer width in bytes LDA leadingBitMask,Y \ Set A to the Y-th entry from the leadingBitMask table, \ which will give us a bit mask with a matching number \ of leading zeroes as A .dith2 STA bitMaskDither \ Set bitMaskDither to a bit mask with a matching number \ of leading zeroes as the buffer width in bytes .dith3 SEI \ Disable interrupts so we can fetch a random number \ without clashing with the dithering process in the \ handler (which is activated on the game over screen, \ which also uses this routine) JSR GetRandomNumber \ Set A to a random number LDY randomGenerator+1 \ Set ditherRandom to the second byte of the random STY ditherRandom \ number generator, so this is also a random number CLI \ Re-enable interrupts CLC \ Set A = A + pixelByteToDither ADC pixelByteToDither \ \ This sets A to a random value in the range 0 to 255 \ that's unlikely to be the same as pixelByteToDither, \ which is the pixel byte that we dithered last time we \ were here \ \ We don't care that pixelByteToDither isn't initialised \ outside of the reset routine, as we want this to be \ random AND bitMaskDither \ We set bitMaskDither above to a bit mask that has a \ matching number of leading zeroes as the number of \ bytes in the buffer width, so this instruction \ converts A into a number with the same range of \ non-zero bits as bufferWidthBytes CMP bufferWidthBytes \ If A < bufferWidthBytes then jump to dith4 to skip the BCC dith4 \ next instruction SBC bufferWidthBytes \ Subtract bufferWidthBytes from A to bring it into the \ range 0 to bufferWidthBytes (which works because we \ already reduced A to a maximum value of bitMaskDither, \ so A must be less than bufferWidthBytes * 2) \ \ The subtraction works because we just passed through a \ BCC, so we know the C flag is set .dith4 STA pixelByteToDither \ Set pixelByteToDither to the result in A, which is a \ valid pixel byte number within the range of the pixel \ bytes in the screen buffer row \ We now set (ditherStore A) to the address of a random \ row in the screen buffer, as an offset from the first \ screen row buffer at screenBufferRow0, using bits 0 to \ 4 of the random number in ditherRandom LDA ditherRandom \ Set ditherStore to bits 0 to 4 of our random number, AND #%00011111 \ so that's a number in the range 0 to &1F STA ditherStore LSR A \ Set ditherStore = ditherStore / 2 + ditherStore CLC \ ADC ditherStore \ and round the result down to the nearest even number, AND #%11111110 \ so that's an even number in the range 0 to &2E STA ditherStore ASL A \ Set A = ditherStore * 4 ASL A \ \ So that's a multiple of 8 in the range 0 to &B8 ADC ditherStore \ Set ditherStore = ditherStore + A STA ditherStore \ \ So that's an even number in the range 0 to &2E LDA #0 \ Set (ditherStore A) = (ditherStore 0) / 8 LSR ditherStore \ ROR A \ This gives us the address of a randomly picked screen LSR ditherStore \ row in (ditherStore A), as an offset from within ROR A \ screen memory LSR ditherStore ROR A ADC pixelByteToDither \ Set the following: STA fromAddr \ LDA ditherStore \ fromAddr(1 0) = (ditherStore A) + pixelByteToDither ADC #0 \ STA fromAddr+1 \ So fromAddr(1 0) now contains the offset address \ within screen memory of a randomly picked pixel byte \ within the width of the screen buffer \ \ This is calculated by adding the address of a randomly \ picked screen row in (ditherStore A) and a randomly \ picked pixel byte within the width of the screen \ buffer in pixelByteToDither \ The address in fromAddr(1 0) is now the offset within \ screen memory of the random pixel byte that we can use \ for dithering one pixel of the screen buffer to the \ screen (as we will dither just one pixel of this byte \ on each iteration of the inner loop) \ \ The next step is to convert this offset into both a \ screen memory address and the corresponding screen \ buffer address, so we can copy the pixel from the \ screen buffer to the screen \ \ We start with the screen memory address LDA objScreenAddr \ Set (A toAddr) = objScreenAddr(1 0) + fromAddr(1 0) CLC \ ADC fromAddr \ This gives us the address in screen memory of the STA toAddr \ random pixel byte, as objScreenAddr(1 0) contains the LDA objScreenAddr+1 \ screen address of the object ADC fromAddr+1 CMP #&80 \ If the high byte in A >= &80 then the new address is BCC dith5 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 .dith5 STA toAddr+1 \ Store the high byte of the result, so we now have: \ \ toAddr(1 0) = objScreenAddr(1 0) + fromAddr(1 0) \ \ with the address wrapped around as required \ And now we calculate the equivalent address in the \ screen buffer, which is a column buffer, so we start \ by adding the offset in fromAddr(1 0) to the address \ of the first screen buffer row at screenBufferRow0 \ (we can skip adding the low bytes as the low byte of \ screenBufferRow0 is zero) \ \ It's worth noting that the structure of the screen \ buffer is similar to the screen, with each row in \ the screen buffer being spaced out by 320 bytes, just \ as in screen memory, so this means we can just add \ screenBufferRow0 and fromAddr(1 0) to get the address \ \ However, as we use a column buffer to update objects \ on-screen, we need to wrap around after buffer row 15, \ so we also need to check for this LDA fromAddr+1 \ Set fromAddr(1 0) = screenBufferRow0 + fromAddr(1 0) CLC ADC #HI(screenBufferRow0) STA fromAddr+1 CMP #&53 \ If the result of the addition is less than &5300, then BCC dith6 \ we have not reached the end of row 15 in the screen \ buffer, so jump to dith6 to skip the following LDA fromAddr \ Set fromAddr(1 0) = fromAddr(1 0) - &1360 SEC \ SBC #&60 \ If we get here then we have reached character row 16 STA fromAddr \ in the screen buffer, so we need to wrap around LDA fromAddr+1 \ SBC #&13 \ The value of &1360 is calculated as follows STA fromAddr+1 \ \ Character row 15 in the screen buffer is at address \ &51C0, and adding another 320 to that address gives \ us &5300, but this is past the end of the screen \ buffer, so we need to wrap it around \ \ Character row 16 is actually at address &3FA0, so we \ need to subtract &5300 - &3FA0 = &1360 from the \ address to perform the wraparound .dith6 LDA ditherRandom \ Set X to bits 6 and 7 of ditherRandom, shifted into ASL A \ bits 0 and 1 to create a random number in the range ROL A \ 0 to 3 ROL A AND #%00000011 TAX LDY #0 \ Set A to the pixel byte that we want to dither onto LDA (fromAddr),Y \ the screen, taking it from the screen buffer at \ address fromAddr(1 0) AND pixelBitMask,X \ Convert X from a number in the range 0 to 3 into a bit \ mask with that pixel number set (where pixel 0 is on \ the left and pixel 3 is on the right), and clear all \ the bits in A apart from the two bits for that pixel \ \ So if X is 2, for example, the resulting mask will be \ %00100010, in which pixel 2 is set, so only these two \ bits of the pixel byte will be kept STA ditherStore \ Store the pixel that we want to dither in ditherStore LDA (toAddr),Y \ Set A to the current screen contents of the pixel byte \ that we are updating, taking it from the screen at \ address toAddr(1 0) AND clearPixelMask,X \ Clear the pixel number in A by applying a pixel bit \ mask from clearPixelMask where the bits for every \ pixel are set except for pixel X ORA ditherStore \ Set the colour of pixel X to the colour of the pixel \ from the screen buffer by OR'ing in the pixel bits \ that we set above STA (toAddr),Y \ Store the updated pixel byte in screen memory to draw \ the pixel from the screen buffer on-screen BIT doNotDitherObject \ If bit 7 of doNotDitherObject is set then the dithered BMI dith8 \ effect has been disabled (while we focus on processing \ an action key), so jump to dith8 to return from the \ subroutine without doing any more dithering DEC ditherInnerLoop \ Decrement the inner loop counter BNE dith7 \ If we have not yet finished the inner loop, jump to \ dith7 to loop back to dither another pixel onto the \ screen, until we have done all 255 pixels in the \ inner loop JSR ProcessSound \ Process any sounds or music that are being made in the \ background DEC ditherOuterLoop \ Decrement the outer loop counter BEQ dith8 \ If we have finished the outer loop, jump to dith8 to \ return from the subroutine .dith7 JMP dith3 \ Loop back to dither another pixel onto the screen .dith8 RTS \ Return from the subroutine