; 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 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 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 _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 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 _RenderSprites ; Once the BG0 X and Y positions are committed, update sprite data ; jsr _ApplyTiles ; This function actually draws the new tiles into the code field jsr _ScanlineBG0XPos ; Patch the code field instructions with exit BRA opcode ; jsr _ApplyBG1XPos ; Update the direct page value based on the horizontal position ; 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 ; 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+2 ; Blit the full virtual buffer to the screen ; ldy Overlays+4 ; 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+2 ; beq :skip ; jsr _BltRange :skip ; jsr _DoOverlay ; ldx Overlays+4 ; 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 _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 spritge 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. ; ; 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 scaneline 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