diff --git a/Makefile b/Makefile index 2572283..9794455 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ DEP = $(patsubst %.o,%.d,$(SRC)) res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1)))) res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png))) -GFX_TILESETS = $(call res_png,4bpp,pokered/gfx/tilesets/) +GFX_TILESETS = $(call res_png,2bpp,pokered/gfx/tilesets/) GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/) MAPS_BLOCKS = $(call res,blk,pokered/maps/) @@ -28,14 +28,14 @@ endef generated: $(GENERATED) -res/%.4bpp: pokered/%.png +res/%.2bpp: pokered/%.png @mkdir -p $(dir $@) - python tools/png_to_4bpp.py $< $@ + python tools/png_to_nbpp.py $< 2 $@ -%.4bpp.h: +%.2bpp.h: $(BUILD_BINARY_H) -%.4bpp.o: %.4bpp %.4bpp.h +%.2bpp.o: %.2bpp %.2bpp.h $(BUILD_BINARY_O) res/%.blk: pokered/%.blk diff --git a/main.cpp b/main.cpp index 79a7ef1..586918d 100644 --- a/main.cpp +++ b/main.cpp @@ -23,12 +23,28 @@ void palette_data() uint32_t cell_data(const start_size_t& buf, const uint32_t top) { // round to nearest multiple of 32 - const uint32_t table_size = (buf.size + 0x20 - 1) & (-0x20); + const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20); const uint32_t base_address = top - table_size; // in bytes - copy(&vdp2.vram.u32[(base_address / 4)], - reinterpret_cast(buf.start), - buf.size); + uint32_t * vram = &vdp2.vram.u32[(base_address / 4)]; + for (uint32_t ix = 0; ix < buf.size / 4; ix += 1) { + const uint32_t pixels = reinterpret_cast(buf.start)[ix]; + const uint32_t px0 = pixels >> 16 & 0xffff; + const uint32_t px1 = pixels >> 0 & 0xffff; + +#define lshift(n) ((7 - n) * 2) +#define rshift(n) ((7 - n) * 4) +#define px(p, n) (((p >> lshift(n)) & 0b11) << rshift(n)) +#define p0(n) (px(px0, n)) +#define p1(n) (px(px1, n)) + vram[ix * 2 + 0] = p0(7) | p0(6) | p0(5) | p0(4) | p0(3) | p0(2) | p0(1) | p0(0); + vram[ix * 2 + 1] = p1(7) | p1(6) | p1(5) | p1(4) | p1(3) | p1(2) | p1(1) | p1(0); +#undef p1 +#undef p0 +#undef px +#undef lshift +#undef rshift + } return base_address; } @@ -115,7 +131,7 @@ void main() vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0 vdp2.reg.MPCDN0 = MPABN0__N0MPD(0) | MPABN0__N0MPC(0); // bits 5~0 - uint32_t top = (sizeof (union vdp2_vram));// - ((sizeof (union vdp2_vram)) / 8); + uint32_t top = (sizeof (union vdp2_vram)); palette_data(); uint32_t base_address = top = cell_data(tilesets[tileset_t::overworld].tileset, top); uint32_t base_pattern = base_address / 32; diff --git a/tools/generate/__main__.py b/tools/generate/__main__.py index 1f836af..b5c67a0 100644 --- a/tools/generate/__main__.py +++ b/tools/generate/__main__.py @@ -1,22 +1,178 @@ +from pathlib import Path from pprint import pprint +import io from parse import parse +def sorted_map_headers(): + # hack to remove unused/duplicate underground_path_route_7 + map_headers = sorted(parse.map_headers, key=lambda m: m.name2) + return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers) + +def includes(): + for map_header in sorted_map_headers(): + block_path = parse.maps_blocks_list[map_header.blocks()] + yield f'#include "res/{block_path}.h"' + for tileset_name in sorted(parse.tileset_constants_list): + tileset_index = parse.tileset_constants_list[tileset_name] + tileset_header = parse.tileset_headers_list[tileset_index] + + blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()] + gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()] + + yield f'#include "res/{blockset_path}.h"' + yield f'#include "res/{gfx_path}.h"' + yield "" + +def struct_start_size_t(): + return [ + "struct start_size_t {", + "uint8_t const * const start;", + "uint32_t size;", + "};", + ] + +def struct_tileset_t(): + tileset_names = ( + f"{name.lower()}," + for name in sorted(parse.tileset_constants_list) + ) + return [ + "struct tileset_t {", + "start_size_t blockset;", + "start_size_t tileset;", + "", + "enum tileset {", + *tileset_names, + "};", + "};", + ] + +def struct_map_t(): + map_names = ( + f"{map_header.name2.lower()}," + for map_header in sorted_map_headers() + ) + return [ + "struct map_t {", + "start_size_t blocks;", + "enum tileset_t::tileset tileset;", + "uint32_t width;", + "uint32_t height;", + "", + "enum map {", + *map_names, + "};", + "};", + ] + +def binary_res(path, suffix): + # _binary_res_gfx_blocksets_overworld_bst_start + name = path.replace('/', '_').replace('.', '_') + return f"_binary_res_{name}_{suffix}" + +def start_size_value(path): + start = binary_res(path, "start") + size = binary_res(path, "size") + + return [ + f"reinterpret_cast(&{start}),", + f"reinterpret_cast(&{size}),", + ] + +def blockset_tileset(name: str): + tileset_index = parse.tileset_constants_list[name] + tileset_header = parse.tileset_headers_list[tileset_index] + + blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()] + gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()] + + return [ + f"[tileset_t::{name.lower()}] = {{", + ".blockset = {", + *start_size_value(blockset_path), + "},", + ".tileset = {", + *start_size_value(gfx_path), + "}", + "}," + ] + +def tilesets(): + yield "const tileset_t tilesets[] = {" + for tileset in sorted(parse.tileset_constants_list): + yield from blockset_tileset(tileset) + yield "};" + +def map(map_header): + block_path = parse.maps_blocks_list[map_header.blocks()] + map_constant = parse.map_constants_list[map_header.name2] + return [ + f"[map_t::{map_header.name2.lower()}] = {{", + ".blocks = {", + *start_size_value(block_path), + "},", + f".tileset = tileset_t::{map_header.tileset.lower()},", + f".width = {map_constant.width},", + f".height = {map_constant.height},", + "},", + ] + +def maps(): + yield "const map_t maps[] = {" + for map_header in sorted_map_headers(): + yield from map(map_header) + yield "};" + +def _render(out, lines): + indent = " " + level = 0 + for l in lines: + if l and l[0] == "}": + level -= 2 + assert level >= 0, out.getvalue() + + out.write(indent * level + l + "\n") + + if l and l[-1] == "{": + level += 2 + + if level == 0 and l and l[-1] == ";": + out.write("\n") + return out + +def renderer(): + out = io.StringIO() + def render(lines): + return _render(out, lines) + return render, out + def generate(): - pprint(parse.maps_blocks_list) + #pprint(parse.maps_blocks_list) #pprint(parse.map_headers) #pprint(parse.tileset_constants_list) #pprint(parse.tileset_headers_list) #pprint(parse.gfx_tilesets_list) + render, out = renderer() + + render(includes()) + render(struct_start_size_t()) + render(struct_tileset_t()) + render(struct_map_t()) + render(tilesets()) + render(maps()) + + print(out.getvalue(), end='') generate() """ map_headers[0].tileset == 'OVERWORLD' -map_headers[0].width() == 'PALLET_TOWN_WIDTH' -map_headers[0].height() == 'PALLET_TOWN_HEIGHT' +map_headers[0].name1 = 'PalletTown' +map_headers[0].name2 = 'PALLET_TOWN' map_headers[0].blocks() == 'PalletTown_Blocks' -maps_blocks['PalletTown_Blocks'] == 'maps/PalletTown.blk' +map_constants_list['PALLET_TOWN'] == MapConstant(10, 19) +maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk' tileset_constants_list['OVERWORLD'] == 0 tileset_headers_list[0].name == 'Overworld' tileset_headers_list[0].blockset() == 'Overworld_Block' diff --git a/tools/parse/map_constants.py b/tools/parse/map_constants.py index f6ab57a..57dab0d 100644 --- a/tools/parse/map_constants.py +++ b/tools/parse/map_constants.py @@ -12,7 +12,6 @@ def tokenize_lines(lines): @dataclass class MapConstant: - name: str width: int height: int @@ -20,8 +19,7 @@ def flatten(tokens): for macro, args in tokens: if macro == 'map_const': name, width, height = args - yield MapConstant( - name, + yield name, MapConstant( int(width), int(height) ) @@ -30,4 +28,7 @@ def parse(prefix): path = prefix / "constants/map_constants.asm" with open(path) as f: tokens = tokenize_lines(f.read().split("\n")) - return list(flatten(tokens)) + l = list(flatten(tokens)) + d = dict(l) + assert len(l) == len(d) + return d diff --git a/tools/png_to_4bpp.py b/tools/png_to_4bpp.py deleted file mode 100644 index 281948a..0000000 --- a/tools/png_to_4bpp.py +++ /dev/null @@ -1,39 +0,0 @@ -import sys - -from PIL import Image - -def two_bpp_index(px): - indices = {0x00: 0, 0x55: 1, 0xaa: 2, 0xff: 3} - assert px in indices, px - return indices[px] - -def convert(image): - assert image.mode == 'L', image.mode - width, height = image.size - - buf = bytearray(width * height // 2) - - for cell_y in range(height//8): - for cell_x in range(width//8): - for y in range(8): - for x in range(8): - px = im.getpixel((cell_x * 8 + x, cell_y * 8 + y)) - index = two_bpp_index(px) - buf_ix = x//2 + (4 * (cell_x * 8 + (cell_y * width) + y)) - buf[buf_ix] |= (index << 4 * (1 - (x % 2))) - return buf - -def debug(buf): - for row in range(len(buf) // 4): - for x in range(4): - px = buf[row * 4 + x] - print((px >> 4) & 0xf, end='') - print((px >> 0) & 0xf, end='') - print() - if (row % 8 == 7): - print() - -im = Image.open(sys.argv[1]) -buf = convert(im) -with open(sys.argv[2], 'wb') as f: - f.write(buf) diff --git a/tools/png_to_nbpp.py b/tools/png_to_nbpp.py new file mode 100644 index 0000000..a30c81c --- /dev/null +++ b/tools/png_to_nbpp.py @@ -0,0 +1,59 @@ +import os +import sys + +from PIL import Image + +def intensity_to_index(px): + indices = {0x00: 0, 0x55: 1, 0xaa: 2, 0xff: 3} + assert px in indices, px + return indices[px] + +def convert(image, bpp): + assert bpp in {8, 4, 2}, bpp + px_per_byte = 8 // bpp + px_per_row = 8 + bits_per_byte = 8 + bytes_per_row = (px_per_row // (bits_per_byte // bpp)) + + assert image.mode == 'L', image.mode + width, height = image.size + + buf = bytearray(width * height // px_per_byte) + + for cell_y in range(height//8): + for cell_x in range(width//8): + for y in range(8): + for x in range(8): + px = im.getpixel((cell_x * 8 + x, cell_y * 8 + y)) + index = intensity_to_index(px) + buf_ix = x//px_per_byte + (bytes_per_row * (cell_x * 8 + (cell_y * width) + y)) + buf[buf_ix] |= (index << bpp * ((px_per_byte - 1) - (x % px_per_byte))) + return buf + +def debug(buf, bpp): + assert bpp in {8, 4, 2}, bpp + px_per_byte = 8 // bpp + px_per_row = 8 + bits_per_byte = 8 + bytes_per_row = (px_per_row // (bits_per_byte // bpp)) + bit_mask = (2 ** bpp) - 1 + + for row in range(len(buf) // bytes_per_row): + for x in range(bytes_per_row): + px = buf[row * bytes_per_row + x] + for shift in reversed(range(0, px_per_row, bpp)): + print((px >> shift) & bit_mask, end='') + print() + if (row % 8 == 7): + print() + +in_path = sys.argv[1] +bpp = int(sys.argv[2]) +out_path = sys.argv[3] + +im = Image.open(in_path) +buf = convert(im, bpp) +if 'NBPP_DEBUG' in os.environ: + debug(buf, bpp) +with open(out_path, 'wb') as f: + f.write(buf)