Monorepo for Aesthetic.Computer aesthetic.computer
at main 160 lines 8.7 kB view raw
1; Fullscreen Background Example for the Nintendo Game Boy 2; by Dave VanEe 2022 3; Tested with RGBDS 1.0.0 4; License: CC0 (https://creativecommons.org/publicdomain/zero/1.0/) 5 6include "hardware.inc" ; Include hardware definitions so we can use nice names for things 7 8 9SECTION "MemCopy Routine", ROM0 10; Since we're copying data few times, we'll define a reusable memory copy routine 11; Copy BC bytes of data from HL to DE 12MemCopy: 13 ld a, [hli] ; Load a byte from the address HL points to into the register A, increment HL 14 ld [de], a ; Load the byte in the A register to the address DE points to 15 inc de ; Increment the destination pointer in DE 16 dec bc ; Decrement the loop counter in BC 17 ld a, b ; Load the value in B into A 18 or c ; Logical OR the value in A (from B) with C 19 jr nz, MemCopy ; If B and C are both zero, OR B will be zero, otherwise keep looping 20 ret ; Return back to where the routine was called from 21 22 23; The VBlank vector is where execution is passed when the VBlank interrupt fires 24SECTION "VBlank Vector", ROM0[$40] 25; We only have 8 bytes here, so take care of what we can in 5 bytes and then jump to the rest of the handler 26VBlank: 27 push af ; Push AF to the stack 28 ld a, 1 ; Load a non-zero value into A 29 ldh [hVBlankDone], a; Set a flag indicating VBlank has fired, which will allow WaitVBlank to exit 30 jp VBlankHandler ; Jump to the rest of the handler 31 32 33; The STAT vector is where execution is passed when the STAT interrupt fires 34SECTION "STAT Vector", ROM0[$48] 35; We can fit the entire handler inside the 8 bytes we have, so there's no need to jump away here 36 push af ; Push AF to the stack 37 ld a, LCDC_ON | LCDC_BLOCK21 | LCDC_BG_ON | LCDC_OBJ_OFF | LCDC_WIN_OFF 38 ldh [rLCDC], a ; Set the background to use the tile starting at $8800 after the LYC interrupt line 39 pop af ; Pop AF off the stack 40 reti ; Return and enable interrupts (ret + ei) 41 42 43; The rest of the VBlank handler is contained in ROM0 to ensure it's always accessible without banking 44SECTION "VBlank Handler", ROM0 45VBlankHandler: 46 ld a, LCDC_ON | LCDC_BLOCK01 | LCDC_BG_ON | LCDC_OBJ_OFF | LCDC_WIN_OFF 47 ldh [rLCDC], a ; Set the background to use the tile starting at $8000 from the top of the screen 48 pop af ; Pop AF off the stack 49 reti ; Return and enable interrupts (ret + ei) 50 51 52; Define a section that starts at the point the bootrom execution ends 53SECTION "Start", ROM0[$0100] 54 jp EntryPoint ; Jump past the header space to our actual code 55 56 ds $150-@, 0 ; Allocate space for RGBFIX to insert our ROM header by allocating 57 ; the number of bytes from our current location (@) to the end of the 58 ; header ($150) 59 60EntryPoint: 61 di ; Disable interrupts during setup 62 ld sp, $e000 ; Set the stack pointer to the end of WRAM 63 64 ; Turn off the LCD when it's safe to do so (during VBlank) 65.waitVBlank 66 ldh a, [rLY] ; Read the LY register to check the current scanline 67 cp SCREEN_HEIGHT_PX ; Compare the current scanline to the first scanline of VBlank 68 jr c, .waitVBlank ; Loop as long as the carry flag is set 69 xor a ; Once we exit the loop we're safely in VBlank 70 ldh [rLCDC], a ; Disable the LCD (must be done during VBlank to protect the LCD) 71 72 ; Copy the first 240 tiles to VRAM starting at $8000 73 ld hl, TileData ; Load the source address of our tiles into HL 74 ld de, STARTOF(VRAM); Load the destination address in VRAM into DE 75 ld bc, $10*240 ; Load the number of bytes to copy into BC (16 bytes per tile) 76 call MemCopy ; Call our general-purpose memory copy routine 77 78 ; Copy the remaining 120 tiles to VRAM starting at $9000 79 ; Note: HL is already pointing at the start of the data we want to copy 80 ld de, STARTOF(VRAM)+$1000 ; Load the destination address in VRAM into DE 81 ld bc, $10*120 ; Load the number of bytes to copy into BC (16 bytes per tile) 82 call MemCopy ; Call our general-purpose memory copy routine 83 84 ; Create a tilemap which includes all 360 tiles, in order, starting at tile 0 85 ld hl, TILEMAP0 ; Point HL to the first byte of the tilemap ($9800) 86 ld de, TILEMAP_WIDTH - SCREEN_WIDTH ; Load the number of tiles to skip per row (32-20) into DE 87 xor a ; Store the tile ID we're writing in A, starting with zero 88 ld c, SCREEN_HEIGHT ; Load the total number of rows (18) into C 89.yLoop 90 ld b, SCREEN_WIDTH ; Load the number of tiles per row (20) into B 91.xLoop 92 ld [hli], a ; Load the current tile ID into HL and increment HL 93 inc a ; Increment the tile ID in A 94 dec b ; Decrement the X counter 95 jr nz, .xLoop ; Loop until we've written a full row 96 add hl, de ; Add the offset to advance to the next row in VRAM to HL 97 ld b, a ; Move the tile ID (A) to B temporarily to free A up 98 ld a, c ; Load the rows remaining into A 99 cp 7 ; Compare A to 7 to see if we only have 6 rows remaining 100 ; (we compare to 7 instead of 6 since we haven't decremented C yet) 101 jr nz, .dontReset ; If we aren't at the "6 rows remaining" point don't reset the tile ID 102 ld b, 0 ; Load zero into B as the starting tile ID for the bottom portion 103.dontReset 104 ld a, b ; Recover the tile ID from B (either the old one or the reset one) into A 105 dec c ; Decrement the row counter 106 jr nz, .yLoop ; Loop until we've written the top set of tile IDs 107 108 ; Setup palettes and scrolling 109 ld a, %11100100 ; Define a 4-shade palette from darkest (11) to lightest (00) 110 ldh [rBGP], a ; Set the background palette 111 112 ld a, 0 ; Load zero into the register A 113 ldh [rSCX], a ; Set the background scroll registers to show the top-left 114 ldh [rSCY], a ; corner of the background in the top-left corner of the screen 115 116 ldh [hVBlankDone], a; Initialize the hVBlankDone flag just to be safe 117 118 ; Setup the VBlank and STAT interrupts 119 ld a, STAT_LYC ; Load the flag to enable LYC STAT interrupts into A 120 ldh [rSTAT], a ; Load the prepared flag into rSTAT to enable the LY=LYC interrupt source 121 ld a, 96 ; Set which line to trigger the LY=LYC interrupt on by setting the rLYC register 122 ldh [rLYC], a ; ... 123 ld a, IE_VBLANK | IE_STAT ; Load the flag to enable the VBlank and STAT interrupts into A 124 ldh [rIE], a ; Load the prepared flag into the interrupt enable register 125 xor a ; Set A to zero 126 ldh [rIF], a ; Clear any lingering flags from the interrupt flag register to avoid false interrupts 127 ei ; enable interrupts! 128 129 ; Combine flag constants defined in hardware.inc into a single value with logical ORs and load it into A 130 ; Note that some of these constants (LCDC_BG_OFF, LCDC_OBJ8, LCDC_WIN_OFF) are zero, but are included for clarity 131 ld a, LCDC_ON | LCDC_BLOCK01 | LCDC_BG_ON | LCDC_OBJ_OFF | LCDC_WIN_OFF 132 ldh [rLCDC], a ; Enable and configure the LCD to show the background 133 134LoopForever: 135 call WaitVBlank ; Call our routine which halts until the hVBlankDone flag is set 136 jr LoopForever ; Loop forever 137 138SECTION "WaitVBlank", ROM0 139; Since we have multiple interrupts enabled, using a single HALT in our mainloop isn't enough to sync the 140; mainloop to the screen refresh. Since we only have two interrupts (LY=LYC and VBlank), we could HALT twice, 141; but that scales poorly. Instead, we zero a flag, and then halt in a loop until our VBlank handler sets 142; that flag, letting us know it's fired. 143WaitVBlank: 144 xor a ; Zero the hVBlankDone flag before starting our loop 145 ldh [hVBlankDone], a; ... 146.loop 147 halt ; Halt the CPU, waiting until an interrupt fires (it could be STAT or VBlank) 148 ldh a, [hVBlankDone]; Load the hVBlankDone flag value into A 149 or a ; This is a shortcut version of 'cp 0', to see if the flag has been set by our VBlank handler 150 jr z, .loop ; If the flag isn't set, halt again 151 ret ; Return back to where the routine was called from 152 153SECTION "VBlank Variables", HRAM 154; Reserve space in HRAM to track when the VBlank interrupt has fired 155hVBlankDone: 156 ds 1 157 158SECTION "Tile Data", ROMX 159TileData: 160 incbin "starry-night-tiles.2bpp" ; Include binary tile data inline using incbin