mirror of
https://github.com/lscharen/iigs-game-engine.git
synced 2024-06-10 01:29:29 +00:00
834 lines
29 KiB
ArmAsm
834 lines
29 KiB
ArmAsm
; Renders a frame of animation
|
|
;
|
|
; The render function is the point of committment -- most of the APIs that set sprites and
|
|
; update coordinates are lazy; they simply save their values and set a dirty flag in the
|
|
; 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.
|
|
;
|
|
; 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.
|
|
;
|
|
; 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.
|
|
|
|
; It's important to do _ApplyBG0YPos first because it calculates the value of StartY % 208 which is
|
|
; used in all of the other loops
|
|
_Render
|
|
sta RenderFlags
|
|
|
|
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
|
|
|
|
jsr _DoTimers ; Run any pending timer tasks
|
|
|
|
stz SpriteRemovedFlag ; If we remove a sprite, then we need to flag a rebuild for the next frame
|
|
|
|
jsr _ApplyBG0YPos ; Set stack addresses for the virtual lines to the physical screen
|
|
|
|
lda #RENDER_BG1_ROTATION
|
|
bit RenderFlags
|
|
bne :skip_bg1_y
|
|
jsr _ApplyBG1YPos ; Set the y-register values of the blitter
|
|
:skip_bg1_y
|
|
|
|
; _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 _RenderSprites ; Once the BG0 X and Y positions are committed, update sprite data
|
|
|
|
jsr _UpdateBG0TileMap ; and the tile maps. These subroutines build up a list of tiles
|
|
; jsr _UpdateBG1TileMap ; that need to be updated in the code field
|
|
|
|
|
|
jsr _ApplyTiles ; This function actually draws the new tiles into the code field
|
|
jsr _ApplyBG0XPos ; Patch the code field instructions with exit BRA opcode
|
|
|
|
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
|
|
|
|
; 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.
|
|
|
|
lda Overlays+OVERLAY_ID
|
|
beq :no_ovrly
|
|
|
|
jsr _ShadowOff
|
|
|
|
; 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.
|
|
|
|
ldx Overlays+OVERLAY_TOP ; Blit the full virtual buffer to the screen
|
|
ldy Overlays+OVERLAY_BOTTOM
|
|
iny
|
|
jsr _BltRange
|
|
|
|
; Turn shadowing back on
|
|
|
|
jsr _ShadowOn
|
|
|
|
; Now render all of the remaining lines in top-to-bottom (or bottom-to-top) order
|
|
|
|
jsr _DoOverlay
|
|
|
|
ldx Overlays+OVERLAY_BOTTOM
|
|
inx
|
|
cpx ScreenHeight
|
|
beq :done
|
|
ldy ScreenHeight
|
|
jsr _BltRange
|
|
bra :done
|
|
|
|
:no_ovrly
|
|
ldx #0 ; Blit the full virtual buffer to the screen
|
|
ldy ScreenHeight
|
|
jsr _BltRange
|
|
:done
|
|
|
|
ldx #0
|
|
ldy ScreenHeight
|
|
jsr _BltSCB
|
|
|
|
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
|
|
|
|
_DoOverlay
|
|
lda Overlays+OVERLAY_PROC
|
|
stal :disp+1
|
|
lda Overlays+OVERLAY_PROC+1
|
|
stal :disp+2
|
|
|
|
lda ScreenY0 ; pass the address of the first line of the overlay
|
|
clc
|
|
adc Overlays+OVERLAY_TOP
|
|
asl
|
|
tax
|
|
lda ScreenAddr,x
|
|
clc
|
|
adc ScreenX0
|
|
:disp jsl $000000
|
|
rts
|
|
|
|
|
|
; 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
|
|
lda BG1YTable ; Make sure we're in the right mode (0 = scanline mode, $1800 = normal mode)
|
|
beq :ytbl_ok
|
|
lda #1
|
|
jsr _ResetBG1YTable
|
|
:ytbl_ok
|
|
|
|
jsr _ApplyBG0YPos ; Set stack addresses for the virtual lines to the physical screen
|
|
jsr _ApplyScanlineBG1YPos ; 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 _ApplyScanlineBG0XPos ; Patch the code field instructions with exit BRA opcode
|
|
jsr _ApplyScanlineBG1XPos
|
|
|
|
jsr _BuildShadowList ; Create the rages based on the sorted sprite y-values
|
|
|
|
jsr _ShadowOff ; Turn off shadowing and draw all the scanlines with sprites on them
|
|
jsr _DrawShadowList
|
|
jsr _DrawDirectSprites ; Draw the sprites directly to the Bank $01 graphics buffer (skipping the render-to-tile step)
|
|
|
|
jsr _ShadowOn ; Turn shadowing back on
|
|
jsr _DrawFinalPass
|
|
|
|
lda StartYMod208 ; Restore the fields back to their original state
|
|
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
|
|
|
|
; Run through all of the tiles on the DirtyTile list and render them
|
|
_ApplyTiles
|
|
ldx DirtyTileCount
|
|
|
|
phd ; sve the current direct page
|
|
tdc
|
|
clc
|
|
adc #$100 ; move to the next page
|
|
tcd
|
|
|
|
stx DP2_DIRTY_TILE_COUNT ; Cache the dirty tile count
|
|
jsr _PopDirtyTilesFast
|
|
|
|
pld ; Move back to the original direct page
|
|
stz DirtyTileCount ; Reset the dirty tile count
|
|
rts
|
|
|
|
; This is a specialized render function that only updates the dirty tiles *and* draws them
|
|
; 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.
|
|
;
|
|
; In this renderer, we assume that there is no scrolling, so no need to update any information about
|
|
; the BG0/BG1 positions
|
|
_RenderDirty
|
|
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
|
|
jsr _ResetToDirtyTileProcs ; Switch the tile procs to the dirty tile rendering functions
|
|
; jsr _ClearSpritesFromCodeField ; Restore the tiles to their non-sprite versions
|
|
:norecalc
|
|
jsr _RenderSprites
|
|
jsr _ApplyDirtyTiles
|
|
|
|
lda #1
|
|
sta LastRender
|
|
rts
|
|
|
|
_ApplyDirtyTiles
|
|
phd ; save the current direct page
|
|
tdc
|
|
clc
|
|
adc #$100 ; move to the next page
|
|
tcd
|
|
|
|
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
|
|
|
|
:begin ldy DirtyTileCount
|
|
bne :loop
|
|
|
|
pld ; Move back to the original direct page
|
|
stz DirtyTileCount ; Reset the dirty tile count
|
|
rts
|
|
|
|
; 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.
|
|
;
|
|
; This mode is also necessary if per-scanling rendering it used since sprites would not look correct
|
|
; 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
|
|
; jsr _UpdateBG1TileMap ; that need to be updated in the code field
|
|
|
|
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.
|
|
|
|
jsr _BuildShadowList ; Create the rages based on the sorted sprite y-values
|
|
|
|
jsr _ShadowOff ; Turn off shadowing and draw all the scanlines with sprites on them
|
|
jsr _DrawShadowList
|
|
jsr _DrawDirectSprites ; Draw the sprites directly to the Bank $01 graphics buffer (skipping the render-to-tile step)
|
|
|
|
jsr _ShadowOn ; Turn shadowing back on
|
|
; jsr _DrawComplementList ; Alternate drawing scanlines and PEI slam to expose the full fram
|
|
jsr _DrawFinalPass
|
|
|
|
;
|
|
; The objects that need to be reasoned about are
|
|
;
|
|
; 1. Sprites
|
|
; 2. Overlays
|
|
; a. Solid High Priority
|
|
; b. Solid Low Priority
|
|
; c. Masked High Priority
|
|
; d. Masked Low Priority
|
|
; 3. Background
|
|
;
|
|
; Notes:
|
|
;
|
|
; A High Priority overlay is rendered above the sprites
|
|
; A Low Priority overlay is rendered below the sprites
|
|
; A Solid High Priority overlay obscured everything and if the only thing drawn on the scanline
|
|
;
|
|
; The order of draw oprations is:
|
|
;
|
|
; 1. Turn off shadowing
|
|
; 2. Draw the background for scanlines with (Sprites OR a Masked Low Priority overlay) AND NOT a Solid Low Priority overlay
|
|
; 3. Draw the Solid Low Priority overlays
|
|
; 4. Draw the Sprites
|
|
; 5. Draw the Masked Low Priority overlays
|
|
; 6. Turn on shadowing
|
|
; 7. Draw, in top-to-bottom order
|
|
; a. Background lines not drawn yet
|
|
; b. PEI Slam lines with (Sprites OR a Masked Low Priority Overlay) AND NOT a High Priority overlay
|
|
; c. High Priority overlays
|
|
;
|
|
; The work of this routine is to quickly build a sorted list of scanline ranges that can the appropriate
|
|
; sub-renderer
|
|
|
|
; jsr BuildShadowSegments
|
|
;
|
|
; The trick is to create a bit-field mapping for the different actions to define
|
|
|
|
; lda Overlays
|
|
; beq :no_ovrly
|
|
;
|
|
; jsr _ShadowOff
|
|
|
|
; 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.
|
|
|
|
; ldx Overlays+OVERLAY_TOP ; Blit the full virtual buffer to the screen
|
|
; ldy Overlays+OVERLAY_BOTTOM
|
|
; jsr _BltRange
|
|
|
|
; Turn shadowing back on
|
|
|
|
; jsr _ShadowOn
|
|
|
|
; Now render all of the remaining lines in top-to-bottom (or bottom-to-top) order
|
|
|
|
; ldx #0
|
|
; ldy Overlays+OVERLAY_TOP
|
|
; beq :skip
|
|
; jsr _BltRange
|
|
;:skip
|
|
; jsr _DoOverlay
|
|
|
|
; ldx Overlays+OVERLAY_BOTTOM
|
|
; cpx ScreenHeight
|
|
; beq :done
|
|
; ldy ScreenHeight
|
|
; jsr _BltRange
|
|
; bra :done
|
|
|
|
;:no_ovrly
|
|
|
|
; ldx #0 ; Blit the full virtual buffer to the screen
|
|
; ldy ScreenHeight
|
|
; jsr _BltRange
|
|
|
|
;:done
|
|
|
|
; ldx #0
|
|
; ldy ScreenHeight
|
|
; jsr _BltSCB
|
|
|
|
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
|
|
|
|
; Look at the overlay list and the sprite list and figure out which scanline ranges need to be
|
|
; blitted in what order. We try to build all of the scan line segments lists because that
|
|
; saves the work of re-scanning the lists.
|
|
;
|
|
; The semgent list definitions are:
|
|
;
|
|
; BLIT_W_SHADOW_OF
|
|
BuildShadowSegments
|
|
; ldx _SortedHead
|
|
; bmi :no_sprite
|
|
;:loop
|
|
; lda _Sprites+CLIP_TOP,x
|
|
; lda _Sprites+SORTED_NEXT,x
|
|
; tax
|
|
; bpl :loop
|
|
;
|
|
; lda #0 ; Start at the top of the
|
|
|
|
rts
|
|
|
|
; Function to iterate through the sprite list and build a merged scanline list of sprites. Once this is
|
|
; done, we re-scan the list to build the complement for scanlines that do not need shadowing.
|
|
_BuildShadowList
|
|
|
|
ldy #0 ; This is the index into the list of shadow segments
|
|
|
|
ldx _SortedHead
|
|
bmi :empty
|
|
bra :insert
|
|
|
|
; Start of loop
|
|
:advance
|
|
iny
|
|
iny
|
|
|
|
:insert
|
|
lda _Sprites+SPRITE_CLIP_TOP,x ; Load the sprite's top line
|
|
sta _ShadowListTop,y ; Set the top entry of the list to the sprite top
|
|
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x ; Optimistically set the end of the segment to the bottom of this sprite
|
|
inc ; Clip values are on the scanline, so add one to make it a proper interval
|
|
|
|
:replace
|
|
sta _ShadowListBottom,y
|
|
:skip
|
|
lda _Sprites+SORTED_NEXT,x ; If there another sprite in the list?
|
|
bmi :no_more_sprites ; If not, we can finish up
|
|
|
|
tax
|
|
lda _ShadowListBottom,y ; If the bottom of the current sprite is _less than_ the top of the next
|
|
cmp _Sprites+SPRITE_CLIP_TOP,x ; sprite, then there is a gap and we create a new entry
|
|
bcc :advance
|
|
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x ; Get the bottom value of the next sprite.
|
|
inc
|
|
cmp _ShadowListBottom,y ; If it extends the segment then replace the value, otherwise skip
|
|
bcc :skip
|
|
bra :replace
|
|
|
|
:no_more_sprites
|
|
iny ; Set the list count to N * 2
|
|
iny
|
|
:empty
|
|
sty _ShadowListCount
|
|
rts
|
|
|
|
; Run through the shadow list and make a complementary list, e.g
|
|
; [[0, 7], [12, 19]] -> [[7, 12], [19, end]]
|
|
; [[2, 10], [20, 40]] -> [[0, 2], [10, 20], [40, end]]
|
|
|
|
_ComplementList
|
|
ldy #0
|
|
tyx
|
|
|
|
lda _ShadowListCount
|
|
beq :empty_list
|
|
|
|
lda _ShadowListTop
|
|
beq :loop
|
|
|
|
stz _DirectListTop
|
|
sta _DirectListBottom
|
|
|
|
inx
|
|
inx
|
|
|
|
:loop
|
|
lda _ShadowListBottom,y
|
|
sta _DirectListTop,x
|
|
|
|
iny ; Move to the next shadow list record
|
|
iny
|
|
cpy _ShadowListCount ; Are there any other segments to process
|
|
bcs :eol
|
|
|
|
lda _ShadowListTop,y
|
|
sta _DirectListBottom,x ; Finish the direct list entry
|
|
|
|
inx
|
|
inx
|
|
bra :loop
|
|
|
|
:eol
|
|
lda ScreenHeight
|
|
sta _DirectListBottom,x
|
|
|
|
inx ; Set the count to N * 2
|
|
inx
|
|
stx _DirectListCount
|
|
rts
|
|
|
|
:empty_list
|
|
lda #1
|
|
sta _DirectListCount
|
|
stz _DirectListTop
|
|
lda ScreenHeight
|
|
sta _DirectListBottom
|
|
rts
|
|
|
|
; Iterate through the shadow list and call _BltRange on each
|
|
_DrawShadowList
|
|
ldx #0
|
|
bra :start
|
|
|
|
:loop
|
|
phx ; Save the index
|
|
lda _ShadowListTop,x
|
|
ldy _ShadowListBottom,x
|
|
tax
|
|
jsr _BltRange
|
|
|
|
plx
|
|
inx
|
|
inx
|
|
:start
|
|
cpx _ShadowListCount
|
|
bcc :loop
|
|
|
|
rts
|
|
|
|
; Run through the list of sprites that are not IS_OFFSCREEN and not OVERLAYS and draw them directly to the graphics screen. We can use
|
|
; compiled sprites here, with limitations.
|
|
_DrawDirectSprites
|
|
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
|
|
jsr :render
|
|
|
|
:next inx
|
|
inx
|
|
lda tmp15
|
|
bne :iloop
|
|
rts
|
|
|
|
:sorted
|
|
ldx _SortedHead
|
|
bmi :empty
|
|
|
|
:loop
|
|
jsr :render
|
|
lda _Sprites+SORTED_NEXT,x ; If there another sprite in the list?
|
|
tax
|
|
bpl :loop
|
|
:empty
|
|
rts
|
|
|
|
:render
|
|
lda _Sprites+SPRITE_ID,x
|
|
bit #SPRITE_OVERLAY
|
|
beq *+3
|
|
rts
|
|
lda _Sprites+SPRITE_STATUS,x
|
|
bit #SPRITE_STATUS_HIDDEN
|
|
beq *+3
|
|
rts
|
|
phx
|
|
jsr _DrawStampToScreen
|
|
plx
|
|
rts
|
|
|
|
|
|
; 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 ---|
|
|
|
|
_DrawFinalPass
|
|
:curr_top equ tmp0
|
|
:curr_bottom equ tmp1
|
|
:curr_type equ tmp2
|
|
|
|
ldx _SortedHead
|
|
bmi :empty
|
|
|
|
lda _Sprites+SPRITE_CLIP_TOP,x ; Load the first object's top edge
|
|
beq :loop ; If it's at the top edge of the screen, proceed. Othrewise _BltRange the top range
|
|
|
|
ldx #0
|
|
tay
|
|
jsr _BltRange
|
|
ldx _SortedHead ; Reload the register
|
|
|
|
:loop
|
|
lda _Sprites+SPRITE_ID,x ; Save the type of the current segment. Do this first because it can be skipped
|
|
and #SPRITE_OVERLAY ; when merging ranges of the same type
|
|
sta :curr_type
|
|
|
|
lda _Sprites+SPRITE_CLIP_TOP,x
|
|
sta :curr_top
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,x ; Optimistically set the end of the segment to the bottom of this object
|
|
inc ; Clip values are on the scanline, so add one to make it a proper interval
|
|
|
|
:update
|
|
sta :curr_bottom
|
|
|
|
:skip
|
|
ldy _Sprites+SORTED_NEXT,x ; If there another object in the list?
|
|
bmi :no_more ; If not, we can finish up
|
|
|
|
lda :curr_bottom ; If the bottom of the current object is _less than_ the top of the next
|
|
cmp _Sprites+SPRITE_CLIP_TOP,y ; sprite, then there is a gap and we can draw the current object and a
|
|
bcc :advance ; _BltRange up to the next one
|
|
|
|
; Here, we've established that there is another object segment that starts at or within the bounds of the current
|
|
; object. If they are of the same type, then we can merge them and look at the next object in the list; treating
|
|
; the merges range as a larger, single object range.
|
|
;
|
|
; If they are different, then clip the current object range to the top of the next one, render the current object
|
|
; range and then take the new object as the current one.
|
|
;
|
|
; If the first object extends past the second, we are going to miss the remainder of that object. We really need a
|
|
; stack to put it on so that it can eventually be processed later.
|
|
|
|
lda _Sprites+SPRITE_ID,y
|
|
and #SPRITE_OVERLAY
|
|
cmp :curr_type
|
|
bne :no_merge
|
|
|
|
tyx ; Move the next index into the current
|
|
lda _Sprites+SPRITE_CLIP_BOTTOM,y ; Get the bottom value of the next sprite.
|
|
inc
|
|
cmp :curr_bottom ; If it extends the segment then replace the bottom value, otherwise skip. In
|
|
bcc :skip ; either case, the type and top value remain the same
|
|
bra :update
|
|
|
|
; This is a simpler version of the 'advance' below. In this case there are overlapping ranges, so we just need to draw a
|
|
; clipped version of the top range and then restart the loop with the next range.
|
|
:no_merge
|
|
lda _Sprites+SPRITE_CLIP_TOP,y ; Get the top of the next segment
|
|
sta :curr_bottom ; Use it as the bottom of the current segment
|
|
phy ; Save the next index...
|
|
jsr :PEIOrOverlay ; Draw the current segment type
|
|
plx ; ...and restore as the current
|
|
bra :loop ; Start again
|
|
|
|
:advance
|
|
phy
|
|
jsr :PEIOrOverlay ; Draw the appropriate filler
|
|
lda 1,s
|
|
tax
|
|
ldy _Sprites+SPRITE_CLIP_TOP,x ; Draw the background in between
|
|
ldx :curr_bottom
|
|
; brk $34
|
|
jsr _BltRange
|
|
plx
|
|
bra :loop
|
|
|
|
; List is empty, so just do one big _BltRange with a tail call
|
|
:empty
|
|
ldx #0
|
|
:no_more2
|
|
ldy ScreenHeight
|
|
jmp _BltRange
|
|
|
|
; Found the end of the list. Draw current object and then blit the rest of the screen
|
|
:no_more
|
|
jsr :PEIOrOverlay
|
|
ldx :curr_bottom
|
|
cpx ScreenHeight
|
|
bcc :no_more2
|
|
rts
|
|
|
|
; Help to select between calling an Overlay or PEISlam routine
|
|
:PEIOrOverlay
|
|
lda :curr_type
|
|
bne :overlay
|
|
|
|
ldx :curr_top
|
|
ldy :curr_bottom
|
|
jmp _PEISlam
|
|
:overlay
|
|
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
|
|
tax
|
|
lda ScreenAddr,x
|
|
clc
|
|
adc ScreenX0
|
|
ldx :curr_top
|
|
ldy :curr_bottom
|
|
; brk $33
|
|
|
|
:disp jsl $000000
|
|
rts
|
|
|
|
|
|
_DrawComplementList
|
|
|
|
ldx #0
|
|
|
|
lda _DirectListCount ; Skip empty lists
|
|
beq :out
|
|
|
|
lda _DirectListTop ; If the first segment starts at 0, begin with _BltRange
|
|
beq :blt_range
|
|
|
|
lda #0
|
|
bra :pei_first
|
|
|
|
:blt_range
|
|
phx
|
|
lda _DirectListTop,x
|
|
ldy _DirectListBottom,x
|
|
tax
|
|
jsr _BltRange
|
|
plx
|
|
|
|
lda _DirectListBottom,x ; Grab a copy of the bottom of the blit range
|
|
inx
|
|
inx ; Advance to the next entry
|
|
cpx _DirectListCount
|
|
bcs :last ; Done, so check if there is any remaining part of the screen to slam
|
|
|
|
:pei_first
|
|
phx
|
|
ldy _DirectListTop,x
|
|
tax
|
|
jsr _PEISlam
|
|
plx
|
|
bra :blt_range
|
|
|
|
:last
|
|
cmp ScreenHeight ; If the bottom on the last segment didn't come to the bottom of the
|
|
bcs :out ; screen, then expose that range
|
|
tax
|
|
ldy ScreenHeight
|
|
jsr _PEISlam
|
|
:out
|
|
rts
|
|
|
|
; Helper to set a palette index on a range of SCBs to help show which actions are applied to which lines
|
|
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
|
|
|
|
|