Prev: B294 Up: Map Next: B453
B2BC: Draw Sprite
Used by the routine at DrawList.
Draws one sprite into the off-screen play window buffer. The sprite is found using the type in the draw list entry, as an index into the table of sprite pointers at SpritePointers. A sprite has a two byte header (the width in bytes plus flags, then the height in pixel rows) followed by its pixel rows, each made of pairs of bytes, one pair for each byte of width. The first byte of each pair is ORed onto the buffer and the second is then XORed onto the result. The rows are stored from the bottom of the sprite to the top. A sprite whose width and height are both zero is blank and is not drawn. Bit 7 of the type in the draw list entry means the sprite is drawn facing the other way. The flag in bit 7 of the sprite's own header records which way its data is currently stored, and if the two differ then the sprite's data is flipped in place first, using FlipSprite. The x position is a pixel position in the buffer, where 10 is the left edge of the play window. The y position is the height of the bottom of the sprite above the bottom of the buffer, so it increases up the screen: 30 is the bottom of the play window and A0 is the top. Sprites that overlap the top or bottom are clipped, and 10 pixels either side are available for sprites that overhang the left or right edge. If the sprite is not on a byte boundary then each pair of bytes is shifted right by the number of pixels using the tables built by BuildShiftTables, and an extra byte is written for each row. The drawing is done by unrolled loops at DrawSprite_Shifted (shifted) and DrawSprite_Unrolled (byte aligned), which are entered part way along according to the width of the sprite so that exactly the right number of bytes are drawn on each row. The sprite data is read by pointing the stack pointer at it and using POP.
Before After
draw-sprite-before draw-sprite-after
Input
IX Draw list entry: type, x position, y position
Find the sprite for this entry.
DrawSprite B2BC LD A,(IX+$00) Fetch the type.
B2BF ADD A,A Double it, discarding the "face the other way" bit, to give the offset into the table of pointers.
B2C0 LD L,A Add the offset to the address of the table of sprite pointers at SpritePointers.
B2C1 LD H,$00
B2C3 LD BC,$60A3
B2C6 ADD HL,BC
B2C7 LD E,(HL) Fetch the address of the sprite into DE.
B2C8 INC HL
B2C9 LD D,(HL)
B2CA LD A,(DE) Fetch the width byte of the header into C.
B2CB LD C,A
B2CC INC DE Fetch the height from the header.
B2CD LD A,(DE)
B2CE OR C Combine them and keep bits 0-5.
B2CF AND $3F
B2D1 RET Z Return if both the width and the height are zero, as the sprite is blank.
B2D2 DEC DE Point DE at the start of the header again.
B2D3 CALL FlipSprite Flip the sprite data if it is stored facing the wrong way, using FlipSprite.
Find which page of the shift tables to use, which depends on the x position within a byte.
B2D6 LD A,(IX+$01) Fetch the x position.
B2D9 DEC A Take one off, shift the result left by one (multiplying by two) and keep only the three bits of the shift, so that this is the shift amount multiplied by two.
B2DA RLCA
B2DB AND $0E
B2DD OR $E0 Add the base of the shift tables to give the page number, in H.
B2DF LD H,A
B2E0 LD A,(IX+$01) Fetch the x position again.
B2E3 AND $07 Test whether it is a multiple of 08.
B2E5 LD A,(DE) Fetch the width byte of the header.
B2E6 JP Z,DrawSprite_Aligned Jump to DrawSprite_Aligned to draw the sprite without shifting if the x position is exactly on a byte boundary.
The sprite needs shifting, so it will cover one more byte on each row than its width.
B2E9 INC A Add one to the width in bytes.
B2EA AND $0F
B2EC LD ($9621),A Store it as the number of bytes written to the buffer for each row.
B2EF LD C,A Take a copy of it.
B2F0 ADD A,$17 Add 17 to give the amount to subtract to get from the end of one row of the buffer to the start of the row above, since each row is 18 bytes long.
B2F2 LD ($B3A5),A Write that as the number to subtract in the SUB instruction at B3A4.
B2F5 LD A,C Fetch the width back, plus one.
B2F6 DEC A Take one off to get the width.
B2F7 INC DE Point DE at the height.
B2F8 NEG Negate the width and keep the bottom two bits, so that this is the number of unused copies of the unrolled loop out of the four available.
B2FA AND $03
B2FC ADD A,A Multiply by 0E, the length of one copy of the unrolled loop.
B2FD LD B,A
B2FE ADD A,A
B2FF ADD A,B
B300 ADD A,A
B301 ADD A,B
B302 LD B,H Hold the shift table page in B.
B303 LD HL,$B36B Point HL at DrawSprite_Shifted.
B306 CALL AddAToHL Add the offset using AddAToHL, to skip the copies that are not needed.
B309 LD ($B369),HL Write the address into the jump at DrawSprite_Shifted_Jump, so that it enters the loop at the right place.
B30C LD A,(DE) Fetch the height of the sprite.
B30D LD ($9622),A Store it as the number of rows still to draw.
B310 LD H,B Put the page number back into H.
B311 LD C,(IX+$01) Fetch the x position into C.
B314 LD B,(IX+$02) Fetch the y position into B.
Clip the bottom of the sprite, if it is below the bottom of the play window.
B317 LD A,B Is the bottom of the sprite on or above the bottom of the play window (30)?
B318 CP $30
B31A JR NC,DrawSprite_Shifted_Address Jump to DrawSprite_Shifted_Address if so.
B31C LD L,A Keep the y position.
B31D LD A,(DE) Fetch the height of the sprite.
B31E ADD A,L Add the y position to get the top of the sprite.
B31F CP $30 Is the top of the sprite below the bottom of the play window?
B321 RET C Return if it is, as the sprite is not visible at all.
B322 LD A,$30 Work out how many rows of the sprite are below the play window.
B324 SUB L Subtract the y position from the bottom of the play window.
B325 LD L,A Keep that number of rows.
B326 LD A,(DE) Fetch the height of the sprite.
B327 SUB L Take away the number of rows that are cut off.
B328 RET Z Return if nothing is left.
B329 LD ($9622),A Store the number of rows that are left to be drawn.
B32C PUSH HL Save the number of rows that are cut off.
B32D LD A,($9621) Fetch the number of bytes written for each row.
B330 DEC A Take one off to get the width in bytes.
B331 LD B,A Use it as a counter.
B332 XOR A Start the total at zero, and clear the high byte of HL.
B333 LD H,A
DrawSprite_Shifted_Multiply B334 ADD A,L Add the number of rows that were cut off to the total, once for each byte of the width, to multiply them.
B335 DJNZ DrawSprite_Shifted_Multiply
B337 LD L,A Keep the size of the hidden rows.
B338 ADD HL,HL Double it, as each byte of the width has a pair of bytes of data.
B339 ADD HL,DE Add the address of the sprite to give the address of the first visible row of data.
B33A EX DE,HL Keep the address of the first visible row.
B33B POP HL Restore HL.
B33C LD B,$30 Start drawing from the bottom of the play window.
Find the address in the buffer and point the stack pointer at the sprite data.
DrawSprite_Shifted_Address B33E CALL GetBufferAddress Find the address in the buffer of the first byte to draw, using GetBufferAddress.
B341 LD ($9623),SP Save the stack pointer.
B345 EX DE,HL Point the stack pointer at the sprite, so that POP fetches the data.
B346 LD SP,HL
B347 EX DE,HL
B348 INC SP Take it past the height byte, to the first pair of data bytes.
Clip the top of the sprite, if it is above the top of the play window.
B349 LD A,(IX+$02) Fetch the y position.
B34C CP $A0 Jump to DrawSprite_Done to finish if it is, as the sprite is not visible.
B34E JP NC,DrawSprite_Done
B351 LD L,A Keep the y position.
B352 LD A,($9622) Fetch the number of rows to draw.
B355 ADD A,L Add the y position to get the top of the sprite.
B356 SUB $A0 Subtract the top of the play window.
B358 JR C,DrawSprite_Shifted_Rows Jump if the sprite does not reach above the top of the play window.
B35A LD L,A Keep the number of rows that are cut off.
B35B LD A,(DE) Take the cut off rows away from the number of rows to draw.
B35C SUB L
B35D LD ($9622),A
DrawSprite_Shifted_Rows B360 LD A,($9622) Fetch the number of rows to be drawn.
B363 EXX Switch to the shadow registers, and keep the number of rows to draw in B'.
B364 LD B,A
DrawSprite_Shifted_Row B365 EXX Switch back to the main registers.
B366 POP DE Fetch the first pair of data bytes into DE.
B367 LD A,(BC) Fetch the first byte of the buffer to be changed.
DrawSprite_Shifted_Jump B368 JP DrawSprite_Shifted_Jump Jump into the unrolled loop.
The unrolled loop that draws one row of a sprite that is not on a byte boundary. BC points at the buffer, DE holds the current pair of data bytes (OR byte in E and XOR byte in D) and H holds the shift table page. Each pair of bytes is split by the shift table into a left part that is merged into the buffer byte already in A and a right part that is merged into the next buffer byte.
DrawSprite_Shifted B36B LD L,E Merge the left part of the shifted pair into the buffer byte already in A: OR on the shifted OR byte and XOR on the shifted XOR byte. Store the result and move on to the next buffer byte, and the right half of the shift table.
B36C OR (HL)
B36D LD L,D
B36E XOR (HL)
B36F LD (BC),A
B370 INC BC
B371 INC H
B372 LD L,E Merge the right part of the shifted pair into the next buffer byte and leave it in A, as the next pair also overlaps it. Go back to the left half of the table and fetch the next pair of data bytes.
B373 LD A,(BC)
B374 OR (HL)
B375 LD L,D
B376 XOR (HL)
B377 DEC H
B378 POP DE
B379 LD L,E Merge the left part of the second pair and store it.
B37A OR (HL)
B37B LD L,D
B37C XOR (HL)
B37D LD (BC),A
B37E INC BC
B37F INC H
B380 LD L,E Merge the right part of the second pair and fetch the third.
B381 LD A,(BC)
B382 OR (HL)
B383 LD L,D
B384 XOR (HL)
B385 DEC H
B386 POP DE
B387 LD L,E Merge the left part of the third pair and store it.
B388 OR (HL)
B389 LD L,D
B38A XOR (HL)
B38B LD (BC),A
B38C INC BC
B38D INC H
B38E LD L,E Merge the right part of the third pair and fetch the fourth.
B38F LD A,(BC)
B390 OR (HL)
B391 LD L,D
B392 XOR (HL)
B393 DEC H
B394 POP DE
B395 LD L,E Merge the left part of the fourth pair and store it.
B396 OR (HL)
B397 LD L,D
B398 XOR (HL)
B399 LD (BC),A
B39A INC BC
B39B INC H
B39C LD L,E Merge the right part of the fourth pair.
B39D LD A,(BC)
B39E OR (HL)
B39F LD L,D
B3A0 XOR (HL)
B3A1 DEC H
B3A2 LD (BC),A Store the last buffer byte of the row.
Move up to the start of the previous row of the buffer. The number to subtract is written by B2F2 for each sprite.
B3A3 LD A,C Subtract the number of bytes to step back (the operand of this instruction is modified) from the low byte of the buffer address.
B3A4 SUB $00
B3A6 LD C,A
B3A7 JP NC,DrawSprite_Shifted_Next Take a carry from the high byte if there was a borrow.
B3AA DEC B
DrawSprite_Shifted_Next B3AB EXX Switch to the shadow registers, where B' holds the number of rows left to draw.
B3AC DJNZ DrawSprite_Shifted_Row Loop back until every row of the sprite has been drawn.
DrawSprite_Done B3AE LD SP,($9623) Restore the stack pointer.
B3B2 RET Return.
The sprite is on a byte boundary, so no shifting is needed and each pair of bytes is combined with just one byte of the buffer.
DrawSprite_Aligned B3B3 AND $0F Keep the width of the sprite in bytes.
B3B5 LD ($9621),A Store it as the number of bytes written to the buffer for each row.
B3B8 LD C,A Take a copy of it.
B3B9 ADD A,$17 Add 17 to give the amount to subtract to get back to the start of the row above, once the last byte of a row has been written.
B3BB LD ($B447),A Write that as the number to subtract in the SUB instruction at B446.
B3BE LD A,C Fetch the width back.
B3BF INC DE Point DE at the height.
B3C0 NEG Negate the width and keep the bottom two bits, so that this is the number of unused copies of the unrolled loop out of the four available.
B3C2 AND $03
B3C4 LD C,A Keep the number of unused copies of the loop.
B3C5 ADD A,A Multiply by 06, the length of one copy of the unrolled loop.
B3C6 ADD A,C
B3C7 ADD A,A
B3C8 LD HL,$B42E Point HL at DrawSprite_Unrolled.
B3CB CALL AddAToHL Add the offset using AddAToHL, to skip the copies that are not needed.
B3CE LD ($B42C),HL Write the address into the jump at DrawSprite_Aligned_Jump, so that it enters the loop at the right place.
B3D1 LD A,(DE) Fetch the height of the sprite.
B3D2 LD ($9622),A Store it as the number of rows still to draw.
B3D5 LD C,(IX+$01) Fetch the x position into C.
B3D8 LD B,(IX+$02) Fetch the y position into B.
Clip the bottom of the sprite, as in B317.
B3DB LD A,B Is the bottom of the sprite on or above the bottom of the play window (30)?
B3DC CP $30
B3DE JR NC,DrawSprite_Aligned_Clip Jump to DrawSprite_Aligned_Clip if so.
B3E0 LD L,A Keep the y position.
B3E1 LD A,(DE) Fetch the height of the sprite.
B3E2 ADD A,L Add the y position to get the top of the sprite.
B3E3 CP $30 Is the top of the sprite below the bottom of the play window?
B3E5 RET C Return if it is, as the sprite is not visible at all.
B3E6 LD A,$30 Work out how many rows of the sprite are below the play window.
B3E8 SUB L Subtract the y position from the bottom of the play window.
B3E9 LD L,A Keep that number of rows.
B3EA LD A,(DE) Fetch the height of the sprite.
B3EB SUB L Take away the number of rows that are cut off.
B3EC RET Z Return if nothing is left.
B3ED LD ($9622),A Store the number of rows that are left to be drawn.
B3F0 PUSH HL Save the number of rows that are cut off.
B3F1 LD A,($9621) Fetch the width of the sprite in bytes.
B3F4 LD B,A Use it as a counter.
B3F5 XOR A Start the total at zero, and clear the high byte of HL.
B3F6 LD H,A
DrawSprite_Aligned_Multiply B3F7 ADD A,L Add the number of rows that were cut off to the total, once for each byte of the width, to multiply them.
B3F8 DJNZ DrawSprite_Aligned_Multiply
B3FA LD L,A Keep the size of the hidden rows.
B3FB ADD HL,HL Double it, as each byte of the width has a pair of bytes of data.
B3FC ADD HL,DE Add the address of the sprite to give the address of the first visible row of data.
B3FD EX DE,HL Keep the address of the first visible row.
B3FE POP HL Restore HL.
B3FF LD B,$30 Start drawing from the bottom of the play window.
DrawSprite_Aligned_Clip B401 CALL GetBufferAddress Find the address in the buffer of the first byte to draw, using GetBufferAddress.
B404 LD ($9623),SP Save the stack pointer.
B408 EX DE,HL Point the stack pointer at the sprite, so that POP fetches the data.
B409 LD SP,HL
B40A EX DE,HL
B40B INC SP Take it past the height byte, to the first pair of data bytes.
Clip the top of the sprite, as in B349.
B40C LD A,(IX+$02) Fetch the y position.
B40F CP $A0 Jump to DrawSprite_Done to finish if it is, as the sprite is not visible.
B411 JP NC,DrawSprite_Done
B414 LD L,A Keep the y position.
B415 LD A,($9622) Fetch the number of rows to draw.
B418 ADD A,L Add the y position to get the top of the sprite.
B419 SUB $A0 Subtract the top of the play window.
B41B JR C,DrawSprite_Aligned_Rows Jump if the sprite does not reach above the top of the play window.
B41D LD L,A Keep the number of rows that are cut off.
B41E LD A,(DE) Fetch the height of the sprite.
B41F SUB L Take the cut off rows away from the number of rows to draw.
B420 LD ($9622),A Store the number of rows to be drawn.
DrawSprite_Aligned_Rows B423 LD A,($9622) Fetch the number of rows to be drawn.
B426 LD H,B Move the buffer address into HL.
B427 LD L,C
B428 EXX Switch to the shadow registers, and keep the number of rows to draw in B'.
B429 LD B,A
DrawSprite_Aligned_Row B42A EXX Switch back to the main registers.
DrawSprite_Aligned_Jump B42B JP DrawSprite_Aligned_Jump Jump into the unrolled loop.
The unrolled loop that draws one row of a sprite on a byte boundary. HL points at the buffer and each pair of data bytes is fetched into DE: the OR byte (E) is ORed onto the buffer byte and the XOR byte (D) is XORed onto the result.
DrawSprite_Unrolled B42E POP DE Merge the first pair of data bytes with the buffer byte and move on.
B42F LD A,E
B430 OR (HL)
B431 XOR D
B432 LD (HL),A
B433 INC HL
B434 POP DE Merge the second pair of data bytes with the buffer byte and move on.
B435 LD A,E
B436 OR (HL)
B437 XOR D
B438 LD (HL),A
B439 INC HL
B43A POP DE Merge the third pair of data bytes with the buffer byte and move on.
B43B LD A,E
B43C OR (HL)
B43D XOR D
B43E LD (HL),A
B43F INC HL
B440 POP DE Merge the fourth pair of data bytes with the buffer byte.
B441 LD A,E
B442 OR (HL)
B443 XOR D
B444 LD (HL),A
Move up to the start of the previous row of the buffer.
B445 LD A,L Subtract the number of bytes to step back (the operand of this instruction is modified) from the low byte of the buffer address.
B446 SUB $00
B448 LD L,A Keep the result.
B449 JP NC,DrawSprite_Aligned_Next Take a carry from the high byte if there was a borrow.
B44C DEC H
DrawSprite_Aligned_Next B44D EXX Switch to the shadow registers, where B' holds the number of rows left to draw.
B44E DJNZ DrawSprite_Aligned_Row Loop back until every row of the sprite has been drawn.
B450 JP DrawSprite_Done Jump to DrawSprite_Done.
Prev: B294 Up: Map Next: B453