git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

commit f785b9e93cc127ff7ded14e53dc46476c02ce748
parent 74b5d95c42ecbc73ff29260f67918eea876badf7
Author: Toxa <56631470+untoxa@users.noreply.github.com>
Date:   Sat,  5 Aug 2023 00:15:04 +0300

Merge pull request #560 from michel-iwaniec/nes_transfer_buffer_docs

Update 06b_supported_consoles.md with NES-specific details
Diffstat:
Mdocs/pages/06b_supported_consoles.md112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 112 insertions(+), 0 deletions(-)

diff --git a/docs/pages/06b_supported_consoles.md b/docs/pages/06b_supported_consoles.md @@ -337,6 +337,118 @@ This behavior is emulated for the SMS/GG when using @ref set_bkg_tiles() and @re ## From Game Boy to NES +The NES graphics architecture is similar to the GB's. However, there are a number of design choices in the NES hardware that make the NES a particularly cumbersome platform to develop for, and that will require special attention. + +Most notably: +* PPU memory can only be written in a serial fashion using a data port at 0x2007 (PPUDATA) +* PPU memory can only be written to during vblank, or when manually disabling rendering via PPUMASK. Hblank writes to PPU memory are not possible +* PPU memory write address is re-purposed for scrolling coordinates when rendering is enabled which means PPU memory updates / scrolling must cooperate +* PPU internal palette memory is also mapped to external VRAM area making palette updates during rendering very expensive and error-prone +* The base NES system has no support for any scanline interrupts. And cartridge mappers that add scanline interrupts do so using wildly varying solutions +* There's no easy way to determine the current scanline or CPU-to-PPU alignment meaning timed code is often required on the NES +* The PAL variant of the NES has very different CPU / PPU timings, as do the Dendy clone and other clone systems + +To provide an easier experience, gbdk-nes attempts to hide most of these quirks so that in theory the programming experience for gbdk-nes should be as close as possible to that of the GB/GBC. However, to avoid surprises it is recommended to familiarize yourself with the NES-specific quirks and implementation choices mentioned here. + +This entire section is written as a guide on porting GB projects to NES. If you are new to GBDK, you may wish to familiarize yourself with using GBDK for GB development first as the topics covered will make a lot more sense after gaining experience with GB development. + +### Buffered mode vs direct mode + +On the GB, the vblank period serves as an optimal time to write data to PPU memory, and PPU memory can also be written efficiently with VRAM DMA. + +On the NES, writing PPU memory during the vblank period is not optional. Whenever rendering is turned on the PPU is in a state where accessing PPU memory results in undefined behavior outside the short vblank period. The NES also has no VRAM DMA hardware to help with data writes. This makes the vblank period not only more precious, but important to never exceed to avoid glitched games. + +To deal with this limitation, all functions in gbdk-nes that write to PPU memory can run in either *Buffered* or *Direct* mode. + +The good news is that switching between buffered and direct mode in gbdk-nes is usually done behind-the-scenes and normally shouldn't affect your code too much, as long as you use the portable GBDK functions and macros to do this. + +* DISPLAY_ON / SHOW_BG / SHOW_SPR will all switch the system into buffered mode, allowing limited amounts of transfers during vblank, not the display of graphics +* DISPLAY_OFF will switch the system into direct mode, allowing much larger/faster transfers while the screen is blanked + +The following sections describe how the buffered / direct modes work in more detail. As buffered / direct mode is mostly hidden by the API calls, feel free to skip these sections if you wish. + +#### Buffered mode implementation details + +To take maximum advantage of the short vblank period, gbdk-nes implements the same system as nearly every other NES engine: An unrolled loop that pulls prepared data bytes from the stack. + + PLA + STA PPUDATA + ... + PLA + STA PPUDATA + RTS + +The data structure to facilitate this is usually called a vram transfer buffer, often affectionately called a "popslide" buffer after Damian Yerrick's implementation. This buffer essentially forms a list of commands where each comand sets up a new PPU address and then writes a sequence of bytes with an auto-increment of either +1 or +32. Each such command is often called a "stripe" in the nesdev community. + +It starts at 0x100 and takes around half of the hardware stack page. You can think of the transfer buffer as a software-implemented DMA that allows writing bytes at the optimal rate of 8 cycles / byte. (ignoring the PPU address setup cost) + +The buffer allows writing up to 32 continuous bytes at a time. This allows updating a full screen row / column, or two 8x8 tiles worth of tile data in one command / "stripe". + +By doing writes to this buffer during game logic, your game will effectively keep writing data transfer commands for the vblank NMI handler to process in the next vblank period, without having to wait until the vblank. + +Given that transfer buffer only has space for around 100 data bytes, it is important to not overfill the buffer, as this will bring code execution to a screeching halt, until the NMI handler empties the old contents of the buffer to free up space to allow new commands to be written. + +Buffered mode is typically used for scrolling updates or dynamically animated tiles, where only a small amount of bytes need updating per frame. + +#### Direct mode implementation details + +During direct mode, all graphics routines will write directly to the PPUADDR / PPUDATA ports and the transfer buffer limit is never a concern because the transfer buffer is effectively avoided. + +Direct mode is typically used for initializing large amounts of tile data at boot and/or level loading time. Unless you plan to have an animated loading screen and decompress a lot of data, it makes more sense to just fade the screen to black and allow direct mode to write data as fast as possible. + +#### Caveat: Make sure the transfer buffer is emptied before switching to direct mode + +Because the switch to the direct mode is instant and doesn't wait for the next invocation of the vblank, it is possible to create situations where there is still remaining data in the transfer buffer that would only get written once the system is switched back to buffered mode. + +To avoid this situation, make sure to always "drain" the buffer by doing a call to wait_vbl_done when you expect your code to finish. + +#### Caveat: Only update the PPU palette during buffered mode + +The oddity that PPU palette values are accessed through the same mechanism as other PPU memory bytes comes with the side effect that the vblank NMI handler will only write the palette values in buffered mode. + +The reason for this design choice is two-fold: +* Having the NMI handler keep doing the palette updates when in direct mode would result in a race condition when the NMI handler interrupts the direct mode code and messes with the PPUADDR state that the direct mode code expects to remain unchanged +* Having the palette updates also switch to direct mode would run into another quirk of the system: Pointing PPUADDR at palette registers when display is turned off will make the display output that palette color instead of the common background color. The result would be glitchy artifacts on screen when updating the palette, leading to slightly-glitchy looking game whenever the palette is updated with the screen off + +To work around this, you are advised to never fully turn the display off during a palette fade. If you don't follow this advice all your palette updates will get delayed until the screen is turned back on. + +### Shadow PPU registers + +Like the SMS, the NES hardware is designed to only allow loading the full X/Y scroll on the very first scanline. i.e., under normal operation you are only allowed to change the Y-scroll once. + +In contrast to the SMS, this limitation can be circumvented with a specific set of out-of-order writes to the PPUSCROLL/PPUADDR registers, taking advantage of the quirk that the PPUADDR and PPUSCROLL share register bits. But this write sequence is very timing-sensitive as the writes need to fall into (a smaller portion of) the hblank period in order to avoid race conditions when the CPU and PPU both try to update the same register during scanline rendering. + +To simplify the programming interface, gbdk-nes functions like move_bkg / scroll_bkg only ever update shadow registers in RAM. The vblank NMI handler will later pick these values up and write them to the actual PPU registers registers. + +### Implementation of (fake) vbl / lcd handlers + +GBDK provides an API for installing Interrupt Service Routines that execute on start of vblank (VBL handler), or on a specific scanline (LCD handler). + +But the base NES system has no suitable scanline interrupts that can provide such functionality. So instead, gbdk-nes API allows *fake* handlers to be installed in the goal of keeping code compatible with other platforms. + +* An installed VBL handler will be called immediately when calling wait_vbl_done. This handler should only update PPU shadow registers +* An installed LCD handler for a specific scanline will be called after the vblank NMI handler has finished execution, and will then manually run a delay loop to reach that scanline before calling your installed LCD handler. + +Because the LCD "ISR" is actually implemented with a delay loop, it will burn a lot of CPU cycles in the frame - the further down the scanline is the larger the CPU cycle loss. In practice this makes this faked-LCD-ISR functionality only suited for status bars at the top screen, or simple parallax cutscenes where the CPU has little else to do. + +@note The support for VBL and LCD handlers is currently under consideration and subject to change in newer versions of gbdk-nes. + +### Caveat: Make sure to call wait_vbl_done on every frame + +On the GB, the call to wait_vbl_done is an optional call that serves two purposes: + +1. It provides a consistent frame timing for your game +2. It allows future register writes to be synchronized to the screen + +On gbdk-nes the second point is no longer true, because writes need to be made to the shadow registers *before* wait_vbl_done is called. + +But the wait_vbl_done call serves two other very important purposes: + +A. It calls the optional VBL handler, where shadow registers can be written (and will later be picked up by the actual vblank NMI handler) +B. It calls flush_shadow_attributes so that updates to background attributes actually get written to PPU memory + +For these reasons you should always include a call to wait_vbl_done if you expect to see any graphical updates on the screen. + ### Tile Data and Tile Map loading #### Tile and Map Data in 2bpp Game Boy Format

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.