asset pipeline?

This commit is contained in:
zlago 2025-11-11 23:15:54 +01:00
parent 7ca0d351f7
commit 5e3b3c2bf9
8 changed files with 584 additions and 3 deletions

View File

@ -26,9 +26,12 @@ deps := $(wildcard out/*.d)
.PHONY: all res bin run clean .PHONY: all res bin run clean
all: res bin all: res bin
bin: ${ns}/${NAME}.${EXTENSION} bin: ${ns}/${NAME}.${EXTENSION}
run: ${ns}/${NAME}.${EXTENSION} res: out/${NAME}.assets
$<
run: ${ns}/${NAME}.${EXTENSION} out/${NAME}.assets
$^
clean: clean:
${RM} -r ${ns}/ ${RM} -r ${ns}/
@ -45,7 +48,7 @@ ${ns}/%.o: src/%.c++ | ${ns}/
%/: %/:
@${MKDIR} $@ @${MKDIR} $@
#include assets.mk include assets.mk
ifeq ($(filter clean mostlyclean,${MAKECMDGOALS}),) ifeq ($(filter clean mostlyclean,${MAKECMDGOALS}),)
include ${deps} include ${deps}

35
assets.mk Normal file
View File

@ -0,0 +1,35 @@
PRITE ?= libresprite
glsl := $(wildcard src/*.glsl)
frag := $(wildcard src/*.frag)
#shaders := ${glsl:src/%.glsl=out/%.shader} ${frag:src/%.frag=out/%.shader}
shaders := $(patsubst src/%,out/%.shader,${glsl:.glsl=} ${frag:.frag=})
pictures := $(patsubst src/%.ase,out/%.picture, $(wildcard src/*.ase))
out/suwi.assets: ${shaders} ${pictures} | out/assetsmk
printf '%s\0' $^ | sed -nz 'p;s:.*/::p' | $| -c0f out/suwi.assets -F - -N -
out/%.shader: src/%.glsl utl/vert.awk utl/frag.awk
awk -f utl/vert.awk $< > $@
printf '\0' >> $@
awk -f utl/frag.awk $< >> $@
printf '\0' >> $@
out/%.shader: src/%.frag utl/vert.awk utl/frag.awk src/default.glsl
awk -f utl/vert.awk src/default.glsl > $@
printf '\0' >> $@
cat $< >> $@
printf '\0' >> $@
out/%.png: src/%.ase
${PRITE} -b $< --save-as $@
out/%.picture: out/picmake out/%.png
$^ -o $@
out/picmake: utl/picmake.c
${CC} -o $@ $< -lpng
out/assetsmk: utl/assetsmk.c src/assread.c src/asswrite.c
${CC} -o $@ $^ -lz -I src/

View File

@ -200,6 +200,9 @@ int assets_load(FILE *file, char const *name) {
struct pack_result res = guilotine(pictures.rect.data(), pictures.rect.size(), 512, 1); struct pack_result res = guilotine(pictures.rect.data(), pictures.rect.size(), 512, 1);
if (res.bins == 0) { if (res.bins == 0) {
if (pictures.rect.size() == 0) {
fprintf(stderr, "error: %s: no textures?? lmao\n", name);
}
return 1; return 1;
} }

BIN
src/suwi.ase Normal file

Binary file not shown.

368
utl/assetsmk.c Normal file
View File

@ -0,0 +1,368 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "ass.h"
#if defined(__unix__)
#include <unistd.h>
#if defined(_POSIX_VERSION)
#include <fcntl.h>
#endif
#endif
struct blob {
void *data;
size_t size;
};
static void blob_free(struct blob *const blob);
// on error blob.data == NULL
static struct blob load_file(FILE *file);
struct ctx {
char **args;
FILE *assets;
FILE *names;
};
static char *read_one_line(FILE *file);
int verbose = 0, zero = 0;
void create_cb(char *restrict *filename, void *restrict *filedata, size_t *restrict filesize, void *restrict ctx_) {
struct ctx *ctx = ctx_;
free(*filename);
free(*filedata);
if (filesize == NULL) {
return;
}
if (*ctx->args == NULL && ctx->assets == NULL) {
*filename = NULL;
*filedata = NULL;
return;
}
if (*ctx->args != NULL) {
*filename = strdup(*ctx->args++);
FILE *file = fopen(*filename, "rb");
if (file == NULL) {
perror(*filename);
return;
}
struct blob data = load_file(file);
if (data.data == NULL) {
perror(*filename);
return;
}
*filedata = data.data;
*filesize = data.size;
} else {
*filename = read_one_line(ctx->assets);
if (*filename == NULL) {
*filedata = NULL;
if (ctx->names != NULL) {
char *n = read_one_line(ctx->names);
if (n != NULL) {
fputs("more names than paths\n", stderr);
free(n);
}
}
return;
}
FILE *file = fopen(*filename, "rb");
if (file == NULL) {
perror(*filename);
return;
}
struct blob data = load_file(file);
if (data.data == NULL) {
perror(*filename);
return;
}
if (ctx->names != NULL) {
char *name = read_one_line(ctx->names);
if (name == NULL) {
fputs("more paths than names\n", stderr);
ctx->names = NULL;
} else {
free(*filename);
*filename = name;
}
}
*filedata = data.data;
*filesize = data.size;
}
}
int extract_cb(char const *restrict filename, size_t length, void const *restrict data, size_t size, void *restrict ctx_) {
return 1; // todo
struct ctx *ctx = ctx_;
#if defined (_POSIX_VERSION)
open(*ctx->args++, O_WRONLY | O_EXCL | O_CREAT, 0666);
#endif
return 0;
}
int list_cb(char const *restrict filename, size_t length, void const *restrict data, size_t size, void *restrict ctx_) {
struct ctx *ctx = ctx_;
if (*ctx->args != NULL || ctx->assets || ctx->names) {
fputs("--list does not take arguments\n", stderr);
return 1;
}
if (zero && verbose) {
fputs("--list doesnt support --verbose and --nul at the same time\n", stderr);
return 1;
} else if (zero) {
fwrite(filename, 1, length + 1, stdout);
} else if (verbose) {
fprintf(stdout, "%zu\t%s\n", size, filename);
} else {
fprintf(stdout, "%s\n", filename);
}
return 0;
}
int main(int argc, char **argv) {
struct option const options[] = {
{"help", 0, NULL, 'h'},
{"verbose", 0, NULL, 'v'},
{"nul", 0, NULL, '0'},
{"create", 0, NULL, 'c'},
{"extract", 0, NULL, 'x'},
{"list", 0, NULL, 'l'},
{"file", 1, NULL, 'f'},
{"files", 1, NULL, 'F'},
{"names", 1, NULL, 'N'},
{NULL, 0, NULL, 0 },
};
int opt, fail = 0;
enum {
A_NONE,
A_CREATE,
A_EXTRACT,
A_LIST,
} action = A_NONE;
char *filename = NULL;
FILE *assets = NULL, *names = NULL;
while ((opt = getopt_long(argc, argv, "hv0cxlf:F:N:", options, NULL)) != -1) {
switch (opt) {
case 'h':
fprintf(stderr, "usage: %s [-h | -c | -l | -x] [-0v] [-f ASSET_FILE] [-F PATHS] [-N NAMES]\n", argv[0]);
fputs("\n", stderr);
fputs("-h --help this message\n", stderr);
fputs("-c --create create an asset pack\n", stderr);
fputs("-l --list list files in an asset pack\n", stderr);
fputs("-x --extract extract files from an asset pack\n", stderr);
fputs("\n", stderr);
fputs("-0 --nul entries are NUL-separated (for use with -l -F -N)\n", stderr);
fputs("-v --verbose\n", stderr);
fputs("\t(-c) print names of files put in the pack\n", stderr);
fputs("\t(-l) print sizes of files in the pack\n", stderr);
fputs("\t(-x) print files extracted from the pack\n", stderr);
fputs("\n", stderr);
fputs("-f --file=FILE file to use as the asset pack\n", stderr);
fputs("-F --files=FILE file containing the list of files to put in/extract from the asset pack\n", stderr);
fputs("-N --names=FILE file containing the list of names to use for the files (-c only)\n", stderr);
fputs("\n", stderr);
fputs("-F and -N may both be \"-\", if stdin contains the paths and names interleaved\n", stderr);
return 0;
case 'v':
verbose = 1;
break;
case '0':
zero = 1;
break;
case 'c':
action = A_CREATE;
break;
case 'x':
action = A_EXTRACT;
break;
case 'l':
action = A_LIST;
break;
case 'f':
filename = optarg;
break;
case 'F':
if (strcmp(optarg, "-")) {
if ((assets = fopen(optarg, "rb")) == NULL) {
perror(optarg);
fail = 1;
}
} else {
assets = stdin;
}
break;
case 'N':
if (strcmp(optarg, "-")) {
if ((names = fopen(optarg, "rb")) == NULL) {
perror(optarg);
fail = 1;
}
} else {
names = stdin;
}
break;
default:
fail = 1;
}
}
FILE *file;
if (action == A_CREATE) {
if (filename == NULL || strcmp(filename, "-") == 0) {
file = stdout;
} else {
file = fopen(filename, "wb");
}
} else {
if (filename == NULL || strcmp(filename, "-") == 0) {
file = stdin;
} else {
file = fopen(filename, "rb");
}
}
if (file == NULL) {
perror(filename);
fail = 1;
}
if (file == stdin && (assets == stdin || names == stdin)) {
fputs("not enough stdins, try `-f'\n", stderr);
fail = 1;
}
if (assets == NULL && names != NULL) {
fputs("-N is useless without -F\n", stderr);
fail = 1;
}
if (optind != argc && assets != NULL) {
fputs("mixing -F and arguments is not supported\n", stderr);
fail = 1;
}
if (fail) {
return 1;
}
struct ctx ctx = {
.args = argv + optind,
.assets = assets,
.names = names,
};
int ret;
switch (action) {
case A_CREATE:
ret = asswrite(file, create_cb, &ctx);
break;
case A_EXTRACT:
#if !defined(_POSIX_VERSION)
if (optind == argc && assets == NULL) {
fputs("cannot use --extract without specifying any files\n(as a safety measure, specifying no files would prevent overwriting existing files, but this safety measure is not available in this version of the program, please use --list)\n", stderr);
return 1;
}
#endif
ret = assread(file, extract_cb, &ctx);
break;
case A_LIST:
ret = assread(file, list_cb, &ctx);
break;
default:
return 1;
}
switch (ret) {
case ASS_OK:
return 0;
case ASS_BAD:
fputs("not an asset pack, or its corrupt\n", stderr);
return ASS_BAD;
case ASS_USER:
return ASS_USER; // the callback printed its own error message, probably
case ASS_TRUNC:
fputs("truncated asset pack\n", stderr);
return ASS_TRUNC;
case ASS_MEM:
fputs("failed to allocate memory\n", stderr);
return ASS_MEM;
case ASS_LAZY:
fputs("some kind of failure which i could theoretically handle, but it would make the code more complicated than i want it to be, has occured\n", stderr);
return ASS_LAZY;
case ASS_UNSUPPORTED:
fputs("unsupported version\n", stderr);
return ASS_UNSUPPORTED;
default:
fputs("unknown error\n", stderr);
return ret;
}
}
static void blob_free(struct blob *const blob) {
free(blob->data);
blob->data = NULL;
blob->size = 0;
}
static struct blob load_file(FILE *file) {
const size_t START_SIZE = 8192;
char *data = malloc(START_SIZE);
size_t allocated = START_SIZE;
size_t used = 0;
while (1) {
size_t read = fread(data + used, 1, allocated - used, file);
if (read != allocated - used) {
used += read;
break;
}
used += read;
allocated *= 2;
void *const newdata = realloc(data, allocated);
if (newdata == NULL) {
goto realloc_error;
}
data = newdata;
}
if (used == 0) {
//free(data);
fclose(file);
return (struct blob) {.data = data, .size = 0};
}
void *const newdata = realloc(data, used);
if (newdata == NULL) {
goto realloc_error;
}
fclose(file);
return (struct blob) {.data = newdata, .size = used};
realloc_error:
free(data);
fclose(file);
return (struct blob) {.data = NULL};
}
static char *read_one_line(FILE *file) {
size_t allocated = 32;
size_t used = 0;
char *buf = malloc(allocated);
while (1) {
int c = fgetc(file);
if ((c == EOF && used == 0) || (c == '\0' && zero == 0)) {
free(buf);
return NULL;
}
if (used >= allocated) {
allocated *= 2;
if (allocated == 0) {
free(buf);
return NULL;
}
void *tmp = realloc(buf, allocated);
if (tmp == NULL) {
free(buf);
return NULL;
}
buf = tmp;
}
if (c == (zero? '\0': '\n') || c == EOF) {
buf[used] = '\0';
return buf;
}
buf[used++] = c;
}
}

12
utl/frag.awk Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env -S awk -f
BEGIN {mode = "any"; lineno = 1;}
{lineno++;}
/@@/ {mode = "any"; $0 = ("#line " lineno);}
/%%/ {mode = "vert";}
/##/ {mode = "frag"; $0 = ("#line " lineno);}
mode == "any"
mode == "frag"

148
utl/picmake.c Normal file
View File

@ -0,0 +1,148 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <getopt.h>
#include <png.h>
static char const pic_sig[8] = {'\xdd', '\xdd', 'p', 'i', 'c', '\n', '\0', 0};
static char const help_text[] = "%s [-fh] [-o OUTPUT.dat] INPUT.png\n";
static void write_vlq(size_t value, FILE *file) {
unsigned char buf[9];
size_t bytes;
for (bytes = 0; bytes < sizeof (size_t); bytes++) {
size_t byte = value >> (bytes * 8);
if ((byte & (0x7f >> bytes)) == byte) {
break;
}
buf[8 - bytes] = value >> (bytes * 8);
}
buf[8 - bytes] = (value >> (bytes * 8)) | (0xff00 >> bytes);
fwrite(buf + 8 - bytes, 1, bytes + 1, file);
}
int main(int argc, char **argv) {
struct option const options[] = {
{"force", 0, NULL, 'f'},
{"help", 0, NULL, 'h'},
{"output", 1, NULL, 'o'},
{"verbose", 0, NULL, 'v'},
{NULL, 0, NULL, 0 },
};
int opt, force = 0, verbose = 0;
char *outname = NULL;
while ((opt = getopt_long(argc, argv, "fho:v", options, NULL)) != -1) {
switch (opt) {
case 'f':
force++;
break;
case 'h':
fprintf(stderr, help_text, argv[0]);
fputs("-f --force allow outputting not-quite-successful conversions\n", stderr);
fputs("-h --help this message\n", stderr);
fputs("-o --output=FILE output converted image to FILE\n", stderr);
fputs("-v --verbose print diagnostics\n", stderr);
return EXIT_SUCCESS;
case 'o':
outname = optarg;
break;
case 'v':
verbose++;
break;
default:
return EXIT_FAILURE;
}
}
if (optind + 1 != argc) {
fprintf(stderr, help_text, argv[0]);
return EXIT_FAILURE;
}
// half-assed libpng use, will be fixed in a project where proper libpng use is necessary
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) NULL, NULL, NULL);
if (png_ptr == NULL) {
return EXIT_FAILURE;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_read_struct(&png_ptr, NULL, NULL);
return EXIT_FAILURE;
}
FILE *fp = NULL;
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
if (fp != NULL) {
fclose(fp);
}
return EXIT_FAILURE;
}
fp = fopen(argv[optind], "rb");
if (fp == NULL) {
perror(argv[optind]);
return EXIT_FAILURE;
}
png_init_io(png_ptr, fp);
// we do NOT want gamma!
//png_set_gamma_fixed(png_ptr, PNG_GAMMA_LINEAR, PNG_GAMMA_LINEAR);
//png_set_alpha_mode(png_ptr, PNG_ALPHA_PNG, PNG_GAMMA_LINEAR);
png_read_info(png_ptr, info_ptr);
png_uint_32 width, height;
int bit_depth, color_type;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_ptr);
} else if (bit_depth < 8) {
png_set_packing(png_ptr);
} else if (bit_depth > 8) {
png_set_scale_16(png_ptr);
}
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png_ptr);
} else {
png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
}
png_set_rgb_to_gray(png_ptr, 1, -1, -1);
//png_set_expand_gray_1_2_4_to_8(png_ptr);
png_read_update_info(png_ptr, info_ptr);
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
png_bytepp row_pointers = malloc(sizeof (png_bytep) * height);
unsigned char *image = malloc(rowbytes * height);
for (unsigned i = 0; i < height; i++) {
row_pointers[i] = image + (height - i - 1) * rowbytes;
}
png_read_image(png_ptr, row_pointers);
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
//free(row_pointers);
if (outname != NULL) {
FILE *out = fopen(outname, "wb");
if (out == NULL) {
perror(outname);
return EXIT_FAILURE;
}
fwrite(pic_sig, 1, sizeof (pic_sig), out);
write_vlq(width - 1, out);
write_vlq(height - 1, out);
for (size_t i = 0; i < width * height; i++) {
if (image[i * 2 + 1] < 0x80) {
fputc(0x00, out);
} else if (image[i * 2 + 0] < 0x80) {
fputc(0x80, out);
} else {
fputc(0xff, out);
}
}
fclose(out);
}
return EXIT_SUCCESS;
}

12
utl/vert.awk Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env -S awk -f
BEGIN {mode = "any"; lineno = 1;}
{lineno++;}
/@@/ {mode = "any"; $0 = ("#line " lineno);}
/%%/ {mode = "vert"; $0 = ("#line " lineno);}
/##/ {mode = "frag";}
mode == "any"
mode == "vert"