2021-07-08 12:46:35 +00:00
|
|
|
; Renders a frame of animation
|
|
|
|
;
|
2022-02-21 16:31:35 +00:00
|
|
|
; The render function is the point of committment -- most of the APIs that set sprites and
|
2022-02-25 23:05:32 +00:00
|
|
|
; update coordinates are lazy; they simply save their values and set a dirty flag in the
|
2021-07-09 19:18:49 +00:00
|
|
|
; DirtyBits word.
|
|
|
|
;
|
|
|
|
; This function examines the dirty bits and actually performs the work to update the code field
|
|
|
|
; and internal data structure to properly render the play field. Then the update pipeline is
|
|
|
|
; executed.
|
2022-02-25 23:05:32 +00:00
|
|
|
;
|
2023-01-02 17:04:26 +00:00
|
|
|
; There are two major rendering modes: a composited mode and a scanline mode. The composited mode
|
|
|
|
; will render all of the sprites into the playfield tiles, and then perform a single blit to update
|
|
|
|
; the entire playfield. The scanline mode utilized shadowing and blits the background scanlines
|
|
|
|
; on sprite lines first, then draws the sprites and finally exposes the updated scanlines.
|
|
|
|
;
|
|
|
|
; The composited mode has the advantages of being able to render sprites behind tile data as well
|
|
|
|
; as avoiding most overdraw. The scanline mode is able to draw sprites correctly even when scanline
|
|
|
|
; effect are used on the background and has lower overhead, which can make it faster in some cases,
|
|
|
|
; even with the additional overdraw.
|
2022-04-29 17:38:04 +00:00
|
|
|
;
|
2021-07-09 20:38:32 +00:00
|
|
|
; TODO -- actually check the dirty bits and be selective on what gets updated. For example, if
|
|
|
|
; only the Y position changes, then we should only need to set new values on the
|
|
|
|
; virtual lines that were brought on screen. If the X position only changes by one
|
|
|
|
; byte, then we may have to change the CODE_ENTRY values or restore/set new OPCODE
|
|
|
|
; values, but not both.
|
|
|
|
|
2021-07-18 02:00:46 +00:00
|
|
|
; It's important to do _ApplyBG0YPos first because it calculates the value of StartY % 208 which is
|
|
|
|
; used in all of the other loops
|
2021-08-25 14:38:02 +00:00
|
|
|
_Render
|
2022-07-16 20:22:23 +00:00
|
|
|
sta RenderFlags
|
|
|
|
|
2022-07-07 19:46:37 +00:00
|
|
|
lda LastRender ; Check to see what kind of rendering was done on the last frame. If
|
|
|
|
beq :no_change ; it was not this renderer,
|
|
|
|
jsr _ResetToNormalTileProcs
|
|
|
|
jsr _Refresh
|
|
|
|
:no_change
|
|
|
|
|
2022-07-05 04:55:32 +00:00
|
|
|
jsr _DoTimers ; Run any pending timer tasks
|
2022-06-25 16:17:50 +00:00
|
|
|
|
2022-05-27 00:36:40 +00:00
|
|
|
stz SpriteRemovedFlag ; If we remove a sprite, then we need to flag a rebuild for the next frame
|
|
|
|
|
2021-07-09 20:38:32 +00:00
|
|
|
jsr _ApplyBG0YPos ; Set stack addresses for the virtual lines to the physical screen
|
2022-07-22 07:01:34 +00:00
|
|
|
|
|
|
|
lda #RENDER_BG1_ROTATION
|
|
|
|
bit RenderFlags
|
|
|
|
bne :skip_bg1_y
|
|
|
|
jsr _ApplyBG1YPos ; Set the y-register values of the blitter
|
|
|
|
:skip_bg1_y
|
2021-08-06 19:42:18 +00:00
|
|
|
|
|
|
|
; _ApplyBG0Xpos need to be split because we have to set the offsets, then draw in any updated tiles, and
|
|
|
|
; finally patch out the code field. Right now, the BRA operand is getting overwritten by tile data.
|
2022-07-22 07:01:34 +00:00
|
|
|
|
2021-08-06 19:42:18 +00:00
|
|
|
jsr _ApplyBG0XPosPre
|
2022-06-27 05:32:44 +00:00
|
|
|
jsr _ApplyBG1XPosPre
|
2021-08-06 19:42:18 +00:00
|
|
|
|
2022-05-23 20:18:34 +00:00
|
|
|
jsr _RenderSprites ; Once the BG0 X and Y positions are committed, update sprite data
|
2021-10-31 20:42:59 +00:00
|
|
|
|
2022-06-27 03:08:42 +00:00
|
|
|
jsr _UpdateBG0TileMap ; and the tile maps. These subroutines build up a list of tiles
|
2022-08-17 00:47:19 +00:00
|
|
|
; jsr _UpdateBG1TileMap ; that need to be updated in the code field
|
2021-08-06 19:42:18 +00:00
|
|
|
|
2021-07-09 20:38:32 +00:00
|
|
|
|
2023-03-03 05:24:03 +00:00
|
|
|
jsr _ApplyTiles ; This function actually draws the new tiles into the code field
|
2022-07-22 07:01:34 +00:00
|
|
|
jsr _ApplyBG0XPos ; Patch the code field instructions with exit BRA opcode
|
2023-01-02 17:04:26 +00:00
|
|
|
|
2022-07-22 07:01:34 +00:00
|
|
|
lda #RENDER_BG1_ROTATION
|
|
|
|
bit RenderFlags
|
|
|
|
bne :skip_bg1_x
|
|
|
|
jsr _ApplyBG1XPos ; Update the direct page value based on the horizontal position
|
|
|
|
:skip_bg1_x
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2022-06-27 16:24:04 +00:00
|
|
|
; The code fields are locked in now and ready to be rendered. See if there is an overlay or any
|
|
|
|
; other reason to render with shadowing off. Otherwise, just do things quickly.
|
2021-07-20 03:42:51 +00:00
|
|
|
|
2023-03-06 20:39:23 +00:00
|
|
|
lda Overlays+OVERLAY_ID
|
2022-06-27 16:24:04 +00:00
|
|
|
beq :no_ovrly
|
|
|
|
|
|
|
|
jsr _ShadowOff
|
2021-07-20 03:42:51 +00:00
|
|
|
|
2021-10-31 00:24:23 +00:00
|
|
|
; Shadowing is turned off. Render all of the scan lines that need a second pass. One
|
|
|
|
; optimization that can be done here is that the lines can be rendered in any order
|
|
|
|
; since it is not shown on-screen yet.
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2023-01-02 17:04:26 +00:00
|
|
|
ldx Overlays+OVERLAY_TOP ; Blit the full virtual buffer to the screen
|
|
|
|
ldy Overlays+OVERLAY_BOTTOM
|
2023-03-06 20:39:23 +00:00
|
|
|
iny
|
2022-06-27 16:24:04 +00:00
|
|
|
jsr _BltRange
|
2021-10-21 13:50:07 +00:00
|
|
|
|
|
|
|
; Turn shadowing back on
|
2021-07-20 03:42:51 +00:00
|
|
|
|
2022-06-27 16:24:04 +00:00
|
|
|
jsr _ShadowOn
|
2021-07-20 03:42:51 +00:00
|
|
|
|
2021-10-21 13:50:07 +00:00
|
|
|
; Now render all of the remaining lines in top-to-bottom (or bottom-to-top) order
|
|
|
|
|
2022-06-27 16:24:04 +00:00
|
|
|
jsr _DoOverlay
|
2021-10-31 00:24:23 +00:00
|
|
|
|
2023-01-02 17:04:26 +00:00
|
|
|
ldx Overlays+OVERLAY_BOTTOM
|
2023-03-06 20:39:23 +00:00
|
|
|
inx
|
2022-06-27 16:24:04 +00:00
|
|
|
cpx ScreenHeight
|
|
|
|
beq :done
|
|
|
|
ldy ScreenHeight
|
|
|
|
jsr _BltRange
|
|
|
|
bra :done
|
|
|
|
|
|
|
|
:no_ovrly
|
2021-11-15 18:23:38 +00:00
|
|
|
ldx #0 ; Blit the full virtual buffer to the screen
|
2021-07-09 20:38:32 +00:00
|
|
|
ldy ScreenHeight
|
|
|
|
jsr _BltRange
|
2022-06-27 16:24:04 +00:00
|
|
|
:done
|
2021-07-09 20:38:32 +00:00
|
|
|
|
2022-06-27 05:41:30 +00:00
|
|
|
ldx #0
|
|
|
|
ldy ScreenHeight
|
|
|
|
jsr _BltSCB
|
2021-11-15 18:23:38 +00:00
|
|
|
|
2022-05-23 04:54:47 +00:00
|
|
|
lda StartYMod208 ; Restore the fields back to their original state
|
2021-07-09 20:38:32 +00:00
|
|
|
ldx ScreenHeight
|
|
|
|
jsr _RestoreBG0Opcodes
|
|
|
|
|
2021-08-10 12:59:14 +00:00
|
|
|
lda StartY
|
|
|
|
sta OldStartY
|
|
|
|
lda StartX
|
|
|
|
sta OldStartX
|
|
|
|
|
2021-08-19 06:22:36 +00:00
|
|
|
lda BG1StartY
|
|
|
|
sta OldBG1StartY
|
|
|
|
lda BG1StartX
|
|
|
|
sta OldBG1StartX
|
|
|
|
|
2021-08-10 12:59:14 +00:00
|
|
|
stz DirtyBits
|
2022-02-19 02:43:55 +00:00
|
|
|
stz LastRender ; Mark that a full render was just performed
|
2022-05-27 00:36:40 +00:00
|
|
|
|
|
|
|
lda SpriteRemovedFlag ; If any sprite was removed, set the rebuild flag
|
|
|
|
beq :no_removal
|
|
|
|
lda #DIRTY_BIT_SPRITE_ARRAY
|
|
|
|
sta DirtyBits
|
|
|
|
:no_removal
|
2021-07-08 12:46:35 +00:00
|
|
|
rts
|
2022-01-20 02:58:57 +00:00
|
|
|
|
2023-04-26 13:47:56 +00:00
|
|
|
; Small helper function to draw a single overlay
|
2022-06-27 16:24:04 +00:00
|
|
|
_DoOverlay
|
2023-01-02 17:04:26 +00:00
|
|
|
lda Overlays+OVERLAY_PROC
|
2022-06-27 16:24:04 +00:00
|
|
|
stal :disp+1
|
2023-01-02 17:04:26 +00:00
|
|
|
lda Overlays+OVERLAY_PROC+1
|
2022-06-27 16:24:04 +00:00
|
|
|
stal :disp+2
|
|
|
|
|
|
|
|
lda ScreenY0 ; pass the address of the first line of the overlay
|
|
|
|
clc
|
2023-01-02 17:04:26 +00:00
|
|
|
adc Overlays+OVERLAY_TOP
|
2022-06-27 16:24:04 +00:00
|
|
|
asl
|
|
|
|
tax
|
|
|
|
lda ScreenAddr,x
|
|
|
|
clc
|
|
|
|
adc ScreenX0
|
|
|
|
:disp jsl $000000
|
|
|
|
rts
|
|
|
|
|
2022-08-14 12:45:58 +00:00
|
|
|
|
2022-08-11 19:14:25 +00:00
|
|
|
; Use the per-scanline tables to set the screen. This is really meant to be used without the built-in tilemap
|
|
|
|
; support and is more of a low-level way to control the background rendering
|
|
|
|
_RenderScanlines
|
2023-03-12 20:39:41 +00:00
|
|
|
lda BG1YTable ; Make sure we're in the right mode (0 = scanline mode, $1800 = normal mode)
|
2023-03-10 21:50:42 +00:00
|
|
|
beq :ytbl_ok
|
|
|
|
lda #1
|
|
|
|
jsr _ResetBG1YTable
|
|
|
|
:ytbl_ok
|
|
|
|
|
2023-04-26 13:47:56 +00:00
|
|
|
jsr _ApplyBG0YPos ; Set stack addresses for the virtual lines to the physical screen
|
2023-03-10 21:50:42 +00:00
|
|
|
jsr _ApplyScanlineBG1YPos ; Set the y-register values of the blitter
|
2022-08-11 19:14:25 +00:00
|
|
|
|
|
|
|
jsr _ApplyBG0XPosPre
|
2022-08-14 12:45:58 +00:00
|
|
|
jsr _ApplyBG1XPosPre
|
2022-08-11 19:14:25 +00:00
|
|
|
|
2023-03-12 20:39:41 +00:00
|
|
|
jsr _ApplyScanlineBG0XPos ; Patch the code field instructions with exit BRA opcode
|
|
|
|
jsr _ApplyScanlineBG1XPos
|
2023-03-09 21:29:58 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
jsr _FilterObjectList ; Walk the sorted list and create an array of objects that need to be rendered
|
2023-03-09 21:29:58 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
jsr _ShadowOff ; Turn off shadowing and draw all the scanlines with sprites on them
|
|
|
|
jsr _DrawObjShadow ; Draw the background
|
|
|
|
jsr _DrawDirectSprites ; Draw the sprites directly to the Bank $01 graphics buffer (skipping the render-to-tile step)
|
2023-03-09 21:29:58 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
jsr _ShadowOn ; Turn shadowing back on
|
|
|
|
jsr _DrawFinalPass ; Expose the shadowed areas and draw overlays
|
2022-08-14 12:45:58 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
lda StartYMod208 ; Restore the fields back to their original state
|
2022-08-11 19:14:25 +00:00
|
|
|
ldx ScreenHeight
|
|
|
|
jsr _RestoreScanlineBG0Opcodes
|
|
|
|
|
|
|
|
lda StartY
|
|
|
|
sta OldStartY
|
|
|
|
lda StartX
|
|
|
|
sta OldStartX
|
|
|
|
|
|
|
|
lda BG1StartY
|
|
|
|
sta OldBG1StartY
|
|
|
|
lda BG1StartX
|
|
|
|
sta OldBG1StartX
|
|
|
|
|
|
|
|
stz DirtyBits
|
|
|
|
stz LastRender ; Mark that a full render was just performed
|
|
|
|
|
|
|
|
lda SpriteRemovedFlag ; If any sprite was removed, set the rebuild flag
|
|
|
|
beq :no_removal
|
|
|
|
lda #DIRTY_BIT_SPRITE_ARRAY
|
|
|
|
sta DirtyBits
|
|
|
|
:no_removal
|
|
|
|
rts
|
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
|
|
|
|
; After the sprites have been filtered, we have a linked list with all of the contiguous sprite regions merged together, so
|
|
|
|
; when provessing this list we really only have to consider complications from overlays.
|
|
|
|
;
|
|
|
|
; Pseudo-code
|
|
|
|
;
|
|
|
|
; 0. Set the cursor to the top of the screen
|
|
|
|
; 1. Load the next segment
|
|
|
|
; a. If no segments, just draw the full screen
|
|
|
|
; 2. Draw the background from the cursor to the top of the current segment
|
|
|
|
; 3. If the current segment is a sprite
|
|
|
|
; a. Peek at the next segment
|
|
|
|
; b. If no more segments, then finish
|
|
|
|
; c. If it's past the bottom, PEI slam the current segment and go to [1]
|
|
|
|
; d. Must be an overlay
|
|
|
|
; i. PEI slam up to the overlay top
|
|
|
|
; ii. Does the sprite extend past the overlay? If yes, split the sprite and insert into the list
|
|
|
|
; iii. Go to [1]
|
|
|
|
; 4. If the current segment is an overlay
|
|
|
|
; a. Peek at the next segment
|
|
|
|
; b. If no more segments, then finish
|
|
|
|
; c. If it's past the bottom, draw the overlay and go to [1]
|
|
|
|
; d. Must be a sprite
|
|
|
|
; i. Draw the overlay
|
|
|
|
; ii. Change the sprite segment to start after the overlay
|
|
|
|
; iii. Go to [1]
|
2023-04-26 13:47:56 +00:00
|
|
|
_DrawFinalPass
|
2023-04-26 04:39:09 +00:00
|
|
|
:cursor equ tmp8
|
|
|
|
:bottom equ tmp9
|
|
|
|
|
|
|
|
stz :cursor
|
|
|
|
ldy #0
|
|
|
|
cpy ObjectListCount
|
|
|
|
bne :enter
|
|
|
|
|
|
|
|
ldx #0 ; If there are no object to render, just draw the screen
|
|
|
|
ldy ScreenHeight
|
|
|
|
jmp _BltRange
|
|
|
|
|
|
|
|
:enter
|
|
|
|
ldx ObjectList+OL_INDEX,y ; Load the index of the next object record
|
|
|
|
|
|
|
|
; Draw the background up to the top line of the next object
|
|
|
|
|
|
|
|
phxy
|
|
|
|
ldy _Sprites+SPRITE_CLIP_TOP,x
|
|
|
|
ldx :cursor
|
|
|
|
sty :cursor ; Update the cursor since we have the value
|
|
|
|
jsr _BltRange
|
|
|
|
plyx
|
|
|
|
|
|
|
|
:_oloop
|
|
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x
|
|
|
|
sta :bottom
|
|
|
|
|
|
|
|
; Load the ID to see what kind of object comes next
|
|
|
|
|
|
|
|
lda _Sprites+SPRITE_ID,x ; See if we are processing an overlay or a sprite region
|
|
|
|
bit #SPRITE_OVERLAY
|
|
|
|
jne :_overlay
|
|
|
|
|
|
|
|
:_sprite
|
|
|
|
iny
|
|
|
|
iny
|
|
|
|
cpy ObjectListCount
|
|
|
|
jeq :_sprite_end ; If this is the last object, end now on the sprite
|
|
|
|
|
|
|
|
ldx ObjectList+OL_INDEX,y ; Load the index of the next item
|
|
|
|
lda :bottom
|
|
|
|
cmp _Sprites+SPRITE_CLIP_TOP,x
|
|
|
|
bcs :_smerge ; If the prior sprite ends before this object, then handle it
|
|
|
|
|
|
|
|
phxy
|
|
|
|
ldy _Sprites+SPRITE_CLIP_TOP,x ; A = :bottom, so load the top of the next object and
|
|
|
|
sty :bottom ; save it as it is the bottom after the PEISlam
|
|
|
|
|
|
|
|
ldx :cursor ; X = :cursor
|
|
|
|
sta :cursor ; The current :bottom becomes the :cursor after the PEISlam
|
|
|
|
tay ; Y = :bottom
|
|
|
|
jsr _PEISlam
|
|
|
|
ldx :cursor ; This is the previous :bottom value
|
|
|
|
ldy :bottom ; This is the SPRITE_CLIP_TOP,x value
|
|
|
|
sty :cursor
|
|
|
|
jsr _BltRange
|
|
|
|
plyx
|
|
|
|
brl :_oloop ; Branch back, it's like starting from from scratch
|
|
|
|
|
|
|
|
:_smerge
|
|
|
|
lda _Sprites+SPRITE_ID,x ; Before we merge, need to know if objects are compatible
|
|
|
|
bit #SPRITE_OVERLAY
|
|
|
|
bne :_somerge
|
|
|
|
|
|
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x ; Can be merged, so pick the largest bottom value and
|
|
|
|
max :bottom ; continue on as a sprite
|
|
|
|
sta :bottom
|
|
|
|
brl :_sprite
|
|
|
|
|
|
|
|
:_somerge
|
|
|
|
phxy
|
|
|
|
ldy _Sprites+SPRITE_CLIP_TOP,x ; PEI Slam to the top of the overlay (:bottom is greater than this value)
|
2023-04-26 13:47:56 +00:00
|
|
|
ldx :cursor
|
2023-04-26 04:39:09 +00:00
|
|
|
sty :cursor
|
2023-04-26 13:47:56 +00:00
|
|
|
; brk $44
|
2023-04-26 04:39:09 +00:00
|
|
|
jsr _PEISlam
|
|
|
|
lda 3,s ; Retrieve the sprite index
|
|
|
|
tax
|
|
|
|
jsr _DrawOverlay
|
|
|
|
plyx
|
|
|
|
|
|
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x ; This is how far we've drawn. Check to see if we're beyond the current :bottom
|
|
|
|
sta :cursor
|
|
|
|
cmp :bottom
|
|
|
|
jcc :_sprite ; Previous sprite extends past the overlay, continue
|
|
|
|
|
|
|
|
; The overlay can cause the cursor to jump ahead an arbitrary distance. We need to continue to scan through the list until
|
|
|
|
; we find an item that has a bottom greater than the current :cursor
|
|
|
|
:_so_loop
|
|
|
|
iny
|
|
|
|
iny
|
|
|
|
cpy ObjectListCount
|
|
|
|
beq :_end
|
|
|
|
|
|
|
|
ldx ObjectList+OL_INDEX,y
|
|
|
|
lda :cursor
|
|
|
|
cmp _Sprites+SPRITE_CLIP_BOTTOM,x
|
|
|
|
bcs :_so_loop
|
|
|
|
|
|
|
|
cmp _Sprites+SPRITE_CLIP_TOP,x ; Check to see if there is any background that need to be drawn
|
|
|
|
jcs :_oloop ; If not, go back the see what kind of object it is
|
|
|
|
|
|
|
|
phxy
|
|
|
|
ldy _Sprites+SPRITE_CLIP_TOP,x
|
|
|
|
ldx :cursor
|
|
|
|
sty :cursor
|
|
|
|
jsr _BltRange
|
|
|
|
plyx
|
|
|
|
brl :_oloop
|
|
|
|
|
|
|
|
; If the last item is a sprite, do a PEI slam from the cursor to the sprite bottom and then blit any remaining
|
|
|
|
; backround
|
|
|
|
:_sprite_end
|
|
|
|
ldx :cursor
|
|
|
|
ldy :bottom
|
|
|
|
jsr _PEISlam
|
|
|
|
ldx :bottom
|
|
|
|
ldy ScreenHeight
|
|
|
|
jmp _BltRange
|
|
|
|
|
|
|
|
; If there are no more items to process, but we haven't reached the end of the screen, blit the rest of the
|
|
|
|
; background
|
|
|
|
:_end
|
|
|
|
ldx :cursor
|
|
|
|
ldy ScreenHeight
|
|
|
|
jmp _BltRange
|
|
|
|
|
|
|
|
; An overlay is a bit easier. It just needs to be rendered and then advance to the next object that's not
|
|
|
|
; covered by it
|
|
|
|
:_overlay
|
|
|
|
phxy
|
|
|
|
jsr _DrawOverlay ; Draw the overlay
|
|
|
|
plyx
|
|
|
|
lda :bottom
|
|
|
|
sta :cursor
|
|
|
|
brl :_so_loop
|
|
|
|
|
2022-07-06 04:48:33 +00:00
|
|
|
; Run through all of the tiles on the DirtyTile list and render them
|
|
|
|
_ApplyTiles
|
2022-05-31 13:43:26 +00:00
|
|
|
ldx DirtyTileCount
|
|
|
|
|
2022-07-06 04:48:33 +00:00
|
|
|
phd ; sve the current direct page
|
2022-05-18 05:34:25 +00:00
|
|
|
tdc
|
|
|
|
clc
|
|
|
|
adc #$100 ; move to the next page
|
|
|
|
tcd
|
|
|
|
|
2022-05-31 13:43:26 +00:00
|
|
|
stx DP2_DIRTY_TILE_COUNT ; Cache the dirty tile count
|
2022-05-19 02:39:39 +00:00
|
|
|
jsr _PopDirtyTilesFast
|
2022-05-18 05:34:25 +00:00
|
|
|
|
2022-07-06 04:48:33 +00:00
|
|
|
pld ; Move back to the original direct page
|
2022-05-31 13:43:26 +00:00
|
|
|
stz DirtyTileCount ; Reset the dirty tile count
|
2022-05-18 05:34:25 +00:00
|
|
|
rts
|
|
|
|
|
2022-02-19 02:43:55 +00:00
|
|
|
; This is a specialized render function that only updates the dirty tiles *and* draws them
|
2022-01-20 02:58:57 +00:00
|
|
|
; directly onto the SHR graphics buffer. The playfield is not used at all. In some way, this
|
|
|
|
; ignores almost all of the capabilities of GTE, but it does provide a convenient way to use
|
|
|
|
; the sprite subsystem + tile attributes for single-screen games which should be able to run
|
|
|
|
; close to 60 fps.
|
2022-02-25 23:05:32 +00:00
|
|
|
;
|
|
|
|
; In this renderer, we assume that there is no scrolling, so no need to update any information about
|
2022-01-20 02:58:57 +00:00
|
|
|
; the BG0/BG1 positions
|
|
|
|
_RenderDirty
|
2022-07-06 19:55:27 +00:00
|
|
|
lda LastRender ; If the full renderer was last called, we assume that
|
|
|
|
bne :norecalc ; the scroll positions have likely changed, so recalculate
|
|
|
|
jsr _RecalcTileScreenAddrs ; them to make sure sprites draw at the correct screen address
|
2022-07-07 19:46:37 +00:00
|
|
|
jsr _ResetToDirtyTileProcs ; Switch the tile procs to the dirty tile rendering functions
|
2022-05-18 05:34:25 +00:00
|
|
|
; jsr _ClearSpritesFromCodeField ; Restore the tiles to their non-sprite versions
|
2022-02-04 05:44:46 +00:00
|
|
|
:norecalc
|
2022-07-06 19:55:27 +00:00
|
|
|
jsr _RenderSprites
|
|
|
|
jsr _ApplyDirtyTiles
|
2022-04-29 17:38:04 +00:00
|
|
|
|
2022-02-04 05:44:46 +00:00
|
|
|
lda #1
|
|
|
|
sta LastRender
|
2022-01-20 02:58:57 +00:00
|
|
|
rts
|
|
|
|
|
|
|
|
_ApplyDirtyTiles
|
2022-07-06 19:55:27 +00:00
|
|
|
phd ; save the current direct page
|
|
|
|
tdc
|
|
|
|
clc
|
|
|
|
adc #$100 ; move to the next page
|
|
|
|
tcd
|
|
|
|
|
2022-01-20 02:58:57 +00:00
|
|
|
bra :begin
|
|
|
|
|
|
|
|
:loop
|
|
|
|
; Retrieve the offset of the next dirty Tile Store items in the Y-register
|
|
|
|
|
|
|
|
jsr _PopDirtyTile2
|
|
|
|
|
|
|
|
; Call the generic dispatch with the Tile Store record pointer at by the Y-register.
|
|
|
|
|
|
|
|
jsr _RenderDirtyTile
|
|
|
|
|
|
|
|
; Loop again until the list of dirty tiles is empty
|
|
|
|
|
2022-02-18 19:42:37 +00:00
|
|
|
:begin ldy DirtyTileCount
|
2022-01-20 02:58:57 +00:00
|
|
|
bne :loop
|
2022-07-06 19:55:27 +00:00
|
|
|
|
|
|
|
pld ; Move back to the original direct page
|
|
|
|
stz DirtyTileCount ; Reset the dirty tile count
|
2022-01-20 02:58:57 +00:00
|
|
|
rts
|
|
|
|
|
2023-01-02 17:04:26 +00:00
|
|
|
; This rendering mode turns off shadowing and draws all of the relevant background lines and then
|
|
|
|
; draws sprites on top of the background before turning shadowing on and exposing the lines to the
|
|
|
|
; screen. Even though entire lines are drawn twice, it's so efficient that it is often faster
|
|
|
|
; than using all of the logic to draw/erase tiles in the TileBuffer, even though less visible words
|
|
|
|
; are touched.
|
|
|
|
;
|
2023-04-26 04:39:09 +00:00
|
|
|
; This mode is also necessary if per-scanling rendering is used since sprites would not look correct
|
2023-01-02 17:04:26 +00:00
|
|
|
; if each line had independent offsets.
|
|
|
|
_RenderWithShadowing
|
|
|
|
sta RenderFlags
|
|
|
|
jsr _DoTimers ; Run any pending timer tasks
|
|
|
|
|
|
|
|
jsr _ApplyBG0YPos ; Set stack addresses for the virtual lines to the physical screen
|
|
|
|
jsr _ApplyBG1YPos ; Set the y-register values of the blitter
|
|
|
|
|
|
|
|
; _ApplyBG0Xpos need to be split because we have to set the offsets, then draw in any updated tiles, and
|
|
|
|
; finally patch out the code field. Right now, the BRA operand is getting overwritten by tile data.
|
|
|
|
|
|
|
|
jsr _ApplyBG0XPosPre
|
|
|
|
jsr _ApplyBG1XPosPre
|
|
|
|
|
|
|
|
jsr _UpdateBG0TileMap ; and the tile maps. These subroutines build up a list of tiles
|
2023-02-27 21:30:56 +00:00
|
|
|
; jsr _UpdateBG1TileMap ; that need to be updated in the code field
|
2023-01-02 17:04:26 +00:00
|
|
|
|
|
|
|
jsr _ApplyTiles ; This function actually draws the new tiles into the code field
|
|
|
|
|
|
|
|
jsr _ApplyBG0XPos ; Patch the code field instructions with exit BRA opcode
|
|
|
|
jsr _ApplyBG1XPos ; Update the direct page value based on the horizontal position
|
|
|
|
|
|
|
|
; At this point, everything in the background has been rendered into the code field. Next, we need
|
|
|
|
; to create priority lists of scanline ranges.
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-28 05:35:35 +00:00
|
|
|
jsr _FilterObjectList ; Walk the sorted list and create an array of objects that need to be rendered
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 13:47:56 +00:00
|
|
|
jsr _ShadowOff ; Turn off shadowing and draw all the scanlines with sprites on them
|
2023-04-28 05:35:35 +00:00
|
|
|
jsr _DrawObjShadow ; Draw the background
|
2023-04-26 13:47:56 +00:00
|
|
|
jsr _DrawDirectSprites ; Draw the sprites directly to the Bank $01 graphics buffer (skipping the render-to-tile step)
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 13:47:56 +00:00
|
|
|
jsr _ShadowOn ; Turn shadowing back on
|
2023-03-06 20:39:23 +00:00
|
|
|
jsr _DrawFinalPass
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-01-02 17:04:26 +00:00
|
|
|
lda StartYMod208 ; Restore the fields back to their original state
|
|
|
|
ldx ScreenHeight
|
|
|
|
jsr _RestoreBG0Opcodes
|
|
|
|
|
|
|
|
lda StartY
|
|
|
|
sta OldStartY
|
|
|
|
lda StartX
|
|
|
|
sta OldStartX
|
|
|
|
|
|
|
|
lda BG1StartY
|
|
|
|
sta OldBG1StartY
|
|
|
|
lda BG1StartX
|
|
|
|
sta OldBG1StartX
|
|
|
|
|
|
|
|
stz DirtyBits
|
|
|
|
stz LastRender ; Mark that a full render was just performed
|
|
|
|
|
|
|
|
lda SpriteRemovedFlag ; If any sprite was removed, set the rebuild flag
|
|
|
|
beq :no_removal
|
|
|
|
lda #DIRTY_BIT_SPRITE_ARRAY
|
|
|
|
sta DirtyBits
|
|
|
|
:no_removal
|
|
|
|
rts
|
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
; Run through the list of sprites that are not OFFSCREEN and not OVERLAYS and draw them directly to the graphics screen. We can use
|
2023-02-27 21:30:56 +00:00
|
|
|
; compiled sprites here, with limitations.
|
|
|
|
_DrawDirectSprites
|
2023-03-09 06:23:12 +00:00
|
|
|
lda RenderFlags
|
|
|
|
bit #RENDER_SPRITES_SORTED
|
|
|
|
bne :sorted
|
|
|
|
|
|
|
|
; Shift through the sprites
|
|
|
|
|
|
|
|
lda SpriteMap
|
|
|
|
beq :empty
|
|
|
|
sta tmp15
|
|
|
|
ldx #0
|
|
|
|
|
|
|
|
:iloop
|
|
|
|
lsr tmp15
|
|
|
|
bcc :next
|
2023-04-26 04:39:09 +00:00
|
|
|
|
|
|
|
phx
|
|
|
|
jsr _DrawStampToScreen
|
|
|
|
plx
|
2023-03-09 06:23:12 +00:00
|
|
|
|
|
|
|
:next inx
|
|
|
|
inx
|
|
|
|
lda tmp15
|
|
|
|
bne :iloop
|
|
|
|
rts
|
|
|
|
|
|
|
|
:sorted
|
2023-02-27 21:30:56 +00:00
|
|
|
ldx _SortedHead
|
|
|
|
bmi :empty
|
|
|
|
|
|
|
|
:loop
|
2023-04-26 04:39:09 +00:00
|
|
|
phx
|
|
|
|
jsr _DrawStampToScreen
|
|
|
|
plx
|
|
|
|
|
2023-03-09 06:23:12 +00:00
|
|
|
lda _Sprites+SORTED_NEXT,x ; If there another sprite in the list?
|
|
|
|
tax
|
|
|
|
bpl :loop
|
|
|
|
:empty
|
|
|
|
rts
|
|
|
|
|
|
|
|
|
2023-03-06 20:39:23 +00:00
|
|
|
; Run through the sorted list and perform a final render the jumps between calling _PEISlam for shadowed lines,
|
|
|
|
; _BltRange for clean backgrounds and Overlays as needed.
|
|
|
|
;
|
|
|
|
; The trick here is to merge runs of shared render types.
|
|
|
|
;
|
|
|
|
; Loop invariant: X-register is the current object index, Y-register is the next object index
|
|
|
|
;
|
|
|
|
; TODO: This does not yet handle the case of a narrow overlay in the middle of a sprite. The second half of the sprite will not be exposed
|
|
|
|
; by a PEISlam.
|
|
|
|
;
|
|
|
|
; e.g. |--- Overlay ---|
|
|
|
|
; |-------------- Sprite ----------------|
|
|
|
|
;
|
|
|
|
; Output Should be |-- PEI --||--- Overlay ---||--- PEI --|
|
|
|
|
; But currently is |-- PEI --||--- Overlay ---|
|
2023-04-26 04:39:09 +00:00
|
|
|
;
|
|
|
|
; The conceptual model of this routine is that it toggles between BltRange and PEISlam modes, but overlays are special and get drawn
|
|
|
|
; immediately but don't change the mode.
|
|
|
|
;
|
|
|
|
; General case to handle is this
|
|
|
|
;
|
|
|
|
; 0 1 2 3 4 5 6 7 8 9
|
|
|
|
; |------ sprite ---------| = A
|
|
|
|
; |-- overlay ------| = B
|
|
|
|
; |-- sprite -| = C
|
|
|
|
; |--- sprite ---| = D
|
|
|
|
;
|
|
|
|
; To handle this for each, we need to be able to slice off a piece of a sprite or overlay and insert it into the list for
|
|
|
|
; handling later. In this case, after the range [0, 1] is exposed for A, it should be dropped and moved like this
|
|
|
|
;
|
|
|
|
; 0 1 2 3 4 5 6 7 8 9
|
|
|
|
; |-- overlay ------| = B
|
|
|
|
; |-- sprite -| = C
|
|
|
|
; |--- sprite ---| = D
|
|
|
|
; |--| = A
|
|
|
|
;
|
|
|
|
; We can't alter that actual sorted list of items, so we create a reduced list which allows items to be filtered and
|
|
|
|
; to keep a simple, single-linked list
|
|
|
|
EOL equ $FFFF
|
2023-03-06 20:39:23 +00:00
|
|
|
|
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
; New approach here. Walk the sorted, double linked list and copy the IDs into an array. There is
|
|
|
|
; a parallel structure to use later, but this is the easiest thing to work with
|
|
|
|
_FilterObjectList
|
|
|
|
ldy #0
|
|
|
|
ldx _SortedHead ; Walk the list
|
|
|
|
bra :entry
|
2023-03-06 20:39:23 +00:00
|
|
|
|
|
|
|
:loop
|
2023-04-26 04:39:09 +00:00
|
|
|
txa
|
|
|
|
sta ObjectList+OL_INDEX,y
|
|
|
|
iny
|
|
|
|
iny
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
lda _Sprites+SORTED_NEXT,x
|
|
|
|
tax
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
:entry
|
|
|
|
jsr _GetNextItem ; Get the first item from the list
|
|
|
|
cpx #EOL
|
|
|
|
bne :loop ; Exit if there are no more items
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
sty ObjectListCount
|
|
|
|
rts
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
_DrawObjShadow
|
|
|
|
:top equ tmp8
|
|
|
|
:bottom equ tmp9
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
ldy #0
|
|
|
|
cpy ObjectListCount ; Exit if the list of objects is empty
|
|
|
|
beq :exit
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
; Initialize with the record
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
ldx ObjectList+OL_INDEX,y
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
:loop
|
|
|
|
lda _Sprites+SPRITE_CLIP_TOP,x ; Get the top scanline
|
|
|
|
sta :top
|
|
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x
|
|
|
|
:skip sta :bottom
|
|
|
|
|
|
|
|
; Advance to the next record.
|
|
|
|
|
|
|
|
iny
|
|
|
|
iny
|
|
|
|
cpy ObjectListCount ; Is this the last item
|
|
|
|
beq :done
|
|
|
|
|
|
|
|
; Check to see if the two items overlap
|
|
|
|
|
|
|
|
ldx ObjectList+OL_INDEX,y
|
|
|
|
cmp _Sprites+SPRITE_CLIP_TOP,x ; Compare to the top line of the next item
|
|
|
|
bcc :no_merge
|
|
|
|
|
|
|
|
max _Sprites+SPRITE_CLIP_BOTTOM,x ; Keep the largest of the two bottom values
|
|
|
|
bra :skip
|
|
|
|
|
|
|
|
:no_merge
|
|
|
|
phx
|
2023-03-06 20:39:23 +00:00
|
|
|
phy
|
2023-04-26 04:39:09 +00:00
|
|
|
ldx :top
|
|
|
|
ldy :bottom
|
|
|
|
jsr _BltRange
|
|
|
|
ply
|
2023-03-06 20:39:23 +00:00
|
|
|
plx
|
|
|
|
bra :loop
|
2023-04-26 04:39:09 +00:00
|
|
|
:exit
|
|
|
|
rts
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
:done
|
|
|
|
ldx :top ; X = top line
|
|
|
|
ldy :bottom ; Y = bottom line
|
|
|
|
jmp _BltRange ; If so, draw the background and return
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
;:loop
|
|
|
|
; Check if the current node and the next node are both sprites and, if they overlap, merge their ranges
|
|
|
|
; lda _Sprites+SPRITE_ID,x
|
|
|
|
; ora ObjectList+OL_SPRITE_ID,y
|
|
|
|
; and #SPRITE_OVERLAY
|
|
|
|
; bne :no_merge;
|
|
|
|
|
|
|
|
; lda ObjectList+OL_CLIP_BOTTOM,y
|
|
|
|
; cmp _Sprites+SPRITE_CLIP_TOP,x
|
|
|
|
; bcc :no_merge
|
|
|
|
|
|
|
|
; lda _Sprites+SPRITE_CLIP_BOTTOM,x
|
|
|
|
; max ObjectList+OL_CLIP_BOTTOM,y
|
|
|
|
; sta ObjectList+OL_CLIP_BOTTOM,y
|
|
|
|
; bra :skip
|
|
|
|
|
|
|
|
;:no_merge
|
|
|
|
; iny
|
|
|
|
; iny
|
|
|
|
; tya
|
|
|
|
; sta ObjectList+OL_NEXT-2,y ; Store link to this record in the previous node
|
|
|
|
|
|
|
|
;:entry
|
|
|
|
; lda _Sprites+SPRITE_ID,x
|
|
|
|
; sta ObjectList+OL_SPRITE_ID,y
|
|
|
|
; lda _Sprites+SPRITE_CLIP_TOP,x
|
|
|
|
; sta ObjectList+OL_CLIP_TOP,y
|
|
|
|
; lda _Sprites+SPRITE_CLIP_BOTTOM,x
|
|
|
|
; sta ObjectList+OL_CLIP_BOTTOM,y
|
|
|
|
|
|
|
|
;:skip
|
|
|
|
; lda _Sprites+SORTED_NEXT,x ; Advance to the next source item
|
|
|
|
; tax
|
|
|
|
; jsr _GetNextItem ; Get the first item from the list
|
|
|
|
; cpx #EOL
|
|
|
|
; bne :loop ; Exit if there are no valid entries
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
;:exit
|
|
|
|
; lda #EOL ; End-of-list marker
|
|
|
|
; sta ObjectList+OL_NEXT,y
|
|
|
|
;:empty
|
|
|
|
; rts
|
2023-03-06 20:39:23 +00:00
|
|
|
|
2023-04-28 05:11:46 +00:00
|
|
|
; Helper function to only return object from the sorted list if they are relevant for
|
|
|
|
; display.
|
2023-04-26 04:39:09 +00:00
|
|
|
_GetNextItem
|
|
|
|
cpx #EOL ; early out if we're at the end of the list
|
|
|
|
bne *+3
|
|
|
|
rts
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
lda _Sprites+SPRITE_ID,x ; always return overlays
|
|
|
|
bit #SPRITE_OVERLAY
|
|
|
|
beq *+3
|
|
|
|
rts
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
bit #SPRITE_HIDE ; skip hidden sprites
|
|
|
|
bne :next
|
|
|
|
lda _Sprites+IS_OFF_SCREEN,x ; skip off-screen sprites
|
|
|
|
bne :next
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
rts ; found an object to return
|
|
|
|
:next
|
|
|
|
lda _Sprites+SORTED_NEXT,x
|
|
|
|
tax
|
|
|
|
bra _GetNextItem
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
DrawOverlayY
|
2023-02-27 21:30:56 +00:00
|
|
|
phx
|
2023-04-26 04:39:09 +00:00
|
|
|
phy
|
|
|
|
|
|
|
|
txy ; Swap X/Y
|
2023-02-27 21:30:56 +00:00
|
|
|
plx
|
2023-04-26 04:39:09 +00:00
|
|
|
phx
|
|
|
|
jsr _DrawOverlay
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
ply
|
|
|
|
plx
|
|
|
|
rts
|
2023-02-27 21:30:56 +00:00
|
|
|
|
2023-04-26 04:39:09 +00:00
|
|
|
; A = top line
|
|
|
|
; X = sprite record
|
|
|
|
; Y = bottom line
|
|
|
|
_DrawOverlay
|
|
|
|
pha
|
|
|
|
lda _Sprites+OVERLAY_PROC,x
|
|
|
|
stal :disp+1
|
|
|
|
lda _Sprites+OVERLAY_PROC+1,x
|
|
|
|
stal :disp+2
|
|
|
|
|
|
|
|
lda ScreenY0 ; pass the address of the first line of the overlay
|
|
|
|
clc
|
|
|
|
adc _Sprites+OVERLAY_TOP,x
|
|
|
|
asl
|
2023-02-27 21:30:56 +00:00
|
|
|
tax
|
2023-04-26 04:39:09 +00:00
|
|
|
lda ScreenAddr,x
|
|
|
|
clc
|
|
|
|
adc ScreenX0
|
|
|
|
plx
|
|
|
|
:disp jsl $000000
|
2023-02-28 17:17:43 +00:00
|
|
|
rts
|
|
|
|
|
2023-03-12 20:39:41 +00:00
|
|
|
; Helper to set a palette index on a range of SCBs to help show which actions are applied to which lines
|
2023-02-28 17:17:43 +00:00
|
|
|
DebugSCBs
|
|
|
|
phx
|
|
|
|
phy
|
|
|
|
sep #$30 ; short m/x
|
|
|
|
|
|
|
|
pha ; save the SCB value
|
|
|
|
|
|
|
|
phx
|
|
|
|
tya
|
|
|
|
sec
|
|
|
|
sbc 1,s
|
|
|
|
tay ; number of scanlines
|
|
|
|
|
|
|
|
pla
|
|
|
|
clc
|
|
|
|
adc ScreenY0
|
|
|
|
tax ; physical line index
|
|
|
|
|
|
|
|
pla
|
|
|
|
:loop
|
|
|
|
stal SHR_SCB,x
|
|
|
|
inx
|
|
|
|
dey
|
|
|
|
bne :loop
|
|
|
|
|
|
|
|
rep #$30
|
|
|
|
ply
|
|
|
|
plx
|
|
|
|
rts
|
|
|
|
|
|
|
|
|