Convert textures at compile time

This commit is contained in:
Sean Maas 2021-02-24 15:40:42 -05:00
parent 8b4e478297
commit 2a3813c5eb
7 changed files with 185 additions and 118 deletions

View file

@ -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

View file

@ -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

View file

@ -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;

1
tools/.gitignore vendored
View file

@ -1,3 +1,4 @@
/adpcm_xq
/aifc_decode
/aiff_extract_codebook
/armips

View file

@ -1,3 +1,4 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
@ -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 {

View file

@ -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

View file

@ -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;