gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 66351b26870816445aa41ea4640b2982db7dcbf6 parent acb3df0c4bc752e71a266c712026f8a0b0e8097b Author: Toxa <56631470+untoxa@users.noreply.github.com> Date: Sat, 14 Jan 2023 21:34:49 +0400 Merge pull request #466 from michel-iwaniec/nes_attribute_coordinate_refactor NES: Refactor attributes to use 8x8 attribute coordinate system, to be more consistent with other platforms Diffstat:
| M | gbdk-lib/examples/cross-platform/logo/src/main.c | 10 | +++++----- |
| M | gbdk-lib/include/nes/nes.h | 53 | ++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | gbdk-lib/libc/targets/mos6502/nes/Makefile | 3 | ++- |
| M | gbdk-lib/libc/targets/mos6502/nes/crt0.s | 1 | + |
| A | gbdk-lib/libc/targets/mos6502/nes/flush_attributes.s | 110 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | gbdk-lib/libc/targets/mos6502/nes/set_bk_attributes.s | 88 | +++++++++++++++++-------------------------------------------------------------- |
| M | gbdk-support/png2asset/png2asset.cpp | 106 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
7 files changed, 265 insertions(+), 106 deletions(-)
diff --git a/gbdk-lib/examples/cross-platform/logo/src/main.c b/gbdk-lib/examples/cross-platform/logo/src/main.c @@ -26,11 +26,11 @@ void main() { VBK_REG = VBK_TILES; } #elif defined(SYSTEM_NES) - // Make sure attribute coordinates are rounded to 2 - set_bkg_attributes((((DEVICE_SCREEN_WIDTH - (GBDK_2020_logo_WIDTH >> 3)) >> 1) & 0xFE) >> 1, - (((DEVICE_SCREEN_HEIGHT - (GBDK_2020_logo_HEIGHT >> 3)) >> 1) & 0xFE) >> 1, - GBDK_2020_logo_MAP_ATTRIBUTES_WIDTH, - GBDK_2020_logo_MAP_ATTRIBUTES_HEIGHT, + // Make sure tile coordinates are rounded to 2, to match attribute table + set_bkg_attributes(((DEVICE_SCREEN_WIDTH - (GBDK_2020_logo_WIDTH >> 3)) >> 1) & 0xFE, + ((DEVICE_SCREEN_HEIGHT - (GBDK_2020_logo_HEIGHT >> 3)) >> 1) & 0xFE, + GBDK_2020_logo_WIDTH >> 3, + GBDK_2020_logo_HEIGHT >> 3, GBDK_2020_logo_map_attributes); #endif #if defined(SYSTEM_NES) diff --git a/gbdk-lib/include/nes/nes.h b/gbdk-lib/include/nes/nes.h @@ -487,7 +487,58 @@ void set_bkg_1bpp_data(uint8_t first_tile, uint8_t nb_tiles, const uint8_t *data void set_bkg_tiles(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *tiles) OLDCALL; #define set_tile_map set_bkg_tiles -void set_bkg_attributes(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes) OLDCALL; +/** Sets a rectangular region of Background Tile Map Attributes. + + @param x X Start position in Background Map tile coordinates. Range 0 - 15 + @param y Y Start position in Background Map tile coordinates. Range 0 - 14 + @param w Width of area to set in tiles. Range 1 - 16 + @param h Height of area to set in tiles. Range 1 - 15 + @param tiles Pointer to source tile map data + + Entries are copied from map at __tiles__ to the Background Tile Map starting at + __x__, __y__ writing across for __w__ tiles and down for __h__ tiles. + + NES 16x16 Tile Attributes are tightly packed into 4 attributes per byte, + with each 16x16 area of a 32x32 pixel block using the bits as follows: + D1-D0: Top-left 16x16 pixels + D3-D2: Top-right 16x16 pixels + D5-D4: Bottom-left 16x16 pixels + D7-D6: Bottom-right 16x16 pixels + + https://www.nesdev.org/wiki/PPU_attribute_tables + + @see SHOW_BKG + @see set_bkg_data, set_bkg_submap_attributes, set_win_tiles, set_tiles +*/ +void set_bkg_attributes_nes16x16(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes) OLDCALL; + +/** Sets a rectangular region of Background Tile Map Attributes. + + Entries are copied from map at __tiles__ to the Background Tile Map starting at + __x__, __y__ writing across for __w__ tiles and down for __h__ tiles. + + Use @ref set_bkg_submap_attributes() 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. + + Please note that this is just a wrapper function for set_bkg_attributes_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. + + @see SHOW_BKG + @see set_bkg_data, set_bkg_submap_attributes, set_win_tiles, set_tiles +*/ +inline void set_bkg_attributes(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *attributes) +{ + set_bkg_attributes_nes16x16(x >> 1, y >> 1, (w + 1) >> 1, (h + 1) >> 1, attributes); +} extern uint8_t _map_tile_offset; diff --git a/gbdk-lib/libc/targets/mos6502/nes/Makefile b/gbdk-lib/libc/targets/mos6502/nes/Makefile @@ -10,7 +10,8 @@ CSRC = crlf.c ASSRC = f_ibm_full.s f_ibm_sh.s f_italic.s f_min.s f_spect.s \ font.s font_color.s set_data.s set_1bit_data.s color.s mode.s \ metasprites.s metasprites_hide.s metasprites_hide_spr.s \ - set_bk_ts.s set_tile_submap.s fill_rect_bk.s set_bk_attributes.s \ + set_bk_ts.s set_tile_submap.s fill_rect_bk.s \ + set_bk_attributes.s flush_attributes.s \ nes_palettes.s \ pad.s pad_ex.s \ rle_decompress.s \ diff --git a/gbdk-lib/libc/targets/mos6502/nes/crt0.s b/gbdk-lib/libc/targets/mos6502/nes/crt0.s @@ -89,6 +89,7 @@ __crt0_textTemp: .ds 1 _bkg_scroll_x:: .ds 1 _bkg_scroll_y:: .ds 1 _attribute_row_dirty:: .ds 1 +_attribute_column_dirty:: .ds 1 .crt0_textStringBegin: .ds 1 .crt0_forced_blanking:: .ds 1 diff --git a/gbdk-lib/libc/targets/mos6502/nes/flush_attributes.s b/gbdk-lib/libc/targets/mos6502/nes/flush_attributes.s @@ -0,0 +1,110 @@ + .include "global.s" + + .area _HOME + +_flush_shadow_attributes:: + jsr _flush_shadow_attributes_rows + jmp _flush_shadow_attributes_columns + +; +; Writes every row of attributes from _shadow_attributes that's been marked +; as dirty in the _attribute_row_dirty byte to PPU memory. +; +_flush_shadow_attributes_rows: + lda #<PPU_AT0 + sta *.tmp + lda #>PPU_AT0 + sta *.tmp+1 + ldy #0 +_flush_shadow_attributes_row_loop: + lsr *_attribute_row_dirty + bcc 1$ + jmp _flush_shadow_attributes_update_row +1$: + beq _flush_shadow_attributes_end +_flush_shadow_attributes_next_row: + ; Y += 8 + tya + clc + adc #8 + tay + ; .tmp += 8 + lda *.tmp + adc #8 + sta *.tmp + jmp _flush_shadow_attributes_row_loop +_flush_shadow_attributes_end: + rts + +; +; Flushes all dirty rows of _attribute_shadow by writing them to PPU memory +; +_flush_shadow_attributes_update_row: + ; Update all 8 bytes of row for now, as each row in _attribute_row_dirty only stores 1 bit + ; TODO: Could store 8 bytes and update range, at expense of 7 more bytes. + lda *.tmp+1 + tax + lda *.tmp + jsr .ppu_stripe_begin_horizontal + ; Write 8 bytes + i = 0 + .rept 8 + lda _attribute_shadow+i,y + jsr .ppu_stripe_write_byte + i = i + 1 + .endm + jsr .ppu_stripe_end + jmp _flush_shadow_attributes_next_row + +; +; Writes every column of attributes from _shadow_attributes that's been marked +; as dirty in the _attribute_column_dirty byte to PPU memory. +; +; +_flush_shadow_attributes_columns: + lda #<PPU_AT0 + sta *.tmp + lda #>PPU_AT0 + sta *.tmp+1 + ldy #0 +_flush_shadow_attributes_columns_loop: + lsr *_attribute_column_dirty + bcc 1$ + jmp _flush_shadow_attributes_update_column +1$: + beq _flush_shadow_attributes_columns_end +_flush_shadow_attributes_columns_next_column: + ; Y += 1 + iny + ; .tmp += 1 + inc *.tmp + jmp _flush_shadow_attributes_columns_loop +_flush_shadow_attributes_columns_end: + rts + +; +; Flushes all dirty rows of _attribute_shadow by writing them to PPU memory +; +_flush_shadow_attributes_update_column: + ; 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 + lda *.tmp+1 + tax + lda *.tmp + jsr .ppu_stripe_begin_vertical + lda _attribute_shadow+8*i,y + jsr .ppu_stripe_write_byte + lda _attribute_shadow+8*i+32,y + jsr .ppu_stripe_write_byte + jsr .ppu_stripe_end + ; + lda *.tmp + clc + adc #8 + sta *.tmp + i = i + 1 + .endm + jmp _flush_shadow_attributes_columns_next_column diff --git a/gbdk-lib/libc/targets/mos6502/nes/set_bk_attributes.s b/gbdk-lib/libc/targets/mos6502/nes/set_bk_attributes.s @@ -1,32 +1,32 @@ .include "global.s" .area OSEG (PAG, OVR) - _set_bkg_attributes_PARM_3:: .ds 1 - _set_bkg_attributes_PARM_4:: .ds 1 - _set_bkg_attributes_PARM_5:: .ds 2 - .xpos: .ds 1 - .ypos: .ds 1 - .num_columns: .ds 1 - .num_rows: .ds 1 - .src: .ds 2 - .dst: .ds 2 - .attribute_x_odd: .ds 1 - .attribute_y_odd: .ds 1 - .attribute_num_columns_odd: .ds 1 - .attribute_num_rows_odd: .ds 1 - .row_dirty_mask: .ds 1 + _set_bkg_attributes_nes16x16_PARM_3:: .ds 1 + _set_bkg_attributes_nes16x16_PARM_4:: .ds 1 + _set_bkg_attributes_nes16x16_PARM_5:: .ds 2 + .xpos: .ds 1 + .ypos: .ds 1 + .num_columns: .ds 1 + .num_rows: .ds 1 + .src: .ds 2 + .dst: .ds 2 + .attribute_x_odd: .ds 1 + .attribute_y_odd: .ds 1 + .attribute_num_columns_odd: .ds 1 + .attribute_num_rows_odd: .ds 1 + .row_dirty_mask: .ds 1 .area _HOME -.define .width "_set_bkg_attributes_PARM_3" -.define .height "_set_bkg_attributes_PARM_4" -.define .tiles "_set_bkg_attributes_PARM_5" +.define .width "_set_bkg_attributes_nes16x16_PARM_3" +.define .height "_set_bkg_attributes_nes16x16_PARM_4" +.define .tiles "_set_bkg_attributes_nes16x16_PARM_5" ; ; Fast version writing directly to PPU memory. ; Does not handle unaligned x & y and assumes even number of columns / rows ; -_set_bkg_attributes_fast:: +_set_bkg_attributes_nes16x16_fast:: 1$: lda *.ypos asl @@ -62,7 +62,7 @@ _set_bkg_attributes_fast:: bne 1$ rts -_set_bkg_attributes:: +_set_bkg_attributes_nes16x16:: lsr ; Make xpos count 32x32 areas / full bytes ror *.attribute_x_odd ; ...and potentially mark x as odd-numbered sta *.xpos @@ -510,56 +510,6 @@ unaligned_xy_column_loop: 1$: jmp _flush_shadow_attributes -; -; Writes every row of attributes from _shadow_attributes that's been marked -; as dirty in the _attribute_row_dirty byte to PPU memory. -; -_flush_shadow_attributes: - lda #<PPU_AT0 - sta *.tmp - lda #>PPU_AT0 - sta *.tmp+1 - ldy #0 -_flush_shadow_attributes_row_loop: - lsr *_attribute_row_dirty - bcc 1$ - jmp _flush_shadow_attributes_update_row -1$: - beq _flush_shadow_attributes_end -_flush_shadow_attributes_next_row: - ; Y += 8 - tya - clc - adc #8 - tay - ; .tmp += 8 - lda *.tmp - adc #8 - sta *.tmp - jmp _flush_shadow_attributes_row_loop -_flush_shadow_attributes_end: - rts - -; -; Flushes all dirty rows of _attribute_shadow by writing them to PPU memory -; -_flush_shadow_attributes_update_row: - ; Update all 8 bytes of row for now, as each row in _attribute_row_dirty only stores 1 bit - ; TODO: Could store 8 bytes and update range, at expense of 7 more bytes. - lda *.tmp+1 - tax - lda *.tmp - jsr .ppu_stripe_begin_horizontal - ; Write 8 bytes - i = 0 - .rept 8 - lda _attribute_shadow+i,y - jsr .ppu_stripe_write_byte - i = i + 1 - .endm - jsr .ppu_stripe_end - jmp _flush_shadow_attributes_next_row - .attribute_set_dirty: ; A = min(7, .num_rows + .attribute_num_rows_odd) << 3 lda *.attribute_num_rows_odd diff --git a/gbdk-support/png2asset/png2asset.cpp b/gbdk-support/png2asset/png2asset.cpp @@ -481,6 +481,37 @@ unsigned int PaletteCountApplyMaxLimit(unsigned int max_palettes, unsigned int c return cur_palette_size; } +int FindOrCreateSubPalette(const SetPal& pal, vector< SetPal >& palettes) +{ + // Return -1 if colors can't even fit in sub-palette hardware limit + if (pal.size() > colors_per_pal) + { + return -1; + } + //Check if it matches any palettes or create a new one + int i; + for (i = 0; i < palettes.size(); ++i) + { + //Try to merge this palette with any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) + SetPal merged(palettes[i]); + merged.insert(pal.begin(), pal.end()); + if (merged.size() <= colors_per_pal) + { + if (palettes[i].size() <= colors_per_pal) + palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) + return i; //Found palette + } + } + + if (i == palettes.size()) + { + //Palette not found, add a new one + palettes.push_back(pal); + } + return i; +} + + // // Builds palettes and palette-per-tile (attributes) for an image // @@ -502,7 +533,9 @@ int* BuildPalettesAndAttributes(const PNGImage& image32, vector< SetPal >& palet { //Get palette colors on (x, y, tile_w, tile_h) SetPal pal = GetPaletteColors(image32, (x / sx) * sx, (y / sy) * sy, sx * tile_w, sy * tile_h); - if (pal.size() > colors_per_pal) + + int subPalIndex = FindOrCreateSubPalette(pal, palettes); + if (subPalIndex < 0) { printf("Error: more than %d colors found in tile at x:%d, y:%d of size w:%d, h:%d\n", (unsigned int)colors_per_pal, @@ -510,29 +543,7 @@ int* BuildPalettesAndAttributes(const PNGImage& image32, vector< SetPal >& palet (y / sy) * sy, sx * tile_w, sy * tile_h); - delete[] palettes_per_tile; - return nullptr; - } - - //Check if it matches any palettes or create a new one - size_t i; - for (i = 0; i < palettes.size(); ++i) - { - //Try to merge this palette with any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) - SetPal merged(palettes[i]); - merged.insert(pal.begin(), pal.end()); - if (merged.size() <= colors_per_pal) - { - if (palettes[i].size() <= colors_per_pal) - palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) - break; //Found palette - } - } - - if (i == palettes.size()) - { - //Palette not found, add a new one - palettes.push_back(pal); + subPalIndex = 0; // Force to sub-palette 0, to allow getting a partially-incorrect output image } // Assign single or multiple entries in palettes_per_tile, to keep it independent of // of half_resolution parameter @@ -543,7 +554,7 @@ int* BuildPalettesAndAttributes(const PNGImage& image32, vector< SetPal >& palet { for (int xx = 0; xx < sx; xx++) { - palettes_per_tile[(dy + yy) * w + dx + xx] = i; + palettes_per_tile[(dy + yy) * w + dx + xx] = subPalIndex; } } } @@ -580,14 +591,47 @@ void ReduceMapAttributes2x2(const vector< SetPal >& palettes) } // +// Aligns map attribute data to be aligned properly for NES and set_bkg_submap_attributes +// Namely: +// * Width aligned to multiples of 2 to reflect the NES's packed attribute table +// * Every 16th row is blank to reflect the unused row in the NES's packed attribute table +// +void AlignMapAttributes() +{ + const size_t ATTRIBUTE_HEIGHT = 15; + const size_t ATTRIBUTE_ALIGNED_HEIGHT = 16; + vector< unsigned char > map_attributes_aligned; + size_t map_attributes_aligned_width = 2 * ((map_attributes_width + 1) / 2); + size_t num_vertical_nametables = (map_attributes_height + ATTRIBUTE_HEIGHT - 1) / ATTRIBUTE_HEIGHT; + map_attributes_aligned.resize(map_attributes_aligned_width * (num_vertical_nametables * ATTRIBUTE_ALIGNED_HEIGHT)); + for (size_t i = 0; i < num_vertical_nametables; i++) + { + bool last_nametable = (i == num_vertical_nametables - 1); + size_t height = last_nametable ? (map_attributes_height - i * ATTRIBUTE_HEIGHT) : ATTRIBUTE_HEIGHT; + for (size_t y = 0; y < height; y++) + { + for (size_t x = 0; x < map_attributes_width; x++) + { + map_attributes_aligned[(i * ATTRIBUTE_ALIGNED_HEIGHT + y) * map_attributes_aligned_width + x] = + map_attributes[(i * ATTRIBUTE_HEIGHT + y) * map_attributes_width + x]; + } + } + } + // Overwrite old attributes + map_attributes_width = map_attributes_aligned_width; + map_attributes_height = num_vertical_nametables * ATTRIBUTE_ALIGNED_HEIGHT; + map_attributes = map_attributes_aligned; +} + +// // Pack map attributes -// (NES has packs multiple 2-bit entries into one byte) +// (NES packs multiple 2-bit entries into one byte) // void PackMapAttributes() { vector< unsigned char > map_attributes_packed; map_attributes_packed_width = (map_attributes_width + 1) / 2; - map_attributes_packed_height = (map_attributes_width + 1) / 2; + map_attributes_packed_height = (map_attributes_height + 1) / 2; map_attributes_packed.resize(map_attributes_packed_width * map_attributes_packed_height); for (size_t y = 0; y < map_attributes_packed_height; y++) { @@ -1125,9 +1169,10 @@ int main(int argc, char* argv[]) // NES attribute map dimensions are half-resolution ReduceMapAttributes2x2(palettes); } - // Optionally pack map attributes into NES PPU format + // Optionally align and pack map attributes into NES PPU format if (pack_map_attributes) { + AlignMapAttributes(); PackMapAttributes(); } else @@ -1213,8 +1258,9 @@ bool export_h_file(void) { if (use_map_attributes) { - fprintf(file, "#define %s_MAP_ATTRIBUTES_WIDTH %d\n", data_name.c_str(), (int)map_attributes_width); - fprintf(file, "#define %s_MAP_ATTRIBUTES_HEIGHT %d\n", data_name.c_str(), (int)map_attributes_height); + int scale = use_2x2_map_attributes ? 2 : 1; + fprintf(file, "#define %s_MAP_ATTRIBUTES_WIDTH %d\n", data_name.c_str(), (int)(scale * map_attributes_width)); + fprintf(file, "#define %s_MAP_ATTRIBUTES_HEIGHT %d\n", data_name.c_str(), (int)(scale * map_attributes_height)); fprintf(file, "#define %s_MAP_ATTRIBUTES_PACKED_WIDTH %d\n", data_name.c_str(), (int)map_attributes_packed_width); fprintf(file, "#define %s_MAP_ATTRIBUTES_PACKED_HEIGHT %d\n", data_name.c_str(), (int)map_attributes_packed_height); }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.