From 2a3813c5ebca31f851397aa5050a3e87f135d614 Mon Sep 17 00:00:00 2001 From: Sean Maas Date: Wed, 24 Feb 2021 15:40:42 -0500 Subject: [PATCH] Convert textures at compile time --- Makefile | 8 ++- Makefile.split | 6 +- src/nds/nds_renderer.c | 113 +++++---------------------------- tools/.gitignore | 1 + tools/n64graphics.c | 139 +++++++++++++++++++++++++++++++++++++---- tools/n64graphics.h | 11 ++++ tools/skyconv.c | 25 +++++++- 7 files changed, 185 insertions(+), 118 deletions(-) diff --git a/Makefile b/Makefile index bcab689b..84e2ba97 100644 --- a/Makefile +++ b/Makefile @@ -572,14 +572,18 @@ $(BUILD_DIR)/src/game/ingame_menu.o: $(BUILD_DIR)/include/text_strings.h #==============================================================================# TEXTURE_ENCODING := u8 +ifdef TARGET_NDS + TEXTURE_OPTIONS := -d +endif + # Convert PNGs to RGBA32, RGBA16, IA16, IA8, IA4, IA1, I8, I4 binary files $(BUILD_DIR)/%: %.png $(call print,Converting:,$<,$@) - $(V)$(N64GRAPHICS) -s raw -i $@ -g $< -f $(lastword $(subst ., ,$@)) + $(V)$(N64GRAPHICS) -s raw -i $@ -g $< -f $(lastword $(subst ., ,$@)) $(TEXTURE_OPTIONS) $(BUILD_DIR)/%.inc.c: %.png $(call print,Converting:,$<,$@) - $(V)$(N64GRAPHICS) -s $(TEXTURE_ENCODING) -i $@ -g $< -f $(lastword ,$(subst ., ,$(basename $<))) + $(V)$(N64GRAPHICS) -s $(TEXTURE_ENCODING) -i $@ -g $< -f $(lastword ,$(subst ., ,$(basename $<))) $(TEXTURE_OPTIONS) # Color Index CI8 $(BUILD_DIR)/%.ci8: %.ci8.png diff --git a/Makefile.split b/Makefile.split index 05036119..bb824aa5 100644 --- a/Makefile.split +++ b/Makefile.split @@ -180,10 +180,10 @@ $(eval $(call level_rules,menu,generic)) # Menu (File Select) # Ending cake textures are generated in a special way $(BUILD_DIR)/levels/ending/cake_eu.inc.c: levels/ending/cake_eu.png @$(PRINT) "$(GREEN)Splitting $(YELLOW)$< $(GREEN)to: $(BLUE)$@ $(NO_COL)\n" - @$(SKYCONV) --type cake-eu --split $^ $(BUILD_DIR)/levels/ending + @$(SKYCONV) --type cake-eu --split $^ $(BUILD_DIR)/levels/ending $(TEXTURE_OPTIONS) $(BUILD_DIR)/levels/ending/cake.inc.c: levels/ending/cake.png @$(PRINT) "$(GREEN)Splitting $(YELLOW)$< $(GREEN)to: $(BLUE)$@ $(NO_COL)\n" - @$(SKYCONV) --type cake --split $^ $(BUILD_DIR)/levels/ending + @$(SKYCONV) --type cake --split $^ $(BUILD_DIR)/levels/ending $(TEXTURE_OPTIONS) # -------------------------------------- # Texture Bin Rules @@ -251,7 +251,7 @@ $(BUILD_DIR)/bin/eu/translation_fr.elf: SEGMENT_ADDRESS := 0x19000000 $(BUILD_DIR)/bin/%_skybox.c: textures/skyboxes/%.png @$(PRINT) "$(GREEN)Splitting $(YELLOW)$< $(GREEN)to: $(BLUE)$@ $(NO_COL)\n" - @$(SKYCONV) --type sky --split $^ $(BUILD_DIR)/bin + @$(SKYCONV) --type sky --split $^ $(BUILD_DIR)/bin $(TEXTURE_OPTIONS) $(BUILD_DIR)/bin/%_skybox.elf: SEGMENT_ADDRESS := 0x0A000000 diff --git a/src/nds/nds_renderer.c b/src/nds/nds_renderer.c index cbfe025d..040b6693 100644 --- a/src/nds/nds_renderer.c +++ b/src/nds/nds_renderer.c @@ -17,8 +17,7 @@ struct Vertex { }; struct Texture { - uint8_t *original; - uint8_t *converted; + uint8_t *address; int name; uint8_t type; uint8_t size_x; @@ -82,14 +81,14 @@ static int frame_count; static void load_texture() { // Look up the current texture using a simple hash calculated from its address uint32_t index = ((uint32_t)texture_address >> 5) & 0x7FF; - while (texture_map[index].original != texture_address && texture_map[index].original != NULL) { + while (texture_map[index].address != texture_address && texture_map[index].address != NULL) { index = (index + 1) & 0x7FF; } struct Texture *cur = &texture_map[index]; // Load the texture if it was found - if (cur->original != NULL) { + if (cur->address != NULL) { if (cur->name) { glBindTexture(GL_TEXTURE_2D, cur->name); return; @@ -98,7 +97,7 @@ static void load_texture() { // Copy the texture back into VRAM if it was pushed out, pushing out other textures if necessary glGenTextures(1, &cur->name); glBindTexture(GL_TEXTURE_2D, cur->name); - while (!glTexImage2D(GL_TEXTURE_2D, 0, cur->type, cur->size_x, cur->size_y, 0, TEXGEN_TEXCOORD, cur->converted)) { + while (!glTexImage2D(GL_TEXTURE_2D, 0, cur->type, cur->size_x, cur->size_y, 0, TEXGEN_TEXCOORD, cur->address)) { glDeleteTextures(1, &texture_map[texture_fifo[texture_fifo_end]].name); texture_map[texture_fifo[texture_fifo_end]].name = 0; texture_fifo_end = (texture_fifo_end + 1) & 0x7FF; @@ -108,100 +107,12 @@ static void load_texture() { return; } - cur->original = texture_address; + cur->address = texture_address; - // The DS only supports texture sizes of 8 << x, but the N64 is less restricted - // If a size is unsupported, the next size up is used and the texture is repeated to fill extra space - - // Determine the width of the new texture - const int width = texture_row_size << (4 - texture_bit_width); - for (cur->size_x = 0; (width - 1) >> (cur->size_x + 3) != 0; cur->size_x++); - const int conv_width = 8 << cur->size_x; - - // Determine the height of the new texture - const int height = ((texture_size << 1) >> texture_bit_width) / width; - for (cur->size_y = 0; (height - 1) >> (cur->size_y + 3) != 0; cur->size_y++); - const int conv_height = 8 << cur->size_y; - - // Convert the texture to a format the DS understands + // Set the texture format; textures are converted to DS formats at compile time switch (texture_format) { - case G_IM_FMT_RGBA: - switch (texture_bit_width) { - case G_IM_SIZ_16b: - cur->converted = (uint8_t*)malloc(conv_width * conv_height * 2); - for (int y = 0; y < conv_height; y++) { - for (int x = 0; x < conv_width; x++) { - const int index = ((y % height) * width + (x % width)) * 2; - const uint16_t color = (texture_address[index] << 8) | texture_address[index + 1]; - const uint8_t r = ((color >> 11) & 0x1F); - const uint8_t g = ((color >> 6) & 0x1F); - const uint8_t b = ((color >> 1) & 0x1F); - const uint8_t a = ((color >> 0) & 0x01); - ((uint16_t*)cur->converted)[y * conv_width + x] = (a << 15) | (b << 10) | (g << 5) | r; - } - } - DC_FlushRange(cur->converted, conv_width * conv_height * 2); - cur->type = GL_RGBA; - break; - - default: - //printf("Unsupported RGBA texture bit width: %d\n", texture_bit_width); - glBindTexture(GL_TEXTURE_2D, cur->name = no_texture); - return; - } - break; - - case G_IM_FMT_IA: - switch (texture_bit_width) { - case G_IM_SIZ_4b: - cur->converted = (uint8_t*)malloc(conv_width * conv_height); - for (int y = 0; y < conv_height; y++) { - for (int x = 0; x < conv_width; x++) { - const int index = (y % height) * width + (x % width); - const uint8_t color = (texture_address[index / 2] >> ((index & 1) ? 0 : 4)) & 0x0F; - const uint8_t i = ((color >> 1) & 0x07); - const uint8_t a = ((color >> 0) & 0x01) ? 31 : 0; - cur->converted[y * conv_width + x] = (a << 3) | i; - } - } - DC_FlushRange(cur->converted, conv_width * conv_height); - cur->type = GL_RGB8_A5; - break; - - case G_IM_SIZ_8b: - cur->converted = (uint8_t*)malloc(conv_width * conv_height); - for (int y = 0; y < conv_height; y++) { - for (int x = 0; x < conv_width; x++) { - const uint8_t color = texture_address[(y % height) * width + (x % width)]; - const uint8_t i = ((color >> 4) & 0x0F) * 7 / 15; - const uint8_t a = ((color >> 0) & 0x0F) * 31 / 15; - cur->converted[y * conv_width + x] = (a << 3) | i; - } - } - DC_FlushRange(cur->converted, conv_width * conv_height); - cur->type = GL_RGB8_A5; - break; - - case G_IM_SIZ_16b: - cur->converted = (uint8_t*)malloc(conv_width * conv_height); - for (int y = 0; y < conv_height; y++) { - for (int x = 0; x < conv_width; x++) { - const int index = ((y % height) * width + (x % width)) * 2; - const uint8_t i = texture_address[index + 0] * 7 / 255; - const uint8_t a = texture_address[index + 1] * 31 / 255; - cur->converted[y * conv_width + x] = (a << 3) | i; - } - } - DC_FlushRange(cur->converted, conv_width * conv_height); - cur->type = GL_RGB8_A5; - break; - - default: - //printf("Unsupported IA texture bit width: %d\n", texture_bit_width); - glBindTexture(GL_TEXTURE_2D, cur->name = no_texture); - return; - } - break; + case G_IM_FMT_RGBA: cur->type = GL_RGBA; break; + case G_IM_FMT_IA: cur->type = GL_RGB8_A5; break; default: //printf("Unsupported texture format: %d\n", texture_format); @@ -209,10 +120,16 @@ static void load_texture() { return; } + // Determine the texture size in terms of 8 << x; textures are fitted to these constraints at compile time + const int width = texture_row_size << (4 - texture_bit_width); + const int height = ((texture_size << 1) >> texture_bit_width) / width; + for (cur->size_x = 0; (width - 1) >> (cur->size_x + 3) != 0; cur->size_x++); + for (cur->size_y = 0; (height - 1) >> (cur->size_y + 3) != 0; cur->size_y++); + // Copy the texture into VRAM, pushing out other textures if necessary glGenTextures(1, &cur->name); glBindTexture(GL_TEXTURE_2D, cur->name); - while (!glTexImage2D(GL_TEXTURE_2D, 0, cur->type, cur->size_x, cur->size_y, 0, TEXGEN_TEXCOORD, cur->converted)) { + while (!glTexImage2D(GL_TEXTURE_2D, 0, cur->type, cur->size_x, cur->size_y, 0, TEXGEN_TEXCOORD, cur->address)) { glDeleteTextures(1, &texture_map[texture_fifo[texture_fifo_end]].name); texture_map[texture_fifo[texture_fifo_end]].name = 0; texture_fifo_end = (texture_fifo_end + 1) & 0x7FF; diff --git a/tools/.gitignore b/tools/.gitignore index 469b3062..d9eb16e4 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1,3 +1,4 @@ +/adpcm_xq /aifc_decode /aiff_extract_codebook /armips diff --git a/tools/n64graphics.c b/tools/n64graphics.c index f1783137..99ecfb34 100644 --- a/tools/n64graphics.c +++ b/tools/n64graphics.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -320,6 +321,78 @@ int i2raw(uint8_t *raw, const ia *img, int width, int height, int depth) } +//--------------------------------------------------------- +// internal RGBA/IA -> NDS RGBA/IA +// returns length written to 'raw' used or -1 on error +//--------------------------------------------------------- + +int rgba2nds(uint8_t *raw, const rgba *img, int width, int height, int depth, int nds_width, int nds_height) +{ + int size = (nds_width * nds_height * 16 + 7) / 8; + INFO("Converting RGBA%d %dx%d to NDS raw\n", depth, width, height); + + switch (depth) { + case 16: case 32: + for (int y = 0; y < nds_height; y++) { + for (int x = 0; x < nds_width; x++) { + int i = (y % height) * width + (x % width); + int j = y * nds_width + x; + uint8_t r = SCALE_8_5(img[i].red); + uint8_t g = SCALE_8_5(img[i].green); + uint8_t b = SCALE_8_5(img[i].blue); + uint8_t a = img[i].alpha ? 0x1 : 0x0; + raw[j*2] = ((g & 0x7) << 5) | r; + raw[j*2+1] = (a << 7) | (b << 2) | (g >> 3); + } + } + break; + default: + ERROR("Error invalid depth %d\n", depth); + size = -1; + break; + } + + return size; +} + +int ia2nds(uint8_t *raw, const ia *img, int width, int height, int depth, int nds_width, int nds_height) +{ + int size = (nds_width * nds_height * 8 + 7) / 8; + INFO("Converting IA%d %dx%d to NDS raw\n", depth, width, height); + + switch (depth) { + case 1: + for (int y = 0; y < nds_height; y++) { + for (int x = 0; x < nds_width; x++) { + int i = (y % height) * width + (x % width); + int j = y * nds_width + x; + uint8_t val = img[i].intensity ? 0x7 : 0x0; + uint8_t alpha = img[i].intensity ? 0x1F : 0x00; + raw[j] = (alpha << 3) | val; + } + } + break; + case 4: case 8: case 16: + for (int y = 0; y < nds_height; y++) { + for (int x = 0; x < nds_width; x++) { + int i = (y % height) * width + (x % width); + int j = y * nds_width + x; + uint8_t val = SCALE_8_3(img[i].intensity); + uint8_t alpha = SCALE_8_5(img[i].alpha); + raw[j] = (alpha << 3) | val; + } + } + break; + default: + ERROR("Error invalid depth %d\n", depth); + size = -1; + break; + } + + return size; +} + + //--------------------------------------------------------- // internal RGBA/IA -> PNG //--------------------------------------------------------- @@ -602,6 +675,7 @@ typedef struct int height; int bin_truncate; int pal_truncate; + bool nds_format; } graphics_config; static const graphics_config default_config = @@ -619,6 +693,7 @@ static const graphics_config default_config = .height = 32, .bin_truncate = 1, .pal_truncate = 1, + .nds_format = false, }; typedef struct @@ -701,7 +776,7 @@ static int parse_encoding(write_encoding *encoding, const char *str) static void print_usage(void) { - ERROR("Usage: n64graphics -e/-i BIN_FILE -g IMG_FILE [-p PAL_FILE] [-o BIN_OFFSET] [-P PAL_OFFSET] [-f FORMAT] [-c CI_FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n" + ERROR("Usage: n64graphics -e/-i BIN_FILE -g IMG_FILE [-p PAL_FILE] [-o BIN_OFFSET] [-P PAL_OFFSET] [-f FORMAT] [-c CI_FORMAT] [-w WIDTH] [-h HEIGHT] [-V] [-d]\n" "\n" "n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n" "\n" @@ -721,7 +796,8 @@ static void print_usage(void) " -P PAL_OFFSET starting offset in PAL_FILE (prevents truncation during import)\n" "Other arguments:\n" " -v verbose logging\n" - " -V print version information\n", + " -V print version information\n" + " -d export binary files in NDS format\n", format2str(&default_config.format), encoding2str(default_config.encoding), default_config.width, @@ -749,6 +825,9 @@ static int parse_arguments(int argc, char *argv[], graphics_config *config) return 0; } break; + case 'd': + config->nds_format = true; + break; case 'e': if (++i >= argc) return 0; config->bin_filename = argv[i]; @@ -868,23 +947,56 @@ int main(int argc, char *argv[]) switch (config.format.format) { case IMG_FORMAT_RGBA: imgr = png2rgba(config.img_filename, &config.width, &config.height); - raw_size = (config.width * config.height * config.format.depth + 7) / 8; - raw = malloc(raw_size); - if (!raw) { - ERROR("Error allocating %u bytes\n", raw_size); + if (config.nds_format) { + // The DS only supports texture sizes of 8 << x; use the smallest size big enough to hold the texture + int size_x, size_y; + for (size_x = 0; (config.width - 1) >> (size_x + 3) != 0; size_x++); + for (size_y = 0; (config.height - 1) >> (size_y + 3) != 0; size_y++); + const int nds_width = 8 << size_x; + const int nds_height = 8 << size_y; + raw_size = (nds_width * nds_height * 16 + 7) / 8; + raw = malloc(raw_size); + if (!raw) { + ERROR("Error allocating %u bytes\n", raw_size); + } + length = rgba2nds(raw, imgr, config.width, config.height, config.format.depth, nds_width, nds_height); + } else { + raw_size = (config.width * config.height * config.format.depth + 7) / 8; + raw = malloc(raw_size); + if (!raw) { + ERROR("Error allocating %u bytes\n", raw_size); + } + length = rgba2raw(raw, imgr, config.width, config.height, config.format.depth); } - length = rgba2raw(raw, imgr, config.width, config.height, config.format.depth); break; case IMG_FORMAT_IA: imgi = png2ia(config.img_filename, &config.width, &config.height); - raw_size = (config.width * config.height * config.format.depth + 7) / 8; - raw = malloc(raw_size); - if (!raw) { - ERROR("Error allocating %u bytes\n", raw_size); + if (config.nds_format) { + // The DS only supports texture sizes of 8 << x; use the smallest size big enough to hold the texture + int size_x, size_y; + for (size_x = 0; (config.width - 1) >> (size_x + 3) != 0; size_x++); + for (size_y = 0; (config.height - 1) >> (size_y + 3) != 0; size_y++); + const int nds_width = 8 << size_x; + const int nds_height = 8 << size_y; + raw_size = (nds_width * nds_height * 8 + 7) / 8; + raw = malloc(raw_size); + if (!raw) { + ERROR("Error allocating %u bytes\n", raw_size); + } + length = ia2nds(raw, imgi, config.width, config.height, config.format.depth, nds_width, nds_height); + } else { + raw_size = (config.width * config.height * config.format.depth + 7) / 8; + raw = malloc(raw_size); + if (!raw) { + ERROR("Error allocating %u bytes\n", raw_size); + } + length = ia2raw(raw, imgi, config.width, config.height, config.format.depth); } - length = ia2raw(raw, imgi, config.width, config.height, config.format.depth); break; case IMG_FORMAT_I: + if (config.nds_format) { + ERROR("I texture conversion to NDS format is unimplemented\n"); + } imgi = png2ia(config.img_filename, &config.width, &config.height); raw_size = (config.width * config.height * config.format.depth + 7) / 8; raw = malloc(raw_size); @@ -905,6 +1017,9 @@ int main(int argc, char *argv[]) int pal_success; int pal_length; + if (config.nds_format) { + ERROR("CI texture conversion to NDS format is unimplemented\n"); + } if (config.pal_truncate) { pal_fp = fopen(config.pal_filename, "wb"); } else { diff --git a/tools/n64graphics.h b/tools/n64graphics.h index 9ced50e7..40938db6 100644 --- a/tools/n64graphics.h +++ b/tools/n64graphics.h @@ -53,6 +53,17 @@ int ia2raw(uint8_t *raw, const ia *img, int width, int height, int depth); // intermediate IA -> N64 raw I4/I8 int i2raw(uint8_t *raw, const ia *img, int width, int height, int depth); +//--------------------------------------------------------- +// intermediate RGBA/IA -> NDS RGBA/IA +// returns length written to 'raw' used or -1 on error +//--------------------------------------------------------- + +// intermediate RGBA -> NDS raw RGBA16/RGBA32 +int rgba2nds(uint8_t *raw, const rgba *img, int width, int height, int depth, int nds_width, int nds_height); + +// intermediate IA -> NDS raw IA1/IA4/IA8/IA16 +int ia2nds(uint8_t *raw, const ia *img, int width, int height, int depth, int nds_width, int nds_height); + //--------------------------------------------------------- // N64 CI <-> N64 RGBA16/IA16 diff --git a/tools/skyconv.c b/tools/skyconv.c index 79d6f041..acf847ee 100644 --- a/tools/skyconv.c +++ b/tools/skyconv.c @@ -77,6 +77,7 @@ char *writeDir; char skyboxName[256]; bool expanded = false; bool writeTiles; +bool ndsFormat; static void allocate_tiles() { const ImageProps props = IMAGE_PROPERTIES[type][true]; @@ -267,8 +268,21 @@ static unsigned int get_index(TextureTile *t, unsigned int i) { static void print_raw_data(FILE *cFile, TextureTile *tile) { ImageProps props = IMAGE_PROPERTIES[type][true]; - uint8_t *raw = malloc(props.tileWidth * props.tileHeight * 2); - int size = rgba2raw(raw, tile->px, props.tileWidth, props.tileHeight, 16); + uint8_t *raw; + int size; + if (ndsFormat) { + // The DS only supports texture sizes of 8 << x; use the smallest size big enough to hold the texture + int size_x, size_y; + for (size_x = 0; (props.tileWidth - 1) >> (size_x + 3) != 0; size_x++); + for (size_y = 0; (props.tileHeight - 1) >> (size_y + 3) != 0; size_y++); + const int nds_width = 8 << size_x; + const int nds_height = 8 << size_y; + raw = malloc(nds_width * nds_height * 2); + size = rgba2nds(raw, tile->px, props.tileWidth, props.tileHeight, 16, nds_width, nds_height); + } else { + raw = malloc(props.tileWidth * props.tileHeight * 2); + size = rgba2raw(raw, tile->px, props.tileWidth, props.tileHeight, 16); + } fprint_write_output(cFile, SKYCONV_ENCODING, raw, size); free(raw); } @@ -479,7 +493,8 @@ static void usage() { "Usage: %s --type sky|cake|cake_eu {--combine INPUT OUTPUT | --split INPUT OUTPUT}\n" "\n" "Optional arguments:\n" - " --write-tiles OUTDIR Also create the individual tiles' PNG files\n", programName); + " --write-tiles OUTDIR Also create the individual tiles' PNG files\n" + " -d export binary files in NDS format\n", programName); } // Modified from n64split @@ -536,6 +551,10 @@ static int parse_arguments(int argc, char *argv[]) { writeTiles = true; writeDir = argv[i]; } + + if (strcmp(argv[i], "-d") == 0) { + ndsFormat = true; + } } return 1;