gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 8327d7ebf0858631d383cbabb697d87d3b6be437 parent ef9e22bc9f9b41e719735781fd1dbcb41c8eabb8 Author: Michel Iwaniec <46843052+michel-iwaniec@users.noreply.github.com> Date: Sat, 16 May 2026 02:03:00 +0100 NES: Add GB window emulation (#891) Configuration: - Add platform configuration switch NES_WINDOW_LAYER, for enabling window layer functionality - Add helper defines VRAM_MAPPER_CFG_TRANSFER / VRAM_MAPPER_CFG_ISR, both dependent on NES_WINDOW_LAYER Window control: - Add WX_REG = win_pos_x and WY_REG = win_pos_y shadow variables - Extend __oam_valid_display_on with WINDOW_ON_MASK, to independently track window ON/OFF setting - Add implementations of SHOW_WIN = show_window / HIDE_WIN = hide_window in nes.h - Add implementations of move_win / scroll_win in nes.h Tile setting changes: - Add get_win_xy_addr and set_win_tile_xy in set_tile.s - Refactor set_bkg_tile_xy. Add set_win_tile_xy. - Refactor fill_bkg_rect. Add fill_win_rect. - Refactor set_bkg[_based]_submap. Add set_win[_based]_submap. - Refactor set_bkg[_based]_tiles. Add set_win[_based]_tiles. Attribute setting changes: - Add attribute_shadow_win / attribute_row_dirty_win / attribute_column_dirty_win bytes in crt0.s - Refactor set_bkg_attribute_xy. Add set_win_attribute_xy. - Refactor flush_attributes.s to flush window layer attributes when enabled. Replace macros with sub-routines. Tile data setting changes: - Add set_win_data = set_bkg_data - Add set_win_1bpp_data VRAM transfer buffer changes: - Add __current_vram_cfg_write, for storing additional mapper reg bits - Add bank-switching sub-routines __switch_bkg / __switch_win, for clearing / setting PPUHI_WIN bit in __current_vram_cfg_write - Update __switch_prg0 to set VRAM configuration when enabled - Add __switch_win_addr_temp, for use in direct PPU writes - Add .ppu_write_mapper sub-routine for handling either direct or indirect wrties Deferred ISR changes: - Add another entry __lcd_isr_mapper_value to deferred ISR data storage - Add function .deferred_isr_handle_window, injecting ISR scroll writes - Refactor .deferred_isr_run to call .deferred_isr_handle_window, and save / restore __current_vram_cfg_write - Rewrite .do_hblank_writes to write __lcd_isr_mapper value to mapper register Diffstat:
18 files changed, 1072 insertions(+), 53 deletions(-)
diff --git a/gbdk-lib/include/nes/hardware.h b/gbdk-lib/include/nes/hardware.h @@ -97,4 +97,11 @@ extern volatile UBYTE TAC_REG; #define LYC_REG _lcd_scanline /**< LY compare */ #define rLYC LYC_REG +#if defined(NES_WINDOW_LAYER) +__SHADOW_REG win_pos_x; +__SHADOW_REG win_pos_y; +#define WX_REG win_pos_x +#define WY_REG win_pos_y +#endif + #endif diff --git a/gbdk-lib/include/nes/nes.h b/gbdk-lib/include/nes/nes.h @@ -606,6 +606,22 @@ void refresh_OAM(void) NO_OVERLAY_LOCALS; #define HIDE_BKG \ shadow_PPUMASK &= ~PPUMASK_SHOW_BG; +/** Turns on the Window layer + Sets bit 5 of the LCDC register to 1. + + This only controls Window visibility. If either + the Background layer (which the window is part of) + or the Display are not turned then the Window contents + will not be visible. Those can be turned on using + @ref SHOW_BKG and @ref DISPLAY_ON. +*/ +#define SHOW_WIN _show_window(); + +/** Turns off the window layer. + Clears bit 5 of the LCDC register to 0. +*/ +#define HIDE_WIN _hide_window(); + /** Turns on the sprites layer. Sets bit 1 of the LCDC register to 1. */ @@ -898,7 +914,7 @@ void set_bkg_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *m @see set_bkg_submap for more details */ -inline void set_bkg_based_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w, uint8_t base_tile); +void set_bkg_based_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w, uint8_t base_tile) NO_OVERLAY_LOCALS; /** Copies a rectangular region of Background Tile Map entries into a buffer. @@ -935,6 +951,15 @@ uint8_t * set_bkg_tile_xy(uint8_t x, uint8_t y, uint8_t t) NO_OVERLAY_LOCALS; void set_bkg_attribute_xy_nes16x16(uint8_t x, uint8_t y, uint8_t a) NO_OVERLAY_LOCALS; /** + Set single attribute data a on window layer at x,y + + @param x X-coordinate + @param y Y-coordinate + @param a tile attributes + */ +void set_win_attribute_xy_nes16x16(uint8_t x, uint8_t y, uint8_t a) NO_OVERLAY_LOCALS; + +/** Set single 2x2 tile attribute a on background layer at x,y Please note that this is just a wrapper function for set_bkg_submap_attributes_nes16x16() @@ -952,6 +977,26 @@ inline void set_bkg_attribute_xy(uint8_t x, uint8_t y, uint8_t a) } #define set_attribute_xy set_bkg_attribute_xy +/** Set single attribute data a on window layer at x,y + + Please note that this is just a wrapper function for set_win_attribute_xy_nes16x16() + and divides the coordinates and dimensions by 2 to achieve this. + It is intended to make code more portable by using the same coordinate system + that systems with the much more common 8x8 attribute resolution would use. + + @param x X-coordinate + @param y Y-coordinate + @param a tile attributes + @return returns the address of tile attribute, so you may use faster set_vram_byte() later + + @note On the Game Boy this is only usable in Game Boy Color mode +*/ +inline uint8_t * set_win_attribute_xy(uint8_t x, uint8_t y, uint8_t a) +{ + set_win_attribute_xy_nes16x16(x >> 1, y >> 1, a); + return NULL; +} + /** * Get single tile t on background layer at x,y * @param x X-coordinate @@ -1006,6 +1051,246 @@ inline void scroll_bkg(int8_t x, int8_t y) { move_bkg(bkg_scroll_x + x, bkg_scroll_y + y); } +/** + * Get address of X,Y tile of window map + */ +#if defined(NES_WINDOW_LAYER) +uint8_t * get_win_xy_addr(uint8_t x, uint8_t y) NO_OVERLAY_LOCALS; +#else +#define get_win_xy_addr get_bkg_xy_addr +#endif + +/** Sets VRAM Tile Pattern data for the Window / Background + + @param first_tile Index of the first tile to write + @param nb_tiles Number of tiles to write + @param data Pointer to (2 bpp) source Tile Pattern data. + + This is the same as @ref set_bkg_data, since the Window Layer and + Background Layer share the same Tile pattern data. + + @see set_bkg_data + @see set_win_tiles, set_bkg_data, set_data + @see SHOW_WIN, HIDE_WIN +*/ +#if defined(NES_WINDOW_LAYER) +void set_win_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS; +#else +#define set_win_data set_bkg_data +#endif + +/** Sets VRAM Tile Pattern data for the Window / Background using 1bpp source data + + @param first_tile Index of the first tile to write + @param nb_tiles Number of tiles to write + @param data Pointer to (1bpp) source Tile Pattern data + + This is the same as @ref set_bkg_1bpp_data, since the Window Layer and + Background Layer share the same Tile pattern data. + + For a given bit that represent a pixel: + \li 0 will be expanded into the Background color + \li 1 will be expanded into the Foreground color + + See @ref set_1bpp_colors for details about setting the Foreground and Background colors. + + @see set_bkg_data, set_win_data, set_1bpp_colors + @see set_bkg_1bpp_data, set_sprite_1bpp_data +*/ +#if defined(NES_WINDOW_LAYER) +void set_win_1bpp_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data) NO_OVERLAY_LOCALS; +#else +#define set_win_1bpp_data set_bkg_1bpp_data +#endif + +/** Sets a rectangular region of the Window Tile Map. + + @param x X Start position in Window Map tile coordinates. Range 0 - 31 + @param y Y Start position in Window Map tile coordinates. Range 0 - 31 + @param w Width of area to set in tiles. Range 1 - 32 + @param h Height of area to set in tiles. Range 1 - 32 + @param tiles Pointer to source tile map data + + Entries are copied from map at __tiles__ to the Window Tile Map starting at + __x__, __y__ writing across for __w__ tiles and down for __h__ tiles. + + Use @ref set_win_submap() instead when: + \li Source map is wider than 32 tiles. + \li Writing a width that does not match the source map width __and__ more + than one row high at a time. + + One byte per source tile map entry. + + Writes that exceed coordinate 31 on the x or y axis will wrap around to + the Left and Top edges. + + @note Patterns 128-255 overlap with patterns 128-255 of the sprite Tile Pattern table. + + GBC only: @ref VBK_REG determines whether Tile Numbers or Tile Attributes get set. + \li VBK_REG = @ref VBK_TILES Tile Numbers are written + \li VBK_REG = @ref VBK_ATTRIBUTES Tile Attributes are written + + For more details about GBC Tile Attributes see @ref set_bkg_tiles. + + @see SHOW_WIN, HIDE_WIN, set_win_submap, set_bkg_tiles, set_bkg_data, set_tiles +*/ +#if defined(NES_WINDOW_LAYER) +void set_win_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles) NO_OVERLAY_LOCALS; +#else +#define set_win_tiles set_bkg_tiles +#endif + +/** Sets a rectangular region of the Window Tile Map. + The offset value in __base_tile__ is added to + the tile ID for each map entry. + + @param x X Start position in Window Map tile coordinates. Range 0 - 31 + @param y Y Start position in Window Map tile coordinates. Range 0 - 31 + @param w Width of area to set in tiles. Range 1 - 32 + @param h Height of area to set in tiles. Range 1 - 32 + @param tiles Pointer to source tile map data + @param base_tile Offset each tile ID entry of the source map by this value. Range 1 - 255 + + This is identical to @ref set_win_tiles() except that it + adds the __base_tile__ parameter for when a tile map's tiles don't + start at index zero. (For example, the tiles used by the map + range from 100 -> 120 in VRAM instead of 0 -> 20). + + @see set_win_tiles for more details +*/ +#if defined(NES_WINDOW_LAYER) +void set_win_based_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles, uint8_t base_tile) NO_OVERLAY_LOCALS; +#else +#define set_win_based_tiles set_bkg_based_tiles +#endif + +/** Sets a rectangular area of the Window Tile Map using a sub-region + from a source tile map. + + @param x X Start position in both the Source Tile Map and hardware Window Map tile coordinates. Range 0 - 255 + @param y Y Start position in both the Source Tile Map and hardware Window Map tile coordinates. Range 0 - 255 + @param w Width of area to set in tiles. Range 1 - 255 + @param h Height of area to set in tiles. Range 1 - 255 + @param map Pointer to source tile map data + @param map_w Width of source tile map in tiles. Range 1 - 255 + + Entries are copied from __map__ to the Window Tile Map starting at + __x__, __y__ writing across for __w__ tiles and down for __h__ tiles, + using __map_w__ as the rowstride for the source tile map. + + The __x__ and __y__ parameters are in Source Tile Map tile + coordinates. The location tiles will be written to on the + hardware Background Map is derived from those, but only uses + the lower 5 bits of each axis, for range of 0-31 (they are + bit-masked: `x & 0x1F` and `y & 0x1F`). As a result the two + coordinate systems are aligned together. + + In order to transfer tile map data in a way where the + coordinate systems are not aligned, an offset from the + Source Tile Map pointer can be passed in: + `(map_ptr + x + (y * map_width))`. + + For example, if you want the tile id at `1,2` from the source map to + show up at `0,0` on the hardware Background Map (instead of at `1,2`) + then modify the pointer address that is passed in: + `map_ptr + 1 + (2 * map_width)` + + Use this instead of @ref set_win_tiles when the source map is wider than + 32 tiles or when writing a width that does not match the source map width. + + One byte per source tile map entry. + + Writes that exceed coordinate 31 on the x or y axis will wrap around to + the Left and Top edges. + + GBC only: @ref VBK_REG determines whether Tile Numbers or Tile Attributes get set. + \li VBK_REG = @ref VBK_TILES Tile Numbers are written + \li VBK_REG = @ref VBK_ATTRIBUTES Tile Attributes are written + + See @ref set_bkg_tiles for details about CGB attribute maps with @ref VBK_REG. + + @see SHOW_WIN, HIDE_WIN, set_win_tiles, set_bkg_submap, set_bkg_tiles, set_bkg_data, set_tiles +**/ +#if defined(NES_WINDOW_LAYER) +void set_win_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w); +#else +#define set_win_submap set_bkg_submap +#endif + +/** Sets a rectangular area of the Window Tile Map using a sub-region + from a source tile map. The offset value in __base_tile__ is added + to the tile ID for each map entry. + + @param x X Start position in both the Source Tile Map and hardware Window Map tile coordinates. Range 0 - 255 + @param y Y Start position in both the Source Tile Map and hardware Window Map tile coordinates. Range 0 - 255 + @param w Width of area to set in tiles. Range 1 - 255 + @param h Height of area to set in tiles. Range 1 - 255 + @param map Pointer to source tile map data + @param map_w Width of source tile map in tiles. Range 1 - 255 + @param base_tile Offset each tile ID entry of the source map by this value. Range 1 - 255 + + This is identical to @ref set_win_submap() except that it + adds the __base_tile__ parameter for when a tile map's tiles don't + start at index zero. (For example, the tiles used by the map + range from 100 -> 120 in VRAM instead of 0 -> 20). + + @see set_win_submap for more details +**/ +#if defined(NES_WINDOW_LAYER) +void set_win_based_submap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *map, uint8_t map_w, uint8_t base_tile) NO_OVERLAY_LOCALS; +#else +#define set_win_based_submap set_bkg_based_submap +#endif + +/** + * Set single tile t on window layer at x,y + * @param x X-coordinate + * @param y Y-coordinate + * @param t tile index + * @return returns the address of tile, so you may use faster set_vram_byte() later + */ +#if defined(NES_WINDOW_LAYER) +uint8_t * set_win_tile_xy(uint8_t x, uint8_t y, uint8_t t) NO_OVERLAY_LOCALS; +#else +#define set_win_tile_xy set_bkg_tile_xy +#endif + +/** Moves the Window to the __x__, __y__ position on the screen. + + @param x X coordinate for Left edge of the Window (actual displayed location will be X - 7) + @param y Y coordinate for Top edge of the Window + + 7,0 is the top left corner of the screen in Window coordinates. The Window is locked to the bottom right corner. + + The Window is always over the Background layer. + + @see SHOW_WIN, HIDE_WIN +*/ +#if defined(NES_WINDOW_LAYER) +inline void move_win(scroll_x_t x, scroll_y_t y) { + WX_REG=x, WY_REG=y; +} +#else +#define move_win move_bkg +#endif + + +/** Move the Window relative to its current position. + + @param x Number of pixels to move the window on the __X axis__ + \n Range: -128 - 127 + @param y Number of pixels to move the window on the __Y axis__ + \n Range: -128 - 127 + + @see move_win +*/ +#if defined(NES_WINDOW_LAYER) +inline void scroll_win(int8_t x, int8_t y) { + WX_REG+=x, WY_REG+=y; +} +#else +#define scroll_win scroll_bkg +#endif /** Sets VRAM Tile Pattern data for Sprites @@ -1313,6 +1598,20 @@ void vmemset (void *s, uint8_t c, size_t n) NO_OVERLAY_LOCALS; void fill_bkg_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t tile) NO_OVERLAY_LOCALS; #define fill_rect fill_bkg_rect +/** Fills a rectangular region of Tile Map entries for the Window layer with tile. + + @param x X Start position in Window Map tile coordinates. Range 0 - 31 + @param y Y Start position in Window Map tile coordinates. Range 0 - 31 + @param w Width of area to set in tiles. Range 1 - 32 + @param h Height of area to set in tiles. Range 1 - 32 + @param tile Fill value +*/ +#if defined(NES_WINDOW_LAYER) +void fill_win_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t tile) NO_OVERLAY_LOCALS; +#else +#define fill_win_rect fill_bkg_rect +#endif + /** "Flushes" the updates to the shadow attributes so they are written to the transfer buffer, and then written to PPU memory on next vblank. diff --git a/gbdk-lib/libc/targets/mos6502/nes/crt0.s b/gbdk-lib/libc/targets/mos6502/nes/crt0.s @@ -7,6 +7,7 @@ ; * 16-bit frame counter _sys_time, to support VM routines .module crt0 .include "global.s" +.include "mapper_macros.s" ; OAM CPU page _shadow_OAM = 0x200 @@ -68,10 +69,24 @@ _bkg_scroll_x:: .ds 1 _bkg_scroll_y:: .ds 1 _attribute_row_dirty:: .ds NUM_NT _attribute_column_dirty:: .ds NUM_NT +.ifdef NES_WINDOW_LAYER +_attribute_row_dirty_win:: .ds NUM_NT +_attribute_column_dirty_win:: .ds NUM_NT +.endif __oam_valid_display_on:: .ds 1 __SYSTEM:: .ds 1 __hblank_writes_index:: .ds 1 +.ifdef NES_WINDOW_LAYER +_win_pos_x:: .ds 1 +_win_pos_y:: .ds 1 +.endif +.ifdef VRAM_MAPPER_CFG_ISR +__lcd_isr_save_s: .ds 1 +__current_vram_cfg_write:: .ds 1 +.define __crt0_MAPPER_VRAM_CFG_TEMP "___SDCC_m6502_ret4" +.endif + .define __crt0_NMITEMP "___SDCC_m6502_ret4" .area _BSS @@ -82,6 +97,9 @@ _attribute_shadow = 0x188 ; Otherwise allocate attribute shadow in data segment, with 64 bytes for each NT/AT _attribute_shadow:: .ds (64*NUM_NT) .endif +.ifdef NES_WINDOW_LAYER +_attribute_shadow_win:: .ds (64*NUM_NT) +.endif __crt0_paletteShadow:: .ds 25 .mode:: .ds 1 __lcd_isr_PPUCTRL:: .ds (2*.MAX_DEFERRED_ISR_CALLS) @@ -90,6 +108,9 @@ __lcd_isr_scroll_x:: .ds (2*.MAX_DEFERRED_ISR_CALLS) __lcd_isr_scroll_y:: .ds (2*.MAX_DEFERRED_ISR_CALLS) __lcd_isr_delay_num_scanlines:: .ds (2*.MAX_DEFERRED_ISR_CALLS) __lcd_isr_ppuaddr_lo:: .ds (2*.MAX_DEFERRED_ISR_CALLS) +.ifdef VRAM_MAPPER_CFG_ISR +__lcd_isr_mapper:: .ds (2*.MAX_DEFERRED_ISR_CALLS) +.endif .area _CODE @@ -122,11 +143,18 @@ ProcessDrawList_DoOneTransfer: sta PPUADDR ; +4 pla ; +4 sta PPUADDR ; +4 - nop ; +2 + tay ; +2 jmp [ProcessDrawList_addr] ; +5 ; Total = 4 + 2 + 2 + 4 + 3 + 6*4 + 2 + 2 + 5 = 48 for each transfer (...+ 8*NumBytesCopied) ; 4 + 3 + 14 = 7 + 14 = 21 fixed-cost exit +.ifdef VRAM_MAPPER_CFG_TRANSFER +ProcessDrawList_mapper_switch:: + sta MAPPER_WRITE_REG,y ; +5 + jmp ProcessDrawList_DoOneTransfer ; +3 + ; Total = 4 + 4 + 4 + 2 + 5 + 5 = 24 for each mapper switch +.endif + ; .bndry 0x100 (skip alignment as previous alignment means page-cross won't happen) __crt0_doSpriteDMA: bit *__oam_valid_display_on @@ -258,9 +286,18 @@ __crt0_NMI: lda __lcd_isr_PPUMASK,y sta PPUMASK +.ifdef VRAM_MAPPER_CFG_ISR + ; Write mapper reg (note: assumes no bus conflicts!) + lda __lcd_isr_mapper,y + sta MAPPER_WRITE_REG + sta *__crt0_MAPPER_VRAM_CFG_TEMP + sta *__crt0_MAPPER_VRAM_CFG_TEMP + nop +.endif + ; Delay and call fake LCD isr if list not empty lda __lcd_isr_delay_num_scanlines+1,y ; Check for first potential LCD write - beq __crt0_NMI_skip + beq __crt0_NMI_skip_hblank_writes ; First delay until end-of-vblank, depending on transfer buffer contents... ; (X set to correct delay value by DoUpdateVRAM) 1$: @@ -285,6 +322,12 @@ __crt0_NMI: iny ; Move index past VBL write, to first potential LCD write ; Call the write reg subroutine jsr .do_hblank_writes +__crt0_NMI_skip_hblank_writes: +.ifdef VRAM_MAPPER_CFG_ISR + lda *__current_bank + ora *__crt0_MAPPER_VRAM_CFG_TEMP + SWITCH_PRG0_A +.endif __crt0_NMI_skip: @@ -578,7 +621,19 @@ __crt0_waitForever: .define .ldx_PPUMASK "__crt0_NMITEMP+3" ; Delay to make hblank at end of scanline 0 +.ifdef VRAM_MAPPER_CFG_ISR + ; Save stack pointer (5 cycles) + tsx + stx *__lcd_isr_save_s + ; 6 cycles to adjust for no-RTS of .delay_fractional (now a macro) + ldx #0x00 + txs + nop + ; -5 cycles in loop, to account for cycles spent saving stack pointer above + ldx #9 +.else ldx #10 +.endif 0$: dex bne 0$ @@ -596,6 +651,16 @@ __crt0_waitForever: cpx #0 beq 2$ ; Exit if empty buffer (no calls were made within frame) dex +.ifdef VRAM_MAPPER_CFG_ISR + txa + ldx *__lcd_isr_save_s + txs + tax + nop + jsr .delay_to_lcd_scanline + jsr .delay_12_cycles + jsr .delay_28_cycles +.else jsr .delay_to_lcd_scanline jsr .delay_12_cycles jsr .delay_28_cycles @@ -604,10 +669,32 @@ __crt0_waitForever: nop nop lda *0x00 +.endif 3$: +.ifdef VRAM_MAPPER_CFG_ISR + ; Delay for 23.666 NTSC cycles / 16.5625 PAL cycles + ldx __lcd_isr_mapper,y + txs + ; + lda #144 ; Initialize A with PAL fractional cycle count + ; +7 cycles for NTSC scanlines + bit *__SYSTEM + bvs 30$ + lda #171 ; NTSC fractional cycle count + nop + nop + nop +30$: ; -> 15 NTSC cycles / 8 PAL cycles + ; Add fractional cycles and branch on carry + adc *.acc + bcs 40$ +40$: + sta *.acc ; -> 8.666 NTSC cycles / 8.5625 PAL cycles +.else ; Delay for 35.666 NTSC cycles / 28.5625 PAL cycles jsr .delay_fractional +.endif ; Pre-write PPUADDR (1st write) and y-scroll sty PPUADDR @@ -638,12 +725,25 @@ __crt0_waitForever: sta PPUADDR ldx *.ldx_PPUMASK stx PPUMASK +.ifdef VRAM_MAPPER_CFG_ISR + ; Mapper write (note: assumes no bus conflicts!) + tsx + stx MAPPER_WRITE_REG +.endif sty PPUCTRL inc *.reg_write_index ldy *.reg_write_index jmp 1$ 2$: +.ifdef VRAM_MAPPER_CFG_ISR + ; Store MAPPER VRAM CFG + tsx + stx *__crt0_MAPPER_VRAM_CFG_TEMP + ; Restore stack pointer + ldx *__lcd_isr_save_s + txs +.endif rts ; Interrupt / RESET vector table diff --git a/gbdk-lib/libc/targets/mos6502/nes/deferred_isr.s b/gbdk-lib/libc/targets/mos6502/nes/deferred_isr.s @@ -5,6 +5,7 @@ .define .lcd_scanline_previous "REGTEMP" .define .lcd_buf_index "REGTEMP+1" .define .lcd_buf_end "REGTEMP+2" + .define .last_scanline_value ; ; Writes shadow registers to buffer @@ -13,21 +14,74 @@ ; X: Scanline number ; .write_shadow_registers_to_buffer:: + inx + cpx #.SCREENHEIGHT + bcc 6$ + txa + sec + sbc #.SCREENHEIGHT + tax +6$: ; Copy shadow registers ldy *.lcd_buf_index +.ifdef NES_WINDOW_LAYER + lda #0 + sta __lcd_isr_mapper,y +.endif lda *_shadow_PPUMASK sta __lcd_isr_PPUMASK,y lda *_shadow_PPUCTRL sta __lcd_isr_PPUCTRL,y - lda *_bkg_scroll_x +.ifdef NES_WINDOW_LAYER + ; is WIN in front of BKG? + ; Never in front if not enabled + lda *__oam_valid_display_on + and #WINDOW_ON_MASK + beq 0$ + ; always in front if _win_pos_y == 0 + lda *_win_pos_y + beq 9$ + ; in front if current_scanline >= win_pos_y + txa + cmp #.SCREENHEIGHT + bcc 7$ + sbc #.SCREENHEIGHT +7$: + cmp *_win_pos_y + bcc 0$ +9$: + ; Enable window + lda __lcd_isr_mapper,y + ora #PPUHI_WIN + sta __lcd_isr_mapper,y + ; Write window registers + lda #0 + sec + sbc *_win_pos_x sta __lcd_isr_scroll_x,y lsr lsr lsr sta __lcd_isr_ppuaddr_lo,y - ; Add _bkg_scroll_y+1 to _lcd_scanline to generate final Y-scroll, with 239->0 wrap-around txa sec + sbc *_win_pos_y + cmp #.SCREENHEIGHT + bcc 8$ + sbc #.SCREENHEIGHT +8$: + jmp 2$ +.endif +0$: + lda *_bkg_scroll_x + sta __lcd_isr_scroll_x,y + lsr + lsr + lsr + sta __lcd_isr_ppuaddr_lo,y + ; Add _bkg_scroll_y to current scanline to generate final Y-scroll, with 239->0 wrap-around + txa + clc adc *_bkg_scroll_y bcc 1$ sbc #.SCREENHEIGHT @@ -87,7 +141,15 @@ pha lda *_bkg_scroll_y pha - +.ifdef NES_WINDOW_LAYER + lda *_win_pos_x + pha + lda *_win_pos_y + pha + lda *__oam_valid_display_on + pha +.endif + ; Allow VBL isr to modify shadow registers if present jsr .jmp_to_VBL_isr @@ -142,17 +204,27 @@ cmp #.SCREENHEIGHT bcs .deferred_isr_run_done pha + +.ifdef NES_WINDOW_LAYER + ldx *__lcd_scanline + jsr .deferred_isr_handle_window + pla + txa + pha +.endif + sec sbc *.lcd_scanline_previous sta __lcd_isr_delay_num_scanlines,y ; Call LCD isr jsr .jmp_to_LCD_isr - ; Grab previous LCD scanline value from stack and store in X + ; Grab LCD scanline value from stack and store in X pla tax pha jsr .write_shadow_registers_to_buffer - + +30$: iny sty *.lcd_buf_index cpy *.lcd_buf_end @@ -162,6 +234,12 @@ pla .deferred_isr_run_done: + +.ifdef NES_WINDOW_LAYER + ldx #.SCREENHEIGHT + jsr .deferred_isr_handle_window +.endif + ; Flip __hblank_writes_index for NMI handler ldy #0 lda *__hblank_writes_index @@ -170,7 +248,14 @@ 21$: sty *__hblank_writes_index - ; Restore shadow registers +.ifdef NES_WINDOW_LAYER + pla + sta *__oam_valid_display_on + pla + sta *_win_pos_y + pla + sta *_win_pos_x +.endif pla sta *_bkg_scroll_y pla @@ -180,4 +265,41 @@ pla sta *_shadow_PPUMASK - rts - + rts + +.ifdef NES_WINDOW_LAYER +; +; Injects an extra scanline for window switch +; +.deferred_isr_handle_window: +;;; + ; If window is enabled and WY_REG is smaller than next LCD scanline, inject a deferred ISR for Window start / finish + lda *__oam_valid_display_on + and #WINDOW_ON_MASK + beq 21$ + txa + cmp *_win_pos_y + bcs 22$ + ; lcd_scanline_next < win_pos_y + jmp 21$ +22$: + ; lcd_scanline_next >= win_pos_y -> win_pos_y < lcd_scanline_next + lda *_win_pos_y + beq 21$ + sec + sbc *.lcd_scanline_previous + sta __lcd_isr_delay_num_scanlines,y + + ; Make window scroll Y coordinate source data from top of nametable + ldx *_win_pos_y + dex + jsr .write_shadow_registers_to_buffer + + ; Grab LCD scanline value from win_pos_y and store in X + dex + iny + sty *.lcd_buf_index + ; TODO: Check to not overflow deferred ISR buffer +21$: + rts +.endif diff --git a/gbdk-lib/libc/targets/mos6502/nes/fill_rect_bk.s b/gbdk-lib/libc/targets/mos6502/nes/fill_rect_bk.s @@ -2,9 +2,12 @@ .area GBDKOVR (PAG, OVR) .fill_bkg_rect_padding:: .ds 4 ; Padding to avoid clash with _set_bkg_tile_xy vars - _fill_bkg_rect_PARM_3:: .ds 1 - _fill_bkg_rect_PARM_4:: .ds 1 - _fill_bkg_rect_PARM_5:: .ds 1 + _fill_bkg_rect_PARM_3:: + _fill_win_rect_PARM_3:: .ds 1 + _fill_bkg_rect_PARM_4:: + _fill_win_rect_PARM_4:: .ds 1 + _fill_bkg_rect_PARM_5:: + _fill_win_rect_PARM_5:: .ds 1 .xpos: .ds 1 .ypos: .ds 1 .xpos_save: .ds 1 @@ -12,6 +15,20 @@ .area _HOME +.ifdef NES_WINDOW_LAYER +_fill_win_rect:: + tay + lda *__current_vram_cfg_write + pha + ora #PPUHI_WIN + sta *__current_vram_cfg_write + tya + jsr _fill_bkg_rect + pla + sta *__current_vram_cfg_write + rts +.endif + _fill_bkg_rect:: .define .width "_fill_bkg_rect_PARM_3" .define .height "_fill_bkg_rect_PARM_4" diff --git a/gbdk-lib/libc/targets/mos6502/nes/flush_attributes.s b/gbdk-lib/libc/targets/mos6502/nes/flush_attributes.s @@ -2,34 +2,101 @@ .area GBDKOVR (PAG, OVR) .x_save: .ds 1 + .y_save: .ds 1 .attribute_row_dirty: .ds 1 .attribute_column_dirty: .ds 1 .area _HOME +.macro LOAD_ROW_DIRTY ?lbl, ?lbl2 +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl lbl + lda *_attribute_row_dirty_win,x + jmp lbl2 +.endif +lbl: + lda *_attribute_row_dirty,x +lbl2: +.endm + +.macro LOAD_COLUMN_DIRTY ?lbl, ?lbl2 +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl lbl + lda *_attribute_column_dirty_win,x + jmp lbl2 +.endif +lbl: + lda *_attribute_column_dirty,x +lbl2: +.endm + +.macro LOAD_ROW_ATTRIBUTE ?lbl, ?lbl2 +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl lbl + lda *_attribute_row_dirty_win,x + jmp lbl2 +.endif +lbl: + lda *_attribute_row_dirty,x +lbl2: +.endm + +.macro CLEAR_ATTRIBUTE_DIRTY ?lbl, ?lbl2 + lda #0 +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl lbl + sta _attribute_row_dirty_win,x + sta _attribute_column_dirty_win,x + jmp lbl2 +.endif +lbl: + sta _attribute_row_dirty,x + sta _attribute_column_dirty,x +lbl2: +.endm + _flush_shadow_attributes:: +.ifdef NES_WINDOW_LAYER + ; First process window layer + lda *__current_vram_cfg_write + pha + ora #PPUHI_WIN + sta *__current_vram_cfg_write + jsr _flush_shadow_attributes_impl + ; ...then background layer + lda *__current_vram_cfg_write + and #~PPUHI_WIN + sta *__current_vram_cfg_write + jsr _flush_shadow_attributes_impl + pla + sta *__current_vram_cfg_write + rts +_flush_shadow_attributes_impl:: +.endif ldx #0 .ifndef NES_TILEMAP_S 1$: .endif stx *.x_save - lda *_attribute_row_dirty,x + LOAD_ROW_DIRTY beq 2$ sta *.attribute_row_dirty ldy .xy_shift_tab,x jsr _flush_shadow_attributes_rows ldx *.x_save 2$: - lda *_attribute_column_dirty,x + LOAD_COLUMN_DIRTY beq 3$ sta *.attribute_column_dirty ldy .xy_shift_tab,x jsr _flush_shadow_attributes_columns 3$: ldx *.x_save - lda #0 - sta _attribute_row_dirty,x - sta _attribute_column_dirty,x + CLEAR_ATTRIBUTE_DIRTY .ifndef NES_TILEMAP_S inx cpx #NUM_NT @@ -45,6 +112,12 @@ _flush_shadow_attributes_rows: lda #<PPU_AT0 sta *.tmp lda .ppu_hi_tab,x +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl 0$ + ora #PPUHI_WIN +0$: +.endif sta *.tmp+1 _flush_shadow_attributes_row_loop: lsr *.attribute_row_dirty @@ -78,12 +151,25 @@ _flush_shadow_attributes_update_row: lda *.tmp jsr .ppu_stripe_begin_horizontal ; Write 8 bytes +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl 1$ + i = 0 + .rept ATTRIBUTE_PACKED_WIDTH + lda _attribute_shadow_win+i,y + jsr .ppu_stripe_write_byte + i = i + 1 + .endm + jmp 2$ +1$: +.endif i = 0 - .rept 8 + .rept ATTRIBUTE_PACKED_WIDTH lda _attribute_shadow+i,y jsr .ppu_stripe_write_byte i = i + 1 .endm +2$: jsr .ppu_stripe_end ldx *REGTEMP+3 jmp _flush_shadow_attributes_next_row @@ -97,6 +183,12 @@ _flush_shadow_attributes_columns: lda #<PPU_AT0 sta *.tmp lda .ppu_hi_tab,x +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl 0$ + ora #PPUHI_WIN +0$: +.endif sta *.tmp+1 _flush_shadow_attributes_columns_loop: lsr *.attribute_column_dirty @@ -113,33 +205,44 @@ _flush_shadow_attributes_columns_next_column: _flush_shadow_attributes_columns_end: rts -.macro WRITEVERT +_write_vert: lda *.tmp+1 tax lda *.tmp - clc - adc #(ATTRIBUTE_PACKED_WIDTH*i) jsr .ppu_stripe_begin_vertical - lda _attribute_shadow+AT_SHADOW_WIDTH*i,y + lda _attribute_shadow,y jsr .ppu_stripe_write_byte - lda _attribute_shadow+AT_SHADOW_WIDTH*i+(AT_SHADOW_WIDTH*4),y + lda _attribute_shadow+(AT_SHADOW_WIDTH*4),y jsr .ppu_stripe_write_byte jsr .ppu_stripe_end -.endm + ; inc src index + tya + clc + adc #AT_SHADOW_WIDTH + tay + ; inc ppu addr + lda *.tmp + adc #ATTRIBUTE_PACKED_WIDTH + sta *.tmp + rts ; ; Flushes all dirty rows of _attribute_shadow by writing them to PPU memory ; _flush_shadow_attributes_update_column: stx *REGTEMP+3 + sty *.y_save + lda *.tmp + pha ; Update all 8 bytes of column for now, as each column in _attribute_column_dirty only stores 1 bit ; As PPU has no increment-by-8 feature, split writes into 4 separate stripes 2 bytes each ; TODO: Could make a dedicated unrolled transfer routine in nmi handler that writes all 8 bytes as one stripe. - i = 0 .rept 4 - WRITEVERT - i = i + 1 + jsr _write_vert .endm + pla + sta *.tmp + ldy *.y_save ldx *REGTEMP+3 jmp _flush_shadow_attributes_columns_next_column diff --git a/gbdk-lib/libc/targets/mos6502/nes/global.s b/gbdk-lib/libc/targets/mos6502/nes/global.s @@ -7,8 +7,6 @@ ;; Transfer buffer (lower half of hardware stack) __vram_transfer_buffer = 0x100 - ;; Number of 8-cycles available each frame for transfer buffer - VRAM_DELAY_CYCLES_X8 = 171 ; Bits for quick checking of OAM validity and display ON/OFF OAM_VALID_MASK = 0x80 @@ -186,6 +184,39 @@ .globl __crt0_paletteShadow .globl _attribute_shadow, _attribute_row_dirty +.ifdef NES_WINDOW_LAYER + .globl _attribute_shadow_win, __current_vram_cfg_write + ; Number of 8-cycles available each frame for transfer buffer are 1 less with mapper write added + VRAM_DELAY_CYCLES_X8 = 169 + ; The VRAM write routines rely on bit 15 of the address (=bit 7 of the high portion) to indicate + ; that any address in the nametable region (0x2000 - 0x2FFF) writes to the window instead of bkg. + ; This constant should never be changed as instructions are frequently used to test bit7 efficiently. + ; Note that bit 15 does not exist in hardware, as PPU address is only 14 bits. It is purely + ; a software concept to simplify the mapper state handling for VRAM updates. + PPUHI_WIN = 0x80 + + ; + ; SHOW_WINDOW bit + ; + WINDOW_ON_MASK = 0x20 + + ; + ; Support mapper vram configuration for VRAM transfer buffer. + ; Currently only useful when NES_WINDOW_LAYER is enabled. + ; + VRAM_MAPPER_CFG_TRANSFER = 1 + + ; + ; Support mapper vram configuration for deferred-ISR routines. + ; Currently only useful when NES_WINDOW_LAYER is enabled. + ; + VRAM_MAPPER_CFG_ISR = 1 + + MAPPER_CFG_NT_MASK = 0x80 +.else + VRAM_DELAY_CYCLES_X8 = 171 +.endif + ;; Identity table for register-to-register-adds and bankswitching .globl .identity, _identity diff --git a/gbdk-lib/libc/targets/mos6502/nes/mapper.s b/gbdk-lib/libc/targets/mos6502/nes/mapper.s @@ -3,5 +3,39 @@ __switch_prg0:: sta *__current_bank +.ifdef VRAM_MAPPER_CFG_TRANSFER + ora *__current_vram_cfg_write +.endif SWITCH_PRG0_A rts + +.ifdef VRAM_MAPPER_CFG_TRANSFER +__switch_win_addr_temp:: + pha + lda #0 + cpx #PPUHI_WIN + bcc 0$ + ora #MAPPER_CFG_NT_MASK +0$: + ora *__current_vram_cfg_write + ora *__current_bank + sta MAPPER_WRITE_REG + pla + rts +.endif + +__show_window:: +.ifdef NES_WINDOW_LAYER + lda *__oam_valid_display_on + ora #WINDOW_ON_MASK + sta *__oam_valid_display_on +.endif + rts + +__hide_window:: +.ifdef NES_WINDOW_LAYER + lda *__oam_valid_display_on + and #~WINDOW_ON_MASK + sta *__oam_valid_display_on +.endif + rts diff --git a/gbdk-lib/libc/targets/mos6502/nes/mapper_macros.s b/gbdk-lib/libc/targets/mos6502/nes/mapper_macros.s @@ -1,4 +1,9 @@ ; +; Define for mapper register +; +.define MAPPER_WRITE_REG ".identity" + +; ; Set PRG bank to A register ; ; Trashes Y register. @@ -14,6 +19,20 @@ ; Trashes A register. ; .macro SWITCH_PRG0_Y - tay + tya sta .identity,Y .endm + +; +; Modify PPU high address bits in A based on VRAM switch config +; +; +; +.macro SET_PPUHI_ADDRESS_A ?.skip +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl .skip + ora #PPUHI_WIN +.skip: +.endif +.endm diff --git a/gbdk-lib/libc/targets/mos6502/nes/platform_cfg.s b/gbdk-lib/libc/targets/mos6502/nes/platform_cfg.s @@ -22,12 +22,24 @@ NES_TILEMAP_S = 1 ;NES_TILEMAP_F = 1 ; +; GB window emulation via second nametable +; +; This setting allocates shadow memory for a second nametable and adds code to the +; internal tile/attribute set functions to allow use with the GB window functions. +; It also adds code and RAM storage to the transfer buffer routines and +; deferred-LCD-isr routines to support switching between window layer and bkg layer. +; + +;NES_WINDOW_LAYER = 1 + +; ; LOMEM setting ; ; This places the 64 bytes of attribute shadow (assuming single-screen layout) in the stack area ; saving 64 bytes of RAM memory for user variables, at the expense of a reduced stack for function calls. ; -; This setting is *only* valid when NES_TILEMAP_S = 1 (using it with other tilmap layouts will corrupt the stack) +; This setting is *only* valid when NES_TILEMAP_S = 1 and NES_WINDOW_LAYER is not enabled. +; (using it with other tilemap layouts will corrupt the stack) ; NES_LOMEM = 1 diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_1bit_data.s b/gbdk-lib/libc/targets/mos6502/nes/set_1bit_data.s @@ -3,7 +3,8 @@ .globl __current_1bpp_colors .area GBDKOVR (PAG, OVR) - _set_bkg_1bpp_data_PARM_3:: .ds 2 + _set_bkg_1bpp_data_PARM_3:: + _set_win_1bpp_data_PARM_3:: .ds 2 src: .ds 2 num_tiles: .ds 1 num_planes: .ds 1 @@ -19,6 +20,7 @@ .area _HOME _set_bkg_1bpp_data:: +_set_win_1bpp_data:: stx *num_tiles ; sta *src diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_attribute.s b/gbdk-lib/libc/targets/mos6502/nes/set_attribute.s @@ -3,14 +3,46 @@ .include "global.s" .area GBDKOVR (PAG, OVR) -_set_bkg_attribute_xy_nes16x16_PARM_3:: .ds 1 +_set_bkg_attribute_xy_nes16x16_PARM_3:: +_set_win_attribute_xy_nes16x16_PARM_3:: .ds 1 .x_odd: .ds 1 .y_odd: .ds 1 .val: .ds 1 +.ifdef NES_WINDOW_LAYER +.winbit: .ds 1 +.endif .area _HOME +.macro WRITE_ATTRIBUTE ?lbl, ?lbl2 +.ifdef NES_WINDOW_LAYER + bit *.winbit + bpl lbl + and _attribute_shadow_win,y + ora *.val + sta _attribute_shadow_win,y + jmp lbl2 +.endif +lbl: + and _attribute_shadow,y + ora *.val + sta _attribute_shadow,y +lbl2: +.endm + +.ifdef NES_WINDOW_LAYER +_set_win_attribute_xy_nes16x16:: + sec + ror *.winbit + jmp _set_attribute_xy_nes16x16_impl +.endif + _set_bkg_attribute_xy_nes16x16:: +.ifdef NES_WINDOW_LAYER + clc + ror *.winbit +.endif +_set_attribute_xy_nes16x16_impl:: lsr ror *.x_odd tay @@ -55,9 +87,7 @@ _set_bkg_attribute_xy_nes16x16:: rol tax lda .mask_tab,x - and _attribute_shadow,y - ora *.val - sta _attribute_shadow,y + WRITE_ATTRIBUTE ; Set dirty bit for row. ; Assume writing rows, as the potential to optimize column writing is limited anyway. pla @@ -68,12 +98,29 @@ _set_bkg_attribute_xy_nes16x16:: lda .bitmask_dirty_tab,x ; Merge A with current attribute_row_dirty flag .ifdef NES_TILEMAP_S +.ifdef NES_WINDOW_LAYER + bit *.winbit + bpl 9$ + ora *_attribute_row_dirty_win + sta *_attribute_row_dirty_win + jmp 8$ +9$: ora *_attribute_row_dirty sta *_attribute_row_dirty +8$: +.endif .endif .ifdef NES_TILEMAP_H pha ldx #0 +.ifdef NES_WINDOW_LAYER + ; 2 nametables - window dirty byte is += 2 bytes + bit *.winbit + bpl 9$ + inx + inx +9$: +.endif tya and #AT_WIDTH beq 10$ @@ -86,6 +133,14 @@ _set_bkg_attribute_xy_nes16x16:: .ifdef NES_TILEMAP_V pha ldx #0 +.ifdef NES_WINDOW_LAYER + ; 2 nametables - window dirty byte is += 2 bytes + bit *.winbit + bpl 9$ + inx + inx +9$: +.endif tya and #(AT_HEIGHT*AT_SHADOW_WIDTH) beq 10$ @@ -98,6 +153,16 @@ _set_bkg_attribute_xy_nes16x16:: .ifdef NES_TILEMAP_F pha ldx #0 +.ifdef NES_WINDOW_LAYER + ; With 4 nametables, window dirty byte is += 4 bytes + bit *.winbit + bpl 9$ + inx + inx + inx + inx +9$: +.endif tya and #AT_WIDTH beq 10$ diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_bk_ts.s b/gbdk-lib/libc/targets/mos6502/nes/set_bk_ts.s @@ -1,14 +1,25 @@ .include "global.s" - ; NOTE: This overlay arrangement MUST match that of set_bkg_based_submap + ; NOTE: This overlay arrangement MUST match that of set_bkg/win[_based]_submap .area GBDKOVR (PAG, OVR) _set_bkg_tiles_PARM_3:: - _set_bkg_based_tiles_PARM_3:: .ds 1 + _set_bkg_based_tiles_PARM_3:: + _set_win_tiles_PARM_3:: + _set_win_based_tiles_PARM_3:: .ds 1 + ; _set_bkg_tiles_PARM_4:: - _set_bkg_based_tiles_PARM_4:: .ds 1 + _set_bkg_based_tiles_PARM_4:: + _set_win_tiles_PARM_4:: + _set_win_based_tiles_PARM_4:: .ds 1 + ; _set_bkg_tiles_PARM_5:: - _set_bkg_based_tiles_PARM_5:: .ds 2 - _set_bkg_based_tiles_PARM_6:: .ds 1 + _set_bkg_based_tiles_PARM_5:: + _set_win_tiles_PARM_5:: + _set_win_based_tiles_PARM_5:: .ds 2 + ; + _set_bkg_based_tiles_PARM_6:: + _set_win_based_tiles_PARM_6:: .ds 1 + ; .padding:: .ds 1 .xpos: .ds 1 .ypos: .ds 1 @@ -25,6 +36,23 @@ .area _HOME +.ifdef NES_WINDOW_LAYER +_set_win_tiles:: + ldy #0 + sty *_set_win_based_tiles_PARM_6 +_set_win_based_tiles:: + tay + lda *__current_vram_cfg_write + pha + ora #PPUHI_WIN + sta *__current_vram_cfg_write + tya + jsr _set_bkg_tiles + pla + sta *__current_vram_cfg_write + rts +.endif + _set_bkg_tiles:: ldy #0 sty *_set_bkg_based_tiles_PARM_6 diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_data.s b/gbdk-lib/libc/targets/mos6502/nes/set_data.s @@ -3,7 +3,8 @@ .area GBDKOVR (PAG, OVR) _set_sprite_data_PARM_3:: - _set_bkg_data_PARM_3:: .ds 2 + _set_bkg_data_PARM_3:: + _set_win_data_PARM_3:: .ds 2 .ppu_addr_lo: .ds 1 .ppu_addr_hi: .ds 1 src: .ds 2 @@ -12,6 +13,7 @@ .area _HOME _set_bkg_data:: +_set_win_data:: sta *.ppu_addr_lo lda *_set_bkg_data_PARM_3 sta *src diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_tile.s b/gbdk-lib/libc/targets/mos6502/nes/set_tile.s @@ -1,7 +1,8 @@ .include "global.s" .area GBDKOVR (PAG, OVR) - _set_bkg_tile_xy_PARM_3:: .ds 1 ; (shared with _set_vram_byte_PARM_2) + _set_bkg_tile_xy_PARM_3:: + _set_win_tile_xy_PARM_3:: .ds 1 ; (shared with _set_vram_byte_PARM_2) .bkg_tile_ppu_addr:: .ds 2 .ppuhi: .ds 1 @@ -13,6 +14,17 @@ .define PPUHI_MASK "*.ppuhi" .endif +.ifdef NES_WINDOW_LAYER +_get_win_xy_addr:: + jsr _get_bkg_xy_addr + tay + txa + ora #PPUHI_WIN + tax + tya + rts +.endif + _get_bkg_xy_addr:: ; XA = (PPU_NT0) | (X << 5) | A ; (A = x_pos, X = y_pos) @@ -74,3 +86,15 @@ _get_bkg_xy_addr:: _set_bkg_tile_xy:: jsr _get_bkg_xy_addr jmp _set_vram_byte + +.ifdef NES_WINDOW_LAYER +_set_win_tile_xy:: + jsr _get_bkg_xy_addr + pha + txa + ora #PPUHI_WIN + tax + pla + jsr _set_vram_byte + rts +.endif diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_tile_submap.s b/gbdk-lib/libc/targets/mos6502/nes/set_tile_submap.s @@ -2,14 +2,23 @@ .area GBDKOVR (PAG, OVR) _set_bkg_submap_PARM_3:: - _set_bkg_based_submap_PARM_3:: .ds 1 + _set_bkg_based_submap_PARM_3:: + _set_win_submap_PARM_3:: + _set_win_based_submap_PARM_3:: .ds 1 _set_bkg_submap_PARM_4:: - _set_bkg_based_submap_PARM_4:: .ds 1 + _set_bkg_based_submap_PARM_4:: + _set_win_submap_PARM_4:: + _set_win_based_submap_PARM_4:: .ds 1 _set_bkg_submap_PARM_5:: - _set_bkg_based_submap_PARM_5:: .ds 2 + _set_bkg_based_submap_PARM_5:: + _set_win_submap_PARM_5:: + _set_win_based_submap_PARM_5:: .ds 2 _set_bkg_submap_PARM_6:: - _set_bkg_based_submap_PARM_6:: .ds 1 - _set_bkg_based_submap_PARM_7:: .ds 1 + _set_bkg_based_submap_PARM_6:: + _set_win_submap_PARM_6:: + _set_win_based_submap_PARM_6:: .ds 1 + _set_bkg_based_submap_PARM_7:: + _set_win_based_submap_PARM_7:: .ds 1 .xpos: .ds 1 .ypos: .ds 1 .num_rows: .ds 1 @@ -32,6 +41,21 @@ .define PPUHI_MASK "*.ppuhi" .endif +.ifdef NES_WINDOW_LAYER +_set_win_submap:: + ldy #0 + sty *.tile_offset +_set_win_based_submap:: + lda *__current_vram_cfg_write + pha + ora #PPUHI_WIN + sta *__current_vram_cfg_write + jsr _set_bkg_based_submap + pla + sta *__current_vram_cfg_write + rts +.endif + _set_bkg_submap:: ldy #0 sty *.tile_offset diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_tile_submap_attributes.s b/gbdk-lib/libc/targets/mos6502/nes/set_tile_submap_attributes.s @@ -57,7 +57,7 @@ INC_ROW_SRC .endm -.macro SET_DIRTY_ROW +.macro SET_DIRTY_ROW ?lbl lda *.xpos and #AT_SHADOW_WIDTH-1 tax @@ -71,6 +71,12 @@ cpx #ATTRIBUTE_PACKED_WIDTH rol .endif +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl lbl + eor #(2*NUM_NT) +lbl: +.endif tax lda .bitmask_table,y ora _attribute_row_dirty,x @@ -85,7 +91,7 @@ .endif .endm -.macro SET_DIRTY_COLUMN +.macro SET_DIRTY_COLUMN ?lbl lda *.xpos and #AT_SHADOW_WIDTH-1 tay @@ -99,6 +105,12 @@ cpy #ATTRIBUTE_PACKED_WIDTH rol .endif +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bpl lbl + eor #(2*NUM_NT) +lbl: +.endif tax lda .bitmask_table,y ora _attribute_column_dirty,x @@ -430,10 +442,22 @@ _set_bkg_submap_attributes_verticalStripes_columnLoop: .write_to_shadow: COORDS_TO_IDX +.ifdef NES_WINDOW_LAYER + bit *__current_vram_cfg_write + bmi 1$ +.endif and _attribute_shadow,x ora *.tmp sta _attribute_shadow,x rts +; Window version +.ifdef NES_WINDOW_LAYER +1$: + and _attribute_shadow_win,x + ora *.tmp + sta _attribute_shadow_win,x + rts +.endif .bitmask_table: .db 0b00000001 diff --git a/gbdk-lib/libc/targets/mos6502/nes/vram_transfer_buffer.s b/gbdk-lib/libc/targets/mos6502/nes/vram_transfer_buffer.s @@ -6,6 +6,8 @@ .title "VRAMBuffer" .module VRAMBuffer +.include "mapper_macros.s" + ; ; Format of transfer buffer ; @@ -61,6 +63,7 @@ __vram_transfer_buffer_pos_w:: .ds 1 __vram_transfer_buffer_pos_old:: .ds 1 .define __vram_transfer_buffer_temp "(REGTEMP+6)" +.define ppu_addr ".tmp" .area _HOME @@ -74,6 +77,9 @@ __vram_transfer_buffer_pos_old:: .ds 1 ; Direct write stx PPUADDR sta PPUADDR +.ifdef VRAM_MAPPER_CFG_TRANSFER + jsr __switch_win_addr_temp +.endif ; Clear inc-by-32 bit in both PPUCTRL and _shadow_PPUCTRL, as NMI may re-write PPUCTRL lda *_shadow_PPUCTRL and #~PPUCTRL_INC32 @@ -91,6 +97,9 @@ __vram_transfer_buffer_pos_old:: .ds 1 ; Direct write stx PPUADDR sta PPUADDR +.ifdef VRAM_MAPPER_CFG_TRANSFER + jsr __switch_win_addr_temp +.endif ; Set inc-by-32 bit in both PPUCTRL and _shadow_PPUCTRL, as NMI may re-write PPUCTRL lda *_shadow_PPUCTRL ora #PPUCTRL_INC32 @@ -104,6 +113,13 @@ __vram_transfer_buffer_pos_old:: .ds 1 pha txa pha +.ifdef NES_WINDOW_LAYER + lda *__current_vram_cfg_write + and #MAPPER_CFG_NT_MASK + ora .identity,x + sta *ppu_addr+1 + tay +.endif lda #0 rol asl @@ -115,6 +131,20 @@ __vram_transfer_buffer_pos_old:: .ds 1 lda *__vram_transfer_buffer_pos_w cmp #128-VRAM_MAX_STRIPE_SIZE bcs 2$ +.ifdef NES_WINDOW_LAYER + ldx *__vram_transfer_buffer_pos_w + ; if transfer buffer is empty, we must always re-initialize mapper with fresh write + beq 3$ + ; otherwise check the previous address. if there's a difference in bank bits, we need to re-initialize mapper + ldx *__vram_transfer_buffer_pos_old + tya + eor __vram_transfer_buffer+VRAM_HDR_PPUHI,x + and #PPUHI_WIN + beq 4$ +3$: + jsr .ppu_write_mapper_indirect +4$: +.endif ; Lock buffer and store current write pointer for later VRAM_BUFFER_LOCK ldy *__vram_transfer_buffer_pos_w @@ -206,13 +236,15 @@ __vram_transfer_buffer_pos_old:: .ds 1 ; _set_vram_byte:: .ppu_stripe_append:: - .define ppu_addr ".tmp" ; bit *__oam_valid_display_on bvc .ppu_stripe_append_indirect ; Direct write stx PPUADDR sta PPUADDR +.ifdef VRAM_MAPPER_CFG_TRANSFER + jsr __switch_win_addr_temp +.endif ldy *_set_vram_byte_PARM_2 sty PPUDATA rts @@ -281,6 +313,9 @@ _set_vram_byte:: jmp .ppu_stripe_append_1byte .ppu_stripe_append_failed: +.ifdef NES_WINDOW_LAYER + jsr .ppu_write_mapper_indirect +.endif ; Just create a new stripe with a single byte ldy *__vram_transfer_buffer_pos_w sty *__vram_transfer_buffer_pos_old @@ -358,3 +393,75 @@ _set_vram_byte:: lda *ppu_addr ldx *ppu_addr+1 rts + +.ifdef NES_WINDOW_LAYER +.ppu_write_mapper: + clc + bit *__oam_valid_display_on + bvc .ppu_write_mapper_indirect +.ppu_write_mapper_direct: + ; Direct write + SWITCH_PRG0_A + rts + +.ppu_write_mapper_indirect: + pha + ; Unlock buffer (if this routine was called while unlocked) + VRAM_BUFFER_UNLOCK + ; Ensure there's at least VRAM_MAX_STRIPE_SIZE bytes remaining to write before progressing + ; This conservative limit simplifies conditions for rest of stripe in order to write single bytes with no checks +2$: + lda *__vram_transfer_buffer_pos_w + cmp #128-VRAM_MAX_STRIPE_SIZE + bcs 2$ + ; Lock buffer and store current write pointer for later + VRAM_BUFFER_LOCK + ldy *__vram_transfer_buffer_pos_w + sty *__vram_transfer_buffer_pos_old + + ; Write direction (assume horizontal) and new terminator byte + lda #0 + sta __vram_transfer_buffer+VRAM_HDR_DIRECTION,y + sta __vram_transfer_buffer+VRAM_HDR_SIZEOF+1,y + ; Write jmp addr + lda #<ProcessDrawList_mapper_switch + sta __vram_transfer_buffer+VRAM_HDR_JMPADDR,y + ; Write mapper value to PPUADDR_LO + lda *ppu_addr+1 + ora *__current_vram_cfg_write + and #PPUHI_WIN + sta __vram_transfer_buffer+VRAM_HDR_PPULO,y + + ; Increase write pointer + tya + clc + adc #VRAM_HDR_SIZEOF + ; store new write pointer + sta *__vram_transfer_buffer_pos_w + + ; __vram_transfer_buffer_num_cycles_x8 -= 5 (assumes carry clear) + lda *__vram_transfer_buffer_num_cycles_x8 + sbc #4 + sta *__vram_transfer_buffer_num_cycles_x8 + ldy *__vram_transfer_buffer_temp + + dec *__vram_transfer_buffer_num_cycles_x8 + + VRAM_BUFFER_UNLOCK + pla + rts +.endif + +.ifdef NES_WINDOW_LAYER +__switch_bkg:: + lda *__current_vram_cfg_write + and #~PPUHI_WIN + sta *__current_vram_cfg_write + rts + +__switch_win:: + lda *__current_vram_cfg_write + ora #PPUHI_WIN + sta *__current_vram_cfg_write + rts +.endif
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.