gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 3e2a1742d8f18d37ccbdc608a987c504a14604c8 parent aecf7a0b45e0a42ee10b7a31d57f55fba1196a63 Author: Toxa <56631470+untoxa@users.noreply.github.com> Date: Tue, 11 Jul 2023 04:07:59 +0300 Merge pull request #537 from michel-iwaniec/nes_fake_vbl_and_lcd_isr_handlers NES: Add support for fake VBL / LCD ISR handling to support simple split screen / top status bar Diffstat:
| M | gbdk-lib/examples/cross-platform/scroller/Makefile | 3 | ++- |
| M | gbdk-lib/examples/cross-platform/scroller/Makefile.targets | 5 | +++++ |
| M | gbdk-lib/examples/cross-platform/scroller/src/text_scroller.c | 16 | ++++++++++++++-- |
| M | gbdk-lib/include/nes/hardware.h | 2 | +- |
| M | gbdk-lib/include/nes/nes.h | 82 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | gbdk-lib/libc/targets/mos6502/nes/Makefile | 1 | + |
| M | gbdk-lib/libc/targets/mos6502/nes/crt0.s | 52 | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | gbdk-lib/libc/targets/mos6502/nes/lcd.s | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
8 files changed, 214 insertions(+), 4 deletions(-)
diff --git a/gbdk-lib/examples/cross-platform/scroller/Makefile b/gbdk-lib/examples/cross-platform/scroller/Makefile @@ -6,7 +6,7 @@ LCC = $(GBDK_HOME)bin/lcc # Set platforms to build here, spaced separated. (These are in the separate Makefile.targets) # They can also be built/cleaned individually: "make gg" and "make gg-clean" # Possible are: gb gbc pocket megaduck sms gg -TARGETS=gb pocket megaduck sms gg +TARGETS=gb pocket megaduck sms gg nes # Configure platform specific LCC flags here: LCCFLAGS_gb = -Wl-yt0x1B # Set an MBC for banking (1B-ROM+MBC5+RAM+BATT) @@ -15,6 +15,7 @@ LCCFLAGS_duck = -Wl-yt0x1B # Usually the same as required for .gb LCCFLAGS_gbc = -Wl-yt0x1B -Wm-yc # Same as .gb with: -Wm-yc (gb & gbc) or Wm-yC (gbc exclusive) LCCFLAGS_sms = LCCFLAGS_gg = +LCCFLAGS_nes = -Wl-yt0x1B LCCFLAGS += $(LCCFLAGS_$(EXT)) # This adds the current platform specific LCC Flags diff --git a/gbdk-lib/examples/cross-platform/scroller/Makefile.targets b/gbdk-lib/examples/cross-platform/scroller/Makefile.targets @@ -48,3 +48,7 @@ gg-clean: gg: ${MAKE} build-target PORT=z80 PLAT=gg EXT=gg +nes-clean: + ${MAKE} clean-target EXT=nes +nes: + ${MAKE} build-target PORT=mos6502 PLAT=nes EXT=nes + diff --git a/gbdk-lib/examples/cross-platform/scroller/src/text_scroller.c b/gbdk-lib/examples/cross-platform/scroller/src/text_scroller.c @@ -11,7 +11,11 @@ const uint8_t * scanline_offsets = scanline_offsets_tbl; uint8_t scroller_x = 0; void scanline_isr(void) { -#if defined(NINTENDO) +#if defined(NINTENDO_ENTERTAINMENT_SYSTEM) + // Write directly to hardware scroll registers (only first write will have an effect) + PPUSCROLL = scroller_x; + PPUSCROLL = 0; // 2nd write (dummy) +#elif defined(NINTENDO) switch (LYC_REG) { case 0: SCX_REG = 0; @@ -45,6 +49,8 @@ const uint8_t * scroller_next_char = scroller_text; uint8_t * scroller_vram_addr; uint8_t * base, * limit; +extern uint8_t _lcd_scanline; + void main(void) { printf(" Scrolling %d chars", sizeof(scroller_text) - 1); @@ -57,8 +63,9 @@ void main(void) { #if defined(SEGA) __WRITE_VDP_REG(VDP_R10, 0x07); #endif +#if defined(NINTENDO) || defined(SEGA) set_interrupts(VBL_IFLAG | LCD_IFLAG); - +#endif HIDE_LEFT_COLUMN; base = (uint8_t *)((uint16_t)get_bkg_xy_addr(0, SCROLL_POS) & (DEVICE_SCREEN_MAP_ENTRY_SIZE==1?0xffe0:0xffc0)); limit = base + (DEVICE_SCREEN_BUFFER_WIDTH * DEVICE_SCREEN_MAP_ENTRY_SIZE); @@ -82,6 +89,11 @@ void main(void) { // put next char set_vram_byte(scroller_vram_addr, *scroller_next_char - 0x20); } +#ifdef NINTENDO_ENTERTAINMENT_SYSTEM + // Normal indirect setting of scroll via shadow registers (written by vblank handler) + move_bkg(0,0); + _lcd_scanline = SCROLL_POS; +#endif vsync(); } } diff --git a/gbdk-lib/include/nes/hardware.h b/gbdk-lib/include/nes/hardware.h @@ -44,7 +44,7 @@ __REG(0x4014) OAMDMA; #define DEVICE_SCREEN_HEIGHT 30 #define DEVICE_SCREEN_BUFFER_WIDTH 32 #define DEVICE_SCREEN_BUFFER_HEIGHT 30 -#define DEVICE_SCREEN_MAP_ENTRY_SIZE 2 +#define DEVICE_SCREEN_MAP_ENTRY_SIZE 1 #define DEVICE_SPRITE_PX_OFFSET_X 0 #define DEVICE_SPRITE_PX_OFFSET_Y -1 #define DEVICE_WINDOW_PX_OFFSET_X 0 diff --git a/gbdk-lib/include/nes/nes.h b/gbdk-lib/include/nes/nes.h @@ -148,6 +148,88 @@ void set_sprite_palette_entry(uint8_t palette, uint8_t entry, palette_color_t rg */ #define SCREENHEIGHT DEVICE_SCREEN_PX_HEIGHT +/** Interrupt handlers + */ +typedef void (*int_handler)(void) NONBANKED; + +/** The remove functions will remove any interrupt handler. + + A handler of NULL will cause bad things + to happen if the given interrupt is enabled. + + Removes the VBL interrupt handler. @see add_VBL() +*/ +void remove_VBL(int_handler h); + +/** Removes the LCD interrupt handler. + @see add_LCD(), remove_VBL() +*/ +void remove_LCD(int_handler h); + +/** Adds a Vertical Blanking interrupt handler. + + @param h The handler to be called whenever a V-blank + interrupt occurs. + + Only a single handler is currently supported for NES. + + __Do not__ use the function definition attributes + @ref CRITICAL and @ref INTERRUPT when declaring + ISR functions added via add_VBL() (or LCD, etc). + Those attributes are only required when constructing + a bare jump from the interrupt vector itself (such as + with @ref ISR_VECTOR()). + + ISR handlers added using add_VBL()/etc are instead + called via the GBDK ISR dispatcher which makes + the extra function attributes unecessary. + + @note The default GBDK VBL is installed automatically. + + @note On the current NES implementation, this handler + is actually faked, and called before vblank occurs, by + wait_vbl_done. Writes to PPU registers should be done to + the shadow_ versions, so they are updated by the default + VBL handler only when vblank actually occurs. + + @see ISR_VECTOR() +*/ +void add_VBL(int_handler h); + +/** Adds a LCD interrupt handler. + + Called when the scanline matches the _lcd_scanline variables. + + Only a single handler is currently supported for NES. + + The use-case is to indicate to the user when the + video hardware is about to redraw a given LCD line. + This can be useful for dynamically controlling the + scrolling registers to perform special video effects. + + __Do not__ use the function definition attributes + @ref CRITICAL and @ref INTERRUPT when declaring + ISR functions added via add_VBL() (or LCD, etc). + Those attributes are only required when constructing + a bare jump from the interrupt vector itself (such as + with @ref ISR_VECTOR()). + + ISR handlers added using add_VBL()/etc are instead + called via the GBDK ISR dispatcher which makes + the extra function attributes unecessary. + + @note On the current NES implementation, this handler + is actually faked, and called by the default VBL handler + after a manual delay loop. Only one such faked "interrupt" + is possible per frame. + This means the CPU cycles wasted in the delay loop increase + with higher values of _lcd_scanline. In practice, it makes + this functionality mostly suited for a top status bar. + + @see add_VBL, nowait_int_handler, ISR_VECTOR() +*/ +void add_LCD(int_handler h); + /** Set the current screen mode - one of M_* modes Normally used by internal functions only. diff --git a/gbdk-lib/libc/targets/mos6502/nes/Makefile b/gbdk-lib/libc/targets/mos6502/nes/Makefile @@ -19,6 +19,7 @@ ASSRC = f_ibm_full.s f_ibm_sh.s f_italic.s f_min.s f_spect.s \ pad.s pad_ex.s \ rle_decompress.s \ far_ptr.s sdcc_bcall.s mapper.s \ + lcd.s \ crt0.s CRT0 = crt0.s diff --git a/gbdk-lib/libc/targets/mos6502/nes/crt0.s b/gbdk-lib/libc/targets/mos6502/nes/crt0.s @@ -134,6 +134,18 @@ NotInsideNMI: ora *__crt0_ScrollHV sta PPUCTRL + ; Call fake LCD isr + ldx *__lcd_scanline + beq 1$ + jsr .delay_to_lcd_scanline +1$: + ; Adjust to align to just-before-hblank + nop + nop + nop + ; Call the handler + jsr .jmp_to_LCD_isr + pla tay pla @@ -261,6 +273,45 @@ ProcessDrawList_EndOfList: rts ; +6 ; = 3 + 2 + 2 + 3 + 3 + 6 = 19 +; +; Delays until specified (non-zero) scanline is reached +; +; First scanline's delay needs adjusting for cycle cost of subroutine execution: +; beq-not-taken -1 +; jsr +6 +; lda #0 +2 +; sta *.acc +3 +; ldy #N +2 +; nop +2 +; nop +2 +; bne-taken +3 +; rts +6 +; -> 25 cycles less +; -> N = 19-25/5 = 19-5 +; +.define .acc "___SDCC_m6502_ret4" +.delay_to_lcd_scanline:: + lda #0 + sta *.acc + ldy #(19-5) + nop + nop + bne 2$ +1$: + ldy #19 +2$: + dey + bne 2$ ; -> 2 + 18*5 + 4 = 91 cycles + lda *.acc + clc + adc #85 + bcc 3$ +3$: + sta *.acc ; -> 12.666 cycles + dex + bne 1$ ; -> 5 cycles + rts + .bndry 0x100 ProcessDrawList_NumBytesToAddress: i = 0 @@ -331,6 +382,7 @@ __crt0_clearVRAM_loop: .wait_vbl_done:: _wait_vbl_done:: _vsync:: + jsr .jmp_to_VBL_isr lda *_sys_time _wait_vbl_done_waitForNextFrame_loop: cmp *_sys_time diff --git a/gbdk-lib/libc/targets/mos6502/nes/lcd.s b/gbdk-lib/libc/targets/mos6502/nes/lcd.s @@ -0,0 +1,57 @@ +.include "global.s" + +.area OSEG (PAG, OVR) + +.area _ZP (PAG) +__lcd_scanline:: .ds 1 + +.area _DATA +.jmp_to_VBL_isr:: .ds 3 +.jmp_to_LCD_isr:: .ds 3 + +.area _XINIT +; .jmp_to_VBL_isr +rts +nop +nop +; .jmp_to_LCD_isr +rts +nop +nop + +.area _CODE + +_add_VBL:: +.add_VBL:: + ldy #0 + beq .add_common +_add_LCD:: +.add_LCD:: + ldy #3 +.add_common: + ; Remove old handler + jsr .remove_common + ; Store new handler address + sta .jmp_to_VBL_isr+1,y + txa + sta .jmp_to_VBL_isr+2,y + ; Re-enable handler by replacing RTS with JMP instruction + lda #0x4C + sta .jmp_to_VBL_isr,y + rts + +_remove_VBL:: +.remove_VBL:: + ldy #0 + beq .remove_common +_remove_LCD:: +.remove_LCD:: + ldy #3 +.remove_common: + pha + ; Replace jump instruction with RTS instruction to disable handler + lda #0x60 + sta .jmp_to_VBL_isr,y + pla +.return_instruction: + rts
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.