Add workable overlay support to the scanline renderer

This commit is contained in:
Lucas Scharenbroich 2023-03-06 14:39:23 -06:00
parent 6832c7f405
commit 5697737a93
5 changed files with 232 additions and 44 deletions

View File

@ -182,13 +182,6 @@ OVERLAY_SOLID equ $8000 ; Overlay covers the scan line and is fu
OVERLAY_ABOVE equ $0000 ; Overlay is drawn above scanline sprites
OVERLAY_BELOW equ $4000 ; Overlay is drawn below scanline sprites
OVERLAY_BASE equ 2 ; 2 bytes for the number of overlays
OVERLAY_REC_SIZE equ 10 ; Size of an overlay record (10 bytes)
OVERLAY_FLAGS equ {OVERLAY_BASE+0}
OVERLAY_TOP equ {OVERLAY_BASE+2}
OVERLAY_BOTTOM equ {OVERLAY_BASE+4}
OVERLAY_PROC equ {OVERLAY_BASE+6}
; DirtyBits definitions
DIRTY_BIT_BG0_X equ $0001
DIRTY_BIT_BG0_Y equ $0002
@ -228,6 +221,7 @@ TILE_CTRL_MASK equ $7E00
; TILE_PROC_MASK equ $7800 ; Select tile proc for rendering
; Sprite constants
SPRITE_OVERLAY equ $8000 ; This is an overlay record. Stored as a sprite for render ordering purposes
SPRITE_COMPILED equ $4000 ; This is a compiled sprite (SPRITE_DISP points to a routine in the compiled cache bank)
SPRITE_HIDE equ $2000 ; Do not render the sprite
SPRITE_16X16 equ $1800 ; 16 pixels wide x 16 pixels tall
@ -283,7 +277,7 @@ VBuffArray EXT
_stamp_step EXT
VBuffVertTableSelect EXT
VBuffHorzTableSelect EXT
Overlays EXT
; Overlays EXT
BG1YCache EXT
ScalingTables EXT

View File

@ -71,7 +71,7 @@ _Render
; 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
lda Overlays+OVERLAY_ID
beq :no_ovrly
jsr _ShadowOff
@ -82,6 +82,7 @@ _Render
ldx Overlays+OVERLAY_TOP ; Blit the full virtual buffer to the screen
ldy Overlays+OVERLAY_BOTTOM
iny
jsr _BltRange
; Turn shadowing back on
@ -93,6 +94,7 @@ _Render
jsr _DoOverlay
ldx Overlays+OVERLAY_BOTTOM
inx
cpx ScreenHeight
beq :done
ldy ScreenHeight
@ -343,14 +345,15 @@ _RenderWithShadowing
jsr _BuildShadowList ; Create the rages based on the sorted sprite y-values
; jsr _MergeOverlay ; Add the overlay range into the shadow list (treat it as a sprite)
jsr _ComplementList ; Create the complement to identify non-sprite / non-overlay scanlines
; jsr _ComplementList ; Create the complement to identify non-sprite / non-overlay scanlines
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 _DrawComplementList ; Alternate drawing scanlines and PEI slam to expose the full fram
jsr _DrawFinalPass
;
; The objects that need to be reasoned about are
@ -598,13 +601,16 @@ _DrawShadowList
rts
; Run through the list of sprites that are not IS_OFFSCREEN and draw them directly to the graphics screen. We can use
; 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
ldx _SortedHead
bmi :empty
:loop
lda _Sprites+SPRITE_ID,x
bit #SPRITE_OVERLAY
bne :next
lda _Sprites+SPRITE_STATUS,x
bit #SPRITE_STATUS_HIDDEN
bne :next
@ -622,7 +628,144 @@ _DrawDirectSprites
:empty
rts
; Run through the complement list and alternate between calling _PEISlam and _BltRange to show to full screen
; 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
sta :curr_top
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
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
:disp jsl $000000
rts
_DrawComplementList
ldx #0

View File

@ -698,6 +698,11 @@ _TSStartScript
_TSExit #0;#6
; SetOverlay(top, bottom, proc)
;
; Overlays are handled as quasi-sprites. They need to be included in the y-sorted list of "stuff", but they are not drawn like
; sprites. As such, they set a special flag in the SPRITE_ID field which allows them to be ignored for other purposes. Also,
; they are not added into the "normal" sprite range on 0 - 15, but are stored in locations 16 and up to further seggregate them from
; the rest of the system. A lot of the SPRITE_* locations are repurposed for Overlay-specific information.
_TSSetOverlay
:proc equ FirstParam+0
:bottom equ FirstParam+4
@ -705,9 +710,40 @@ _TSSetOverlay
_TSEntry
lda #1
sta Overlays
stz Overlays+OVERLAY_FLAGS
ldx #0 ; Always just use the first spot
lda #SPRITE_OVERLAY
sta Overlays+OVERLAY_ID,x
stz Overlays+OVERLAY_FLAGS,x
lda :top,s
sta Overlays+OVERLAY_TOP,x
lda :bottom,s
dec
sta Overlays+OVERLAY_BOTTOM,x
sec
sbc :top,s
inc
sta Overlays+OVERLAY_HEIGHT,x
lda :proc,s
sta Overlays+OVERLAY_PROC,x
lda :proc+2,s
sta Overlays+OVERLAY_PROC+2,x
ldx #{MAX_SPRITES+0}*2 ; Adjust to call the generic routings
jsr _InsertSprite
_TSExit #0;#8
_TSUpdateOverlay
:proc equ FirstParam+0
:bottom equ FirstParam+4
:top equ FirstParam+6
_TSEntry
ldx #0
stz Overlays+OVERLAY_FLAGS,x
lda :top,s
sta Overlays+OVERLAY_TOP
lda :bottom,s

View File

@ -27,7 +27,7 @@ DirtyTiles ENT
ds \,$00 ; pad to the next page boundary
_Sprites ENT
ds SPRITE_REC_SIZE*MAX_SPRITES
ds SPRITE_REC_SIZE*MAX_ELEMENTS
;-------------------------------------------------------------------------------------
;
@ -389,9 +389,10 @@ OldOneSecVec ENT
ds 4
Timers ENT
ds TIMER_REC_SIZE*MAX_TIMERS
Overlays ENT
dw 0 ; count
ds 10 ; only support one for now (flags, start_line, end_line, function call)
;Overlays ENT
; dw 0 ; count
; ds 10 ; only support one for now (flags, start_line, end_line, function call)
; From the IIgs ref
DefaultPalette ENT
@ -513,17 +514,17 @@ _SortedHead ENT
_ShadowListCount ENT
ds 2
_ShadowListTop ENT
ds {2*{MAX_SPRITES+1}} ; space for all of the sprites + overlay range
ds {2*{MAX_ELEMENTS}} ; space for all of the sprites + overlay range
_ShadowListBottom ENT
ds {2*{MAX_SPRITES+1}}
ds {2*{MAX_ELEMENTS}}
; Complement of the Shadow List. Can have one more segment than that list
_DirectListCount ENT
ds 2
_DirectListTop ENT
ds {2*{MAX_SPRITES+2}}
ds {2*{MAX_ELEMENTS+1}}
_DirectListBottom ENT
ds {2*{MAX_SPRITES+2}}
ds {2*{MAX_ELEMENTS+1}}
; Steps to the different sprite stamps

View File

@ -28,6 +28,9 @@ TILE_STORE_NUM equ 12 ; Need this many parallel arra
MAX_SPRITES equ 16
SPRITE_REC_SIZE equ 42
MAX_OVERLAYS equ 2
MAX_ELEMENTS equ {MAX_SPRITES+MAX_OVERLAYS}
; Mark each sprite as ADDED, UPDATED, MOVED, REMOVED depending on the actions applied to it
; on this frame. Quick note, the same Sprite ID cannot be removed and added in the same frame.
; A REMOVED sprite if removed from the sprite list during the Render call, so it's ID is not
@ -41,28 +44,39 @@ SPRITE_STATUS_REMOVED equ $0008 ; Sprite has been removed.
SPRITE_STATUS_HIDDEN equ $0010 ; Sprite is in a hidden state
; These values are set by the user
SPRITE_STATUS equ {MAX_SPRITES*0}
SPRITE_ID equ {MAX_SPRITES*2}
SPRITE_X equ {MAX_SPRITES*4}
SPRITE_Y equ {MAX_SPRITES*6}
VBUFF_ADDR equ {MAX_SPRITES*8} ; Base address of the sprite's stamp in the data/mask banks
SPRITE_STATUS equ {MAX_ELEMENTS*0}
SPRITE_ID equ {MAX_ELEMENTS*2}
SPRITE_X equ {MAX_ELEMENTS*4}
SPRITE_Y equ {MAX_ELEMENTS*6}
VBUFF_ADDR equ {MAX_ELEMENTS*8} ; Base address of the sprite's stamp in the data/mask banks
; These values are cached / calculated during the rendering process
TS_LOOKUP_INDEX equ {MAX_SPRITES*10} ; The index from the TileStoreLookup table that corresponds to the top-left corner of the sprite
TS_COVERAGE_SIZE equ {MAX_SPRITES*12} ; Representation of how many TileStore tiles (NxM) are covered by this sprite
SPRITE_DISP equ {MAX_SPRITES*14} ; Cached address of the specific stamp based on sprite flags
SPRITE_CLIP_LEFT equ {MAX_SPRITES*16}
SPRITE_CLIP_RIGHT equ {MAX_SPRITES*18}
SPRITE_CLIP_TOP equ {MAX_SPRITES*20}
SPRITE_CLIP_BOTTOM equ {MAX_SPRITES*22}
IS_OFF_SCREEN equ {MAX_SPRITES*24}
SPRITE_WIDTH equ {MAX_SPRITES*26}
SPRITE_HEIGHT equ {MAX_SPRITES*28}
SPRITE_CLIP_WIDTH equ {MAX_SPRITES*30}
SPRITE_CLIP_HEIGHT equ {MAX_SPRITES*32}
TS_VBUFF_BASE equ {MAX_SPRITES*34} ; Finalized VBUFF address based on the sprite position and tile offsets
SORTED_PREV equ {MAX_SPRITES*36} ; Doubly-Linked List that maintains the sprites in sorted order based on SPRITE_Y
SORTED_NEXT equ {MAX_SPRITES*38}
TS_LOOKUP_INDEX equ {MAX_ELEMENTS*10} ; The index from the TileStoreLookup table that corresponds to the top-left corner of the sprite
TS_COVERAGE_SIZE equ {MAX_ELEMENTS*12} ; Representation of how many TileStore tiles (NxM) are covered by this sprite
SPRITE_DISP equ {MAX_ELEMENTS*14} ; Cached address of the specific stamp based on sprite flags
SPRITE_CLIP_LEFT equ {MAX_ELEMENTS*16}
SPRITE_CLIP_RIGHT equ {MAX_ELEMENTS*18}
SPRITE_CLIP_TOP equ {MAX_ELEMENTS*20}
SPRITE_CLIP_BOTTOM equ {MAX_ELEMENTS*22}
IS_OFF_SCREEN equ {MAX_ELEMENTS*24}
SPRITE_WIDTH equ {MAX_ELEMENTS*26}
SPRITE_HEIGHT equ {MAX_ELEMENTS*28}
SPRITE_CLIP_WIDTH equ {MAX_ELEMENTS*30}
SPRITE_CLIP_HEIGHT equ {MAX_ELEMENTS*32}
TS_VBUFF_BASE equ {MAX_ELEMENTS*34} ; Finalized VBUFF address based on the sprite position and tile offsets
SORTED_PREV equ {MAX_ELEMENTS*36} ; Doubly-Linked List that maintains the sprites in sorted order based on SPRITE_Y
SORTED_NEXT equ {MAX_ELEMENTS*38}
; The Overlays are part of the _Sprites memory space and come after the maximum number of sprites
Overlays equ {_Sprites+{MAX_SPRITES*2}}
; Aliases of SPRITE_* memory locations that are used for Overlay info when the SPRITE_OVERLAY bit is set on SPRITE_ID
OVERLAY_ID equ SPRITE_ID
OVERLAY_FLAGS equ SPRITE_STATUS
OVERLAY_TOP equ SPRITE_CLIP_TOP ; This is important because SPRITE_CLIP_TOP is used for sorting
OVERLAY_BOTTOM equ SPRITE_CLIP_BOTTOM
OVERLAY_HEIGHT equ SPRITE_HEIGHT
OVERLAY_PROC equ VBUFF_ADDR
; 52 rows by 82 columns + 2 extra rows and columns for sprite sizes
;