improve sw renderer

This commit is contained in:
asrael 2026-01-21 23:19:50 -06:00
parent 415d424057
commit 39ee0fefb7
89 changed files with 9380 additions and 2307 deletions

View file

@ -14,6 +14,7 @@
#include "pxl8_io.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) {
header->file_size = pxl8_read_u32(stream);
@ -58,7 +59,7 @@ static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette
palette->entry_count = total_colors;
palette->first_color = 0;
palette->last_color = total_colors - 1;
palette->colors = (u32*)malloc(total_colors * sizeof(u32));
palette->colors = (u32*)pxl8_malloc(total_colors * sizeof(u32));
if (!palette->colors) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
@ -97,7 +98,7 @@ static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer)
u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) {
layer->name = (char*)malloc(name_len + 1);
layer->name = (char*)pxl8_malloc(name_len + 1);
if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, layer->name, name_len);
layer->name[name_len] = '\0';
@ -120,7 +121,7 @@ static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* pa
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
}
palette->colors = (u32*)malloc(color_count * sizeof(u32));
palette->colors = (u32*)pxl8_malloc(color_count * sizeof(u32));
if (!palette->colors) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
@ -154,7 +155,7 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data
if (user_data->has_text) {
u16 text_len = pxl8_read_u16(stream);
if (text_len > 0) {
user_data->text = (char*)malloc(text_len + 1);
user_data->text = (char*)pxl8_malloc(text_len + 1);
if (!user_data->text) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, user_data->text, text_len);
user_data->text[text_len] = '\0';
@ -174,14 +175,14 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data
u32 num_properties = pxl8_read_u32(stream);
if (num_properties > 0) {
user_data->properties = (pxl8_ase_property*)calloc(num_properties, sizeof(pxl8_ase_property));
user_data->properties = (pxl8_ase_property*)pxl8_calloc(num_properties, sizeof(pxl8_ase_property));
if (!user_data->properties) return PXL8_ERROR_OUT_OF_MEMORY;
user_data->property_count = num_properties;
for (u32 i = 0; i < num_properties; i++) {
u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) {
user_data->properties[i].name = (char*)malloc(name_len + 1);
user_data->properties[i].name = (char*)pxl8_malloc(name_len + 1);
if (!user_data->properties[i].name) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, user_data->properties[i].name, name_len);
user_data->properties[i].name[name_len] = '\0';
@ -207,7 +208,7 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data
case 8: {
u16 str_len = pxl8_read_u16(stream);
if (str_len > 0) {
user_data->properties[i].string_val = (char*)malloc(str_len + 1);
user_data->properties[i].string_val = (char*)pxl8_malloc(str_len + 1);
if (!user_data->properties[i].string_val) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, user_data->properties[i].string_val, str_len);
user_data->properties[i].string_val[str_len] = '\0';
@ -236,7 +237,7 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) {
tileset->name = (char*)malloc(name_len + 1);
tileset->name = (char*)pxl8_malloc(name_len + 1);
if (!tileset->name) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, tileset->name, name_len);
tileset->name[name_len] = '\0';
@ -249,7 +250,7 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
if (tileset->flags & 2) {
u32 compressed_size = pxl8_read_u32(stream);
tileset->pixels_size = tileset->tile_width * tileset->tile_height * tileset->tile_count;
tileset->pixels = (u8*)malloc(tileset->pixels_size);
tileset->pixels = (u8*)pxl8_malloc(tileset->pixels_size);
if (!tileset->pixels) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
@ -257,13 +258,13 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
i32 result = mz_uncompress(tileset->pixels, &dest_len, compressed_data, compressed_size);
if (result != MZ_OK) {
pxl8_error("Failed to decompress tileset data: miniz error %d", result);
free(tileset->pixels);
pxl8_free(tileset->pixels);
tileset->pixels = NULL;
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
}
}
tileset->tile_user_data = (pxl8_ase_user_data*)calloc(tileset->tile_count, sizeof(pxl8_ase_user_data));
tileset->tile_user_data = (pxl8_ase_user_data*)pxl8_calloc(tileset->tile_count, sizeof(pxl8_ase_user_data));
if (!tileset->tile_user_data) return PXL8_ERROR_OUT_OF_MEMORY;
return PXL8_OK;
@ -288,7 +289,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
u32 pixels_size = cel->image.width * cel->image.height;
u32 compressed_size = chunk_size - 20;
cel->image.pixels = (u8*)malloc(pixels_size);
cel->image.pixels = (u8*)pxl8_malloc(pixels_size);
if (!cel->image.pixels) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
@ -296,7 +297,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
i32 result = mz_uncompress(cel->image.pixels, &dest_len, compressed_data, compressed_size);
if (result != MZ_OK) {
pxl8_error("Failed to decompress cel data: miniz error %d", result);
free(cel->image.pixels);
pxl8_free(cel->image.pixels);
cel->image.pixels = NULL;
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
}
@ -321,7 +322,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
u32 uncompressed_size = tile_count * bytes_per_tile;
u32 compressed_size = chunk_size - 36;
u8* temp_buffer = (u8*)malloc(uncompressed_size);
u8* temp_buffer = (u8*)pxl8_malloc(uncompressed_size);
if (!temp_buffer) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
@ -329,13 +330,13 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
i32 result = mz_uncompress(temp_buffer, &dest_len, compressed_data, compressed_size);
if (result != MZ_OK) {
pxl8_error("Failed to decompress tilemap data: miniz error %d", result);
free(temp_buffer);
pxl8_free(temp_buffer);
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
}
cel->tilemap.tiles = (u32*)calloc(tile_count, sizeof(u32));
cel->tilemap.tiles = (u32*)pxl8_calloc(tile_count, sizeof(u32));
if (!cel->tilemap.tiles) {
free(temp_buffer);
pxl8_free(temp_buffer);
return PXL8_ERROR_OUT_OF_MEMORY;
}
@ -353,7 +354,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
}
}
free(temp_buffer);
pxl8_free(temp_buffer);
}
return PXL8_OK;
@ -367,7 +368,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
memset(ase_file, 0, sizeof(pxl8_ase_file));
u8* file_data;
size_t file_size;
usize file_size;
pxl8_result result = pxl8_io_read_binary_file(filepath, &file_data, &file_size);
if (result != PXL8_OK) {
return result;
@ -387,7 +388,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
}
ase_file->frame_count = ase_file->header.frames;
ase_file->frames = (pxl8_ase_frame*)calloc(ase_file->frame_count, sizeof(pxl8_ase_frame));
ase_file->frames = (pxl8_ase_frame*)pxl8_calloc(ase_file->frame_count, sizeof(pxl8_ase_frame));
if (!ase_file->frames) {
pxl8_io_free_binary_data(file_data);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -425,7 +426,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
frame->duration = frame_header.duration;
u32 pixel_count = frame->width * frame->height;
frame->pixels = (u8*)calloc(pixel_count, sizeof(u8));
frame->pixels = (u8*)pxl8_calloc(pixel_count, sizeof(u8));
if (!frame->pixels) {
result = PXL8_ERROR_OUT_OF_MEMORY;
break;
@ -457,7 +458,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
case PXL8_ASE_CHUNK_LAYER: {
pxl8_ase_layer* new_layers =
(pxl8_ase_layer*)realloc(ase_file->layers,
(pxl8_ase_layer*)pxl8_realloc(ase_file->layers,
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
if (!new_layers) {
result = PXL8_ERROR_OUT_OF_MEMORY;
@ -476,7 +477,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
pxl8_ase_cel cel = {0};
result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
if (result == PXL8_OK) {
pxl8_ase_cel* new_cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
pxl8_ase_cel* new_cels = (pxl8_ase_cel*)pxl8_realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
if (!new_cels) {
result = PXL8_ERROR_OUT_OF_MEMORY;
break;
@ -515,14 +516,14 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
case PXL8_ASE_CHUNK_PALETTE:
if (ase_file->palette.colors) {
free(ase_file->palette.colors);
pxl8_free(ase_file->palette.colors);
ase_file->palette.colors = NULL;
}
result = parse_palette_chunk(&stream, &ase_file->palette);
break;
case PXL8_ASE_CHUNK_TILESET: {
pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets,
pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)pxl8_realloc(ase_file->tilesets,
(ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset));
if (!new_tilesets) {
result = PXL8_ERROR_OUT_OF_MEMORY;
@ -579,57 +580,57 @@ void pxl8_ase_destroy(pxl8_ase_file* ase_file) {
if (ase_file->frames) {
for (u32 i = 0; i < ase_file->frame_count; i++) {
if (ase_file->frames[i].pixels) free(ase_file->frames[i].pixels);
if (ase_file->frames[i].pixels) pxl8_free(ase_file->frames[i].pixels);
if (ase_file->frames[i].cels) {
for (u32 j = 0; j < ase_file->frames[i].cel_count; j++) {
pxl8_ase_cel* cel = &ase_file->frames[i].cels[j];
if (cel->cel_type == 2 && cel->image.pixels) {
free(cel->image.pixels);
pxl8_free(cel->image.pixels);
} else if (cel->cel_type == 3 && cel->tilemap.tiles) {
free(cel->tilemap.tiles);
pxl8_free(cel->tilemap.tiles);
}
}
free(ase_file->frames[i].cels);
pxl8_free(ase_file->frames[i].cels);
}
}
free(ase_file->frames);
pxl8_free(ase_file->frames);
}
if (ase_file->palette.colors) {
free(ase_file->palette.colors);
pxl8_free(ase_file->palette.colors);
}
if (ase_file->layers) {
for (u32 i = 0; i < ase_file->layer_count; i++) {
if (ase_file->layers[i].name) {
free(ase_file->layers[i].name);
pxl8_free(ase_file->layers[i].name);
}
}
free(ase_file->layers);
pxl8_free(ase_file->layers);
}
if (ase_file->tilesets) {
for (u32 i = 0; i < ase_file->tileset_count; i++) {
if (ase_file->tilesets[i].name) free(ase_file->tilesets[i].name);
if (ase_file->tilesets[i].pixels) free(ase_file->tilesets[i].pixels);
if (ase_file->tilesets[i].name) pxl8_free(ase_file->tilesets[i].name);
if (ase_file->tilesets[i].pixels) pxl8_free(ase_file->tilesets[i].pixels);
if (ase_file->tilesets[i].tile_user_data) {
for (u32 j = 0; j < ase_file->tilesets[i].tile_count; j++) {
pxl8_ase_user_data* ud = &ase_file->tilesets[i].tile_user_data[j];
if (ud->text) free(ud->text);
if (ud->text) pxl8_free(ud->text);
if (ud->properties) {
for (u32 k = 0; k < ud->property_count; k++) {
if (ud->properties[k].name) free(ud->properties[k].name);
if (ud->properties[k].name) pxl8_free(ud->properties[k].name);
if (ud->properties[k].type == 8 && ud->properties[k].string_val) {
free(ud->properties[k].string_val);
pxl8_free(ud->properties[k].string_val);
}
}
free(ud->properties);
pxl8_free(ud->properties);
}
}
free(ase_file->tilesets[i].tile_user_data);
pxl8_free(ase_file->tilesets[i].tile_user_data);
}
}
free(ase_file->tilesets);
pxl8_free(ase_file->tilesets);
}
memset(ase_file, 0, sizeof(pxl8_ase_file));

View file

@ -8,6 +8,7 @@
#include <unistd.h>
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_CART_MAGIC 0x43585850
#define PXL8_CART_VERSION 1
@ -62,7 +63,7 @@ static bool is_directory(const char* path) {
}
static bool is_pxc_file(const char* path) {
size_t len = strlen(path);
usize len = strlen(path);
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
}
@ -97,7 +98,7 @@ static void collect_files_recursive(const char* dir_path, const char* prefix,
} else {
if (*count >= *capacity) {
*capacity = (*capacity == 0) ? 64 : (*capacity * 2);
*paths = realloc(*paths, *capacity * sizeof(char*));
*paths = pxl8_realloc(*paths, *capacity * sizeof(char*));
}
(*paths)[(*count)++] = strdup(rel_path);
}
@ -123,13 +124,13 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
if (header->magic != PXL8_CART_MAGIC) return PXL8_ERROR_INVALID_FORMAT;
if (header->version > PXL8_CART_VERSION) return PXL8_ERROR_INVALID_FORMAT;
cart->data = malloc(size);
cart->data = pxl8_malloc(size);
if (!cart->data) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(cart->data, data, size);
cart->data_size = size;
cart->file_count = header->file_count;
cart->files = calloc(cart->file_count, sizeof(pxl8_cart_file));
cart->files = pxl8_calloc(cart->file_count, sizeof(pxl8_cart_file));
if (!cart->files) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* toc = cart->data + sizeof(pxl8_cart_header);
@ -137,7 +138,7 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
const pxl8_cart_entry* entry = (const pxl8_cart_entry*)toc;
toc += sizeof(pxl8_cart_entry);
cart->files[i].path = malloc(entry->path_len + 1);
cart->files[i].path = pxl8_malloc(entry->path_len + 1);
memcpy(cart->files[i].path, toc, entry->path_len);
cart->files[i].path[entry->path_len] = '\0';
toc += entry->path_len;
@ -151,7 +152,7 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
}
pxl8_cart* pxl8_cart_create(void) {
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
pxl8_cart* cart = pxl8_calloc(1, sizeof(pxl8_cart));
if (cart) {
cart->resolution = PXL8_RESOLUTION_640x360;
cart->window_size = (pxl8_size){1280, 720};
@ -167,7 +168,7 @@ pxl8_cart* pxl8_get_cart(void) {
void pxl8_cart_destroy(pxl8_cart* cart) {
if (!cart) return;
pxl8_cart_unload(cart);
free(cart);
pxl8_free(cart);
}
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
@ -202,16 +203,16 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
u32 size = (u32)ftell(file);
fseek(file, 0, SEEK_SET);
u8* data = malloc(size);
u8* data = pxl8_malloc(size);
if (!data || fread(data, 1, size, file) != size) {
free(data);
pxl8_free(data);
fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE;
}
fclose(file);
pxl8_result result = load_packed_cart(cart, data, size);
free(data);
pxl8_free(data);
if (result == PXL8_OK) {
pxl8_info("Loaded cart");
@ -242,16 +243,16 @@ pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
}
fseek(file, trailer.cart_offset, SEEK_SET);
u8* data = malloc(trailer.cart_size);
u8* data = pxl8_malloc(trailer.cart_size);
if (!data || fread(data, 1, trailer.cart_size, file) != trailer.cart_size) {
free(data);
pxl8_free(data);
fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE;
}
fclose(file);
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
free(data);
pxl8_free(data);
if (result == PXL8_OK) {
pxl8_info("Loaded embedded cart");
@ -265,7 +266,7 @@ void pxl8_cart_unload(pxl8_cart* cart) {
if (cart->title) {
pxl8_info("Unloaded cart: %s", cart->title);
pxl8_cart_unmount(cart);
free(cart->title);
pxl8_free(cart->title);
cart->title = NULL;
} else if (cart->base_path || cart->data) {
pxl8_info("Unloaded cart");
@ -274,18 +275,18 @@ void pxl8_cart_unload(pxl8_cart* cart) {
if (cart->files) {
for (u32 i = 0; i < cart->file_count; i++) {
free(cart->files[i].path);
pxl8_free(cart->files[i].path);
}
free(cart->files);
pxl8_free(cart->files);
cart->files = NULL;
}
cart->file_count = 0;
free(cart->data);
pxl8_free(cart->data);
cart->data = NULL;
cart->data_size = 0;
free(cart->base_path);
pxl8_free(cart->base_path);
cart->base_path = NULL;
cart->is_folder = false;
}
@ -302,7 +303,7 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
pxl8_original_cwd = getcwd(NULL, 0);
if (chdir(cart->base_path) != 0) {
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
free(pxl8_original_cwd);
pxl8_free(pxl8_original_cwd);
pxl8_original_cwd = NULL;
return PXL8_ERROR_FILE_NOT_FOUND;
}
@ -323,7 +324,7 @@ void pxl8_cart_unmount(pxl8_cart* cart) {
if (pxl8_original_cwd) {
chdir(pxl8_original_cwd);
free(pxl8_original_cwd);
pxl8_free(pxl8_original_cwd);
pxl8_original_cwd = NULL;
}
@ -343,7 +344,7 @@ const char* pxl8_cart_get_title(const pxl8_cart* cart) {
void pxl8_cart_set_title(pxl8_cart* cart, const char* title) {
if (!cart || !title) return;
free(cart->title);
pxl8_free(cart->title);
cart->title = strdup(title);
}
@ -399,16 +400,16 @@ bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
return find_file(cart, path) != NULL;
}
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size) {
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, usize out_size) {
if (!cart || !relative_path || !out_path || out_size == 0) return false;
if (cart->is_folder && cart->base_path) {
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
return written >= 0 && (size_t)written < out_size;
return written >= 0 && (usize)written < out_size;
}
i32 written = snprintf(out_path, out_size, "%s", relative_path);
return written >= 0 && (size_t)written < out_size;
return written >= 0 && (usize)written < out_size;
}
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out) {
@ -427,9 +428,9 @@ pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** da
*size_out = (u32)ftell(file);
fseek(file, 0, SEEK_SET);
*data_out = malloc(*size_out);
*data_out = pxl8_malloc(*size_out);
if (!*data_out || fread(*data_out, 1, *size_out, file) != *size_out) {
free(*data_out);
pxl8_free(*data_out);
*data_out = NULL;
fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE;
@ -442,7 +443,7 @@ pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** da
if (!cf) return PXL8_ERROR_FILE_NOT_FOUND;
*size_out = cf->size;
*data_out = malloc(cf->size);
*data_out = pxl8_malloc(cf->size);
if (!*data_out) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(*data_out, cart->data + cf->offset, cf->size);
@ -450,7 +451,7 @@ pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** da
}
void pxl8_cart_free_file(u8* data) {
free(data);
pxl8_free(data);
}
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
@ -483,7 +484,7 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
u32 data_offset = sizeof(pxl8_cart_header) + toc_size;
u32 total_size = data_offset;
u32* file_sizes = malloc(count * sizeof(u32));
u32* file_sizes = pxl8_malloc(count * sizeof(u32));
for (u32 i = 0; i < count; i++) {
char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
@ -496,11 +497,11 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
}
}
u8* buffer = calloc(1, total_size);
u8* buffer = pxl8_calloc(1, total_size);
if (!buffer) {
free(file_sizes);
for (u32 i = 0; i < count; i++) free(paths[i]);
free(paths);
pxl8_free(file_sizes);
for (u32 i = 0; i < count; i++) pxl8_free(paths[i]);
pxl8_free(paths);
return PXL8_ERROR_OUT_OF_MEMORY;
}
@ -536,20 +537,20 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
}
file_offset += file_sizes[i];
free(paths[i]);
pxl8_free(paths[i]);
}
free(paths);
free(file_sizes);
pxl8_free(paths);
pxl8_free(file_sizes);
FILE* out = fopen(output_path, "wb");
if (!out) {
free(buffer);
pxl8_free(buffer);
return PXL8_ERROR_SYSTEM_FAILURE;
}
fwrite(buffer, 1, total_size, out);
fclose(out);
free(buffer);
pxl8_free(buffer);
pxl8_info("Cart packed: %u files, %u bytes", count, total_size);
return PXL8_OK;
@ -573,7 +574,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
fseek(f, 0, SEEK_END);
cart_size = (u32)ftell(f);
fseek(f, 0, SEEK_SET);
cart_data = malloc(cart_size);
cart_data = pxl8_malloc(cart_size);
fread(cart_data, 1, cart_size, f);
fclose(f);
unlink(temp_pxc);
@ -584,7 +585,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
fseek(f, 0, SEEK_END);
cart_size = (u32)ftell(f);
fseek(f, 0, SEEK_SET);
cart_data = malloc(cart_size);
cart_data = pxl8_malloc(cart_size);
fread(cart_data, 1, cart_size, f);
fclose(f);
free_cart = true;
@ -594,30 +595,30 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
FILE* exe = fopen(exe_path, "rb");
if (!exe) {
if (free_cart) free(cart_data);
if (free_cart) pxl8_free(cart_data);
return PXL8_ERROR_FILE_NOT_FOUND;
}
fseek(exe, 0, SEEK_END);
u32 exe_size = (u32)ftell(exe);
fseek(exe, 0, SEEK_SET);
u8* exe_data = malloc(exe_size);
u8* exe_data = pxl8_malloc(exe_size);
fread(exe_data, 1, exe_size, exe);
fclose(exe);
FILE* out = fopen(output_path, "wb");
if (!out) {
free(exe_data);
if (free_cart) free(cart_data);
pxl8_free(exe_data);
if (free_cart) pxl8_free(cart_data);
return PXL8_ERROR_SYSTEM_FAILURE;
}
fwrite(exe_data, 1, exe_size, out);
free(exe_data);
pxl8_free(exe_data);
u32 cart_offset = exe_size;
fwrite(cart_data, 1, cart_size, out);
if (free_cart) free(cart_data);
if (free_cart) pxl8_free(cart_data);
pxl8_cart_trailer trailer = {
.magic = PXL8_CART_TRAILER_MAGIC,

View file

@ -34,7 +34,7 @@ bool pxl8_cart_is_packed(const pxl8_cart* cart);
bool pxl8_cart_has_embedded(const char* exe_path);
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size);
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, usize out_size);
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out);
void pxl8_cart_free_file(u8* data);

View file

@ -17,6 +17,7 @@
#endif
#include "pxl8_log.h"
#include "pxl8_mem.h"
typedef struct {
u32 magic;
@ -40,7 +41,7 @@ static u32 pxl8_save_checksum(const u8* data, u32 size) {
return hash;
}
static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, size_t path_size) {
static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, usize path_size) {
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP);
} else {
@ -68,7 +69,7 @@ static pxl8_result pxl8_save_ensure_directory(const char* path) {
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
if (!game_name) return NULL;
pxl8_save* save = (pxl8_save*)calloc(1, sizeof(pxl8_save));
pxl8_save* save = (pxl8_save*)pxl8_calloc(1, sizeof(pxl8_save));
if (!save) return NULL;
save->magic = magic;
@ -81,7 +82,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
snprintf(save->directory, sizeof(save->directory),
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
} else {
free(save);
pxl8_free(save);
return NULL;
}
#else
@ -91,7 +92,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
if (pw) home = pw->pw_dir;
}
if (!home) {
free(save);
pxl8_free(save);
return NULL;
}
@ -106,7 +107,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
#endif
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
free(save);
pxl8_free(save);
return NULL;
}
@ -116,7 +117,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
void pxl8_save_destroy(pxl8_save* save) {
if (save) {
free(save);
pxl8_free(save);
}
}
@ -199,14 +200,14 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
return PXL8_ERROR_INVALID_FORMAT;
}
u8* data = (u8*)malloc(header.size);
u8* data = (u8*)pxl8_malloc(header.size);
if (!data) {
fclose(file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
if (fread(data, 1, header.size, file) != header.size) {
free(data);
pxl8_free(data);
fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE;
}
@ -215,7 +216,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
u32 checksum = pxl8_save_checksum(data, header.size);
if (checksum != header.checksum) {
free(data);
pxl8_free(data);
pxl8_error("Save file checksum mismatch");
return PXL8_ERROR_INVALID_FORMAT;
}
@ -234,7 +235,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
void pxl8_save_free(u8* data) {
if (data) {
free(data);
pxl8_free(data);
}
}

View file

@ -6,7 +6,6 @@
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <unistd.h>
@ -14,6 +13,7 @@
#include "pxl8_hal.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_repl.h"
#include "pxl8_replay.h"
#include "pxl8_script.h"
@ -39,23 +39,23 @@ static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32
#endif
pxl8* pxl8_create(const pxl8_hal* hal) {
pxl8* sys = (pxl8*)calloc(1, sizeof(pxl8));
pxl8* sys = (pxl8*)pxl8_calloc(1, sizeof(pxl8));
if (!sys) return NULL;
pxl8_log_init(&sys->log);
if (!hal) {
pxl8_error("hal cannot be null");
free(sys);
pxl8_free(sys);
return NULL;
}
sys->hal = hal;
sys->game = (pxl8_game*)calloc(1, sizeof(pxl8_game));
sys->game = (pxl8_game*)pxl8_calloc(1, sizeof(pxl8_game));
if (!sys->game) {
pxl8_error("failed to allocate game");
free(sys);
pxl8_free(sys);
return NULL;
}
@ -65,11 +65,11 @@ pxl8* pxl8_create(const pxl8_hal* hal) {
void pxl8_destroy(pxl8* sys) {
if (!sys) return;
if (sys->game) free(sys->game);
if (sys->game) pxl8_free(sys->game);
if (sys->cart) pxl8_cart_destroy(sys->cart);
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
free(sys);
pxl8_free(sys);
}
static void pxl8_print_help(void) {
@ -171,7 +171,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
cart_path = ".";
} else {
pxl8_error("no main.fnl or main.lua found in current directory");
free(original_cwd);
pxl8_free(original_cwd);
return PXL8_ERROR_INITIALIZATION_FAILED;
}
}
@ -190,7 +190,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
if (sys->cart) pxl8_cart_destroy(sys->cart);
sys->cart = NULL;
free(original_cwd);
pxl8_free(original_cwd);
return PXL8_ERROR_INITIALIZATION_FAILED;
}
@ -203,7 +203,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
} else if (script_arg) {
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
}
free(original_cwd);
pxl8_free(original_cwd);
const char* window_title = pxl8_cart_get_title(sys->cart);
if (!window_title) window_title = "pxl8";
@ -310,7 +310,7 @@ pxl8_result pxl8_update(pxl8* sys) {
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
const char* err_msg = pxl8_script_get_last_error(game->script);
pxl8_error("failed to setup pxl8 global: %s", err_msg);
pxl8_error("failed to load pxl8 global: %s", err_msg);
}
sys->repl = pxl8_repl_create();

View file

@ -1,35 +1,35 @@
#include "pxl8_bytes.h"
#include <string.h>
void pxl8_pack_u8(u8* buf, size_t offset, u8 val) {
void pxl8_pack_u8(u8* buf, usize offset, u8 val) {
buf[offset] = val;
}
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) {
void pxl8_pack_u16_le(u8* buf, usize offset, u16 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
}
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) {
void pxl8_pack_u16_be(u8* buf, usize offset, u16 val) {
buf[offset] = (u8)(val >> 8);
buf[offset + 1] = (u8)(val);
}
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) {
void pxl8_pack_u32_le(u8* buf, usize offset, u32 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16);
buf[offset + 3] = (u8)(val >> 24);
}
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) {
void pxl8_pack_u32_be(u8* buf, usize offset, u32 val) {
buf[offset] = (u8)(val >> 24);
buf[offset + 1] = (u8)(val >> 16);
buf[offset + 2] = (u8)(val >> 8);
buf[offset + 3] = (u8)(val);
}
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
void pxl8_pack_u64_le(u8* buf, usize offset, u64 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16);
@ -40,7 +40,7 @@ void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
buf[offset + 7] = (u8)(val >> 56);
}
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
void pxl8_pack_u64_be(u8* buf, usize offset, u64 val) {
buf[offset] = (u8)(val >> 56);
buf[offset + 1] = (u8)(val >> 48);
buf[offset + 2] = (u8)(val >> 40);
@ -51,85 +51,85 @@ void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
buf[offset + 7] = (u8)(val);
}
void pxl8_pack_i8(u8* buf, size_t offset, i8 val) {
void pxl8_pack_i8(u8* buf, usize offset, i8 val) {
buf[offset] = (u8)val;
}
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) {
void pxl8_pack_i16_le(u8* buf, usize offset, i16 val) {
pxl8_pack_u16_le(buf, offset, (u16)val);
}
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) {
void pxl8_pack_i16_be(u8* buf, usize offset, i16 val) {
pxl8_pack_u16_be(buf, offset, (u16)val);
}
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) {
void pxl8_pack_i32_le(u8* buf, usize offset, i32 val) {
pxl8_pack_u32_le(buf, offset, (u32)val);
}
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) {
void pxl8_pack_i32_be(u8* buf, usize offset, i32 val) {
pxl8_pack_u32_be(buf, offset, (u32)val);
}
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) {
void pxl8_pack_i64_le(u8* buf, usize offset, i64 val) {
pxl8_pack_u64_le(buf, offset, (u64)val);
}
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) {
void pxl8_pack_i64_be(u8* buf, usize offset, i64 val) {
pxl8_pack_u64_be(buf, offset, (u64)val);
}
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) {
void pxl8_pack_f32_le(u8* buf, usize offset, f32 val) {
u32 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_le(buf, offset, bits);
}
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) {
void pxl8_pack_f32_be(u8* buf, usize offset, f32 val) {
u32 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_be(buf, offset, bits);
}
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) {
void pxl8_pack_f64_le(u8* buf, usize offset, f64 val) {
u64 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_le(buf, offset, bits);
}
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) {
void pxl8_pack_f64_be(u8* buf, usize offset, f64 val) {
u64 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_be(buf, offset, bits);
}
u8 pxl8_unpack_u8(const u8* buf, size_t offset) {
u8 pxl8_unpack_u8(const u8* buf, usize offset) {
return buf[offset];
}
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) {
u16 pxl8_unpack_u16_le(const u8* buf, usize offset) {
return (u16)buf[offset] | ((u16)buf[offset + 1] << 8);
}
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) {
u16 pxl8_unpack_u16_be(const u8* buf, usize offset) {
return ((u16)buf[offset] << 8) | (u16)buf[offset + 1];
}
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) {
u32 pxl8_unpack_u32_le(const u8* buf, usize offset) {
return (u32)buf[offset] |
((u32)buf[offset + 1] << 8) |
((u32)buf[offset + 2] << 16) |
((u32)buf[offset + 3] << 24);
}
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) {
u32 pxl8_unpack_u32_be(const u8* buf, usize offset) {
return ((u32)buf[offset] << 24) |
((u32)buf[offset + 1] << 16) |
((u32)buf[offset + 2] << 8) |
(u32)buf[offset + 3];
}
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
u64 pxl8_unpack_u64_le(const u8* buf, usize offset) {
return (u64)buf[offset] |
((u64)buf[offset + 1] << 8) |
((u64)buf[offset + 2] << 16) |
@ -140,7 +140,7 @@ u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
((u64)buf[offset + 7] << 56);
}
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
u64 pxl8_unpack_u64_be(const u8* buf, usize offset) {
return ((u64)buf[offset] << 56) |
((u64)buf[offset + 1] << 48) |
((u64)buf[offset + 2] << 40) |
@ -151,56 +151,56 @@ u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
(u64)buf[offset + 7];
}
i8 pxl8_unpack_i8(const u8* buf, size_t offset) {
i8 pxl8_unpack_i8(const u8* buf, usize offset) {
return (i8)buf[offset];
}
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) {
i16 pxl8_unpack_i16_le(const u8* buf, usize offset) {
return (i16)pxl8_unpack_u16_le(buf, offset);
}
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) {
i16 pxl8_unpack_i16_be(const u8* buf, usize offset) {
return (i16)pxl8_unpack_u16_be(buf, offset);
}
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) {
i32 pxl8_unpack_i32_le(const u8* buf, usize offset) {
return (i32)pxl8_unpack_u32_le(buf, offset);
}
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) {
i32 pxl8_unpack_i32_be(const u8* buf, usize offset) {
return (i32)pxl8_unpack_u32_be(buf, offset);
}
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) {
i64 pxl8_unpack_i64_le(const u8* buf, usize offset) {
return (i64)pxl8_unpack_u64_le(buf, offset);
}
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) {
i64 pxl8_unpack_i64_be(const u8* buf, usize offset) {
return (i64)pxl8_unpack_u64_be(buf, offset);
}
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) {
f32 pxl8_unpack_f32_le(const u8* buf, usize offset) {
u32 bits = pxl8_unpack_u32_le(buf, offset);
f32 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) {
f32 pxl8_unpack_f32_be(const u8* buf, usize offset) {
u32 bits = pxl8_unpack_u32_be(buf, offset);
f32 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) {
f64 pxl8_unpack_f64_le(const u8* buf, usize offset) {
u64 bits = pxl8_unpack_u64_le(buf, offset);
f64 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) {
f64 pxl8_unpack_f64_be(const u8* buf, usize offset) {
u64 bits = pxl8_unpack_u64_be(buf, offset);
f64 result;
memcpy(&result, &bits, sizeof(result));

View file

@ -10,43 +10,43 @@ void pxl8_bit_set(u32* val, u8 bit);
bool pxl8_bit_test(u32 val, u8 bit);
void pxl8_bit_toggle(u32* val, u8 bit);
void pxl8_pack_u8(u8* buf, size_t offset, u8 val);
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);
void pxl8_pack_i8(u8* buf, size_t offset, i8 val);
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);
void pxl8_pack_u8(u8* buf, usize offset, u8 val);
void pxl8_pack_u16_be(u8* buf, usize offset, u16 val);
void pxl8_pack_u16_le(u8* buf, usize offset, u16 val);
void pxl8_pack_u32_be(u8* buf, usize offset, u32 val);
void pxl8_pack_u32_le(u8* buf, usize offset, u32 val);
void pxl8_pack_u64_be(u8* buf, usize offset, u64 val);
void pxl8_pack_u64_le(u8* buf, usize offset, u64 val);
void pxl8_pack_i8(u8* buf, usize offset, i8 val);
void pxl8_pack_i16_be(u8* buf, usize offset, i16 val);
void pxl8_pack_i16_le(u8* buf, usize offset, i16 val);
void pxl8_pack_i32_be(u8* buf, usize offset, i32 val);
void pxl8_pack_i32_le(u8* buf, usize offset, i32 val);
void pxl8_pack_i64_be(u8* buf, usize offset, i64 val);
void pxl8_pack_i64_le(u8* buf, usize offset, i64 val);
void pxl8_pack_f32_be(u8* buf, usize offset, f32 val);
void pxl8_pack_f32_le(u8* buf, usize offset, f32 val);
void pxl8_pack_f64_be(u8* buf, usize offset, f64 val);
void pxl8_pack_f64_le(u8* buf, usize offset, f64 val);
u8 pxl8_unpack_u8(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);
i8 pxl8_unpack_i8(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);
u8 pxl8_unpack_u8(const u8* buf, usize offset);
u16 pxl8_unpack_u16_be(const u8* buf, usize offset);
u16 pxl8_unpack_u16_le(const u8* buf, usize offset);
u32 pxl8_unpack_u32_be(const u8* buf, usize offset);
u32 pxl8_unpack_u32_le(const u8* buf, usize offset);
u64 pxl8_unpack_u64_be(const u8* buf, usize offset);
u64 pxl8_unpack_u64_le(const u8* buf, usize offset);
i8 pxl8_unpack_i8(const u8* buf, usize offset);
i16 pxl8_unpack_i16_be(const u8* buf, usize offset);
i16 pxl8_unpack_i16_le(const u8* buf, usize offset);
i32 pxl8_unpack_i32_be(const u8* buf, usize offset);
i32 pxl8_unpack_i32_le(const u8* buf, usize offset);
i64 pxl8_unpack_i64_be(const u8* buf, usize offset);
i64 pxl8_unpack_i64_le(const u8* buf, usize offset);
f32 pxl8_unpack_f32_be(const u8* buf, usize offset);
f32 pxl8_unpack_f32_le(const u8* buf, usize offset);
f64 pxl8_unpack_f64_be(const u8* buf, usize offset);
f64 pxl8_unpack_f64_le(const u8* buf, usize offset);
typedef struct {
const u8* bytes;

View file

@ -1,4 +1,5 @@
#include "pxl8_io.h"
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
@ -10,7 +11,7 @@ static inline char pxl8_to_lower(char c) {
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
}
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size) {
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
pxl8_cart* cart = pxl8_get_cart();
@ -19,7 +20,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
u32 cart_size = 0;
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
if (result == PXL8_OK) {
*content = realloc(data, cart_size + 1);
*content = pxl8_realloc(data, cart_size + 1);
if (!*content) {
pxl8_cart_free_file(data);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -44,13 +45,13 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
return PXL8_ERROR_SYSTEM_FAILURE;
}
*content = malloc(file_size + 1);
*content = pxl8_malloc(file_size + 1);
if (!*content) {
fclose(file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
size_t bytes_read = fread(*content, 1, file_size, file);
usize bytes_read = fread(*content, 1, file_size, file);
(*content)[bytes_read] = '\0';
*size = bytes_read;
@ -58,7 +59,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
return PXL8_OK;
}
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size) {
pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size) {
if (!path || !content) return PXL8_ERROR_NULL_POINTER;
FILE* file = fopen(path, "wb");
@ -66,17 +67,17 @@ pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t siz
return PXL8_ERROR_SYSTEM_FAILURE;
}
size_t bytes_written = fwrite(content, 1, size, file);
usize bytes_written = fwrite(content, 1, size, file);
fclose(file);
return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
}
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size) {
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size) {
return pxl8_io_read_file(path, (char**)data, size);
}
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size) {
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size) {
return pxl8_io_write_file(path, (const char*)data, size);
}
@ -112,13 +113,13 @@ pxl8_result pxl8_io_create_directory(const char* path) {
void pxl8_io_free_file_content(char* content) {
if (content) {
free(content);
pxl8_free(content);
}
}
void pxl8_io_free_binary_data(u8* data) {
if (data) {
free(data);
pxl8_free(data);
}
}
@ -144,7 +145,7 @@ static i32 pxl8_key_code(const char* key_name) {
};
char lower_name[64];
size_t i;
usize i;
for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) {
lower_name[i] = pxl8_to_lower(key_name[i]);
}

View file

@ -15,10 +15,10 @@ bool pxl8_io_file_exists(const char* path);
void pxl8_io_free_binary_data(u8* data);
void pxl8_io_free_file_content(char* content);
f64 pxl8_io_get_file_modified_time(const char* path);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size);
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);

View file

@ -34,7 +34,7 @@ void pxl8_log_set_level(pxl8_log_level level) {
if (g_log) g_log->level = level;
}
static void log_timestamp(char* buffer, size_t size) {
static void log_timestamp(char* buffer, usize size) {
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);
strftime(buffer, size, "%H:%M:%S", tm_info);

View file

@ -1,5 +1,6 @@
#include "pxl8_replay.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_sys.h"
#include <stdio.h>
@ -42,8 +43,8 @@ struct pxl8_replay {
static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) {
while (chunk) {
pxl8_replay_chunk* next = chunk->next;
free(chunk->data);
free(chunk);
pxl8_free(chunk->data);
pxl8_free(chunk);
chunk = next;
}
}
@ -52,7 +53,7 @@ static void pxl8_replay_keyframe_entry_free(pxl8_keyframe_entry* entry) {
while (entry) {
pxl8_keyframe_entry* next = entry->next;
pxl8_replay_chunk_free(entry->input_deltas);
free(entry);
pxl8_free(entry);
entry = next;
}
}
@ -64,7 +65,7 @@ pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
return NULL;
}
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) {
fclose(f);
return NULL;
@ -85,7 +86,7 @@ pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
}
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) {
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) return NULL;
r->recording = true;
@ -125,7 +126,7 @@ pxl8_replay* pxl8_replay_open(const char* path) {
return NULL;
}
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) {
fclose(f);
return NULL;
@ -146,14 +147,14 @@ pxl8_replay* pxl8_replay_open(const char* path) {
if (fread(size_bytes, 3, 1, f) != 1) break;
u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16);
u8* data = malloc(size);
u8* data = pxl8_malloc(size);
if (!data || fread(data, size, 1, f) != 1) {
free(data);
pxl8_free(data);
break;
}
if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) {
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry));
pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
if (entry && size >= sizeof(pxl8_keyframe)) {
memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe));
entry->prev = r->current_keyframe;
@ -166,10 +167,10 @@ pxl8_replay* pxl8_replay_open(const char* path) {
}
r->keyframe_count++;
}
free(data);
pxl8_free(data);
} else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) {
if (r->current_keyframe) {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (chunk) {
chunk->type = chunk_type;
chunk->size = size;
@ -185,9 +186,9 @@ pxl8_replay* pxl8_replay_open(const char* path) {
}
}
}
free(data);
pxl8_free(data);
} else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (chunk) {
chunk->type = chunk_type;
chunk->size = size;
@ -202,9 +203,9 @@ pxl8_replay* pxl8_replay_open(const char* path) {
r->audio_events_tail = chunk;
}
}
free(data);
pxl8_free(data);
} else {
free(data);
pxl8_free(data);
}
}
@ -231,7 +232,7 @@ void pxl8_replay_destroy(pxl8_replay* r) {
pxl8_replay_chunk_free(r->pending_inputs);
pxl8_replay_chunk_free(r->audio_events);
free(r);
pxl8_free(r);
}
bool pxl8_replay_is_recording(pxl8_replay* r) {
@ -262,14 +263,14 @@ static void write_chunk(FILE* f, u8 type, const void* data, u32 size) {
}
static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (!chunk) return;
chunk->type = type;
chunk->size = size;
chunk->data = malloc(size);
chunk->data = pxl8_malloc(size);
if (!chunk->data) {
free(chunk);
pxl8_free(chunk);
return;
}
memcpy(chunk->data, data, size);
@ -327,7 +328,7 @@ void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* r
write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf));
fflush(r->file);
} else {
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry));
pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
if (!entry) return;
entry->keyframe = kf;
@ -342,7 +343,7 @@ void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* r
r->keyframes->prev = NULL;
}
pxl8_replay_chunk_free(oldest->input_deltas);
free(oldest);
pxl8_free(oldest);
r->keyframe_count--;
}
@ -446,14 +447,14 @@ void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8
if (r->file) {
write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt));
} else {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (!chunk) return;
chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT;
chunk->size = sizeof(evt);
chunk->data = malloc(sizeof(evt));
chunk->data = pxl8_malloc(sizeof(evt));
if (!chunk->data) {
free(chunk);
pxl8_free(chunk);
return;
}
memcpy(chunk->data, &evt, sizeof(evt));

View file

@ -23,6 +23,9 @@ typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef size_t usize;
typedef ptrdiff_t isize;
#if defined(__SIZEOF_INT128__)
typedef __int128_t i128;
typedef __uint128_t u128;

View file

@ -1,4 +1,5 @@
#include "pxl8_3d_camera.h"
#include "pxl8_mem.h"
#include <math.h>
#include <stdlib.h>
@ -28,7 +29,7 @@ struct pxl8_3d_camera {
};
pxl8_3d_camera* pxl8_3d_camera_create(void) {
pxl8_3d_camera* cam = calloc(1, sizeof(pxl8_3d_camera));
pxl8_3d_camera* cam = pxl8_calloc(1, sizeof(pxl8_3d_camera));
if (!cam) return NULL;
cam->position = (pxl8_vec3){0, 0, 0};
@ -46,7 +47,7 @@ pxl8_3d_camera* pxl8_3d_camera_create(void) {
}
void pxl8_3d_camera_destroy(pxl8_3d_camera* cam) {
free(cam);
pxl8_free(cam);
}
void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) {
@ -56,8 +57,8 @@ void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target,
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye));
cam->pitch = asinf(-forward.y);
cam->yaw = atan2f(forward.x, forward.z);
cam->pitch = asinf(forward.y);
cam->yaw = atan2f(-forward.x, -forward.z);
cam->roll = 0;
(void)up;
@ -104,9 +105,9 @@ pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
f32 sy = sinf(cam->yaw);
return (pxl8_vec3){
cp * sy,
-sp,
cp * cy
-sy * cp,
sp,
-cy * cp
};
}
@ -121,7 +122,7 @@ pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) {
if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) {
return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far);
} else {
return pxl8_mat4_ortho(
return pxl8_mat4_orthographic(
cam->ortho_left, cam->ortho_right,
cam->ortho_bottom, cam->ortho_top,
cam->near, cam->far
@ -183,8 +184,8 @@ void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offs
cam->position = pxl8_vec3_lerp(cam->position, desired, t);
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position));
cam->pitch = asinf(-forward.y);
cam->yaw = atan2f(forward.x, forward.z);
cam->pitch = asinf(forward.y);
cam->yaw = atan2f(-forward.x, -forward.z);
}
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) {
@ -212,3 +213,30 @@ void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
}
}
}
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height) {
pxl8_projected_point result = {0, 0, 0.0f, false};
if (!cam) return result;
pxl8_mat4 view = pxl8_3d_camera_get_view(cam);
pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam);
pxl8_mat4 vp = pxl8_mat4_multiply(proj, view);
pxl8_vec4 clip = pxl8_mat4_multiply_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f});
if (clip.w <= 0.0f) return result;
f32 inv_w = 1.0f / clip.w;
f32 ndc_x = clip.x * inv_w;
f32 ndc_y = clip.y * inv_w;
f32 ndc_z = clip.z * inv_w;
if (ndc_x < -1.0f || ndc_x > 1.0f || ndc_y < -1.0f || ndc_y > 1.0f) return result;
result.x = (i32)((ndc_x + 1.0f) * 0.5f * (f32)screen_width);
result.y = (i32)((1.0f - ndc_y) * 0.5f * (f32)screen_height);
result.depth = ndc_z;
result.visible = true;
return result;
}

View file

@ -32,6 +32,7 @@ pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t);
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration);
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);

View file

@ -7,6 +7,7 @@
#include "pxl8_atlas.h"
#include "pxl8_gfx.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_ANIM_MAX_STATES 32
@ -36,36 +37,36 @@ pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u1
return NULL;
}
pxl8_anim* anim = (pxl8_anim*)calloc(1, sizeof(pxl8_anim));
pxl8_anim* anim = (pxl8_anim*)pxl8_calloc(1, sizeof(pxl8_anim));
if (!anim) {
pxl8_error("Failed to allocate animation");
return NULL;
}
anim->frame_ids = (u32*)malloc(frame_count * sizeof(u32));
anim->frame_ids = (u32*)pxl8_malloc(frame_count * sizeof(u32));
if (!anim->frame_ids) {
pxl8_error("Failed to allocate frame IDs");
free(anim);
pxl8_free(anim);
return NULL;
}
memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32));
if (frame_durations) {
anim->frame_durations = (u16*)malloc(frame_count * sizeof(u16));
anim->frame_durations = (u16*)pxl8_malloc(frame_count * sizeof(u16));
if (!anim->frame_durations) {
pxl8_error("Failed to allocate frame durations");
free(anim->frame_ids);
free(anim);
pxl8_free(anim->frame_ids);
pxl8_free(anim);
return NULL;
}
memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16));
} else {
anim->frame_durations = (u16*)calloc(frame_count, sizeof(u16));
anim->frame_durations = (u16*)pxl8_calloc(frame_count, sizeof(u16));
if (!anim->frame_durations) {
pxl8_error("Failed to allocate frame durations");
free(anim->frame_ids);
free(anim);
pxl8_free(anim->frame_ids);
pxl8_free(anim);
return NULL;
}
for (u16 i = 0; i < frame_count; i++) {
@ -107,12 +108,12 @@ pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
return NULL;
}
u32* frame_ids = (u32*)malloc(ase_file.frame_count * sizeof(u32));
u16* frame_durations = (u16*)malloc(ase_file.frame_count * sizeof(u16));
u32* frame_ids = (u32*)pxl8_malloc(ase_file.frame_count * sizeof(u32));
u16* frame_durations = (u16*)pxl8_malloc(ase_file.frame_count * sizeof(u16));
if (!frame_ids || !frame_durations) {
pxl8_error("Failed to allocate frame arrays");
free(frame_ids);
free(frame_durations);
pxl8_free(frame_ids);
pxl8_free(frame_durations);
pxl8_ase_destroy(&ase_file);
return NULL;
}
@ -122,8 +123,8 @@ pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height);
if (result != PXL8_OK) {
pxl8_error("Failed to create texture for frame %u", i);
free(frame_ids);
free(frame_durations);
pxl8_free(frame_ids);
pxl8_free(frame_durations);
pxl8_ase_destroy(&ase_file);
return NULL;
}
@ -133,8 +134,8 @@ pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count);
free(frame_ids);
free(frame_durations);
pxl8_free(frame_ids);
pxl8_free(frame_durations);
pxl8_ase_destroy(&ase_file);
return anim;
@ -145,15 +146,15 @@ void pxl8_anim_destroy(pxl8_anim* anim) {
if (anim->state_machine) {
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
free(anim->state_machine->states[i].name);
pxl8_free(anim->state_machine->states[i].name);
pxl8_anim_destroy(anim->state_machine->states[i].anim);
}
free(anim->state_machine);
pxl8_free(anim->state_machine);
}
free(anim->frame_ids);
free(anim->frame_durations);
free(anim);
pxl8_free(anim->frame_ids);
pxl8_free(anim->frame_durations);
pxl8_free(anim);
}
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) {
@ -162,7 +163,7 @@ pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* st
}
if (!anim->state_machine) {
anim->state_machine = (pxl8_anim_state_machine*)calloc(1, sizeof(pxl8_anim_state_machine));
anim->state_machine = (pxl8_anim_state_machine*)pxl8_calloc(1, sizeof(pxl8_anim_state_machine));
if (!anim->state_machine) {
return PXL8_ERROR_OUT_OF_MEMORY;
}

View file

@ -7,6 +7,7 @@
#include "pxl8_color.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
typedef struct pxl8_skyline_fit {
bool found;
@ -27,6 +28,8 @@ typedef struct pxl8_skyline {
struct pxl8_atlas {
u32 height, width;
u8* pixels;
u8* pixels_tiled;
u32 tiled_capacity, tiled_size;
bool dirty;
@ -103,7 +106,7 @@ static bool pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w,
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
u32 new_capacity = (skyline->count - nodes_to_remove + 1) * 2;
pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)realloc(
pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)pxl8_realloc(
skyline->nodes,
new_capacity * sizeof(pxl8_skyline_node)
);
@ -142,44 +145,44 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
}
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas));
pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas));
if (!atlas) return NULL;
atlas->height = height;
atlas->width = width;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel);
atlas->pixels = (u8*)pxl8_calloc(width * height, bytes_per_pixel);
if (!atlas->pixels) {
free(atlas);
pxl8_free(atlas);
return NULL;
}
atlas->entry_capacity = PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY;
atlas->entries = (pxl8_atlas_entry*)calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
atlas->entries = (pxl8_atlas_entry*)pxl8_calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
if (!atlas->entries) {
free(atlas->pixels);
free(atlas);
pxl8_free(atlas->pixels);
pxl8_free(atlas);
return NULL;
}
atlas->free_capacity = 16;
atlas->free_list = (u32*)calloc(atlas->free_capacity, sizeof(u32));
atlas->free_list = (u32*)pxl8_calloc(atlas->free_capacity, sizeof(u32));
if (!atlas->free_list) {
free(atlas->entries);
free(atlas->pixels);
free(atlas);
pxl8_free(atlas->entries);
pxl8_free(atlas->pixels);
pxl8_free(atlas);
return NULL;
}
atlas->skyline.capacity = 16;
atlas->skyline.nodes =
(pxl8_skyline_node*)calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
(pxl8_skyline_node*)pxl8_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
if (!atlas->skyline.nodes) {
free(atlas->free_list);
free(atlas->entries);
free(atlas->pixels);
free(atlas);
pxl8_free(atlas->free_list);
pxl8_free(atlas->entries);
pxl8_free(atlas->pixels);
pxl8_free(atlas);
return NULL;
}
@ -192,11 +195,12 @@ pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode)
void pxl8_atlas_destroy(pxl8_atlas* atlas) {
if (!atlas) return;
free(atlas->entries);
free(atlas->free_list);
free(atlas->pixels);
free(atlas->skyline.nodes);
free(atlas);
pxl8_free(atlas->entries);
pxl8_free(atlas->free_list);
pxl8_free(atlas->pixels);
pxl8_free(atlas->pixels_tiled);
pxl8_free(atlas->skyline.nodes);
pxl8_free(atlas);
}
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
@ -209,6 +213,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
atlas->entry_count = preserve_count;
atlas->free_count = 0;
if (preserve_count == 0) {
atlas->tiled_size = 0;
} else {
pxl8_atlas_entry* last = &atlas->entries[preserve_count - 1];
atlas->tiled_size = last->tiled_base + (u32)(last->w * last->h);
}
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
atlas->skyline.count = 1;
@ -222,13 +233,13 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
u32 new_size = atlas->width * 2;
u32 old_width = atlas->width;
u8* new_pixels = (u8*)calloc(new_size * new_size, bytes_per_pixel);
u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, bytes_per_pixel);
if (!new_pixels) return false;
pxl8_skyline new_skyline;
new_skyline.nodes = (pxl8_skyline_node*)calloc(16, sizeof(pxl8_skyline_node));
new_skyline.nodes = (pxl8_skyline_node*)pxl8_calloc(16, sizeof(pxl8_skyline_node));
if (!new_skyline.nodes) {
free(new_pixels);
pxl8_free(new_pixels);
return false;
}
@ -248,8 +259,8 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
);
if (!fit.found) {
free(new_skyline.nodes);
free(new_pixels);
pxl8_free(new_skyline.nodes);
pxl8_free(new_pixels);
return false;
}
@ -269,15 +280,15 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
atlas->entries[i].y = fit.pos.y;
if (!pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h)) {
free(new_skyline.nodes);
free(new_pixels);
pxl8_free(new_skyline.nodes);
pxl8_free(new_pixels);
return false;
}
pxl8_skyline_compact(&new_skyline);
}
free(atlas->pixels);
free(atlas->skyline.nodes);
pxl8_free(atlas->pixels);
pxl8_free(atlas->skyline.nodes);
atlas->pixels = new_pixels;
atlas->skyline = new_skyline;
@ -316,7 +327,7 @@ u32 pxl8_atlas_add_texture(
} else {
if (atlas->entry_count >= atlas->entry_capacity) {
u32 new_capacity = atlas->entry_capacity * 2;
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)realloc(
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)pxl8_realloc(
atlas->entries,
new_capacity * sizeof(pxl8_atlas_entry)
);
@ -334,6 +345,7 @@ u32 pxl8_atlas_add_texture(
entry->y = fit.pos.y;
entry->w = w;
entry->h = h;
entry->log2_w = pxl8_log2(w);
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
for (u32 y = 0; y < h; y++) {
@ -349,6 +361,30 @@ u32 pxl8_atlas_add_texture(
}
}
u32 tiled_tex_size = w * h;
u32 new_tiled_size = atlas->tiled_size + tiled_tex_size;
if (new_tiled_size > atlas->tiled_capacity) {
u32 new_cap = atlas->tiled_capacity ? atlas->tiled_capacity * 2 : 4096;
while (new_cap < new_tiled_size) new_cap *= 2;
u8* new_tiled = (u8*)pxl8_realloc(atlas->pixels_tiled, new_cap);
if (!new_tiled) {
entry->active = false;
return UINT32_MAX;
}
atlas->pixels_tiled = new_tiled;
atlas->tiled_capacity = new_cap;
}
entry->tiled_base = atlas->tiled_size;
u8* tiled_dst = atlas->pixels_tiled + entry->tiled_base;
for (u32 ty = 0; ty < h; ty++) {
for (u32 tx = 0; tx < w; tx++) {
u32 tiled_offset = pxl8_tile_addr(tx, ty, entry->log2_w);
tiled_dst[tiled_offset] = pixels[ty * w + tx];
}
}
atlas->tiled_size = new_tiled_size;
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
entry->active = false;
return UINT32_MAX;
@ -377,6 +413,10 @@ const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels : NULL;
}
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels_tiled : NULL;
}
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) {
return atlas ? atlas->width : 0;
}

View file

@ -8,28 +8,42 @@ typedef struct pxl8_atlas_entry {
bool active;
u32 texture_id;
i32 x, y, w, h;
u32 tiled_base;
u8 log2_w;
} pxl8_atlas_entry;
static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) {
u32 tile_y = v >> 3;
u32 tile_x = u >> 3;
u32 local_y = v & 7;
u32 local_x = u & 7;
return (tile_y << (log2_w + 3)) | (tile_x << 6) | (local_y << 3) | local_x;
}
static inline u8 pxl8_log2(u32 v) {
u8 r = 0;
while (v >>= 1) r++;
return r;
}
#ifdef __cplusplus
extern "C" {
#endif
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_destroy(pxl8_atlas* atlas);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas);
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
#ifdef __cplusplus
}
#endif

View file

@ -1,194 +0,0 @@
#include "pxl8_blend.h"
#include "pxl8_colormap.h"
#include <stdlib.h>
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
struct pxl8_additive_table {
u8 table[PXL8_BLEND_TABLE_SIZE];
};
struct pxl8_overbright_table {
u8 table[PXL8_OVERBRIGHT_TABLE_SIZE];
};
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
i32 dr = (i32)r - (i32)pr;
i32 dg = (i32)g - (i32)pg;
i32 db = (i32)b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal) {
pxl8_additive_table* table = calloc(1, sizeof(pxl8_additive_table));
if (!table) return NULL;
pxl8_additive_table_rebuild(table, pal);
return table;
}
void pxl8_additive_table_destroy(pxl8_additive_table* table) {
free(table);
}
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 src = 0; src < 256; src++) {
u8 sr, sg, sb;
pxl8_palette_get_rgb(pal, (u8)src, &sr, &sg, &sb);
for (u32 dst = 0; dst < 256; dst++) {
u32 idx = src * 256 + dst;
if (src == PXL8_TRANSPARENT) {
table->table[idx] = (u8)dst;
continue;
}
u8 dr, dg, db;
pxl8_palette_get_rgb(pal, (u8)dst, &dr, &dg, &db);
u16 ar = (u16)sr + (u16)dr;
u16 ag = (u16)sg + (u16)dg;
u16 ab = (u16)sb + (u16)db;
if (ar > 255) ar = 255;
if (ag > 255) ag = 255;
if (ab > 255) ab = 255;
table->table[idx] = find_closest_stable(pal, (u8)ar, (u8)ag, (u8)ab);
}
}
}
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst) {
return table->table[(u32)src * 256 + dst];
}
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal) {
pxl8_overbright_table* table = calloc(1, sizeof(pxl8_overbright_table));
if (!table) return NULL;
pxl8_overbright_table_rebuild(table, pal);
return table;
}
void pxl8_overbright_table_destroy(pxl8_overbright_table* table) {
free(table);
}
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 level = 0; level < PXL8_OVERBRIGHT_LEVELS; level++) {
f32 overbright = (f32)level / (f32)(PXL8_OVERBRIGHT_LEVELS - 1);
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u32 idx = level * 256 + pal_idx;
if (pal_idx == PXL8_TRANSPARENT) {
table->table[idx] = PXL8_TRANSPARENT;
continue;
}
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)pal_idx, &r, &g, &b);
u8 or = (u8)(r + (255 - r) * overbright);
u8 og = (u8)(g + (255 - g) * overbright);
u8 ob = (u8)(b + (255 - b) * overbright);
table->table[idx] = find_closest_stable(pal, or, og, ob);
}
}
}
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive) {
u32 level = (u32)(emissive * (PXL8_OVERBRIGHT_LEVELS - 1));
if (level >= PXL8_OVERBRIGHT_LEVELS) level = PXL8_OVERBRIGHT_LEVELS - 1;
return table->table[level * 256 + pal_idx];
}

View file

@ -1,39 +0,0 @@
#pragma once
#include "pxl8_palette.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
#define PXL8_BLEND_TABLE_SIZE (256 * 256)
#define PXL8_OVERBRIGHT_LEVELS 16
#define PXL8_OVERBRIGHT_TABLE_SIZE (256 * PXL8_OVERBRIGHT_LEVELS)
typedef struct pxl8_additive_table pxl8_additive_table;
typedef struct pxl8_overbright_table pxl8_overbright_table;
typedef struct pxl8_palette_cube pxl8_palette_cube;
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal);
void pxl8_additive_table_destroy(pxl8_additive_table* table);
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal);
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst);
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal);
void pxl8_overbright_table_destroy(pxl8_overbright_table* table);
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal);
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus
}
#endif

View file

@ -1,42 +1,12 @@
#include "pxl8_colormap.h"
#include <string.h>
static void rgb_to_hsl(u8 r, u8 g, u8 b, i32* h, i32* s, i32* l) {
i32 max = r > g ? (r > b ? r : b) : (g > b ? g : b);
i32 min = r < g ? (r < b ? r : b) : (g < b ? g : b);
i32 chroma = max - min;
*l = (max + min) / 2;
if (chroma == 0) {
*h = 0;
*s = 0;
} else {
i32 denom = 255 - (*l > 127 ? 2 * (*l) - 255 : 255 - 2 * (*l));
if (denom <= 0) denom = 1;
*s = (chroma * 255) / denom;
if (*s > 255) *s = 255;
if (max == (i32)r) {
*h = (((i32)g - (i32)b) * 60) / chroma;
if (*h < 0) *h += 360;
} else if (max == (i32)g) {
*h = 120 + (((i32)b - (i32)r) * 60) / chroma;
} else {
*h = 240 + (((i32)r - (i32)g) * 60) / chroma;
}
}
}
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
i32 th, ts, tl;
rgb_to_hsl(target_r, target_g, target_b, &th, &ts, &tl);
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
@ -47,17 +17,10 @@ static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 ta
u8 pg = (c >> 8) & 0xFF;
u8 pb = (c >> 16) & 0xFF;
i32 ph, ps, pl;
rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl);
i32 dh = th - ph;
if (dh > 180) dh -= 360;
if (dh < -180) dh += 360;
i32 ds = ts - ps;
i32 dl = tl - pl;
u32 dist = (u32)(dh * dh * 4 + ds * ds + dl * dl);
i32 dr = (i32)target_r - (i32)pr;
i32 dg = (i32)target_g - (i32)pg;
i32 db = (i32)target_b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
@ -69,26 +32,19 @@ static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 ta
return best_idx;
}
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint) {
if (!cm || !palette) return;
void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size) {
if (!cm || !data || size == 0) return;
u32 copy_size = size > PXL8_COLORMAP_SIZE ? PXL8_COLORMAP_SIZE : size;
memcpy(cm->table, data, copy_size);
}
u8 dark_r, dark_g, dark_b;
if (tint && tint->tint_strength > 0.0f) {
f32 t = tint->tint_strength;
f32 inv = 1.0f - t;
dark_r = (u8)(tint->dark_r * inv + tint->tint_r * t);
dark_g = (u8)(tint->dark_g * inv + tint->tint_g * t);
dark_b = (u8)(tint->dark_b * inv + tint->tint_b * t);
} else if (tint) {
dark_r = tint->dark_r;
dark_g = tint->dark_g;
dark_b = tint->dark_b;
} else {
dark_r = dark_g = dark_b = 0;
}
static void generate_light_table(pxl8_colormap* cm, const u32* palette, pxl8_light_color light_color) {
pxl8_rgb light = pxl8_light_colors[light_color];
u32 base_row = (u32)light_color * PXL8_LIGHT_LEVELS;
for (u32 light = 0; light < PXL8_LIGHT_LEVELS; light++) {
f32 brightness = (f32)light / (f32)(PXL8_LIGHT_LEVELS - 1);
for (u32 level = 0; level < PXL8_LIGHT_LEVELS; level++) {
f32 brightness = (f32)level / (f32)(PXL8_LIGHT_LEVELS - 1);
u32 row = base_row + level;
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u8 result_idx;
@ -103,14 +59,65 @@ void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_le
u8 g = (c >> 8) & 0xFF;
u8 b = (c >> 16) & 0xFF;
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness);
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness);
u8 target_b = (u8)(dark_b + (b - dark_b) * brightness);
f32 lr = (f32)light.r / 255.0f;
f32 lg = (f32)light.g / 255.0f;
f32 lb = (f32)light.b / 255.0f;
u8 target_r = (u8)(r * brightness * lr);
u8 target_g = (u8)(g * brightness * lg);
u8 target_b = (u8)(b * brightness * lb);
result_idx = find_closest_color(palette, target_r, target_g, target_b);
}
cm->table[light * 256 + pal_idx] = result_idx;
cm->table[row * 256 + pal_idx] = result_idx;
}
}
}
static void generate_blend_table(pxl8_colormap* cm, const u32* palette) {
for (u32 src = 0; src < 256; src++) {
u32 row = PXL8_LIGHT_ROWS + src;
u8 sr, sg, sb;
if (src == PXL8_TRANSPARENT) {
sr = sg = sb = 0;
} else {
u32 sc = palette[src];
sr = sc & 0xFF;
sg = (sc >> 8) & 0xFF;
sb = (sc >> 16) & 0xFF;
}
for (u32 dst = 0; dst < 256; dst++) {
u8 result_idx;
if (src == PXL8_TRANSPARENT) {
result_idx = (u8)dst;
} else {
u32 dc = palette[dst];
u8 dr = dc & 0xFF;
u8 dg = (dc >> 8) & 0xFF;
u8 db = (dc >> 16) & 0xFF;
u8 blend_r = (u8)((sr + dr) / 2);
u8 blend_g = (u8)((sg + dg) / 2);
u8 blend_b = (u8)((sb + db) / 2);
result_idx = find_closest_color(palette, blend_r, blend_g, blend_b);
}
cm->table[row * 256 + dst] = result_idx;
}
}
}
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette) {
if (!cm || !palette) return;
for (u32 light = 0; light < PXL8_LIGHT_COLORS; light++) {
generate_light_table(cm, palette, (pxl8_light_color)light);
}
generate_blend_table(cm, palette);
}

View file

@ -7,35 +7,65 @@
extern "C" {
#endif
#define PXL8_LIGHT_LEVELS 64
#define PXL8_COLORMAP_SIZE (256 * PXL8_LIGHT_LEVELS)
#define PXL8_LIGHT_COLORS 8
#define PXL8_LIGHT_LEVELS 8
#define PXL8_LIGHT_ROWS (PXL8_LIGHT_COLORS * PXL8_LIGHT_LEVELS)
#define PXL8_BLEND_ROWS 256
#define PXL8_COLORMAP_ROWS (PXL8_LIGHT_ROWS + PXL8_BLEND_ROWS)
#define PXL8_COLORMAP_SIZE (256 * PXL8_COLORMAP_ROWS)
#define PXL8_FULLBRIGHT_START 240
#define PXL8_TRANSPARENT 0
#define PXL8_DYNAMIC_RANGE_START 144
#define PXL8_DYNAMIC_RANGE_COUNT 16
typedef enum {
PXL8_LIGHT_WHITE = 0,
PXL8_LIGHT_RED = 1,
PXL8_LIGHT_ORANGE = 2,
PXL8_LIGHT_YELLOW = 3,
PXL8_LIGHT_GREEN = 4,
PXL8_LIGHT_CYAN = 5,
PXL8_LIGHT_BLUE = 6,
PXL8_LIGHT_PURPLE = 7,
} pxl8_light_color;
typedef struct {
u8 r, g, b;
} pxl8_rgb;
static const pxl8_rgb pxl8_light_colors[PXL8_LIGHT_COLORS] = {
{255, 255, 255},
{255, 64, 64},
{255, 160, 64},
{255, 255, 64},
{64, 255, 64},
{64, 255, 255},
{64, 64, 255},
{255, 64, 255},
};
typedef struct {
u8 table[PXL8_COLORMAP_SIZE];
} pxl8_colormap;
typedef struct {
u8 dark_r, dark_g, dark_b;
u8 tint_r, tint_g, tint_b;
f32 tint_strength;
} pxl8_level_tint;
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette);
void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint);
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, u8 light) {
u32 light_idx = light >> 2;
return cm->table[light_idx * 256 + pal_idx];
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity) {
u32 light_row = ((u32)light_color << 3) + (intensity >> 5);
return cm->table[(light_row << 8) + pal_idx];
}
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, u8 light, u32 x, u32 y) {
u8 dithered = pxl8_dither_light(light, x, y);
u32 light_idx = dithered >> 2;
return cm->table[light_idx * 256 + pal_idx];
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity, u32 x, u32 y) {
u8 dithered = pxl8_dither_light(intensity, x, y);
u32 light_row = ((u32)light_color << 3) + (dithered >> 5);
return cm->table[(light_row << 8) + pal_idx];
}
static inline u8 pxl8_colormap_blend(const pxl8_colormap* cm, u8 src, u8 dst) {
u32 blend_row = PXL8_LIGHT_ROWS + src;
return cm->table[(blend_row << 8) + dst];
}
#ifdef __cplusplus

View file

@ -1,10 +1,10 @@
#include "pxl8_cpu.h"
#include "pxl8_mem.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_simd.h"
struct pxl8_cpu_render_target {
u8* framebuffer;
u32 height;
@ -72,8 +72,8 @@ static pxl8_light_result calc_vertex_light(
if (sky_factor < 0.0f) sky_factor = 0.0f;
intensity += sky_factor * frame->uniforms.celestial_intensity * 0.3f;
for (u32 i = 0; i < frame->uniforms.num_lights; i++) {
const pxl8_light* light = &frame->uniforms.lights[i];
for (u32 i = 0; i < frame->lights_count; i++) {
const pxl8_light* light = &frame->lights[i];
f32 contrib = calc_light_intensity(light, world_pos, normal);
if (contrib > 0.0f) {
intensity += contrib;
@ -119,6 +119,7 @@ struct pxl8_cpu_backend {
pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK];
u32 target_stack_depth;
const pxl8_colormap* colormap;
const pxl8_palette_cube* palette_cube;
const u32* palette;
pxl8_3d_frame frame;
pxl8_mat4 mvp;
@ -173,7 +174,7 @@ static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h
}
pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
pxl8_cpu_backend* cpu = calloc(1, sizeof(pxl8_cpu_backend));
pxl8_cpu_backend* cpu = pxl8_calloc(1, sizeof(pxl8_cpu_backend));
if (!cpu) return NULL;
pxl8_cpu_render_target_desc desc = {
@ -184,7 +185,7 @@ pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
};
pxl8_cpu_render_target* base_target = pxl8_cpu_create_render_target(&desc);
if (!base_target) {
free(cpu);
pxl8_free(cpu);
return NULL;
}
@ -193,10 +194,10 @@ pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
cpu->current_target = base_target;
cpu->output_size = width * height;
cpu->output = calloc(cpu->output_size, sizeof(u32));
cpu->output = pxl8_calloc(cpu->output_size, sizeof(u32));
if (!cpu->output) {
pxl8_cpu_destroy_render_target(base_target);
free(cpu);
pxl8_free(cpu);
return NULL;
}
@ -208,14 +209,14 @@ void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) {
for (u32 i = 0; i < cpu->target_stack_depth; i++) {
pxl8_cpu_destroy_render_target(cpu->target_stack[i]);
}
free(cpu->output);
free(cpu);
pxl8_free(cpu->output);
pxl8_free(cpu);
}
void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) {
if (!cpu || !frame) return;
cpu->frame = *frame;
cpu->mvp = pxl8_mat4_mul(frame->projection, frame->view);
cpu->mvp = pxl8_mat4_multiply(frame->projection, frame->view);
}
void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) {
@ -231,14 +232,12 @@ void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color) {
void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) {
if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target;
u32 count = render_target->width * render_target->height;
if (render_target->zbuffer) {
u32 count = render_target->width * render_target->height;
for (u32 i = 0; i < count; i++) {
render_target->zbuffer[i] = 0xFFFF;
}
memset(render_target->zbuffer, 0xFF, count * sizeof(u16));
}
if (render_target->light_accum) {
memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32));
memset(render_target->light_accum, 0, count * sizeof(u32));
}
}
@ -246,6 +245,10 @@ void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm) {
if (cpu) cpu->colormap = cm;
}
void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube) {
if (cpu) cpu->palette_cube = cube;
}
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) {
if (cpu) cpu->palette = palette;
}
@ -366,8 +369,8 @@ void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8
if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target;
pxl8_vec4 c0 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f});
pxl8_vec4 c0 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f});
if (c0.w <= 0.0f || c1.w <= 0.0f) return;
@ -699,6 +702,22 @@ static void rasterize_triangle_opaque(
f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f;
@ -727,7 +746,7 @@ static void rasterize_triangle_opaque(
if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y);
}
u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx;
u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
prow[px] = pal_idx;
zrow[px] = z16;
if (light_accum) {
@ -948,6 +967,22 @@ static void rasterize_triangle_alpha(
f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f;
@ -974,7 +1009,7 @@ static void rasterize_triangle_alpha(
if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y);
}
u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx;
u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
if (mat_alpha >= 128) {
prow[px] = src_idx;
@ -1002,11 +1037,42 @@ static void rasterize_triangle_alpha(
}
}
static void rasterize_triangle_wireframe(
pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
u8 color, bool double_sided
) {
pxl8_cpu_render_target* render_target = cpu->current_target;
f32 hw = (f32)render_target->width * 0.5f;
f32 hh = (f32)render_target->height * 0.5f;
i32 x0 = (i32)(hw + vo0->clip_pos.x / vo0->clip_pos.w * hw);
i32 y0 = (i32)(hh - vo0->clip_pos.y / vo0->clip_pos.w * hh);
i32 x1 = (i32)(hw + vo1->clip_pos.x / vo1->clip_pos.w * hw);
i32 y1 = (i32)(hh - vo1->clip_pos.y / vo1->clip_pos.w * hh);
i32 x2 = (i32)(hw + vo2->clip_pos.x / vo2->clip_pos.w * hw);
i32 y2 = (i32)(hh - vo2->clip_pos.y / vo2->clip_pos.w * hh);
if (!double_sided) {
i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
if (cross >= 0) return;
}
pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color);
pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color);
pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color);
}
static void dispatch_triangle(
pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
const pxl8_atlas* textures, const pxl8_material* material
const pxl8_atlas* textures, const pxl8_gfx_material* material
) {
if (material->wireframe) {
rasterize_triangle_wireframe(cpu, vo0, vo1, vo2, 15, material->double_sided);
return;
}
pxl8_cpu_render_target* render_target = cpu->current_target;
tri_setup setup;
if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) {
@ -1029,13 +1095,13 @@ void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
const pxl8_mat4* model,
const pxl8_material* material,
const pxl8_gfx_material* material,
const pxl8_atlas* textures
) {
if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return;
pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, *model);
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, mv);
pxl8_mat4 mv = pxl8_mat4_multiply(cpu->frame.view, *model);
pxl8_mat4 mvp = pxl8_mat4_multiply(cpu->frame.projection, mv);
f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f;
@ -1054,13 +1120,13 @@ void pxl8_cpu_draw_mesh(
pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->position.z, 1.0f};
pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f};
vo0.clip_pos = pxl8_mat4_mul_vec4(mvp, p0);
vo1.clip_pos = pxl8_mat4_mul_vec4(mvp, p1);
vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2);
vo0.clip_pos = pxl8_mat4_multiply_vec4(mvp, p0);
vo1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1);
vo2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2);
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(*model, p0);
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(*model, p1);
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(*model, p2);
pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(*model, p0);
pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(*model, p1);
pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(*model, p2);
vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z};
vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z};
vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z};
@ -1078,9 +1144,9 @@ void pxl8_cpu_draw_mesh(
vo2.color = v2->color;
if (material->dynamic_lighting) {
pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v0->normal));
pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v1->normal));
pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v2->normal));
pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v0->normal));
pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v1->normal));
pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v2->normal));
pxl8_light_result lr0 = calc_vertex_light(vo0.world_pos, n0, &cpu->frame);
pxl8_light_result lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame);
@ -1111,44 +1177,6 @@ void pxl8_cpu_draw_mesh(
}
}
void pxl8_cpu_draw_mesh_wireframe(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
u8 color
) {
if (!cpu || !cpu->current_target || !mesh || mesh->index_count < 3) return;
pxl8_cpu_render_target* render_target = cpu->current_target;
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, pxl8_mat4_mul(cpu->frame.view, model));
for (u32 i = 0; i < mesh->index_count; i += 3) {
const pxl8_vertex* v0 = &mesh->vertices[mesh->indices[i]];
const pxl8_vertex* v1 = &mesh->vertices[mesh->indices[i + 1]];
const pxl8_vertex* v2 = &mesh->vertices[mesh->indices[i + 2]];
pxl8_vec4 c0 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v0->position.x, v0->position.y, v0->position.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v1->position.x, v1->position.y, v1->position.z, 1.0f});
pxl8_vec4 c2 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v2->position.x, v2->position.y, v2->position.z, 1.0f});
if (c0.w <= 0.0f || c1.w <= 0.0f || c2.w <= 0.0f) continue;
f32 hw = (f32)render_target->width * 0.5f;
f32 hh = (f32)render_target->height * 0.5f;
i32 x0 = (i32)(hw + c0.x / c0.w * hw);
i32 y0 = (i32)(hh - c0.y / c0.w * hh);
i32 x1 = (i32)(hw + c1.x / c1.w * hw);
i32 y1 = (i32)(hh - c1.y / c1.w * hh);
i32 x2 = (i32)(hw + c2.x / c2.w * hw);
i32 y2 = (i32)(hh - c2.y / c2.w * hh);
pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color);
pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color);
pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color);
}
}
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) {
if (!cpu || cpu->target_stack_depth == 0) return NULL;
return cpu->target_stack[0]->framebuffer;
@ -1167,33 +1195,33 @@ u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu) {
pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc) {
if (!desc) return NULL;
pxl8_cpu_render_target* target = calloc(1, sizeof(pxl8_cpu_render_target));
pxl8_cpu_render_target* target = pxl8_calloc(1, sizeof(pxl8_cpu_render_target));
if (!target) return NULL;
u32 size = desc->width * desc->height;
target->width = desc->width;
target->height = desc->height;
target->framebuffer = calloc(size, sizeof(u8));
target->framebuffer = pxl8_calloc(size, sizeof(u8));
if (!target->framebuffer) {
free(target);
pxl8_free(target);
return NULL;
}
if (desc->with_depth) {
target->zbuffer = calloc(size, sizeof(u16));
target->zbuffer = pxl8_calloc(size, sizeof(u16));
if (!target->zbuffer) {
free(target->framebuffer);
free(target);
pxl8_free(target->framebuffer);
pxl8_free(target);
return NULL;
}
}
if (desc->with_lighting) {
target->light_accum = calloc(size, sizeof(u32));
target->light_accum = pxl8_calloc(size, sizeof(u32));
if (!target->light_accum) {
free(target->zbuffer);
free(target->framebuffer);
free(target);
pxl8_free(target->zbuffer);
pxl8_free(target->framebuffer);
pxl8_free(target);
return NULL;
}
}
@ -1203,10 +1231,10 @@ pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_targ
void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target) {
if (!target) return;
free(target->light_accum);
free(target->zbuffer);
free(target->framebuffer);
free(target);
pxl8_free(target->light_accum);
pxl8_free(target->zbuffer);
pxl8_free(target->framebuffer);
pxl8_free(target);
}
pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu) {
@ -1237,10 +1265,12 @@ void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i3
for (i32 row = 0; row < copy_h; row++) {
u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0;
u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0;
u32* light_row = dst->light_accum ? dst->light_accum + (dst_y0 + row) * dst->width + dst_x0 : NULL;
for (i32 col = 0; col < copy_w; col++) {
u8 pixel = src_row[col];
if (pixel != transparent_idx) {
dst_row[col] = pixel;
if (light_row) light_row[col] = 0;
}
}
}
@ -1300,13 +1330,9 @@ u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu) {
void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows,
u32 glow_count,
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
const pxl8_glow* glows,
u32 glow_count
) {
(void)overbright;
if (!cpu || cpu->target_stack_depth == 0) return;
pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1];
@ -1319,7 +1345,7 @@ void pxl8_cpu_render_glows(
u32* light_accum = target->light_accum;
for (u32 gi = 0; gi < glow_count; gi++) {
const pxl8_glow_source* glow = &glows[gi];
const pxl8_glow* glow = &glows[gi];
i32 cx = glow->x;
i32 cy = glow->y;
i32 radius = glow->radius;
@ -1392,25 +1418,21 @@ void pxl8_cpu_render_glows(
u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255;
u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y);
// Skip very dark pixels to create stippled transparency effect
if (light_level < 8) continue;
u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, light_level);
u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, PXL8_LIGHT_WHITE, light_level);
if (intensity > 1.0f && palette_cube) {
f32 over = intensity - 1.0f;
if (over > 1.0f) over = 1.0f;
u8 r, g, b;
pxl8_palette_cube_get_rgb(palette_cube, shaded, &r, &g, &b);
u8 or = (u8)(r + (255 - r) * over);
u8 og = (u8)(g + (255 - g) * over);
u8 ob = (u8)(b + (255 - b) * over);
shaded = pxl8_palette_cube_lookup_stable(palette_cube, or, og, ob);
}
u8 dst = pixels[idx];
if (additive) {
pixels[idx] = pxl8_additive_blend(additive, shaded, dst);
if (cpu->palette_cube) {
u8 sr, sg, sb, dr, dg, db;
pxl8_palette_cube_get_rgb(cpu->palette_cube, shaded, &sr, &sg, &sb);
pxl8_palette_cube_get_rgb(cpu->palette_cube, pixels[idx], &dr, &dg, &db);
u32 r = (u32)sr + (u32)dr;
u32 g = (u32)sg + (u32)dg;
u32 b = (u32)sb + (u32)db;
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
pixels[idx] = pxl8_palette_cube_lookup(cpu->palette_cube, (u8)r, (u8)g, (u8)b);
} else {
pixels[idx] = shaded;
}

View file

@ -1,12 +1,13 @@
#pragma once
#include "pxl8_atlas.h"
#include "pxl8_blend.h"
#include "pxl8_colormap.h"
#include "pxl8_gfx.h"
#include "pxl8_gfx3d.h"
#include "pxl8_lightmap.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_palette.h"
#include "pxl8_types.h"
#ifdef __cplusplus
@ -34,6 +35,10 @@ void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu);
void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm);
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette);
void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube);
u32 pxl8_cpu_add_lightmap(pxl8_cpu_backend* cpu, pxl8_lightmap* lm);
pxl8_lightmap* pxl8_cpu_get_lightmap(pxl8_cpu_backend* cpu, u32 id);
void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color);
u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y);
@ -48,17 +53,10 @@ void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
const pxl8_mat4* model,
const pxl8_material* material,
const pxl8_gfx_material* material,
const pxl8_atlas* textures
);
void pxl8_cpu_draw_mesh_wireframe(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
u8 color
);
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu);
u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu);
u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu);
@ -66,11 +64,8 @@ u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu);
void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows,
u32 glow_count,
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
const pxl8_glow* glows,
u32 glow_count
);
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu);

View file

@ -1,4 +1,5 @@
#include "pxl8_font.h"
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
@ -15,7 +16,7 @@ pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32*
*atlas_height = rows_needed * font->default_height;
i32 atlas_size = (*atlas_width) * (*atlas_height);
*atlas_data = (u8*)malloc(atlas_size);
*atlas_data = (u8*)pxl8_malloc(atlas_size);
if (!*atlas_data) {
return PXL8_ERROR_OUT_OF_MEMORY;
}

View file

@ -6,7 +6,6 @@
#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_backend.h"
#include "pxl8_blend.h"
#include "pxl8_blit.h"
#include "pxl8_color.h"
#include "pxl8_colormap.h"
@ -15,6 +14,7 @@
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
#include "pxl8_sys.h"
#include "pxl8_types.h"
@ -25,7 +25,6 @@ typedef struct pxl8_sprite_cache_entry {
} pxl8_sprite_cache_entry;
struct pxl8_gfx {
pxl8_additive_table* additive_table;
pxl8_atlas* atlas;
pxl8_gfx_backend backend;
pxl8_colormap* colormap;
@ -35,7 +34,6 @@ struct pxl8_gfx {
pxl8_frustum frustum;
const pxl8_hal* hal;
bool initialized;
pxl8_overbright_table* overbright_table;
pxl8_palette* palette;
pxl8_palette_cube* palette_cube;
pxl8_pixel_mode pixel_mode;
@ -44,6 +42,7 @@ struct pxl8_gfx {
u32 sprite_cache_capacity;
u32 sprite_cache_count;
pxl8_viewport viewport;
pxl8_mat4 view_proj;
};
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
@ -78,10 +77,14 @@ i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_width : 0;
}
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx) {
pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx) {
return gfx ? gfx->palette : NULL;
}
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx) {
return gfx ? gfx->colormap : NULL;
}
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->palette) return 0;
if (color <= 0xFFFFFF) color = (color << 8) | 0xFF;
@ -91,37 +94,24 @@ u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal) {
if (!gfx) return;
if (gfx->palette) {
pxl8_palette_destroy(gfx->palette);
}
gfx->palette = pal;
if (gfx->palette && gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(gfx->palette);
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
}
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
}
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count) {
if (!gfx || !gfx->palette || !colors) return;
pxl8_set_palette(gfx->palette, colors, count);
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR && gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
}
}
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
if (!gfx || !filepath) return -1;
pxl8_palette* pal = gfx->palette;
if (!pal) return -1;
pxl8_result result = pxl8_palette_load_ase(pal, filepath);
if (!gfx || !gfx->palette || !filepath) return -1;
pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
if (result != PXL8_OK) return (i32)result;
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(pal);
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
}
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
}
}
return 0;
@ -133,7 +123,7 @@ pxl8_gfx* pxl8_gfx_create(
pxl8_pixel_mode mode,
pxl8_resolution resolution
) {
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx));
pxl8_gfx* gfx = (pxl8_gfx*)pxl8_calloc(1, sizeof(pxl8_gfx));
if (!gfx) {
pxl8_error("Failed to allocate graphics context");
return NULL;
@ -149,7 +139,7 @@ pxl8_gfx* pxl8_gfx_create(
if (!gfx->platform_data) {
pxl8_error("Platform data cannot be NULL");
free(gfx);
pxl8_free(gfx);
return NULL;
}
@ -168,7 +158,7 @@ pxl8_gfx* pxl8_gfx_create(
gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu);
if (mode != PXL8_PIXEL_HICOLOR) {
gfx->colormap = calloc(1, sizeof(pxl8_colormap));
gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap));
if (gfx->colormap) {
pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap);
}
@ -187,18 +177,16 @@ pxl8_gfx* pxl8_gfx_create(
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
if (!gfx) return;
pxl8_additive_table_destroy(gfx->additive_table);
pxl8_atlas_destroy(gfx->atlas);
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_destroy(gfx->backend.cpu);
}
free(gfx->colormap);
pxl8_overbright_table_destroy(gfx->overbright_table);
pxl8_free(gfx->colormap);
pxl8_palette_cube_destroy(gfx->palette_cube);
pxl8_palette_destroy(gfx->palette);
free(gfx->sprite_cache);
pxl8_free(gfx->sprite_cache);
free(gfx);
pxl8_free(gfx);
}
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
@ -239,7 +227,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
if (!gfx->sprite_cache) {
gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY;
gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc(
gfx->sprite_cache = (pxl8_sprite_cache_entry*)pxl8_calloc(
gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry)
);
if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY;
@ -284,7 +272,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
u32 new_capacity = gfx->sprite_cache_capacity * 2;
pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc(
pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)pxl8_realloc(
gfx->sprite_cache,
new_capacity * sizeof(pxl8_sprite_cache_entry)
);
@ -523,6 +511,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (!gfx || !gfx->atlas) return;
u8* framebuffer = NULL;
u32* light_accum = NULL;
i32 fb_width = 0;
i32 fb_height = 0;
@ -531,6 +520,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
if (!render_target) return;
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
light_accum = pxl8_cpu_render_target_get_light_accum(render_target);
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
break;
@ -567,6 +557,11 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (is_1to1_scale && is_unclipped && !is_flipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
if (light_accum) {
for (i32 py = 0; py < h; py++) {
memset(light_accum + (y + py) * fb_width + x, 0, w * sizeof(u32));
}
}
} else {
for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) {
@ -580,6 +575,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
if (light_accum) light_accum[dest_idx] = 0;
}
}
}
@ -612,13 +608,17 @@ pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8
return frame;
}
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms) {
if (!gfx || !camera) return;
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
frame.lights = lights ? pxl8_lights_data(lights) : NULL;
frame.lights_count = lights ? pxl8_lights_count(lights) : 0;
pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view);
pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.view);
gfx->frustum = pxl8_frustum_from_matrix(vp);
gfx->view_proj = vp;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
@ -634,6 +634,38 @@ const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) {
return &gfx->frustum;
}
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return &gfx->view_proj;
}
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform) {
if (!gfx || !in || !out) return 0;
pxl8_mat4 mvp = transform ? pxl8_mat4_multiply(gfx->view_proj, *transform) : gfx->view_proj;
f32 hw = (f32)gfx->framebuffer_width * 0.5f;
f32 hh = (f32)gfx->framebuffer_height * 0.5f;
u32 visible = 0;
for (u32 i = 0; i < count; i++) {
pxl8_vec4 clip = pxl8_mat4_multiply_vec4(mvp, (pxl8_vec4){in[i].x, in[i].y, in[i].z, 1.0f});
if (clip.w <= 0.0f) {
out[i].z = -1.0f;
continue;
}
f32 inv_w = 1.0f / clip.w;
out[i].x = hw + clip.x * inv_w * hw;
out[i].y = hh - clip.y * inv_w * hh;
out[i].z = clip.w;
visible++;
}
return visible;
}
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) {
if (!gfx) return;
switch (gfx->backend.type) {
@ -667,7 +699,7 @@ void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
}
}
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) {
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) {
if (!gfx || !mesh || !model || !material) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
@ -678,17 +710,6 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
}
}
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color) {
if (!gfx || !mesh) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_mesh_wireframe(gfx->backend.cpu, mesh, model, color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
}
void pxl8_3d_end_frame(pxl8_gfx* gfx) {
if (!gfx) return;
switch (gfx->backend.type) {
@ -733,31 +754,27 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
}
}
static void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return;
if (!gfx->additive_table) {
gfx->additive_table = pxl8_additive_table_create(gfx->palette);
}
if (!gfx->overbright_table) {
gfx->overbright_table = pxl8_overbright_table_create(gfx->palette);
}
if (!gfx->palette_cube) {
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
}
}
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return;
if (gfx->additive_table) {
pxl8_additive_table_rebuild(gfx->additive_table, gfx->palette);
}
if (gfx->overbright_table) {
pxl8_overbright_table_rebuild(gfx->overbright_table, gfx->palette);
}
pxl8_gfx_ensure_blend_tables(gfx);
if (gfx->palette_cube) {
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
}
}
@ -765,29 +782,33 @@ void pxl8_gfx_colormap_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette || !gfx->colormap) return;
u32* colors = pxl8_palette_colors(gfx->palette);
if (colors) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
pxl8_colormap_generate(gfx->colormap, colors);
}
}
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b) {
if (!gfx || !gfx->palette) return 0;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
if (!gfx || !gfx->palette || index >= PXL8_UI_PALETTE_SIZE) return 0;
u32 abgr = pxl8_ui_palette[index];
u8 r = (abgr >> 0) & 0xFF;
u8 g = (abgr >> 8) & 0xFF;
u8 b = (abgr >> 16) & 0xFF;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) {
if (!gfx || !params || count == 0) return;
switch (effect) {
case PXL8_GFX_EFFECT_GLOWS: {
pxl8_gfx_ensure_blend_tables(gfx);
if (!gfx->additive_table || !gfx->overbright_table || !gfx->palette_cube) return;
const pxl8_glow_source* glows = (const pxl8_glow_source*)params;
const pxl8_glow* glows = (const pxl8_glow*)params;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_render_glows(
gfx->backend.cpu,
glows,
count,
gfx->additive_table,
gfx->palette_cube,
gfx->overbright_table
);
pxl8_cpu_render_glows(gfx->backend.cpu, glows, count);
break;
case PXL8_GFX_BACKEND_GPU:
break;

View file

@ -2,9 +2,12 @@
#include "pxl8_gfx2d.h"
#include "pxl8_gfx3d.h"
#include "pxl8_glows.h"
#include "pxl8_hal.h"
#include "pxl8_colormap.h"
#include "pxl8_palette.h"
#include "pxl8_types.h"
#include "pxl8_gui_palette.h"
typedef struct pxl8_gfx pxl8_gfx;
@ -12,53 +15,6 @@ typedef enum pxl8_gfx_effect {
PXL8_GFX_EFFECT_GLOWS = 0,
} pxl8_gfx_effect;
#define PXL8_MAX_GLOWS 256
typedef enum pxl8_glow_shape {
PXL8_GLOW_CIRCLE = 0,
PXL8_GLOW_DIAMOND = 1,
PXL8_GLOW_SHAFT = 2,
} pxl8_glow_shape;
typedef struct pxl8_glow_source {
u8 color;
u16 depth;
u8 height;
u16 intensity;
u8 radius;
pxl8_glow_shape shape;
i16 x;
i16 y;
} pxl8_glow_source;
static inline pxl8_glow_source pxl8_glow_create(i32 x, i32 y, u8 radius, u16 intensity, u8 color) {
return (pxl8_glow_source){
.color = color,
.depth = 0xFFFF,
.height = 0,
.intensity = intensity,
.radius = radius,
.shape = PXL8_GLOW_CIRCLE,
.x = (i16)x,
.y = (i16)y,
};
}
static inline pxl8_glow_source pxl8_glow_with_depth(pxl8_glow_source g, u16 depth) {
g.depth = depth;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_shape(pxl8_glow_source g, pxl8_glow_shape shape) {
g.shape = shape;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_height(pxl8_glow_source g, u8 height) {
g.height = height;
return g;
}
#ifdef __cplusplus
extern "C" {
#endif
@ -76,13 +32,14 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);
pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal);
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
@ -97,6 +54,10 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx);
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);
void pxl8_gfx_colormap_update(pxl8_gfx* gfx);
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b);
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index);
#ifdef __cplusplus
}

View file

@ -1,53 +1,31 @@
#pragma once
#include "pxl8_3d_camera.h"
#include "pxl8_lights.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h"
typedef struct pxl8_gfx pxl8_gfx;
#define PXL8_MAX_LIGHTS 16
typedef struct pxl8_light {
pxl8_vec3 position;
u8 r, g, b;
u8 intensity;
f32 radius;
f32 radius_sq;
f32 inv_radius_sq;
} pxl8_light;
static inline pxl8_light pxl8_light_create(pxl8_vec3 pos, u8 r, u8 g, u8 b, u8 intensity, f32 radius) {
f32 radius_sq = radius * radius;
return (pxl8_light){
.position = pos,
.r = r, .g = g, .b = b,
.intensity = intensity,
.radius = radius,
.radius_sq = radius_sq,
.inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f,
};
}
typedef struct pxl8_3d_uniforms {
u8 ambient;
pxl8_vec3 celestial_dir;
f32 celestial_intensity;
u8 fog_color;
f32 fog_density;
pxl8_light lights[PXL8_MAX_LIGHTS];
u32 num_lights;
f32 time;
} pxl8_3d_uniforms;
typedef struct pxl8_3d_frame {
pxl8_3d_uniforms uniforms;
pxl8_vec3 camera_dir;
pxl8_vec3 camera_pos;
f32 far_clip;
const pxl8_light* lights;
u32 lights_count;
f32 near_clip;
pxl8_mat4 projection;
pxl8_3d_uniforms uniforms;
pxl8_mat4 view;
} pxl8_3d_frame;
@ -55,15 +33,16 @@ typedef struct pxl8_3d_frame {
extern "C" {
#endif
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);
void pxl8_3d_end_frame(pxl8_gfx* gfx);
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx);
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);

62
src/gfx/pxl8_glows.c Normal file
View file

@ -0,0 +1,62 @@
#include "pxl8_glows.h"
#include "pxl8_gfx.h"
#include "pxl8_mem.h"
#include <stdlib.h>
struct pxl8_glows {
pxl8_glow* data;
u32 capacity;
u32 count;
};
pxl8_glows* pxl8_glows_create(u32 capacity) {
pxl8_glows* glows = pxl8_calloc(1, sizeof(pxl8_glows));
if (!glows) return NULL;
glows->data = pxl8_calloc(capacity, sizeof(pxl8_glow));
if (!glows->data) {
pxl8_free(glows);
return NULL;
}
glows->capacity = capacity;
glows->count = 0;
return glows;
}
void pxl8_glows_destroy(pxl8_glows* glows) {
if (!glows) return;
pxl8_free(glows->data);
pxl8_free(glows);
}
void pxl8_glows_add(pxl8_glows* glows, i16 x, i16 y, u8 radius, u16 intensity, u8 color, u8 shape) {
if (!glows || glows->count >= glows->capacity) return;
pxl8_glow* g = &glows->data[glows->count++];
g->x = x;
g->y = y;
g->radius = radius;
g->intensity = intensity;
g->color = color;
g->shape = shape;
g->depth = 0xFFFF;
g->height = 0;
}
void pxl8_glows_clear(pxl8_glows* glows) {
if (!glows) return;
glows->count = 0;
}
u32 pxl8_glows_count(const pxl8_glows* glows) {
return glows ? glows->count : 0;
}
void pxl8_glows_render(pxl8_glows* glows, pxl8_gfx* gfx) {
if (!glows || !gfx || glows->count == 0) return;
pxl8_gfx_apply_effect(gfx, PXL8_GFX_EFFECT_GLOWS, glows->data, glows->count);
}

42
src/gfx/pxl8_glows.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_GLOWS_MAX 16384
typedef enum pxl8_glow_shape {
PXL8_GLOW_CIRCLE = 0,
PXL8_GLOW_DIAMOND = 1,
PXL8_GLOW_SHAFT = 2,
} pxl8_glow_shape;
typedef struct pxl8_glow {
u8 color;
u16 depth;
u8 height;
u16 intensity;
u8 radius;
pxl8_glow_shape shape;
i16 x;
i16 y;
} pxl8_glow;
typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_glows pxl8_glows;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_glows* pxl8_glows_create(u32 capacity);
void pxl8_glows_destroy(pxl8_glows* glows);
void pxl8_glows_add(pxl8_glows* glows, i16 x, i16 y, u8 radius, u16 intensity, u8 color, u8 shape);
void pxl8_glows_clear(pxl8_glows* glows);
u32 pxl8_glows_count(const pxl8_glows* glows);
const pxl8_glow* pxl8_glows_data(const pxl8_glows* glows);
void pxl8_glows_render(pxl8_glows* glows, pxl8_gfx* gfx);
#ifdef __cplusplus
}
#endif

114
src/gfx/pxl8_lightmap.c Normal file
View file

@ -0,0 +1,114 @@
#include "pxl8_lightmap.h"
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale) {
pxl8_lightmap* lm = pxl8_calloc(1, sizeof(pxl8_lightmap));
if (!lm) return NULL;
lm->width = width;
lm->height = height;
lm->scale = scale;
lm->data = pxl8_calloc(width * height * 3, sizeof(u8));
if (!lm->data) {
pxl8_free(lm);
return NULL;
}
pxl8_lightmap_clear(lm, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL);
return lm;
}
void pxl8_lightmap_destroy(pxl8_lightmap* lm) {
if (!lm) return;
pxl8_free(lm->data);
pxl8_free(lm);
}
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b) {
if (!lm || !lm->data) return;
u32 count = lm->width * lm->height;
for (u32 i = 0; i < count; i++) {
lm->data[i * 3 + 0] = r;
lm->data[i * 3 + 1] = g;
lm->data[i * 3 + 2] = b;
}
}
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) return;
u32 idx = (y * lm->width + x) * 3;
lm->data[idx + 0] = r;
lm->data[idx + 1] = g;
lm->data[idx + 2] = b;
}
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) {
*r = *g = *b = PXL8_LIGHTMAP_NEUTRAL;
return;
}
u32 idx = (y * lm->width + x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
) {
if (!lm || !lm->data || radius <= 0.0f) return;
f32 radius_sq = radius * radius;
f32 inv_radius_sq = 1.0f / radius_sq;
i32 cx = (i32)(lx * (f32)lm->width);
i32 cy = (i32)(ly * (f32)lm->height);
i32 rad_pixels = (i32)(radius * (f32)lm->width) + 1;
i32 x0 = cx - rad_pixels;
i32 y0 = cy - rad_pixels;
i32 x1 = cx + rad_pixels;
i32 y1 = cy + rad_pixels;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 >= (i32)lm->width) x1 = (i32)lm->width - 1;
if (y1 >= (i32)lm->height) y1 = (i32)lm->height - 1;
f32 scale_x = 1.0f / (f32)lm->width;
f32 scale_y = 1.0f / (f32)lm->height;
for (i32 y = y0; y <= y1; y++) {
f32 dy = ((f32)y * scale_y) - ly;
for (i32 x = x0; x <= x1; x++) {
f32 dx = ((f32)x * scale_x) - lx;
f32 dist_sq = dx * dx + dy * dy;
if (dist_sq >= radius_sq) continue;
f32 falloff = 1.0f - dist_sq * inv_radius_sq;
f32 contrib = falloff * falloff * intensity;
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
i32 nr = (i32)lm->data[idx + 0] + (i32)((f32)(r - 128) * contrib);
i32 ng = (i32)lm->data[idx + 1] + (i32)((f32)(g - 128) * contrib);
i32 nb = (i32)lm->data[idx + 2] + (i32)((f32)(b - 128) * contrib);
if (nr < 0) nr = 0; if (nr > 255) nr = 255;
if (ng < 0) ng = 0; if (ng > 255) ng = 255;
if (nb < 0) nb = 0; if (nb > 255) nb = 255;
lm->data[idx + 0] = (u8)nr;
lm->data[idx + 1] = (u8)ng;
lm->data[idx + 2] = (u8)nb;
}
}
}

48
src/gfx/pxl8_lightmap.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_LIGHTMAP_MAX 16
#define PXL8_LIGHTMAP_NEUTRAL 128
typedef struct pxl8_lightmap {
u8* data;
u32 width;
u32 height;
u32 scale;
} pxl8_lightmap;
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale);
void pxl8_lightmap_destroy(pxl8_lightmap* lm);
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b);
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b);
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b);
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
);
static inline void pxl8_lightmap_sample(
const pxl8_lightmap* lm,
f32 u, f32 v,
u8* r, u8* g, u8* b
) {
i32 x = (i32)(u * (f32)lm->width) & (i32)(lm->width - 1);
i32 y = (i32)(v * (f32)lm->height) & (i32)(lm->height - 1);
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
#ifdef __cplusplus
}
#endif

64
src/gfx/pxl8_lights.c Normal file
View file

@ -0,0 +1,64 @@
#include "pxl8_lights.h"
#include "pxl8_mem.h"
#include <stdlib.h>
struct pxl8_lights {
pxl8_light* data;
u32 capacity;
u32 count;
};
pxl8_lights* pxl8_lights_create(u32 capacity) {
if (capacity > PXL8_LIGHTS_MAX) capacity = PXL8_LIGHTS_MAX;
pxl8_lights* lights = pxl8_calloc(1, sizeof(pxl8_lights));
if (!lights) return NULL;
lights->data = pxl8_calloc(capacity, sizeof(pxl8_light));
if (!lights->data) {
pxl8_free(lights);
return NULL;
}
lights->capacity = capacity;
lights->count = 0;
return lights;
}
void pxl8_lights_destroy(pxl8_lights* lights) {
if (!lights) return;
pxl8_free(lights->data);
pxl8_free(lights);
}
void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, u8 intensity, f32 radius) {
if (!lights || lights->count >= lights->capacity) return;
f32 radius_sq = radius * radius;
pxl8_light* l = &lights->data[lights->count++];
l->position.x = x;
l->position.y = y;
l->position.z = z;
l->r = r;
l->g = g;
l->b = b;
l->intensity = intensity;
l->radius = radius;
l->radius_sq = radius_sq;
l->inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f;
}
void pxl8_lights_clear(pxl8_lights* lights) {
if (!lights) return;
lights->count = 0;
}
u32 pxl8_lights_count(const pxl8_lights* lights) {
return lights ? lights->count : 0;
}
const pxl8_light* pxl8_lights_data(const pxl8_lights* lights) {
return lights ? lights->data : NULL;
}

33
src/gfx/pxl8_lights.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include "pxl8_math.h"
#include "pxl8_types.h"
#define PXL8_LIGHTS_MAX 256
typedef struct pxl8_light {
pxl8_vec3 position;
f32 inv_radius_sq;
u8 r, g, b;
u8 intensity;
f32 radius;
f32 radius_sq;
} pxl8_light;
typedef struct pxl8_lights pxl8_lights;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_lights* pxl8_lights_create(u32 capacity);
void pxl8_lights_destroy(pxl8_lights* lights);
void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, u8 intensity, f32 radius);
void pxl8_lights_clear(pxl8_lights* lights);
u32 pxl8_lights_count(const pxl8_lights* lights);
const pxl8_light* pxl8_lights_data(const pxl8_lights* lights);
#ifdef __cplusplus
}
#endif

View file

@ -1,3 +1,4 @@
#include "pxl8_mem.h"
#include "pxl8_mesh.h"
#include <stdlib.h>
#include <string.h>
@ -6,16 +7,16 @@ pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity) {
if (vertex_capacity > PXL8_MESH_MAX_VERTICES) vertex_capacity = PXL8_MESH_MAX_VERTICES;
if (index_capacity > PXL8_MESH_MAX_INDICES) index_capacity = PXL8_MESH_MAX_INDICES;
pxl8_mesh* mesh = calloc(1, sizeof(pxl8_mesh));
pxl8_mesh* mesh = pxl8_calloc(1, sizeof(pxl8_mesh));
if (!mesh) return NULL;
mesh->vertices = calloc(vertex_capacity, sizeof(pxl8_vertex));
mesh->indices = calloc(index_capacity, sizeof(u16));
mesh->vertices = pxl8_calloc(vertex_capacity, sizeof(pxl8_vertex));
mesh->indices = pxl8_calloc(index_capacity, sizeof(u16));
if (!mesh->vertices || !mesh->indices) {
free(mesh->vertices);
free(mesh->indices);
free(mesh);
pxl8_free(mesh->vertices);
pxl8_free(mesh->indices);
pxl8_free(mesh);
return NULL;
}
@ -29,9 +30,9 @@ pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity) {
void pxl8_mesh_destroy(pxl8_mesh* mesh) {
if (!mesh) return;
free(mesh->vertices);
free(mesh->indices);
free(mesh);
pxl8_free(mesh->vertices);
pxl8_free(mesh->indices);
pxl8_free(mesh);
}
void pxl8_mesh_clear(pxl8_mesh* mesh) {

View file

@ -17,8 +17,15 @@ typedef enum pxl8_blend_mode {
PXL8_BLEND_ADDITIVE,
} pxl8_blend_mode;
typedef struct pxl8_material {
typedef struct pxl8_gfx_material {
char name[16];
pxl8_vec3 u_axis;
pxl8_vec3 v_axis;
f32 u_offset;
f32 v_offset;
u32 texture_id;
u32 lightmap_id;
u8 alpha;
u8 blend_mode;
bool dither;
@ -26,13 +33,15 @@ typedef struct pxl8_material {
bool dynamic_lighting;
bool per_pixel;
bool vertex_color_passthrough;
bool wireframe;
f32 emissive_intensity;
} pxl8_material;
} pxl8_gfx_material;
typedef struct pxl8_vertex {
pxl8_vec3 position;
pxl8_vec3 normal;
f32 u, v;
f32 lu, lv;
u8 color;
u8 light;
u8 _pad[2];
@ -62,62 +71,6 @@ static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) {
return mesh->index_count / 3;
}
static inline pxl8_material pxl8_material_create(u32 texture_id) {
return (pxl8_material){
.texture_id = texture_id,
.alpha = 255,
.blend_mode = PXL8_BLEND_OPAQUE,
.dither = true,
.double_sided = false,
.dynamic_lighting = false,
.per_pixel = false,
.vertex_color_passthrough = false,
.emissive_intensity = 0.0f,
};
}
static inline pxl8_material pxl8_material_with_alpha(pxl8_material m, u8 alpha) {
m.alpha = alpha;
if (alpha < 255 && m.blend_mode == PXL8_BLEND_OPAQUE) {
m.blend_mode = PXL8_BLEND_ALPHA;
}
return m;
}
static inline pxl8_material pxl8_material_with_blend(pxl8_material m, pxl8_blend_mode mode) {
m.blend_mode = mode;
return m;
}
static inline pxl8_material pxl8_material_with_double_sided(pxl8_material m) {
m.double_sided = true;
return m;
}
static inline pxl8_material pxl8_material_with_emissive(pxl8_material m, f32 intensity) {
m.emissive_intensity = intensity;
return m;
}
static inline pxl8_material pxl8_material_with_lighting(pxl8_material m) {
m.dynamic_lighting = true;
return m;
}
static inline pxl8_material pxl8_material_with_no_dither(pxl8_material m) {
m.dither = false;
return m;
}
static inline pxl8_material pxl8_material_with_passthrough(pxl8_material m) {
m.vertex_color_passthrough = true;
return m;
}
static inline pxl8_material pxl8_material_with_per_pixel(pxl8_material m) {
m.per_pixel = true;
return m;
}
#ifdef __cplusplus
}

View file

@ -5,10 +5,18 @@
#include "pxl8_ase.h"
#include "pxl8_color.h"
#include "pxl8_colormap.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_PALETTE_HASH_SIZE 512
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
typedef struct {
u32 color;
i16 index;
@ -200,7 +208,7 @@ static void update_cycle_colors(pxl8_palette* pal, u8 slot) {
}
pxl8_palette* pxl8_palette_create(void) {
pxl8_palette* pal = calloc(1, sizeof(pxl8_palette));
pxl8_palette* pal = pxl8_calloc(1, sizeof(pxl8_palette));
if (!pal) return NULL;
pal->colors[0] = 0x00000000;
@ -221,7 +229,7 @@ pxl8_palette* pxl8_palette_create(void) {
}
void pxl8_palette_destroy(pxl8_palette* pal) {
free(pal);
pxl8_free(pal);
}
pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path) {
@ -332,10 +340,6 @@ void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b,
unpack_rgba(pal->colors[idx], r, g, b, a);
}
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color) {
if (pal) pal->colors[idx] = color;
}
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) {
if (pal) pal->colors[idx] = pack_rgb(r, g, b);
}
@ -344,6 +348,17 @@ void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a) {
if (pal) pal->colors[idx] = pack_rgba(r, g, b, a);
}
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count) {
if (!pal || !colors) return;
for (u16 i = 0; i < count; i++) {
u32 rgb = colors[i];
u8 r = (rgb >> 16) & 0xFF;
u8 g = (rgb >> 8) & 0xFF;
u8 b = rgb & 0xFF;
pal->colors[i] = pack_rgb(r, g, b);
}
}
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) {
if (!pal || count == 0) return;
@ -472,3 +487,91 @@ pxl8_cycle_range pxl8_cycle_range_disabled(void) {
};
return range;
}
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
i32 dr = (i32)r - (i32)pr;
i32 dg = (i32)g - (i32)pg;
i32 db = (i32)b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = pxl8_calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
pxl8_free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}

View file

@ -5,8 +5,11 @@
#define PXL8_PALETTE_SIZE 256
#define PXL8_MAX_CYCLES 8
#define PXL8_MAX_CYCLE_LEN 16
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
typedef struct pxl8_palette pxl8_palette;
typedef struct pxl8_palette_cube pxl8_palette_cube;
typedef enum pxl8_cycle_mode {
PXL8_CYCLE_LOOP,
@ -49,9 +52,9 @@ u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);
i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);
void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b);
void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a);
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color);
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);
void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a);
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count);
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to);
void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1);
@ -65,6 +68,13 @@ void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks);
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period);
pxl8_cycle_range pxl8_cycle_range_disabled(void);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus
}
#endif

View file

@ -3,6 +3,7 @@
#include <stdlib.h>
#include "pxl8_gfx.h"
#include "pxl8_mem.h"
#include "pxl8_gfx2d.h"
#include "pxl8_palette.h"
#include "pxl8_rng.h"
@ -37,12 +38,12 @@ struct pxl8_particles {
};
pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) {
pxl8_particles* ps = calloc(1, sizeof(pxl8_particles));
pxl8_particles* ps = pxl8_calloc(1, sizeof(pxl8_particles));
if (!ps) return NULL;
ps->particles = calloc(max_count, sizeof(pxl8_particle));
ps->particles = pxl8_calloc(max_count, sizeof(pxl8_particle));
if (!ps->particles) {
free(ps);
pxl8_free(ps);
return NULL;
}
@ -61,8 +62,8 @@ pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) {
void pxl8_particles_destroy(pxl8_particles* ps) {
if (!ps) return;
free(ps->particles);
free(ps);
pxl8_free(ps->particles);
pxl8_free(ps);
}
void pxl8_particles_clear(pxl8_particles* ps) {

View file

@ -6,6 +6,7 @@
#include "pxl8_ase.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_tilesheet.h"
struct pxl8_tilesheet {
@ -58,7 +59,7 @@ static pxl8_tile_chunk* pxl8_get_or_create_chunk(pxl8_tilemap_layer* layer, u32
if (idx >= layer->chunks_wide * layer->chunks_high) return NULL;
if (!layer->chunks[idx]) {
layer->chunks[idx] = calloc(1, sizeof(pxl8_tile_chunk));
layer->chunks[idx] = pxl8_calloc(1, sizeof(pxl8_tile_chunk));
if (!layer->chunks[idx]) return NULL;
layer->chunks[idx]->chunk_x = chunk_x;
@ -75,7 +76,7 @@ pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
return NULL;
}
pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap));
pxl8_tilemap* tilemap = pxl8_calloc(1, sizeof(pxl8_tilemap));
if (!tilemap) return NULL;
tilemap->width = width;
@ -85,7 +86,7 @@ pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
tilemap->tilesheet = pxl8_tilesheet_create(tilemap->tile_size);
if (!tilemap->tilesheet) {
free(tilemap);
pxl8_free(tilemap);
return NULL;
}
@ -103,13 +104,13 @@ pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
layer->visible = (i == 0);
layer->opacity = 255;
layer->chunks = calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*));
layer->chunks = pxl8_calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*));
if (!layer->chunks) {
for (u32 j = 0; j < i; j++) {
free(tilemap->layers[j].chunks);
pxl8_free(tilemap->layers[j].chunks);
}
if (tilemap->tilesheet) pxl8_tilesheet_destroy(tilemap->tilesheet);
free(tilemap);
pxl8_free(tilemap);
return NULL;
}
}
@ -125,17 +126,17 @@ void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
if (layer->chunks) {
for (u32 j = 0; j < layer->chunk_count; j++) {
if (layer->chunks[j]) {
free(layer->chunks[j]);
pxl8_free(layer->chunks[j]);
}
}
free(layer->chunks);
pxl8_free(layer->chunks);
}
}
if (tilemap->tilesheet) pxl8_tilesheet_unref(tilemap->tilesheet);
if (tilemap->tile_user_data) free(tilemap->tile_user_data);
if (tilemap->tile_user_data) pxl8_free(tilemap->tile_user_data);
free(tilemap);
pxl8_free(tilemap);
}
u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap) {
@ -155,7 +156,7 @@ void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* u
if (tile_id >= tilemap->tile_user_data_capacity) {
u32 new_capacity = tile_id + 64;
void** new_data = realloc(tilemap->tile_user_data, new_capacity * sizeof(void*));
void** new_data = pxl8_realloc(tilemap->tile_user_data, new_capacity * sizeof(void*));
if (!new_data) return;
for (u32 i = tilemap->tile_user_data_capacity; i < new_capacity; i++) {
@ -478,7 +479,7 @@ void pxl8_tilemap_compress(pxl8_tilemap* tilemap) {
}
if (!has_tiles) {
free(chunk);
pxl8_free(chunk);
layer->chunks[j] = NULL;
layer->allocated_chunks--;
} else {
@ -535,8 +536,8 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
u32 tilesheet_width = tiles_per_row * tilemap->tile_size;
u32 tilesheet_height = tilesheet_rows * tilemap->tile_size;
if (tilemap->tilesheet->data) free(tilemap->tilesheet->data);
tilemap->tilesheet->data = calloc(tilesheet_width * tilesheet_height, 1);
if (tilemap->tilesheet->data) pxl8_free(tilemap->tilesheet->data);
tilemap->tilesheet->data = pxl8_calloc(tilesheet_width * tilesheet_height, 1);
if (!tilemap->tilesheet->data) {
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -548,8 +549,8 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
tilemap->tilesheet->total_tiles = tileset->tile_count;
tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
if (tilemap->tilesheet->tile_valid) free(tilemap->tilesheet->tile_valid);
tilemap->tilesheet->tile_valid = calloc(tileset->tile_count + 1, sizeof(bool));
if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid);
tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool));
for (u32 i = 0; i < tileset->tile_count; i++) {
u32 sheet_row = i / tiles_per_row;

View file

@ -7,6 +7,7 @@
#include "pxl8_color.h"
#include "pxl8_gfx.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_tilemap.h"
struct pxl8_tilesheet {
@ -32,7 +33,7 @@ struct pxl8_tilesheet {
};
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size) {
pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet));
pxl8_tilesheet* tilesheet = pxl8_calloc(1, sizeof(pxl8_tilesheet));
if (!tilesheet) return NULL;
tilesheet->tile_size = tile_size;
@ -45,37 +46,37 @@ void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
if (!tilesheet) return;
if (tilesheet->data) {
free(tilesheet->data);
pxl8_free(tilesheet->data);
}
if (tilesheet->tile_valid) {
free(tilesheet->tile_valid);
pxl8_free(tilesheet->tile_valid);
}
if (tilesheet->animations) {
for (u32 i = 0; i < tilesheet->animation_count; i++) {
if (tilesheet->animations[i].frames) {
free(tilesheet->animations[i].frames);
pxl8_free(tilesheet->animations[i].frames);
}
}
free(tilesheet->animations);
pxl8_free(tilesheet->animations);
}
if (tilesheet->properties) {
free(tilesheet->properties);
pxl8_free(tilesheet->properties);
}
if (tilesheet->autotile_rules) {
for (u32 i = 0; i <= tilesheet->total_tiles; i++) {
if (tilesheet->autotile_rules[i]) {
free(tilesheet->autotile_rules[i]);
pxl8_free(tilesheet->autotile_rules[i]);
}
}
free(tilesheet->autotile_rules);
free(tilesheet->autotile_rule_counts);
pxl8_free(tilesheet->autotile_rules);
pxl8_free(tilesheet->autotile_rule_counts);
}
free(tilesheet);
pxl8_free(tilesheet);
}
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx) {
@ -89,7 +90,7 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
}
if (tilesheet->data) {
free(tilesheet->data);
pxl8_free(tilesheet->data);
}
u32 width = ase_file.header.width;
@ -111,8 +112,8 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
u16 ase_depth = ase_file.header.color_depth;
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
tilesheet->data = malloc(data_size);
usize data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
tilesheet->data = pxl8_malloc(data_size);
if (!tilesheet->data) {
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -138,9 +139,9 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
} else if (ase_depth == 8 && gfx_hicolor) {
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
u8* new_data = realloc(tilesheet->data, pixel_count);
u8* new_data = pxl8_realloc(tilesheet->data, pixel_count);
if (!new_data) {
free(tilesheet->data);
pxl8_free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -149,16 +150,16 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
memcpy(tilesheet->data, src, pixel_count);
} else {
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
free(tilesheet->data);
pxl8_free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_INVALID_ARGUMENT;
}
}
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool));
tilesheet->tile_valid = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(bool));
if (!tilesheet->tile_valid) {
free(tilesheet->data);
pxl8_free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -259,14 +260,14 @@ pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_til
if (base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
if (!tilesheet->animations) {
tilesheet->animations = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation));
tilesheet->animations = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation));
if (!tilesheet->animations) return PXL8_ERROR_OUT_OF_MEMORY;
}
pxl8_tile_animation* anim = &tilesheet->animations[base_tile_id];
if (anim->frames) free(anim->frames);
if (anim->frames) pxl8_free(anim->frames);
anim->frames = malloc(frame_count * sizeof(u16));
anim->frames = pxl8_malloc(frame_count * sizeof(u16));
if (!anim->frames) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(anim->frames, frames, frame_count * sizeof(u16));
@ -340,7 +341,7 @@ void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id,
if (!tilesheet || !props || tile_id == 0 || tile_id > tilesheet->total_tiles) return;
if (!tilesheet->properties) {
tilesheet->properties = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties));
tilesheet->properties = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties));
if (!tilesheet->properties) return;
}
@ -365,15 +366,15 @@ pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base
}
if (!tilesheet->autotile_rules) {
tilesheet->autotile_rules = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*));
tilesheet->autotile_rule_counts = calloc(tilesheet->total_tiles + 1, sizeof(u32));
tilesheet->autotile_rules = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*));
tilesheet->autotile_rule_counts = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(u32));
if (!tilesheet->autotile_rules || !tilesheet->autotile_rule_counts) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
}
u32 count = tilesheet->autotile_rule_counts[base_tile_id];
pxl8_autotile_rule* new_rules = realloc(tilesheet->autotile_rules[base_tile_id],
pxl8_autotile_rule* new_rules = pxl8_realloc(tilesheet->autotile_rules[base_tile_id],
(count + 1) * sizeof(pxl8_autotile_rule));
if (!new_rules) return PXL8_ERROR_OUT_OF_MEMORY;

View file

@ -6,6 +6,7 @@
#include "pxl8_gfx.h"
#include "pxl8_log.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration) {
if (duration <= 0.0f) {
@ -13,7 +14,7 @@ pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration)
return NULL;
}
pxl8_transition* transition = (pxl8_transition*)calloc(1, sizeof(pxl8_transition));
pxl8_transition* transition = (pxl8_transition*)pxl8_calloc(1, sizeof(pxl8_transition));
if (!transition) {
pxl8_error("Failed to allocate transition");
return NULL;
@ -33,7 +34,7 @@ pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration)
void pxl8_transition_destroy(pxl8_transition* transition) {
if (!transition) return;
free(transition);
pxl8_free(transition);
}
f32 pxl8_transition_get_progress(const pxl8_transition* transition) {

View file

@ -4,9 +4,10 @@
#include <string.h>
#include "pxl8_gfx.h"
#include "pxl8_mem.h"
pxl8_gui_state* pxl8_gui_state_create(void) {
pxl8_gui_state* state = (pxl8_gui_state*)malloc(sizeof(pxl8_gui_state));
pxl8_gui_state* state = (pxl8_gui_state*)pxl8_malloc(sizeof(pxl8_gui_state));
if (!state) return NULL;
memset(state, 0, sizeof(pxl8_gui_state));
@ -15,21 +16,23 @@ pxl8_gui_state* pxl8_gui_state_create(void) {
void pxl8_gui_state_destroy(pxl8_gui_state* state) {
if (!state) return;
free(state);
pxl8_free(state);
}
void pxl8_gui_begin_frame(pxl8_gui_state* state) {
void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return;
state->hot_id = 0;
if (gfx) pxl8_gfx_push_target(gfx);
}
void pxl8_gui_end_frame(pxl8_gui_state* state) {
void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return;
if (!state->cursor_down) {
state->active_id = 0;
}
state->cursor_clicked = false;
if (gfx) pxl8_gfx_pop_target(gfx);
}
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) {
@ -80,16 +83,16 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 offset_y = 0;
if (is_active) {
bg_color = 4;
border_color = 3;
bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
offset_x = 1;
offset_y = 1;
} else if (is_hot || cursor_over) {
bg_color = 4;
border_color = 8;
bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
} else {
bg_color = 3;
border_color = 4;
bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
}
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
@ -98,7 +101,7 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 text_len = (i32)strlen(label);
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
i32 text_y = y + (h / 2) - 5 + offset_y;
pxl8_2d_text(gfx, label, text_x, text_y, 6);
pxl8_2d_text(gfx, label, text_x, text_y, pxl8_gfx_ui_color(gfx, PXL8_UI_FG1));
return clicked;
}
@ -106,14 +109,19 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
if (!gfx || !title) return;
pxl8_2d_rect_fill(gfx, x, y, w, 28, 1);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, 2);
pxl8_2d_rect(gfx, x, y, w, h, 4);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, 4);
u8 title_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1);
u8 body_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
u8 border = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
u8 title_fg = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, x, y, w, 28, title_bg);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, body_bg);
pxl8_2d_rect(gfx, x, y, w, h, border);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, border);
i32 title_x = x + 10;
i32 title_y = y + (28 / 2) - 5;
pxl8_2d_text(gfx, title, title_x, title_y, 8);
pxl8_2d_text(gfx, title, title_x, title_y, title_fg);
}
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) {

View file

@ -22,8 +22,8 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state);
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);
bool pxl8_gui_is_hovering(const pxl8_gui_state* state);
void pxl8_gui_begin_frame(pxl8_gui_state* state);
void pxl8_gui_end_frame(pxl8_gui_state* state);
void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_cursor_down(pxl8_gui_state* state);
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);

View file

@ -0,0 +1,41 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_UI_PALETTE_SIZE 16
#define PXL8_UI_BG0 0
#define PXL8_UI_BG1 1
#define PXL8_UI_BG2 2
#define PXL8_UI_BG3 3
#define PXL8_UI_FG0 4
#define PXL8_UI_FG1 5
#define PXL8_UI_FG2 6
#define PXL8_UI_FG3 7
#define PXL8_UI_RED 8
#define PXL8_UI_GREEN 9
#define PXL8_UI_YELLOW 10
#define PXL8_UI_BLUE 11
#define PXL8_UI_PURPLE 12
#define PXL8_UI_AQUA 13
#define PXL8_UI_ORANGE 14
#define PXL8_UI_GRAY 15
static const u32 pxl8_ui_palette[PXL8_UI_PALETTE_SIZE] = {
0xFF282828,
0xFF3c3836,
0xFF504945,
0xFF665c54,
0xFFc7f1fb,
0xFFb2dbeb,
0xFFa1c4d5,
0xFF93aebd,
0xFF3449fb,
0xFF26bbb8,
0xFF2fbdfa,
0xFF98a583,
0xFF9b86d3,
0xFF7cc08e,
0xFF1980fe,
0xFF928374,
};

View file

@ -1,4 +1,4 @@
#include "pxl8_sdl3.h"
#include "pxl8_hal.h"
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
@ -8,13 +8,15 @@
#include "pxl8_log.h"
#include "pxl8_sys.h"
extern const pxl8_hal pxl8_hal_sdl3;
typedef struct pxl8_sdl3_context {
SDL_Texture* framebuffer;
SDL_Renderer* renderer;
SDL_Window* window;
u32* rgba_buffer;
size_t rgba_buffer_size;
usize rgba_buffer_size;
} pxl8_sdl3_context;
static void* sdl3_create(i32 render_w, i32 render_h,
@ -101,7 +103,7 @@ static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w,
if (!platform_data || !pixels) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
size_t pixel_count = w * h;
usize pixel_count = w * h;
if (bpp == 4) {
SDL_UpdateTexture(ctx->framebuffer, NULL, pixels, w * 4);

8
src/hal/pxl8_mem.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include "pxl8_types.h"
void* pxl8_malloc(usize size);
void* pxl8_calloc(usize count, usize size);
void* pxl8_realloc(void* ptr, usize size);
void pxl8_free(void* ptr);

19
src/hal/pxl8_mem_sdl3.c Normal file
View file

@ -0,0 +1,19 @@
#include "pxl8_mem.h"
#include <SDL3/SDL.h>
void* pxl8_malloc(usize size) {
return SDL_malloc(size);
}
void* pxl8_calloc(usize count, usize size) {
return SDL_calloc(count, size);
}
void* pxl8_realloc(void* ptr, usize size) {
return SDL_realloc(ptr, size);
}
void pxl8_free(void* ptr) {
SDL_free(ptr);
}

View file

@ -1,5 +0,0 @@
#pragma once
#include "pxl8_hal.h"
extern const pxl8_hal pxl8_hal_sdl3;

View file

@ -1,13 +1,15 @@
local anim = require("pxl8.anim")
local bytes = require("pxl8.bytes")
local core = require("pxl8.core")
local effects = require("pxl8.effects")
local gfx2d = require("pxl8.gfx2d")
local gfx3d = require("pxl8.gfx3d")
local gui = require("pxl8.gui")
local input = require("pxl8.input")
local math3d = require("pxl8.math")
local math = require("pxl8.math")
local net = require("pxl8.net")
local particles = require("pxl8.particles")
local procgen = require("pxl8.procgen")
local sfx = require("pxl8.sfx")
local tilemap = require("pxl8.tilemap")
local transition = require("pxl8.transition")
@ -27,16 +29,20 @@ pxl8.debug = core.debug
pxl8.trace = core.trace
pxl8.quit = core.quit
pxl8.rng_seed = core.rng_seed
pxl8.rng_next = core.rng_next
pxl8.hash32 = math.hash32
pxl8.rng_f32 = core.rng_f32
pxl8.rng_next = core.rng_next
pxl8.rng_range = core.rng_range
pxl8.rng_seed = core.rng_seed
pxl8.find_color = core.find_color
pxl8.palette_color = core.palette_color
pxl8.palette_index = core.palette_index
pxl8.ramp_index = core.ramp_index
pxl8.set_colormap = core.set_colormap
pxl8.set_palette = core.set_palette
pxl8.set_palette_rgb = core.set_palette_rgb
pxl8.update_palette_deps = core.update_palette_deps
pxl8.clear = gfx2d.clear
pxl8.pixel = gfx2d.pixel
@ -78,18 +84,28 @@ pxl8.Anim = anim.Anim
pxl8.create_anim = anim.Anim.new
pxl8.create_anim_from_ase = anim.Anim.from_ase
pxl8.bounds = math3d.bounds
pxl8.bounds = math.bounds
pxl8.Camera3D = gfx3d.Camera3D
pxl8.create_camera_3d = gfx3d.Camera3D.new
pxl8.Mesh = gfx3d.Mesh
pxl8.begin_frame_3d = gfx3d.begin_frame
pxl8.clear_3d = gfx3d.clear
pxl8.clear_depth = gfx3d.clear_depth
pxl8.create_camera_3d = gfx3d.Camera3D.new
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.create_vec3_array = gfx3d.create_vec3_array
pxl8.draw_line_3d = gfx3d.draw_line
pxl8.draw_mesh = gfx3d.draw_mesh
pxl8.end_frame_3d = gfx3d.end_frame
pxl8.Mesh = gfx3d.Mesh
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.project_points = gfx3d.project_points
pxl8.GLOW_CIRCLE = effects.GLOW_CIRCLE
pxl8.GLOW_DIAMOND = effects.GLOW_DIAMOND
pxl8.GLOW_SHAFT = effects.GLOW_SHAFT
pxl8.Glows = effects.Glows
pxl8.create_glows = effects.Glows.new
pxl8.Lights = effects.Lights
pxl8.create_lights = effects.Lights.new
pxl8.Compressor = sfx.Compressor
pxl8.create_compressor = sfx.Compressor.new
@ -103,16 +119,18 @@ pxl8.create_gui = gui.Gui.new
pxl8.gui_label = gui.label
pxl8.gui_window = gui.window
pxl8.mat4_identity = math3d.mat4_identity
pxl8.mat4_lookat = math3d.mat4_lookat
pxl8.mat4_multiply = math3d.mat4_multiply
pxl8.mat4_ortho = math3d.mat4_ortho
pxl8.mat4_perspective = math3d.mat4_perspective
pxl8.mat4_rotate_x = math3d.mat4_rotate_x
pxl8.mat4_rotate_y = math3d.mat4_rotate_y
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
pxl8.mat4_scale = math3d.mat4_scale
pxl8.mat4_translate = math3d.mat4_translate
pxl8.mat4_identity = math.mat4_identity
pxl8.mat4_lookat = math.mat4_lookat
pxl8.mat4_multiply = math.mat4_multiply
pxl8.mat4_multiply_vec3 = math.mat4_multiply_vec3
pxl8.mat4_multiply_vec4 = math.mat4_multiply_vec4
pxl8.mat4_orthographic = math.mat4_orthographic
pxl8.mat4_perspective = math.mat4_perspective
pxl8.mat4_rotate_x = math.mat4_rotate_x
pxl8.mat4_rotate_y = math.mat4_rotate_y
pxl8.mat4_rotate_z = math.mat4_rotate_z
pxl8.mat4_scale = math.mat4_scale
pxl8.mat4_translate = math.mat4_translate
pxl8.Net = net.Net
pxl8.create_net = net.Net.new
@ -141,9 +159,10 @@ pxl8.pack_u64_le = bytes.pack_u64_le
pxl8.Particles = particles.Particles
pxl8.create_particles = particles.Particles.new
pxl8.Graph = procgen.Graph
pxl8.create_graph = procgen.create_graph
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
pxl8.procgen_tex = world.procgen_tex
pxl8.SfxContext = sfx.SfxContext
pxl8.SfxNode = sfx.SfxNode

View file

@ -20,27 +20,40 @@ function core.get_fps()
end
function core.palette_color(index)
local pal = C.pxl8_gfx_get_palette(core.gfx)
local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return 0 end
return C.pxl8_palette_color(pal, index)
end
function core.palette_index(color)
local pal = C.pxl8_gfx_get_palette(core.gfx)
local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return -1 end
return C.pxl8_palette_index(pal, color)
end
function core.set_palette_rgb(index, r, g, b)
local pal = C.pxl8_gfx_get_palette(core.gfx)
local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return end
C.pxl8_palette_set_rgb(pal, index, r, g, b)
end
function core.set_palette(colors, count)
C.pxl8_gfx_set_palette_colors(core.gfx, colors, count)
end
function core.set_colormap(data, size)
local cm = C.pxl8_gfx_colormap(core.gfx)
if cm == nil then return end
C.pxl8_set_colormap(cm, data, size)
end
function core.update_palette_deps()
C.pxl8_gfx_blend_tables_update(core.gfx)
C.pxl8_gfx_colormap_update(core.gfx)
end
function core.ramp_index(position)
local pal = C.pxl8_gfx_get_palette(core.gfx)
local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return 0 end
return C.pxl8_palette_ramp_index(pal, position)
end

View file

@ -8,25 +8,72 @@ effects.GLOW_CIRCLE = 0
effects.GLOW_DIAMOND = 1
effects.GLOW_SHAFT = 2
function effects.glows(glows)
if not glows or #glows == 0 then return end
local Glows = {}
Glows.__index = Glows
local count = #glows
local glow_array = ffi.new("pxl8_glow_source[?]", count)
for i, g in ipairs(glows) do
local idx = i - 1
glow_array[idx].x = g.x or 0
glow_array[idx].y = g.y or 0
glow_array[idx].radius = g.radius or 8
glow_array[idx].intensity = g.intensity or 255
glow_array[idx].color = g.color or 15
glow_array[idx].depth = g.depth or 0xFFFF
glow_array[idx].height = g.height or 0
glow_array[idx].shape = g.shape or 0
function Glows.new(capacity)
local ptr = C.pxl8_glows_create(capacity or 1000)
if ptr == nil then
return nil
end
C.pxl8_gfx_apply_effect(core.gfx, C.PXL8_GFX_EFFECT_GLOWS, glow_array, count)
return setmetatable({ _ptr = ptr }, Glows)
end
function Glows:add(x, y, radius, intensity, color, shape)
C.pxl8_glows_add(self._ptr, x, y, radius or 8, intensity or 255, color or 15, shape or 0)
end
function Glows:clear()
C.pxl8_glows_clear(self._ptr)
end
function Glows:count()
return C.pxl8_glows_count(self._ptr)
end
function Glows:destroy()
if self._ptr then
C.pxl8_glows_destroy(self._ptr)
self._ptr = nil
end
end
function Glows:render()
C.pxl8_glows_render(self._ptr, core.gfx)
end
effects.Glows = Glows
local Lights = {}
Lights.__index = Lights
function Lights.new(capacity)
local ptr = C.pxl8_lights_create(capacity or 256)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = ptr }, Lights)
end
function Lights:add(x, y, z, r, g, b, intensity, radius)
C.pxl8_lights_add(self._ptr, x, y, z, r or 255, g or 255, b or 255, intensity or 255, radius or 10)
end
function Lights:clear()
C.pxl8_lights_clear(self._ptr)
end
function Lights:count()
return C.pxl8_lights_count(self._ptr)
end
function Lights:destroy()
if self._ptr then
C.pxl8_lights_destroy(self._ptr)
self._ptr = nil
end
end
effects.Lights = Lights
return effects

View file

@ -75,6 +75,15 @@ function Camera3D:update(dt)
C.pxl8_3d_camera_update(self._ptr, dt)
end
function Camera3D:world_to_screen(x, y, z, width, height)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
local result = C.pxl8_3d_camera_world_to_screen(self._ptr, pos, width, height)
if result.visible then
return {x = result.x, y = result.y, depth = result.depth}
end
return nil
end
gfx3d.Camera3D = Camera3D
local Mesh = {}
@ -123,14 +132,15 @@ gfx3d.Mesh = Mesh
function gfx3d.draw_mesh(mesh, opts)
opts = opts or {}
local model = ffi.new("pxl8_mat4")
model.m[0] = 1
model.m[5] = 1
model.m[10] = 1
local s = opts.scale or 1
model.m[0] = s
model.m[5] = s
model.m[10] = s
model.m[15] = 1
if opts.x then model.m[12] = opts.x end
if opts.y then model.m[13] = opts.y end
if opts.z then model.m[14] = opts.z end
local material = ffi.new("pxl8_material", {
local material = ffi.new("pxl8_gfx_material", {
texture_id = opts.texture or 0,
alpha = opts.alpha or 255,
blend_mode = opts.blend_mode or 0,
@ -139,12 +149,13 @@ function gfx3d.draw_mesh(mesh, opts)
dynamic_lighting = opts.lighting or false,
per_pixel = opts.per_pixel or false,
vertex_color_passthrough = opts.passthrough or false,
wireframe = opts.wireframe or false,
emissive_intensity = opts.emissive or 0.0,
})
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)
end
function gfx3d.begin_frame(camera, uniforms)
function gfx3d.begin_frame(camera, lights, uniforms)
uniforms = uniforms or {}
local u = ffi.new("pxl8_3d_uniforms")
@ -164,27 +175,8 @@ function gfx3d.begin_frame(camera, uniforms)
end
u.celestial_intensity = uniforms.celestial_intensity or 0.0
u.num_lights = 0
if uniforms.lights then
for i, light in ipairs(uniforms.lights) do
if i > 16 then break end
local idx = i - 1
u.lights[idx].position.x = light.x or 0
u.lights[idx].position.y = light.y or 0
u.lights[idx].position.z = light.z or 0
u.lights[idx].r = light.r or 255
u.lights[idx].g = light.g or 255
u.lights[idx].b = light.b or 255
u.lights[idx].intensity = light.intensity or 255
u.lights[idx].radius = light.radius or 100
local radius_sq = u.lights[idx].radius * u.lights[idx].radius
u.lights[idx].radius_sq = radius_sq
u.lights[idx].inv_radius_sq = radius_sq > 0 and (1.0 / radius_sq) or 0
u.num_lights = i
end
end
C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u)
local lights_ptr = lights and lights._ptr or nil
C.pxl8_3d_begin_frame(core.gfx, camera._ptr, lights_ptr, u)
end
function gfx3d.clear(color)
@ -205,4 +197,12 @@ function gfx3d.end_frame()
C.pxl8_3d_end_frame(core.gfx)
end
function gfx3d.project_points(input, output, count, transform)
C.pxl8_3d_project_points(core.gfx, input, output, count, transform)
end
function gfx3d.create_vec3_array(count)
return ffi.new("pxl8_vec3[?]", count)
end
return gfx3d

View file

@ -16,7 +16,7 @@ function Gui.new()
end
function Gui:begin_frame()
C.pxl8_gui_begin_frame(self._ptr)
C.pxl8_gui_begin_frame(self._ptr, core.gfx)
end
function Gui:button(id, x, y, w, h, label)
@ -43,7 +43,7 @@ function Gui:destroy()
end
function Gui:end_frame()
C.pxl8_gui_end_frame(self._ptr)
C.pxl8_gui_end_frame(self._ptr, core.gfx)
end
function Gui:get_cursor_pos()

View file

@ -1,53 +1,65 @@
local ffi = require("ffi")
local C = ffi.C
local math3d = {}
local math = {}
function math3d.mat4_identity()
function math.hash32(x)
return C.pxl8_hash32(x)
end
function math.mat4_identity()
return C.pxl8_mat4_identity()
end
function math3d.mat4_mul(a, b)
return C.pxl8_mat4_mul(a, b)
function math.mat4_multiply(a, b)
return C.pxl8_mat4_multiply(a, b)
end
function math3d.mat4_translate(x, y, z)
function math.mat4_multiply_vec3(m, v)
return C.pxl8_mat4_multiply_vec3(m, v)
end
function math.mat4_multiply_vec4(m, v)
return C.pxl8_mat4_multiply_vec4(m, v)
end
function math.mat4_translate(x, y, z)
return C.pxl8_mat4_translate(x, y, z)
end
function math3d.mat4_rotate_x(angle)
function math.mat4_rotate_x(angle)
return C.pxl8_mat4_rotate_x(angle)
end
function math3d.mat4_rotate_y(angle)
function math.mat4_rotate_y(angle)
return C.pxl8_mat4_rotate_y(angle)
end
function math3d.mat4_rotate_z(angle)
function math.mat4_rotate_z(angle)
return C.pxl8_mat4_rotate_z(angle)
end
function math3d.mat4_scale(x, y, z)
function math.mat4_scale(x, y, z)
return C.pxl8_mat4_scale(x, y, z)
end
function math3d.mat4_ortho(left, right, bottom, top, near, far)
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far)
function math.mat4_orthographic(left, right, bottom, top, near, far)
return C.pxl8_mat4_orthographic(left, right, bottom, top, near, far)
end
function math3d.mat4_perspective(fov, aspect, near, far)
function math.mat4_perspective(fov, aspect, near, far)
return C.pxl8_mat4_perspective(fov, aspect, near, far)
end
function math3d.mat4_lookat(eye, center, up)
function math.mat4_lookat(eye, center, up)
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
end
function math3d.bounds(x, y, w, h)
function math.bounds(x, y, w, h)
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
end
return math3d
return math

View file

@ -12,7 +12,7 @@ function Particles.new(max_count)
if ps == nil then
return nil
end
local pal = C.pxl8_gfx_get_palette(core.gfx)
local pal = C.pxl8_gfx_palette(core.gfx)
if pal ~= nil then
C.pxl8_particles_set_palette(ps, pal)
end

99
src/lua/pxl8/procgen.lua Normal file
View file

@ -0,0 +1,99 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local procgen = {}
procgen.OP_CONST = C.PXL8_OP_CONST
procgen.OP_INPUT_AGE = C.PXL8_OP_INPUT_AGE
procgen.OP_INPUT_SEED = C.PXL8_OP_INPUT_SEED
procgen.OP_INPUT_TIME = C.PXL8_OP_INPUT_TIME
procgen.OP_INPUT_X = C.PXL8_OP_INPUT_X
procgen.OP_INPUT_Y = C.PXL8_OP_INPUT_Y
procgen.OP_ABS = C.PXL8_OP_ABS
procgen.OP_ADD = C.PXL8_OP_ADD
procgen.OP_CEIL = C.PXL8_OP_CEIL
procgen.OP_CLAMP = C.PXL8_OP_CLAMP
procgen.OP_COS = C.PXL8_OP_COS
procgen.OP_DIV = C.PXL8_OP_DIV
procgen.OP_FLOOR = C.PXL8_OP_FLOOR
procgen.OP_FRACT = C.PXL8_OP_FRACT
procgen.OP_GRADIENT_LINEAR = C.PXL8_OP_GRADIENT_LINEAR
procgen.OP_GRADIENT_RADIAL = C.PXL8_OP_GRADIENT_RADIAL
procgen.OP_LERP = C.PXL8_OP_LERP
procgen.OP_MAX = C.PXL8_OP_MAX
procgen.OP_MIN = C.PXL8_OP_MIN
procgen.OP_MOD = C.PXL8_OP_MOD
procgen.OP_MUL = C.PXL8_OP_MUL
procgen.OP_NEGATE = C.PXL8_OP_NEGATE
procgen.OP_NOISE_FBM = C.PXL8_OP_NOISE_FBM
procgen.OP_NOISE_PERLIN = C.PXL8_OP_NOISE_PERLIN
procgen.OP_NOISE_RIDGED = C.PXL8_OP_NOISE_RIDGED
procgen.OP_NOISE_TURBULENCE = C.PXL8_OP_NOISE_TURBULENCE
procgen.OP_NOISE_VALUE = C.PXL8_OP_NOISE_VALUE
procgen.OP_POW = C.PXL8_OP_POW
procgen.OP_QUANTIZE = C.PXL8_OP_QUANTIZE
procgen.OP_SELECT = C.PXL8_OP_SELECT
procgen.OP_SIN = C.PXL8_OP_SIN
procgen.OP_SMOOTHSTEP = C.PXL8_OP_SMOOTHSTEP
procgen.OP_SQRT = C.PXL8_OP_SQRT
procgen.OP_SUB = C.PXL8_OP_SUB
procgen.OP_VORONOI_CELL = C.PXL8_OP_VORONOI_CELL
procgen.OP_VORONOI_EDGE = C.PXL8_OP_VORONOI_EDGE
procgen.OP_VORONOI_ID = C.PXL8_OP_VORONOI_ID
local Graph = {}
Graph.__index = Graph
function Graph.new(capacity)
capacity = capacity or 64
local ptr = C.pxl8_graph_create(capacity)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = ptr }, Graph)
end
function Graph:destroy()
if self._ptr then
C.pxl8_graph_destroy(self._ptr)
self._ptr = nil
end
end
function Graph:clear()
C.pxl8_graph_clear(self._ptr)
end
function Graph:add_node(op, in0, in1, in2, in3, param)
return C.pxl8_graph_add_node(self._ptr, op, in0 or 0, in1 or 0, in2 or 0, in3 or 0, param or 0)
end
function Graph:set_output(reg)
C.pxl8_graph_set_output(self._ptr, reg)
end
function Graph:set_seed(seed)
C.pxl8_graph_set_seed(self._ptr, seed)
end
function Graph:eval_texture(width, height)
width = width or 64
height = height or 64
local buffer = ffi.new("u8[?]", width * height)
C.pxl8_graph_eval_texture(self._ptr, buffer, width, height)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
procgen.Graph = Graph
function procgen.create_graph(capacity)
return Graph.new(capacity)
end
return procgen

View file

@ -85,36 +85,14 @@ function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radiu
return result.x, result.y, result.z
end
function World:set_wireframe(enabled)
C.pxl8_world_set_wireframe(self._ptr, enabled)
end
function World:unload()
C.pxl8_world_unload(self._ptr)
end
world.World = World
function world.procgen_tex(params)
local width = params.width or 64
local height = params.height or 64
local buffer = ffi.new("u8[?]", width * height)
local tex_params = ffi.new("pxl8_procgen_tex_params")
local name = params.name or ""
ffi.copy(tex_params.name, name, math.min(#name, 15))
tex_params.seed = params.seed or 0
tex_params.width = width
tex_params.height = height
tex_params.scale = params.scale or 1.0
tex_params.roughness = params.roughness or 0.0
tex_params.base_color = params.base_color or 0
tex_params.variation = params.variation or 0
C.pxl8_procgen_tex(buffer, tex_params)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
return world

View file

@ -1,5 +1,14 @@
#include "pxl8_math.h"
u32 pxl8_hash32(u32 x) {
x ^= x >> 16;
x *= 0x85EBCA6Bu;
x ^= x >> 13;
x *= 0xC2B2AE35u;
x ^= x >> 16;
return x;
}
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
return (pxl8_vec2){
.x = a.x + b.x,
@ -101,7 +110,7 @@ pxl8_mat4 pxl8_mat4_identity(void) {
return mat;
}
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) {
pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) {
pxl8_mat4 mat = {0};
for (i32 col = 0; col < 4; col++) {
@ -117,7 +126,7 @@ pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) {
return mat;
}
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) {
pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) {
return (pxl8_vec4){
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w,
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w,
@ -126,7 +135,7 @@ pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) {
};
}
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v) {
pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v) {
return (pxl8_vec3){
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z,
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z,
@ -193,7 +202,7 @@ pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z) {
return mat;
}
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
pxl8_mat4 pxl8_mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
pxl8_mat4 mat = {0};
mat.m[0] = 2.0f / (right - left);
@ -246,15 +255,15 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
pxl8_frustum frustum;
const f32* m = vp.m;
frustum.planes[0].normal.x = m[3] - m[0];
frustum.planes[0].normal.y = m[7] - m[4];
frustum.planes[0].normal.z = m[11] - m[8];
frustum.planes[0].distance = m[15] - m[12];
frustum.planes[0].normal.x = m[3] + m[0];
frustum.planes[0].normal.y = m[7] + m[4];
frustum.planes[0].normal.z = m[11] + m[8];
frustum.planes[0].distance = m[15] + m[12];
frustum.planes[1].normal.x = m[3] + m[0];
frustum.planes[1].normal.y = m[7] + m[4];
frustum.planes[1].normal.z = m[11] + m[8];
frustum.planes[1].distance = m[15] + m[12];
frustum.planes[1].normal.x = m[3] - m[0];
frustum.planes[1].normal.y = m[7] - m[4];
frustum.planes[1].normal.z = m[11] - m[8];
frustum.planes[1].distance = m[15] - m[12];
frustum.planes[2].normal.x = m[3] + m[1];
frustum.planes[2].normal.y = m[7] + m[5];
@ -266,15 +275,15 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
frustum.planes[3].normal.z = m[11] - m[9];
frustum.planes[3].distance = m[15] - m[13];
frustum.planes[4].normal.x = m[3] - m[2];
frustum.planes[4].normal.y = m[7] - m[6];
frustum.planes[4].normal.z = m[11] - m[10];
frustum.planes[4].distance = m[15] - m[14];
frustum.planes[4].normal.x = m[3] + m[2];
frustum.planes[4].normal.y = m[7] + m[6];
frustum.planes[4].normal.z = m[11] + m[10];
frustum.planes[4].distance = m[15] + m[14];
frustum.planes[5].normal.x = m[3] + m[2];
frustum.planes[5].normal.y = m[7] + m[6];
frustum.planes[5].normal.z = m[11] + m[10];
frustum.planes[5].distance = m[15] + m[14];
frustum.planes[5].normal.x = m[3] - m[2];
frustum.planes[5].normal.y = m[7] - m[6];
frustum.planes[5].normal.z = m[11] - m[10];
frustum.planes[5].distance = m[15] - m[14];
for (i32 i = 0; i < 6; i++) {
f32 len = pxl8_vec3_length(frustum.planes[i].normal);
@ -289,19 +298,21 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
}
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) {
const f32 FRUSTUM_EPSILON = -75.0f;
for (i32 i = 0; i < 6; i++) {
pxl8_vec3 normal = frustum->planes[i].normal;
f32 d = frustum->planes[i].distance;
pxl8_vec3 p_vertex = {
(normal.x >= 0.0f) ? max.x : min.x,
(normal.y >= 0.0f) ? max.y : min.y,
(normal.z >= 0.0f) ? max.z : min.z
(normal.x > 0.0f) ? max.x : min.x,
(normal.y > 0.0f) ? max.y : min.y,
(normal.z > 0.0f) ? max.z : min.z
};
f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d;
if (p_dist < 0.0f) {
if (p_dist < FRUSTUM_EPSILON) {
return false;
}
}

View file

@ -4,16 +4,49 @@
#include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#include <xmmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#include <arm_neon.h>
#endif
#define PXL8_PI 3.14159265358979323846f
#define PXL8_TAU (PXL8_PI * 2.0f)
static inline f32 pxl8_fast_inv_sqrt(f32 x) {
#if defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
v = _mm_rsqrt_ss(v);
return _mm_cvtss_f32(v);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrsqrte_f32(v);
est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est));
return vget_lane_f32(est, 0);
#else
f32 half = 0.5f * x;
i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1);
x = *(f32*)&i;
x = x * (1.5f - half * x * x);
return x;
#endif
}
static inline f32 pxl8_fast_rcp(f32 x) {
#if defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
__m128 rcp = _mm_rcp_ss(v);
rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp))));
return _mm_cvtss_f32(rcp);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrecpe_f32(v);
est = vmul_f32(est, vrecps_f32(v, est));
return vget_lane_f32(est, 0);
#else
return 1.0f / x;
#endif
}
typedef struct pxl8_vec2 {
@ -33,18 +66,27 @@ typedef struct pxl8_mat4 {
} pxl8_mat4;
typedef struct pxl8_plane {
pxl8_vec3 normal;
f32 distance;
pxl8_vec3 normal;
} pxl8_plane;
typedef struct pxl8_frustum {
pxl8_plane planes[6];
} pxl8_frustum;
typedef struct pxl8_projected_point {
f32 depth;
i32 x;
i32 y;
bool visible;
} pxl8_projected_point;
#ifdef __cplusplus
extern "C" {
#endif
u32 pxl8_hash32(u32 x);
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_length(pxl8_vec2 v);
@ -63,10 +105,10 @@ pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b);
pxl8_mat4 pxl8_mat4_identity(void);
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v);
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v);
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);
pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v);
pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);
pxl8_mat4 pxl8_mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle);
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle);

View file

@ -1,291 +0,0 @@
#pragma once
#include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#define PXL8_SIMD_SSE2 1
#include <emmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#define PXL8_SIMD_NEON 1
#include <arm_neon.h>
#else
#define PXL8_SIMD_SCALAR 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(PXL8_SIMD_SSE2)
typedef struct { __m128 v; } pxl8_f32x4;
typedef struct { __m128i v; } pxl8_i32x4;
typedef struct { __m128i v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ _mm_set1_ps(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){ _mm_set_ps(d, c, b, a) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_add_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_sub_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_mul_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_div_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_min_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_max_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_cmplt_ps(a.v, b.v) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
return _mm_movemask_ps(a.v);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ _mm_cvttps_epi32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
_mm_storeu_ps(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ _mm_set1_epi32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_slli_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_srai_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_and_si128(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_or_si128(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
_mm_storeu_si128((__m128i*)out, a.v);
}
static inline pxl8_u16x8 pxl8_u16x8_cmplt(pxl8_u16x8 a, pxl8_u16x8 b) {
return (pxl8_u16x8){ _mm_cmplt_epi16(a.v, b.v) };
}
static inline pxl8_u16x8 pxl8_u16x8_blend(pxl8_u16x8 a, pxl8_u16x8 b, pxl8_u16x8 mask) {
__m128i not_mask = _mm_andnot_si128(mask.v, a.v);
__m128i and_mask = _mm_and_si128(mask.v, b.v);
return (pxl8_u16x8){ _mm_or_si128(not_mask, and_mask) };
}
static inline i32 pxl8_u16x8_movemask(pxl8_u16x8 a) {
return _mm_movemask_epi8(a.v);
}
#elif defined(PXL8_SIMD_NEON)
typedef struct { float32x4_t v; } pxl8_f32x4;
typedef struct { int32x4_t v; } pxl8_i32x4;
typedef struct { uint16x8_t v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ vdupq_n_f32(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
f32 arr[4] = {a, b, c, d};
return (pxl8_f32x4){ vld1q_f32(arr) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vaddq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vsubq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmulq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vdivq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vminq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmaxq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
uint32x4_t cmp = vcltq_f32(a.v, b.v);
return (pxl8_f32x4){ vreinterpretq_f32_u32(cmp) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
uint32x4_t input = vreinterpretq_u32_f32(a.v);
static const i32 shifts[4] = {0, 1, 2, 3};
uint32x4_t shifted = vshrq_n_u32(input, 31);
return vgetq_lane_u32(shifted, 0) | (vgetq_lane_u32(shifted, 1) << 1) |
(vgetq_lane_u32(shifted, 2) << 2) | (vgetq_lane_u32(shifted, 3) << 3);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ vcvtq_s32_f32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
vst1q_f32(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ vdupq_n_s32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(-n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vandq_s32(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vorrq_s32(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
vst1q_s32(out, a.v);
}
#else
typedef struct { f32 v[4]; } pxl8_f32x4;
typedef struct { i32 v[4]; } pxl8_i32x4;
typedef struct { u16 v[8]; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){{ x, x, x, x }};
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){{ a, b, c, d }};
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]+b.v[0], a.v[1]+b.v[1], a.v[2]+b.v[2], a.v[3]+b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]-b.v[0], a.v[1]-b.v[1], a.v[2]-b.v[2], a.v[3]-b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]*b.v[0], a.v[1]*b.v[1], a.v[2]*b.v[2], a.v[3]*b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]/b.v[0], a.v[1]/b.v[1], a.v[2]/b.v[2], a.v[3]/b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]<b.v[0]?a.v[0]:b.v[0], a.v[1]<b.v[1]?a.v[1]:b.v[1],
a.v[2]<b.v[2]?a.v[2]:b.v[2], a.v[3]<b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]>b.v[0]?a.v[0]:b.v[0], a.v[1]>b.v[1]?a.v[1]:b.v[1],
a.v[2]>b.v[2]?a.v[2]:b.v[2], a.v[3]>b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
pxl8_f32x4 r;
u32* rv = (u32*)r.v;
rv[0] = a.v[0] < b.v[0] ? 0xFFFFFFFF : 0;
rv[1] = a.v[1] < b.v[1] ? 0xFFFFFFFF : 0;
rv[2] = a.v[2] < b.v[2] ? 0xFFFFFFFF : 0;
rv[3] = a.v[3] < b.v[3] ? 0xFFFFFFFF : 0;
return r;
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
u32* av = (u32*)a.v;
return ((av[0] >> 31) & 1) | ((av[1] >> 31) & 1) << 1 |
((av[2] >> 31) & 1) << 2 | ((av[3] >> 31) & 1) << 3;
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){{ (i32)a.v[0], (i32)a.v[1], (i32)a.v[2], (i32)a.v[3] }};
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){{ x, x, x, x }};
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]<<n, a.v[1]<<n, a.v[2]<<n, a.v[3]<<n }};
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]>>n, a.v[1]>>n, a.v[2]>>n, a.v[3]>>n }};
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]&b.v[0], a.v[1]&b.v[1], a.v[2]&b.v[2], a.v[3]&b.v[3] }};
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]|b.v[0], a.v[1]|b.v[1], a.v[2]|b.v[2], a.v[3]|b.v[3] }};
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
#endif
#ifdef __cplusplus
}
#endif

View file

@ -1,4 +1,5 @@
#include "pxl8_net.h"
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
@ -94,7 +95,7 @@ bool pxl8_net_connected(const pxl8_net* net) {
}
pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
pxl8_net* net = calloc(1, sizeof(pxl8_net));
pxl8_net* net = pxl8_calloc(1, sizeof(pxl8_net));
if (!net) return NULL;
net->mode = config->mode;
@ -116,7 +117,7 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
void pxl8_net_destroy(pxl8_net* net) {
if (!net) return;
pxl8_net_disconnect(net);
free(net);
pxl8_free(net);
}
void pxl8_net_disconnect(pxl8_net* net) {
@ -203,11 +204,11 @@ u64 pxl8_net_player_id(const pxl8_net* net) {
bool pxl8_net_poll(pxl8_net* net) {
if (!net || !net->connected) return false;
size_t len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
if (len < sizeof(pxl8_msg_header)) return false;
pxl8_msg_header hdr;
size_t offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap;
@ -243,17 +244,17 @@ void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick) {
net->predicted_tick = tick;
}
size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len) {
usize pxl8_net_recv(pxl8_net* net, u8* buf, usize len) {
if (!net || !net->connected) return 0;
struct sockaddr_in from;
socklen_t from_len = sizeof(from);
ssize_t received = recvfrom(net->sock, (char*)buf, len, 0,
(struct sockaddr*)&from, &from_len);
return (received > 0) ? (size_t)received : 0;
return (received > 0) ? (usize)received : 0;
}
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len) {
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, usize len) {
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
ssize_t sent = sendto(net->sock, (const char*)data, len, 0,
@ -273,7 +274,7 @@ pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd) {
.sequence = 0
};
size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
usize offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
offset += pxl8_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset);
return pxl8_net_send(net, buf, offset);
@ -290,7 +291,7 @@ pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) {
.sequence = 0
};
size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
usize offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
offset += pxl8_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset);
return pxl8_net_send(net, buf, offset);

View file

@ -41,8 +41,8 @@ u64 pxl8_net_player_id(const pxl8_net* net);
bool pxl8_net_poll(pxl8_net* net);
u8* pxl8_net_predicted_state(pxl8_net* net);
void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);
size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len);
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len);
usize pxl8_net_recv(pxl8_net* net, u8* buf, usize len);
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, usize len);
pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);
pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);

View file

@ -1,7 +1,7 @@
#include "pxl8_protocol.h"
#include "pxl8_bytes.h"
size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len) {
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_msg_header)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->sequence);
@ -11,7 +11,7 @@ size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_
return s.offset;
}
size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg) {
usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg) {
if (len < sizeof(pxl8_msg_header)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->sequence = pxl8_read_u32_be(&s);
@ -21,7 +21,7 @@ size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_head
return s.offset;
}
size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len) {
usize pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_input_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->buttons);
@ -35,7 +35,7 @@ size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t
return s.offset;
}
size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg) {
usize pxl8_protocol_deserialize_input(const u8* buf, usize len, pxl8_input_msg* msg) {
if (len < sizeof(pxl8_input_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->buttons = pxl8_read_u32_be(&s);
@ -49,7 +49,7 @@ size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg
return s.offset;
}
size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len) {
usize pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_command_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u16_be(&s, msg->cmd_type);
@ -59,7 +59,7 @@ size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, siz
return s.offset;
}
size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg) {
usize pxl8_protocol_deserialize_command(const u8* buf, usize len, pxl8_command_msg* msg) {
if (len < sizeof(pxl8_command_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->cmd_type = pxl8_read_u16_be(&s);
@ -69,7 +69,7 @@ size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command
return s.offset;
}
size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len) {
usize pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, usize len) {
if (len < sizeof(pxl8_entity_state)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u64_be(&s, state->entity_id);
@ -77,7 +77,7 @@ size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8*
return s.offset;
}
size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state) {
usize pxl8_protocol_deserialize_entity_state(const u8* buf, usize len, pxl8_entity_state* state) {
if (len < sizeof(pxl8_entity_state)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
state->entity_id = pxl8_read_u64_be(&s);
@ -85,7 +85,7 @@ size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_en
return s.offset;
}
size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len) {
usize pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_event_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u8(&s, msg->event_type);
@ -93,7 +93,7 @@ size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t
return s.offset;
}
size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg) {
usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg* msg) {
if (len < sizeof(pxl8_event_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->event_type = pxl8_read_u8(&s);
@ -101,7 +101,7 @@ size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg
return s.offset;
}
size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len) {
usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len) {
if (len < sizeof(pxl8_snapshot_header)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u16_be(&s, hdr->entity_count);
@ -112,7 +112,7 @@ size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr,
return s.offset;
}
size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr) {
usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr) {
if (len < sizeof(pxl8_snapshot_header)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->entity_count = pxl8_read_u16_be(&s);

View file

@ -70,23 +70,23 @@ typedef struct pxl8_snapshot_header {
f32 time;
} pxl8_snapshot_header;
size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len);
size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg);
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg);
size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len);
size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg);
usize pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_input(const u8* buf, usize len, pxl8_input_msg* msg);
size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len);
size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg);
usize pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_command(const u8* buf, usize len, pxl8_command_msg* msg);
size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len);
size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state);
usize pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, usize len);
usize pxl8_protocol_deserialize_entity_state(const u8* buf, usize len, pxl8_entity_state* state);
size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len);
size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg);
usize pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg* msg);
size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len);
size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr);
usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len);
usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr);
#ifdef __cplusplus
}

332
src/procgen/pxl8_graph.c Normal file
View file

@ -0,0 +1,332 @@
#include "pxl8_graph.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_math.h"
#include "pxl8_mem.h"
static inline u32 hash2d(i32 x, i32 y, u32 seed) {
return pxl8_hash32((u32)x ^ ((u32)y * 2654435769u) ^ seed);
}
static inline f32 hash2d_f(i32 x, i32 y, u32 seed) {
return (f32)hash2d(x, y, seed) / (f32)0xFFFFFFFF;
}
static f32 gradient2d(i32 ix, i32 iy, f32 fx, f32 fy, u32 seed) {
u32 h = hash2d(ix, iy, seed);
f32 angle = (f32)h / (f32)0xFFFFFFFF * 6.28318530718f;
f32 gx = cosf(angle);
f32 gy = sinf(angle);
return gx * fx + gy * fy;
}
static inline f32 smoothstep(f32 t) {
return t * t * (3.0f - 2.0f * t);
}
static inline f32 lerp(f32 a, f32 b, f32 t) {
return a + t * (b - a);
}
static f32 noise_value(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = hash2d_f(ix, iy, seed);
f32 n10 = hash2d_f(ix + 1, iy, seed);
f32 n01 = hash2d_f(ix, iy + 1, seed);
f32 n11 = hash2d_f(ix + 1, iy + 1, seed);
return lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
}
static f32 noise_perlin(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = gradient2d(ix, iy, fx, fy, seed);
f32 n10 = gradient2d(ix + 1, iy, fx - 1.0f, fy, seed);
f32 n01 = gradient2d(ix, iy + 1, fx, fy - 1.0f, seed);
f32 n11 = gradient2d(ix + 1, iy + 1, fx - 1.0f, fy - 1.0f, seed);
f32 result = lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
return result * 0.5f + 0.5f;
}
static f32 noise_fbm(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
value += amplitude * noise_perlin(x, y, frequency, seed + (u32)i * 1337);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_ridged(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
n = 1.0f - fabsf(n * 2.0f - 1.0f);
value += amplitude * n;
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_turbulence(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
value += amplitude * fabsf(n * 2.0f - 1.0f);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static void voronoi(f32 x, f32 y, f32 scale, u32 seed, f32* cell_dist, f32* edge_dist, i32* cell_id) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 cx = (i32)floorf(sx);
i32 cy = (i32)floorf(sy);
f32 fx = sx - (f32)cx;
f32 fy = sy - (f32)cy;
f32 min_dist = 1e30f;
f32 second_dist = 1e30f;
i32 closest_id = 0;
for (i32 dy = -1; dy <= 1; dy++) {
for (i32 dx = -1; dx <= 1; dx++) {
i32 nx = cx + dx;
i32 ny = cy + dy;
u32 h = hash2d(nx, ny, seed);
f32 px = (f32)dx + (f32)(h & 0xFF) / 255.0f - 0.5f - fx;
f32 py = (f32)dy + (f32)((h >> 8) & 0xFF) / 255.0f - 0.5f - fy;
f32 dist = px * px + py * py;
if (dist < min_dist) {
second_dist = min_dist;
min_dist = dist;
closest_id = (i32)h;
} else if (dist < second_dist) {
second_dist = dist;
}
}
}
*cell_dist = sqrtf(min_dist);
*edge_dist = sqrtf(second_dist) - sqrtf(min_dist);
*cell_id = closest_id;
}
static f32 gradient_linear(f32 x, f32 y, f32 angle) {
f32 dx = cosf(angle);
f32 dy = sinf(angle);
f32 result = x * dx + y * dy;
return fmaxf(0.0f, fminf(1.0f, result));
}
static f32 gradient_radial(f32 x, f32 y, f32 cx, f32 cy) {
f32 dx = x - cx;
f32 dy = y - cy;
return fmaxf(0.0f, fminf(1.0f, sqrtf(dx * dx + dy * dy)));
}
pxl8_graph* pxl8_graph_create(u32 capacity) {
pxl8_graph* graph = pxl8_calloc(1, sizeof(pxl8_graph));
if (!graph) return NULL;
graph->nodes = pxl8_calloc(capacity, sizeof(pxl8_node));
if (!graph->nodes) {
pxl8_free(graph);
return NULL;
}
graph->capacity = capacity;
graph->count = 0;
graph->seed = 0;
graph->output_reg = 0;
return graph;
}
void pxl8_graph_destroy(pxl8_graph* graph) {
if (!graph) return;
pxl8_free(graph->nodes);
pxl8_free(graph);
}
void pxl8_graph_clear(pxl8_graph* graph) {
if (!graph) return;
graph->count = 0;
graph->output_reg = 0;
}
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param) {
if (!graph || graph->count >= graph->capacity) return 0;
u8 out = (u8)(graph->count + 4);
pxl8_node* node = &graph->nodes[graph->count++];
node->op = (u8)op;
node->out = out;
node->in[0] = in0;
node->in[1] = in1;
node->in[2] = in2;
node->in[3] = in3;
node->param = param;
return out;
}
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg) {
if (graph) graph->output_reg = reg;
}
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed) {
if (graph) graph->seed = seed;
}
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx) {
if (!graph || !ctx) return 0.0f;
for (u32 i = 0; i < graph->count; i++) {
const pxl8_node* n = &graph->nodes[i];
f32 a = ctx->regs[n->in[0]];
f32 b = ctx->regs[n->in[1]];
f32 c = ctx->regs[n->in[2]];
f32 d = ctx->regs[n->in[3]];
f32 result = 0.0f;
switch (n->op) {
case PXL8_OP_CONST: result = n->param; break;
case PXL8_OP_INPUT_AGE: result = ctx->regs[3]; break;
case PXL8_OP_INPUT_SEED: result = (f32)ctx->seed; break;
case PXL8_OP_INPUT_TIME: result = ctx->regs[2]; break;
case PXL8_OP_INPUT_X: result = ctx->regs[0]; break;
case PXL8_OP_INPUT_Y: result = ctx->regs[1]; break;
case PXL8_OP_ABS: result = fabsf(a); break;
case PXL8_OP_CEIL: result = ceilf(a); break;
case PXL8_OP_COS: result = cosf(a); break;
case PXL8_OP_FLOOR: result = floorf(a); break;
case PXL8_OP_FRACT: result = a - floorf(a); break;
case PXL8_OP_NEGATE: result = -a; break;
case PXL8_OP_SIN: result = sinf(a); break;
case PXL8_OP_SQRT: result = sqrtf(a); break;
case PXL8_OP_ADD: result = a + b; break;
case PXL8_OP_DIV: result = b != 0.0f ? a / b : 0.0f; break;
case PXL8_OP_MAX: result = fmaxf(a, b); break;
case PXL8_OP_MIN: result = fminf(a, b); break;
case PXL8_OP_MOD: result = b != 0.0f ? fmodf(a, b) : 0.0f; break;
case PXL8_OP_MUL: result = a * b; break;
case PXL8_OP_POW: result = powf(a, b); break;
case PXL8_OP_SUB: result = a - b; break;
case PXL8_OP_CLAMP: result = fmaxf(b, fminf(c, a)); break;
case PXL8_OP_LERP: result = a + c * (b - a); break;
case PXL8_OP_SELECT: result = c > 0.0f ? a : b; break;
case PXL8_OP_SMOOTHSTEP: {
f32 t = fmaxf(0.0f, fminf(1.0f, (c - a) / (b - a)));
result = t * t * (3.0f - 2.0f * t);
break;
}
case PXL8_OP_NOISE_FBM: result = noise_fbm(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_PERLIN: result = noise_perlin(a, b, c, ctx->seed); break;
case PXL8_OP_NOISE_RIDGED: result = noise_ridged(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_TURBULENCE:result = noise_turbulence(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_VALUE: result = noise_value(a, b, c, ctx->seed); break;
case PXL8_OP_VORONOI_CELL: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = cell;
break;
}
case PXL8_OP_VORONOI_EDGE: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = edge;
break;
}
case PXL8_OP_VORONOI_ID: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = (f32)(id & 0xFF) / 255.0f;
break;
}
case PXL8_OP_GRADIENT_LINEAR: result = gradient_linear(a, b, c); break;
case PXL8_OP_GRADIENT_RADIAL: result = gradient_radial(a, b, c, d); break;
case PXL8_OP_QUANTIZE: {
u8 base = (u8)n->param;
f32 range = b;
f32 clamped = fmaxf(0.0f, fminf(1.0f, a));
result = (f32)(base + (u8)fminf(clamped * range, range - 1.0f));
break;
}
default: break;
}
ctx->regs[n->out] = result;
}
return ctx->regs[graph->output_reg];
}
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height) {
if (!graph || !buffer) return;
pxl8_graph_context ctx = {0};
ctx.seed = graph->seed;
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
ctx.regs[0] = (f32)x / (f32)width;
ctx.regs[1] = (f32)y / (f32)height;
ctx.regs[2] = 0.0f;
ctx.regs[3] = 0.0f;
f32 result = pxl8_graph_eval(graph, &ctx);
buffer[y * width + x] = (u8)fmaxf(0.0f, fminf(255.0f, result));
}
}
}

90
src/procgen/pxl8_graph.h Normal file
View file

@ -0,0 +1,90 @@
#pragma once
#include "pxl8_types.h"
typedef enum pxl8_graph_op {
PXL8_OP_CONST, // param -> out
PXL8_OP_INPUT_AGE, // particle normalized age -> out
PXL8_OP_INPUT_SEED, // seed -> out
PXL8_OP_INPUT_TIME, // time -> out
PXL8_OP_INPUT_X, // x coord -> out
PXL8_OP_INPUT_Y, // y coord -> out
PXL8_OP_ABS, // |a| -> out
PXL8_OP_CEIL, // ceil(a) -> out
PXL8_OP_COS, // cos(a) -> out
PXL8_OP_FLOOR, // floor(a) -> out
PXL8_OP_FRACT, // fract(a) -> out
PXL8_OP_NEGATE, // -a -> out
PXL8_OP_SIN, // sin(a) -> out
PXL8_OP_SQRT, // sqrt(a) -> out
PXL8_OP_ADD, // a + b -> out
PXL8_OP_DIV, // a / b -> out
PXL8_OP_MAX, // max(a, b) -> out
PXL8_OP_MIN, // min(a, b) -> out
PXL8_OP_MOD, // fmod(a, b) -> out
PXL8_OP_MUL, // a * b -> out
PXL8_OP_POW, // pow(a, b) -> out
PXL8_OP_SUB, // a - b -> out
PXL8_OP_CLAMP, // clamp(a, min, max) -> out
PXL8_OP_LERP, // lerp(a, b, t) -> out
PXL8_OP_SELECT, // t > 0 ? a : b -> out
PXL8_OP_SMOOTHSTEP, // smoothstep(edge0, edge1, x) -> out
PXL8_OP_NOISE_FBM, // fbm(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_PERLIN, // perlin noise(x, y, scale) -> out
PXL8_OP_NOISE_RIDGED, // ridged(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_TURBULENCE,// turbulence(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_VALUE, // value noise(x, y, scale) -> out
PXL8_OP_VORONOI_CELL, // voronoi cell distance(x, y, scale) -> out
PXL8_OP_VORONOI_EDGE, // voronoi edge distance(x, y, scale) -> out
PXL8_OP_VORONOI_ID, // voronoi cell id(x, y, scale) -> out
PXL8_OP_GRADIENT_LINEAR, // linear gradient(x, y, angle) -> out
PXL8_OP_GRADIENT_RADIAL, // radial gradient(x, y, cx, cy) -> out
PXL8_OP_QUANTIZE, // quantize to palette: base + floor(a * range) -> out
PXL8_OP_COUNT
} pxl8_graph_op;
typedef struct pxl8_node {
u8 in[4];
u8 op;
u8 out;
f32 param;
} pxl8_node;
typedef struct pxl8_graph {
u32 capacity;
u32 count;
pxl8_node* nodes;
u8 output_reg;
u32 seed;
} pxl8_graph;
typedef struct pxl8_graph_context {
f32 regs[256];
u32 seed;
} pxl8_graph_context;
#ifdef __cplusplus
extern "C" {
#endif
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx);
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param);
void pxl8_graph_clear(pxl8_graph* graph);
pxl8_graph* pxl8_graph_create(u32 capacity);
void pxl8_graph_destroy(pxl8_graph* graph);
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg);
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);
#ifdef __cplusplus
}
#endif

View file

@ -1,14 +1,14 @@
#include "pxl8_repl.h"
#include "pxl8_mem.h"
#include <poll.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <SDL3/SDL.h>
#include <linenoise.h>
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
@ -29,7 +29,7 @@ struct pxl8_repl {
atomic_uint log_read_idx;
atomic_bool should_quit;
pthread_t thread;
SDL_Thread* thread;
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
pxl8_repl_command command;
};
@ -56,15 +56,15 @@ static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) {
"pxl8.error", "pxl8.debug", "pxl8.trace"
};
size_t buf_len = strlen(buf);
usize buf_len = strlen(buf);
for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) {
for (usize i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) {
if (strncmp(buf, fennel_keywords[i], buf_len) == 0) {
linenoiseAddCompletion(lc, fennel_keywords[i]);
}
}
for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) {
for (usize i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) {
if (strncmp(buf, pxl8_functions[i], buf_len) == 0) {
linenoiseAddCompletion(lc, pxl8_functions[i]);
}
@ -105,7 +105,7 @@ static void pxl8_repl_flush_logs(pxl8_repl* repl) {
fflush(stdout);
}
static void* pxl8_repl_thread(void* arg) {
static int pxl8_repl_thread(void* arg) {
pxl8_repl* repl = (pxl8_repl*)arg;
printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n");
@ -204,8 +204,7 @@ static void* pxl8_repl_thread(void* arg) {
lw = atomic_load(&repl->log_write_idx);
}
fflush(stdout);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000};
nanosleep(&ts, NULL);
SDL_Delay(1);
}
atomic_store(&repl->cmd_complete, false);
}
@ -214,11 +213,11 @@ static void* pxl8_repl_thread(void* arg) {
pxl8_repl_flush_logs(repl);
return NULL;
return 0;
}
pxl8_repl* pxl8_repl_create(void) {
pxl8_repl* repl = (pxl8_repl*)calloc(1, sizeof(pxl8_repl));
pxl8_repl* repl = (pxl8_repl*)pxl8_calloc(1, sizeof(pxl8_repl));
if (!repl) return NULL;
repl->accumulator[0] = '\0';
@ -237,8 +236,9 @@ pxl8_repl* pxl8_repl_create(void) {
g_repl = repl;
if (pthread_create(&repl->thread, NULL, pxl8_repl_thread, repl) != 0) {
free(repl);
repl->thread = SDL_CreateThread(pxl8_repl_thread, "pxl8-repl", repl);
if (!repl->thread) {
pxl8_free(repl);
g_repl = NULL;
return NULL;
}
@ -251,19 +251,18 @@ void pxl8_repl_destroy(pxl8_repl* repl) {
atomic_store(&repl->should_quit, true);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 2000000};
nanosleep(&ts, NULL);
SDL_Delay(2);
printf("\r\033[K");
fflush(stdout);
pthread_join(repl->thread, NULL);
SDL_WaitThread(repl->thread, NULL);
pxl8_repl_flush_logs(repl);
g_repl = NULL;
system("stty sane 2>/dev/null");
free(repl);
pxl8_free(repl);
}
pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl) {

View file

@ -17,6 +17,7 @@
#include "pxl8_gui.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_script_ffi.h"
struct pxl8_script {
@ -37,7 +38,7 @@ struct pxl8_script {
static int pxl8_cart_loader(lua_State* L) {
const char* found_path = lua_tostring(L, lua_upvalueindex(1));
const char* code = lua_tostring(L, lua_upvalueindex(2));
size_t code_len = lua_objlen(L, lua_upvalueindex(2));
usize code_len = lua_objlen(L, lua_upvalueindex(2));
bool is_fennel = lua_toboolean(L, lua_upvalueindex(3));
if (is_fennel) {
@ -75,9 +76,9 @@ static int pxl8_cart_searcher(lua_State* L) {
}
char path[512];
size_t len = strlen(modname);
size_t j = 0;
for (size_t i = 0; i < len && j < sizeof(path) - 5; i++) {
usize len = strlen(modname);
usize j = 0;
for (usize i = 0; i < len && j < sizeof(path) - 5; i++) {
if (modname[i] == '.') {
path[j++] = '/';
} else {
@ -114,9 +115,9 @@ static int pxl8_cart_searcher(lua_State* L) {
return 1;
}
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) {
size_t i = 0;
size_t j = 0;
static void pxl8_script_repl_promote_locals(const char* input, char* output, usize output_size) {
usize i = 0;
usize j = 0;
bool in_string = false;
bool in_comment = false;
@ -182,18 +183,18 @@ static void pxl8_script_set_error(pxl8_script* script, const char* error) {
src += 9;
const char* mod_start = src;
while (*src && !(*src == '"' && *(src+1) == ']')) src++;
size_t mod_len = src - mod_start;
usize mod_len = src - mod_start;
if (mod_len > 4 && strncmp(mod_start, "pxl8", 4) == 0) {
const char* prefix = "src/lua/";
while (*prefix && dst < end) *dst++ = *prefix++;
for (size_t i = 0; i < mod_len && dst < end; i++) {
for (usize i = 0; i < mod_len && dst < end; i++) {
*dst++ = (mod_start[i] == '.') ? '/' : mod_start[i];
}
const char* suffix = ".lua";
while (*suffix && dst < end) *dst++ = *suffix++;
} else {
for (size_t i = 0; i < mod_len && dst < end; i++) {
for (usize i = 0; i < mod_len && dst < end; i++) {
*dst++ = mod_start[i];
}
}
@ -244,13 +245,13 @@ static void pxl8_install_embed_searcher(lua_State* L) {
}
pxl8_script* pxl8_script_create(bool repl_mode) {
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script));
pxl8_script* script = (pxl8_script*)pxl8_calloc(1, sizeof(pxl8_script));
if (!script) return NULL;
script->repl_mode = repl_mode;
script->L = luaL_newstate();
if (!script->L) {
free(script);
pxl8_free(script);
return NULL;
}
@ -349,7 +350,7 @@ void pxl8_script_destroy(pxl8_script* script) {
}
lua_close(script->L);
}
free(script);
pxl8_free(script);
}
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) {
@ -395,7 +396,7 @@ void pxl8_script_set_sys(pxl8_script* script, void* sys) {
}
}
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) {
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, usize basename_size) {
char filename_copy[PATH_MAX];
pxl8_strncpy(filename_copy, filename, sizeof(filename_copy));
@ -470,7 +471,7 @@ static time_t get_latest_script_mod_time(const char* dir_path) {
latest = subdir_time;
}
} else {
size_t len = strlen(entry->d_name);
usize len = strlen(entry->d_name);
bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0);
@ -618,8 +619,8 @@ static pxl8_result pxl8_script_eval_internal(pxl8_script* script, const char* co
const char* error = lua_tostring(script->L, -1);
if (error) {
char cleaned_error[2048];
size_t j = 0;
for (size_t i = 0; error[i] && j < sizeof(cleaned_error) - 1; i++) {
usize j = 0;
for (usize i = 0; error[i] && j < sizeof(cleaned_error) - 1; i++) {
if (error[i] == '\t') {
cleaned_error[j++] = ' ';
} else {
@ -877,7 +878,7 @@ typedef struct {
static void ser_buffer_init(ser_buffer* buf) {
buf->capacity = 1024;
buf->data = malloc(buf->capacity);
buf->data = pxl8_malloc(buf->capacity);
buf->size = 0;
}
@ -886,7 +887,7 @@ static void ser_buffer_grow(ser_buffer* buf, u32 needed) {
while (buf->size + needed > buf->capacity) {
buf->capacity *= 2;
}
buf->data = realloc(buf->data, buf->capacity);
buf->data = pxl8_realloc(buf->data, buf->capacity);
}
}
@ -930,7 +931,7 @@ static void ser_write_value(ser_buffer* buf, lua_State* L, int idx, int depth) {
ser_write_f64(buf, lua_tonumber(L, idx));
break;
case LUA_TSTRING: {
size_t len;
usize len;
const char* str = lua_tolstring(L, idx, &len);
ser_write_u8(buf, SER_STRING);
ser_write_u32(buf, (u32)len);
@ -1080,7 +1081,7 @@ void pxl8_script_deserialize_globals(pxl8_script* script, const u8* data, u32 si
}
void pxl8_script_free_serialized(u8* data) {
free(data);
pxl8_free(data);
}
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {

View file

@ -11,6 +11,8 @@ static const char* pxl8_ffi_cdefs =
"typedef int64_t i64;\n"
"typedef float f32;\n"
"typedef double f64;\n"
"typedef size_t usize;\n"
"typedef ptrdiff_t isize;\n"
"typedef struct pxl8 pxl8;\n"
"typedef struct pxl8_gfx pxl8_gfx;\n"
"typedef struct { int x, y, w, h; } pxl8_bounds;\n"
@ -27,11 +29,16 @@ static const char* pxl8_ffi_cdefs =
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
"typedef struct pxl8_palette pxl8_palette;\n"
"pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);\n"
"pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);\n"
"u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\n"
"void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\n"
"void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);\n"
"i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n"
"u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n"
"typedef struct pxl8_colormap pxl8_colormap;\n"
"pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);\n"
"void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);\n"
"void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);\n"
"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n"
"void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
"void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
@ -192,21 +199,27 @@ static const char* pxl8_ffi_cdefs =
"\n"
"typedef struct pxl8_light {\n"
" pxl8_vec3 position;\n"
" f32 inv_radius_sq;\n"
" u8 r, g, b;\n"
" u8 intensity;\n"
" f32 radius;\n"
" f32 radius_sq;\n"
" f32 inv_radius_sq;\n"
"} pxl8_light;\n"
"\n"
"typedef struct pxl8_lights pxl8_lights;\n"
"pxl8_lights* pxl8_lights_create(u32 capacity);\n"
"void pxl8_lights_destroy(pxl8_lights* lights);\n"
"void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, u8 intensity, f32 radius);\n"
"void pxl8_lights_clear(pxl8_lights* lights);\n"
"u32 pxl8_lights_count(const pxl8_lights* lights);\n"
"const pxl8_light* pxl8_lights_data(const pxl8_lights* lights);\n"
"\n"
"typedef struct pxl8_3d_uniforms {\n"
" u8 ambient;\n"
" pxl8_vec3 celestial_dir;\n"
" f32 celestial_intensity;\n"
" u8 fog_color;\n"
" f32 fog_density;\n"
" pxl8_light lights[16];\n"
" u32 num_lights;\n"
" f32 time;\n"
"} pxl8_3d_uniforms;\n"
"\n"
@ -224,12 +237,14 @@ static const char* pxl8_ffi_cdefs =
"pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n"
"pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n"
"void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n"
"typedef struct pxl8_projected_point { i32 x; i32 y; f32 depth; bool visible; } pxl8_projected_point;\n"
"pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);\n"
"\n"
"typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n"
"\n"
"typedef enum pxl8_glow_shape { PXL8_GLOW_CIRCLE = 0, PXL8_GLOW_DIAMOND = 1, PXL8_GLOW_SHAFT = 2 } pxl8_glow_shape;\n"
"\n"
"typedef struct pxl8_glow_source {\n"
"typedef struct pxl8_glow {\n"
" u8 color;\n"
" u16 depth;\n"
" u8 height;\n"
@ -238,22 +253,37 @@ static const char* pxl8_ffi_cdefs =
" pxl8_glow_shape shape;\n"
" i16 x;\n"
" i16 y;\n"
"} pxl8_glow_source;\n"
"} pxl8_glow;\n"
"\n"
"void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);\n"
"void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);\n"
"\n"
"typedef struct pxl8_glows pxl8_glows;\n"
"pxl8_glows* pxl8_glows_create(u32 capacity);\n"
"void pxl8_glows_destroy(pxl8_glows* glows);\n"
"void pxl8_glows_add(pxl8_glows* glows, i16 x, i16 y, u8 radius, u16 intensity, u8 color, u8 shape);\n"
"void pxl8_glows_clear(pxl8_glows* glows);\n"
"u32 pxl8_glows_count(const pxl8_glows* glows);\n"
"void pxl8_glows_render(pxl8_glows* glows, pxl8_gfx* gfx);\n"
"void pxl8_gfx_colormap_update(pxl8_gfx* gfx);\n"
"\n"
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
"\n"
"typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n"
"\n"
"typedef struct pxl8_material {\n"
"typedef struct pxl8_gfx_material {\n"
" char name[16];\n"
" pxl8_vec3 u_axis;\n"
" pxl8_vec3 v_axis;\n"
" f32 u_offset;\n"
" f32 v_offset;\n"
" u32 texture_id;\n"
" u32 lightmap_id;\n"
" u8 alpha;\n"
" u8 blend_mode;\n"
" bool dither;\n"
@ -261,13 +291,15 @@ static const char* pxl8_ffi_cdefs =
" bool dynamic_lighting;\n"
" bool per_pixel;\n"
" bool vertex_color_passthrough;\n"
" bool wireframe;\n"
" f32 emissive_intensity;\n"
"} pxl8_material;\n"
"} pxl8_gfx_material;\n"
"\n"
"typedef struct pxl8_vertex {\n"
" pxl8_vec3 position;\n"
" pxl8_vec3 normal;\n"
" f32 u, v;\n"
" f32 lu, lv;\n"
" u8 color;\n"
" u8 light;\n"
" u8 _pad[2];\n"
@ -287,12 +319,16 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
"\n"
"u32 pxl8_hash32(u32 x);\n"
"\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
"pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);\n"
"pxl8_mat4 pxl8_mat4_ortho(float left, float right, float bottom, float top, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);\n"
"pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v);\n"
"pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);\n"
"pxl8_mat4 pxl8_mat4_orthographic(float left, float right, float bottom, float top, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n"
"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n"
@ -316,18 +352,76 @@ static const char* pxl8_ffi_cdefs =
" int num_rooms;\n"
"} pxl8_procgen_params;\n"
"\n"
"typedef struct pxl8_procgen_tex_params {\n"
" char name[16];\n"
" unsigned int seed;\n"
" int width;\n"
" int height;\n"
" float scale;\n"
" float roughness;\n"
" unsigned char base_color;\n"
" unsigned char variation;\n"
"} pxl8_procgen_tex_params;\n"
"typedef enum pxl8_graph_op {\n"
" PXL8_OP_CONST = 0,\n"
" PXL8_OP_INPUT_AGE,\n"
" PXL8_OP_INPUT_SEED,\n"
" PXL8_OP_INPUT_TIME,\n"
" PXL8_OP_INPUT_X,\n"
" PXL8_OP_INPUT_Y,\n"
"\n"
"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n"
" PXL8_OP_ABS,\n"
" PXL8_OP_CEIL,\n"
" PXL8_OP_COS,\n"
" PXL8_OP_FLOOR,\n"
" PXL8_OP_FRACT,\n"
" PXL8_OP_NEGATE,\n"
" PXL8_OP_SIN,\n"
" PXL8_OP_SQRT,\n"
"\n"
" PXL8_OP_ADD,\n"
" PXL8_OP_DIV,\n"
" PXL8_OP_MAX,\n"
" PXL8_OP_MIN,\n"
" PXL8_OP_MOD,\n"
" PXL8_OP_MUL,\n"
" PXL8_OP_POW,\n"
" PXL8_OP_SUB,\n"
"\n"
" PXL8_OP_CLAMP,\n"
" PXL8_OP_LERP,\n"
" PXL8_OP_SELECT,\n"
" PXL8_OP_SMOOTHSTEP,\n"
"\n"
" PXL8_OP_NOISE_FBM,\n"
" PXL8_OP_NOISE_PERLIN,\n"
" PXL8_OP_NOISE_RIDGED,\n"
" PXL8_OP_NOISE_TURBULENCE,\n"
" PXL8_OP_NOISE_VALUE,\n"
"\n"
" PXL8_OP_VORONOI_CELL,\n"
" PXL8_OP_VORONOI_EDGE,\n"
" PXL8_OP_VORONOI_ID,\n"
"\n"
" PXL8_OP_GRADIENT_LINEAR,\n"
" PXL8_OP_GRADIENT_RADIAL,\n"
"\n"
" PXL8_OP_QUANTIZE,\n"
" PXL8_OP_COUNT\n"
"} pxl8_graph_op;\n"
"\n"
"typedef struct pxl8_node {\n"
" u8 in[4];\n"
" u8 op;\n"
" u8 out;\n"
" f32 param;\n"
"} pxl8_node;\n"
"\n"
"typedef struct pxl8_graph {\n"
" u32 capacity;\n"
" u32 count;\n"
" pxl8_node* nodes;\n"
" u8 output_reg;\n"
" u32 seed;\n"
"} pxl8_graph;\n"
"\n"
"pxl8_graph* pxl8_graph_create(u32 capacity);\n"
"void pxl8_graph_destroy(pxl8_graph* graph);\n"
"void pxl8_graph_clear(pxl8_graph* graph);\n"
"u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param);\n"
"void pxl8_graph_set_output(pxl8_graph* graph, u8 reg);\n"
"void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);\n"
"void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n"
"\n"
"typedef struct pxl8_bsp pxl8_bsp;\n"
"typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
@ -350,12 +444,13 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_world_is_loaded(const pxl8_world* world);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n"
"\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
"pxl8_gui_state* pxl8_gui_state_create(void);\n"
"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n"
"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n"
"void pxl8_gui_end_frame(pxl8_gui_state* state);\n"
"void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);\n"
"void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);\n"
"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n"
"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n"
"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n"
@ -486,40 +581,40 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_bit_test(u32 val, u8 bit);\n"
"void pxl8_bit_toggle(u32* val, u8 bit);\n"
"\n"
"void pxl8_pack_u8(u8* buf, size_t offset, u8 val);\n"
"void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);\n"
"void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);\n"
"void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);\n"
"void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);\n"
"void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);\n"
"void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);\n"
"void pxl8_pack_i8(u8* buf, size_t offset, i8 val);\n"
"void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);\n"
"void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);\n"
"void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);\n"
"void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);\n"
"void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);\n"
"void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);\n"
"void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);\n"
"void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);\n"
"void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);\n"
"void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);\n"
"void pxl8_pack_u8(u8* buf, usize offset, u8 val);\n"
"void pxl8_pack_u16_be(u8* buf, usize offset, u16 val);\n"
"void pxl8_pack_u16_le(u8* buf, usize offset, u16 val);\n"
"void pxl8_pack_u32_be(u8* buf, usize offset, u32 val);\n"
"void pxl8_pack_u32_le(u8* buf, usize offset, u32 val);\n"
"void pxl8_pack_u64_be(u8* buf, usize offset, u64 val);\n"
"void pxl8_pack_u64_le(u8* buf, usize offset, u64 val);\n"
"void pxl8_pack_i8(u8* buf, usize offset, i8 val);\n"
"void pxl8_pack_i16_be(u8* buf, usize offset, i16 val);\n"
"void pxl8_pack_i16_le(u8* buf, usize offset, i16 val);\n"
"void pxl8_pack_i32_be(u8* buf, usize offset, i32 val);\n"
"void pxl8_pack_i32_le(u8* buf, usize offset, i32 val);\n"
"void pxl8_pack_i64_be(u8* buf, usize offset, i64 val);\n"
"void pxl8_pack_i64_le(u8* buf, usize offset, i64 val);\n"
"void pxl8_pack_f32_be(u8* buf, usize offset, f32 val);\n"
"void pxl8_pack_f32_le(u8* buf, usize offset, f32 val);\n"
"void pxl8_pack_f64_be(u8* buf, usize offset, f64 val);\n"
"void pxl8_pack_f64_le(u8* buf, usize offset, f64 val);\n"
"\n"
"u8 pxl8_unpack_u8(const u8* buf, size_t offset);\n"
"u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);\n"
"u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);\n"
"u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);\n"
"u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);\n"
"u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);\n"
"u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);\n"
"i8 pxl8_unpack_i8(const u8* buf, size_t offset);\n"
"i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);\n"
"i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);\n"
"i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);\n"
"i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);\n"
"i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);\n"
"i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);\n"
"f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);\n"
"f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);\n"
"f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);\n"
"f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);\n";
"u8 pxl8_unpack_u8(const u8* buf, usize offset);\n"
"u16 pxl8_unpack_u16_be(const u8* buf, usize offset);\n"
"u16 pxl8_unpack_u16_le(const u8* buf, usize offset);\n"
"u32 pxl8_unpack_u32_be(const u8* buf, usize offset);\n"
"u32 pxl8_unpack_u32_le(const u8* buf, usize offset);\n"
"u64 pxl8_unpack_u64_be(const u8* buf, usize offset);\n"
"u64 pxl8_unpack_u64_le(const u8* buf, usize offset);\n"
"i8 pxl8_unpack_i8(const u8* buf, usize offset);\n"
"i16 pxl8_unpack_i16_be(const u8* buf, usize offset);\n"
"i16 pxl8_unpack_i16_le(const u8* buf, usize offset);\n"
"i32 pxl8_unpack_i32_be(const u8* buf, usize offset);\n"
"i32 pxl8_unpack_i32_le(const u8* buf, usize offset);\n"
"i64 pxl8_unpack_i64_be(const u8* buf, usize offset);\n"
"i64 pxl8_unpack_i64_le(const u8* buf, usize offset);\n"
"f32 pxl8_unpack_f32_be(const u8* buf, usize offset);\n"
"f32 pxl8_unpack_f32_le(const u8* buf, usize offset);\n"
"f64 pxl8_unpack_f64_be(const u8* buf, usize offset);\n"
"f64 pxl8_unpack_f64_le(const u8* buf, usize offset);\n";

View file

@ -6,9 +6,15 @@
#include "pxl8_hal.h"
#include "pxl8_log.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
#define VOICE_SCALE 0.15f
static inline f32 sanitize_audio(f32 x) {
union { f32 f; u32 u; } conv = { .f = x };
return ((conv.u & 0x7F800000) == 0x7F800000) ? 0.0f : x;
}
typedef enum envelope_state {
ENV_ATTACK = 0,
ENV_DECAY,
@ -461,14 +467,14 @@ static void delay_process_stereo(void* state, f32* left, f32* right) {
static void delay_destroy_state(void* state) {
delay_state* d = (delay_state*)state;
if (d) {
free(d->buffer_l);
free(d->buffer_r);
free(d);
pxl8_free(d->buffer_l);
pxl8_free(d->buffer_r);
pxl8_free(d);
}
}
static void comb_init(comb_filter* c, u32 size, f32 feedback, f32 damping) {
c->buffer = (f32*)calloc(size, sizeof(f32));
c->buffer = (f32*)pxl8_calloc(size, sizeof(f32));
c->size = size;
c->feedback = feedback;
c->damping = damping;
@ -477,7 +483,7 @@ static void comb_init(comb_filter* c, u32 size, f32 feedback, f32 damping) {
}
static void comb_destroy(comb_filter* c) {
free(c->buffer);
pxl8_free(c->buffer);
}
static f32 comb_process(comb_filter* c, f32 input) {
@ -492,14 +498,14 @@ static f32 comb_process(comb_filter* c, f32 input) {
}
static void allpass_init(allpass_filter* a, u32 size) {
a->buffer = (f32*)calloc(size, sizeof(f32));
a->buffer = (f32*)pxl8_calloc(size, sizeof(f32));
a->size = size;
a->feedback = 0.5f;
a->index = 0;
}
static void allpass_destroy(allpass_filter* a) {
free(a->buffer);
pxl8_free(a->buffer);
}
static f32 allpass_process(allpass_filter* a, f32 input) {
@ -555,7 +561,7 @@ static void reverb_destroy_state(void* state) {
if (r) {
for (int i = 0; i < 4; i++) comb_destroy(&r->combs[i]);
for (int i = 0; i < 2; i++) allpass_destroy(&r->allpasses[i]);
free(r);
pxl8_free(r);
}
}
@ -601,7 +607,7 @@ static void compressor_process_stereo(void* state, f32* left, f32* right) {
}
static void compressor_destroy_state(void* state) {
free(state);
pxl8_free(state);
}
static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* out_right) {
@ -649,12 +655,12 @@ static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* ou
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
if (!hal || !hal->audio_create) return NULL;
pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)calloc(1, sizeof(pxl8_sfx_mixer));
pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)pxl8_calloc(1, sizeof(pxl8_sfx_mixer));
if (!mixer) return NULL;
mixer->output_buffer = (f32*)calloc(PXL8_SFX_BUFFER_SIZE * 2, sizeof(f32));
mixer->output_buffer = (f32*)pxl8_calloc(PXL8_SFX_BUFFER_SIZE * 2, sizeof(f32));
if (!mixer->output_buffer) {
free(mixer);
pxl8_free(mixer);
return NULL;
}
@ -663,8 +669,8 @@ pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
mixer->audio_handle = hal->audio_create(PXL8_SFX_SAMPLE_RATE, 2);
if (!mixer->audio_handle) {
free(mixer->output_buffer);
free(mixer);
pxl8_free(mixer->output_buffer);
pxl8_free(mixer);
return NULL;
}
@ -681,8 +687,8 @@ void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer) {
mixer->hal->audio_destroy(mixer->audio_handle);
}
free(mixer->output_buffer);
free(mixer);
pxl8_free(mixer->output_buffer);
pxl8_free(mixer);
}
void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
@ -716,8 +722,8 @@ void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
left = fmaxf(-1.0f, fminf(1.0f, left));
right = fmaxf(-1.0f, fminf(1.0f, right));
if (!isfinite(left)) left = 0.0f;
if (!isfinite(right)) right = 0.0f;
left = sanitize_audio(left);
right = sanitize_audio(right);
mixer->output_buffer[i * 2] = left;
mixer->output_buffer[i * 2 + 1] = right;
@ -785,7 +791,7 @@ f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer) {
}
pxl8_sfx_context* pxl8_sfx_context_create(void) {
pxl8_sfx_context* ctx = (pxl8_sfx_context*)calloc(1, sizeof(pxl8_sfx_context));
pxl8_sfx_context* ctx = (pxl8_sfx_context*)pxl8_calloc(1, sizeof(pxl8_sfx_context));
if (!ctx) return NULL;
ctx->volume = 1.0f;
@ -804,7 +810,7 @@ void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx) {
node = next;
}
free(ctx);
pxl8_free(ctx);
}
void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume) {
@ -871,26 +877,26 @@ void pxl8_sfx_node_destroy(pxl8_sfx_node* node) {
if (node->destroy && node->state) {
node->destroy(node->state);
}
free(node);
pxl8_free(node);
}
pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg) {
pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node));
pxl8_sfx_node* node = (pxl8_sfx_node*)pxl8_calloc(1, sizeof(pxl8_sfx_node));
if (!node) return NULL;
delay_state* d = (delay_state*)calloc(1, sizeof(delay_state));
delay_state* d = (delay_state*)pxl8_calloc(1, sizeof(delay_state));
if (!d) {
free(node);
pxl8_free(node);
return NULL;
}
d->buffer_l = (f32*)calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
d->buffer_r = (f32*)calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
d->buffer_l = (f32*)pxl8_calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
d->buffer_r = (f32*)pxl8_calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
if (!d->buffer_l || !d->buffer_r) {
free(d->buffer_l);
free(d->buffer_r);
free(d);
free(node);
pxl8_free(d->buffer_l);
pxl8_free(d->buffer_r);
pxl8_free(d);
pxl8_free(node);
return NULL;
}
@ -933,12 +939,12 @@ void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix) {
}
pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg) {
pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node));
pxl8_sfx_node* node = (pxl8_sfx_node*)pxl8_calloc(1, sizeof(pxl8_sfx_node));
if (!node) return NULL;
reverb_state* r = (reverb_state*)calloc(1, sizeof(reverb_state));
reverb_state* r = (reverb_state*)pxl8_calloc(1, sizeof(reverb_state));
if (!r) {
free(node);
pxl8_free(node);
return NULL;
}
@ -991,12 +997,12 @@ void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix) {
}
pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg) {
pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node));
pxl8_sfx_node* node = (pxl8_sfx_node*)pxl8_calloc(1, sizeof(pxl8_sfx_node));
if (!node) return NULL;
compressor_state* c = (compressor_state*)calloc(1, sizeof(compressor_state));
compressor_state* c = (compressor_state*)pxl8_calloc(1, sizeof(compressor_state));
if (!c) {
free(node);
pxl8_free(node);
return NULL;
}

View file

@ -8,6 +8,7 @@
#include "pxl8_gfx.h"
#include "pxl8_io.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define BSP_VERSION 29
@ -40,15 +41,23 @@ typedef struct {
pxl8_bsp_chunk chunks[CHUNK_COUNT];
} pxl8_bsp_header;
typedef struct {
f32 x0, y0, x1, y1;
} screen_rect;
typedef struct {
u32 leaf;
screen_rect window;
} portal_queue_entry;
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
pxl8_vec3 v;
v.x = pxl8_read_f32(stream);
v.y = pxl8_read_f32(stream);
v.z = pxl8_read_f32(stream);
return v;
f32 x = pxl8_read_f32(stream);
f32 y = pxl8_read_f32(stream);
f32 z = pxl8_read_f32(stream);
return (pxl8_vec3){x, z, y};
}
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, usize file_size) {
if (chunk->size == 0) return true;
if (chunk->offset >= file_size) return false;
if (chunk->offset + chunk->size > file_size) return false;
@ -75,32 +84,13 @@ static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_id
return *out_vert_idx < bsp->num_vertices;
}
static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) {
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
i32 edge_idx = bsp->surfedges[surfedge_idx];
if (edge_idx >= 0) {
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[0];
*out_v1_idx = bsp->edges[edge_idx].vertex[1];
} else {
edge_idx = -edge_idx;
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[1];
*out_v1_idx = bsp->edges[edge_idx].vertex[0];
}
return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices;
}
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
memset(bsp, 0, sizeof(*bsp));
u8* file_data = NULL;
size_t file_size = 0;
usize file_size = 0;
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
if (result != PXL8_OK) {
pxl8_error("Failed to load BSP file: %s", path);
@ -109,7 +99,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (file_size < sizeof(pxl8_bsp_header)) {
pxl8_error("BSP file too small: %s", path);
free(file_data);
pxl8_free(file_data);
return PXL8_ERROR_INVALID_FORMAT;
}
@ -120,7 +110,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (header.version != BSP_VERSION) {
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
free(file_data);
pxl8_free(file_data);
return PXL8_ERROR_INVALID_FORMAT;
}
@ -133,7 +123,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
bsp->num_vertices = chunk->size / 12;
if (bsp->num_vertices > 0) {
bsp->vertices = calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
bsp->vertices = pxl8_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
if (!bsp->vertices) goto error_cleanup;
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_vertices; i++) {
@ -145,7 +135,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_edges = chunk->size / 4;
if (bsp->num_edges > 0) {
bsp->edges = calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
bsp->edges = pxl8_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_edges; i++) {
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
@ -157,7 +147,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_surfedges = chunk->size / 4;
if (bsp->num_surfedges > 0) {
bsp->surfedges = calloc(bsp->num_surfedges, sizeof(i32));
bsp->surfedges = pxl8_calloc(bsp->num_surfedges, sizeof(i32));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_surfedges; i++) {
bsp->surfedges[i] = pxl8_read_i32(&stream);
@ -168,7 +158,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_planes = chunk->size / 20;
if (bsp->num_planes > 0) {
bsp->planes = calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
bsp->planes = pxl8_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_planes; i++) {
bsp->planes[i].normal = read_vec3(&stream);
@ -179,16 +169,20 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
chunk = &header.chunks[CHUNK_TEXINFO];
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
bsp->num_texinfo = chunk->size / 40;
if (bsp->num_texinfo > 0) {
bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo));
bsp->num_materials = chunk->size / 40;
if (bsp->num_materials > 0) {
bsp->materials = pxl8_calloc(bsp->num_materials, sizeof(pxl8_gfx_material));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_texinfo; i++) {
bsp->texinfo[i].u_axis = read_vec3(&stream);
bsp->texinfo[i].u_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].v_axis = read_vec3(&stream);
bsp->texinfo[i].v_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].miptex = pxl8_read_u32(&stream);
for (u32 i = 0; i < bsp->num_materials; i++) {
bsp->materials[i].u_axis = read_vec3(&stream);
bsp->materials[i].u_offset = pxl8_read_f32(&stream);
bsp->materials[i].v_axis = read_vec3(&stream);
bsp->materials[i].v_offset = pxl8_read_f32(&stream);
bsp->materials[i].texture_id = pxl8_read_u32(&stream);
bsp->materials[i].alpha = 255;
bsp->materials[i].dither = true;
bsp->materials[i].dynamic_lighting = true;
bsp->materials[i].double_sided = true;
}
}
@ -196,14 +190,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_faces = chunk->size / 20;
if (bsp->num_faces > 0) {
bsp->faces = calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
bsp->faces = pxl8_calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_faces; i++) {
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
bsp->faces[i].side = pxl8_read_u16(&stream);
bsp->faces[i].first_edge = pxl8_read_u32(&stream);
bsp->faces[i].num_edges = pxl8_read_u16(&stream);
bsp->faces[i].texinfo_id = pxl8_read_u16(&stream);
bsp->faces[i].material_id = pxl8_read_u16(&stream);
bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
@ -219,14 +213,24 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
bsp->num_nodes = chunk->size / 24;
if (bsp->num_nodes > 0) {
bsp->nodes = calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
bsp->nodes = pxl8_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_nodes; i++) {
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream);
i16 nx = pxl8_read_i16(&stream);
i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->nodes[i].mins[0] = nx;
bsp->nodes[i].mins[1] = nz;
bsp->nodes[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->nodes[i].maxs[0] = mx;
bsp->nodes[i].maxs[1] = mz;
bsp->nodes[i].maxs[2] = my;
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
}
@ -236,13 +240,23 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
bsp->num_leafs = chunk->size / 28;
if (bsp->num_leafs > 0) {
bsp->leafs = calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
bsp->leafs = pxl8_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_leafs; i++) {
bsp->leafs[i].contents = pxl8_read_i32(&stream);
bsp->leafs[i].visofs = pxl8_read_i32(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream);
i16 nx = pxl8_read_i16(&stream);
i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->leafs[i].mins[0] = nx;
bsp->leafs[i].mins[1] = nz;
bsp->leafs[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->leafs[i].maxs[0] = mx;
bsp->leafs[i].maxs[1] = mz;
bsp->leafs[i].maxs[2] = my;
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
@ -253,7 +267,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
bsp->num_marksurfaces = chunk->size / 2;
if (bsp->num_marksurfaces > 0) {
bsp->marksurfaces = calloc(bsp->num_marksurfaces, sizeof(u16));
bsp->marksurfaces = pxl8_calloc(bsp->num_marksurfaces, sizeof(u16));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
@ -264,11 +278,21 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
bsp->num_models = chunk->size / 64;
if (bsp->num_models > 0) {
bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model));
bsp->models = pxl8_calloc(bsp->num_models, sizeof(pxl8_bsp_model));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_models; i++) {
for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream);
for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream);
f32 minx = pxl8_read_f32(&stream);
f32 miny = pxl8_read_f32(&stream);
f32 minz = pxl8_read_f32(&stream);
bsp->models[i].mins[0] = minx;
bsp->models[i].mins[1] = minz;
bsp->models[i].mins[2] = miny;
f32 maxx = pxl8_read_f32(&stream);
f32 maxy = pxl8_read_f32(&stream);
f32 maxz = pxl8_read_f32(&stream);
bsp->models[i].maxs[0] = maxx;
bsp->models[i].maxs[1] = maxz;
bsp->models[i].maxs[2] = maxy;
bsp->models[i].origin = read_vec3(&stream);
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
bsp->models[i].visleafs = pxl8_read_i32(&stream);
@ -281,7 +305,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->visdata_size = chunk->size;
if (bsp->visdata_size > 0) {
bsp->visdata = malloc(bsp->visdata_size);
bsp->visdata = pxl8_malloc(bsp->visdata_size);
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
}
@ -289,11 +313,11 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->lightdata_size = chunk->size;
if (bsp->lightdata_size > 0) {
bsp->lightdata = malloc(bsp->lightdata_size);
bsp->lightdata = pxl8_malloc(bsp->lightdata_size);
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
}
free(file_data);
pxl8_free(file_data);
for (u32 i = 0; i < bsp->num_faces; i++) {
pxl8_bsp_face* face = &bsp->faces[i];
@ -327,7 +351,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
error_cleanup:
pxl8_error("BSP chunk validation failed: %s", path);
free(file_data);
pxl8_free(file_data);
pxl8_bsp_destroy(bsp);
return PXL8_ERROR_INVALID_FORMAT;
}
@ -335,18 +359,21 @@ error_cleanup:
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
if (!bsp) return;
free(bsp->edges);
free(bsp->faces);
free(bsp->leafs);
free(bsp->lightdata);
free(bsp->marksurfaces);
free(bsp->models);
free(bsp->nodes);
free(bsp->planes);
free(bsp->surfedges);
free(bsp->texinfo);
free(bsp->vertices);
free(bsp->visdata);
pxl8_free(bsp->cell_portals);
pxl8_free(bsp->edges);
pxl8_free(bsp->faces);
pxl8_free(bsp->leafs);
pxl8_free(bsp->lightdata);
pxl8_free(bsp->marksurfaces);
pxl8_free(bsp->materials);
pxl8_free(bsp->models);
pxl8_free(bsp->nodes);
pxl8_free(bsp->planes);
pxl8_free(bsp->render_face_flags);
pxl8_free(bsp->surfedges);
pxl8_free(bsp->vertex_lights);
pxl8_free(bsp->vertices);
pxl8_free(bsp->visdata);
memset(bsp, 0, sizeof(*bsp));
}
@ -375,85 +402,88 @@ bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
i32 visofs = bsp->leafs[leaf_from].visofs;
if (visofs < 0) return true;
u32 target_byte = leaf_to >> 3;
u32 target_bit = leaf_to & 7;
u32 pvs_size = (bsp->num_leafs + 7) / 8;
u32 row_size = (bsp->num_leafs + 7) >> 3;
u32 byte_idx = (u32)leaf_to >> 3;
u32 bit_idx = (u32)leaf_to & 7;
u32 pos = (u32)visofs;
u32 current_byte = 0;
u8* vis = bsp->visdata + visofs;
u8* vis_end = bsp->visdata + bsp->visdata_size;
u32 out = 0;
while (current_byte < pvs_size && pos < bsp->visdata_size) {
u8 b = bsp->visdata[pos++];
if (b != 0) {
if (current_byte == target_byte) {
return (b & (1 << target_bit)) != 0;
while (out < row_size && vis < vis_end) {
if (*vis) {
if (out == byte_idx) {
return (*vis & (1 << bit_idx)) != 0;
}
current_byte++;
out++;
vis++;
} else {
if (pos >= bsp->visdata_size) return false;
u32 count = bsp->visdata[pos++];
if (target_byte < current_byte + count) {
vis++;
if (vis >= vis_end) break;
u32 count = *vis++;
if (out + count > byte_idx && byte_idx >= out) {
return false;
}
current_byte += count;
out += count;
}
}
return false;
return out > byte_idx ? false : true;
}
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) {
pxl8_bsp_pvs pvs = {0};
u32 pvs_size = (bsp->num_leafs + 7) / 8;
pvs.data = calloc(pvs_size, 1);
pvs.size = pvs_size;
u32 row = (bsp->num_leafs + 7) >> 3;
pvs.data = pxl8_malloc(row);
pvs.size = row;
if (!pvs.data) return pvs;
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
memset(pvs.data, 0xFF, pvs_size);
memset(pvs.data, 0xFF, row);
return pvs;
}
i32 visofs = bsp->leafs[leaf].visofs;
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
memset(pvs.data, 0xFF, pvs_size);
memset(pvs.data, 0xFF, row);
return pvs;
}
u32 pos = (u32)visofs;
u32 out = 0;
u8* in = bsp->visdata + visofs;
u8* out = pvs.data;
u8* out_end = pvs.data + row;
while (out < pvs_size && pos < bsp->visdata_size) {
u8 b = bsp->visdata[pos++];
if (b != 0) {
pvs.data[out++] = b;
do {
if (*in) {
*out++ = *in++;
} else {
if (pos >= bsp->visdata_size) break;
u32 count = bsp->visdata[pos++];
out += count;
in++;
i32 c = *in++;
while (c > 0 && out < out_end) {
*out++ = 0;
c--;
}
}
}
} while (out < out_end);
return pvs;
}
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) {
if (pvs) {
free(pvs->data);
pxl8_free(pvs->data);
pvs->data = NULL;
pvs->size = 0;
}
}
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) {
if (!pvs || !pvs->data || leaf < 0) return false;
u32 byte_idx = leaf >> 3;
u32 bit_idx = leaf & 7;
if (byte_idx >= pvs->size) return false;
if (!pvs || !pvs->data || leaf < 0) return true;
u32 byte_idx = (u32)leaf >> 3;
u32 bit_idx = (u32)leaf & 7;
if (byte_idx >= pvs->size) return true;
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
}
@ -546,6 +576,89 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
}
static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum* frustum) {
pxl8_vec3 mins = {(f32)leaf->mins[0], (f32)leaf->mins[1], (f32)leaf->mins[2]};
pxl8_vec3 maxs = {(f32)leaf->maxs[0], (f32)leaf->maxs[1], (f32)leaf->maxs[2]};
return pxl8_frustum_test_aabb(frustum, mins, maxs);
}
static inline bool screen_rect_valid(screen_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
return (screen_rect){
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
};
}
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
if (nx < r->x0) r->x0 = nx;
if (nx > r->x1) r->x1 = nx;
if (ny < r->y0) r->y0 = ny;
if (ny > r->y1) r->y1 = ny;
}
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
pxl8_vec3 world_corners[4] = {
{portal->x0, 0, portal->z0},
{portal->x1, 0, portal->z1},
{portal->x1, wall_height, portal->z1},
{portal->x0, wall_height, portal->z0},
};
pxl8_vec4 clip[4];
bool in_front[4];
i32 front_count = 0;
const f32 NEAR_W = 0.001f;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
in_front[i] = clip[i].w > NEAR_W;
if (in_front[i]) front_count++;
}
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
i32 j = (i + 1) % 4;
pxl8_vec4 a = clip[i];
pxl8_vec4 b = clip[j];
bool a_in = in_front[i];
bool b_in = in_front[j];
if (a_in) {
f32 inv_w = 1.0f / a.w;
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
}
if (a_in != b_in) {
f32 t = (NEAR_W - a.w) / (b.w - a.w);
pxl8_vec4 intersection = {
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
a.z + t * (b.z - a.z),
NEAR_W
};
f32 inv_w = 1.0f / intersection.w;
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
}
}
if (result.x0 < -1.0f) result.x0 = -1.0f;
if (result.y0 < -1.0f) result.y0 = -1.0f;
if (result.x1 > 1.0f) result.x1 = 1.0f;
if (result.y1 > 1.0f) result.y1 = 1.0f;
return result;
}
static void collect_face_to_mesh(
const pxl8_bsp* bsp,
u32 face_id,
@ -564,17 +677,18 @@ static void collect_face_to_mesh(
}
}
const pxl8_bsp_texinfo* texinfo = NULL;
const pxl8_gfx_material* material = NULL;
f32 tex_scale = 64.0f;
if (face->texinfo_id < bsp->num_texinfo) {
texinfo = &bsp->texinfo[face->texinfo_id];
if (face->material_id < bsp->num_materials) {
material = &bsp->materials[face->material_id];
}
u16 base_idx = (u16)mesh->vertex_count;
u32 num_verts = 0;
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
i32 surfedge_idx = face->first_edge + i;
u32 edge_i = face->side ? (face->num_edges - 1 - i) : i;
i32 surfedge_idx = face->first_edge + edge_i;
u32 vert_idx;
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
@ -584,9 +698,9 @@ static void collect_face_to_mesh(
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 u = 0.0f, v = 0.0f;
if (texinfo) {
u = (pxl8_vec3_dot(pos, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
v = (pxl8_vec3_dot(pos, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
if (material) {
u = (pxl8_vec3_dot(pos, material->u_axis) + material->u_offset) / tex_scale;
v = (pxl8_vec3_dot(pos, material->v_axis) + material->v_offset) / tex_scale;
}
u8 light = 255;
@ -613,8 +727,8 @@ static void collect_face_to_mesh(
}
}
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) {
if (!gfx || !bsp || face_id >= bsp->num_faces) return;
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return;
@ -623,109 +737,115 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_create(texture_id);
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
static int call_count = 0;
if (!gfx || !bsp || bsp->num_faces == 0) {
if (call_count++ < 5) {
pxl8_debug("bsp_render_textured: early return - gfx=%p, bsp=%p, num_faces=%u",
(void*)gfx, (void*)bsp, bsp ? bsp->num_faces : 0);
}
return;
}
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
if (!gfx || !bsp || bsp->num_faces == 0) return;
if (!bsp->cell_portals || bsp->num_cell_portals == 0) return;
if (!bsp->materials || bsp->num_materials == 0) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) {
if (call_count++ < 5) {
pxl8_debug("bsp_render_textured: frustum is NULL!");
}
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
if (!frustum || !vp) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_cell_portals) return;
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
if (!bsp_mut->render_face_flags) {
bsp_mut->render_face_flags = pxl8_calloc(bsp->num_faces, 1);
if (!bsp_mut->render_face_flags) return;
}
memset(bsp_mut->render_face_flags, 0, bsp->num_faces);
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
u8* visited = pxl8_calloc(visited_bytes, 1);
screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect));
portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
if (!visited || !cell_windows || !queue) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_bsp_pvs_destroy(&pvs);
return;
}
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
static u8* rendered_faces = NULL;
static u32 rendered_faces_capacity = 0;
visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
cell_windows[camera_leaf] = full_screen;
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
if (rendered_faces_capacity < bsp->num_faces) {
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
if (!new_buffer) return;
rendered_faces = new_buffer;
rendered_faces_capacity = bsp->num_faces;
}
f32 wall_height = 128.0f;
memset(rendered_faces, 0, bsp->num_faces);
while (head < tail) {
portal_queue_entry entry = queue[head++];
u32 leaf_id = entry.leaf;
screen_rect window = entry.window;
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
u32 current_texture = 0xFFFFFFFF;
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (rendered_faces[face_id]) continue;
rendered_faces[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) {
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) {
screen_rect existing = cell_windows[target];
bool expanded = false;
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
if (expanded && tail < bsp->num_leafs * 4) {
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
}
continue;
}
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 texture_id = 0;
if (face->texinfo_id < bsp->num_texinfo) {
texture_id = bsp->texinfo[face->texinfo_id].miptex;
}
if (texture_id != current_texture && mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh_clear(mesh);
}
current_texture = texture_id;
collect_face_to_mesh(bsp, face_id, mesh);
visited[byte] |= (1 << bit);
cell_windows[target] = new_window;
queue[tail++] = (portal_queue_entry){target, new_window};
}
}
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
return;
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color) {
if (!gfx || !bsp) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
u8 line_color = (u8)(color & 0xFF);
u32 current_material = 0xFFFFFFFF;
u32 total_faces = 0;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
u32 byte = leaf_id >> 3;
u32 bit = leaf_id & 7;
if (!(visited[byte] & (1 << bit))) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
if (!leaf_in_frustum(leaf, frustum)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
@ -734,23 +854,45 @@ void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 cam
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (bsp_mut->render_face_flags[face_id]) continue;
bsp_mut->render_face_flags[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= bsp->num_materials) continue;
total_faces++;
for (u32 e = 0; e < face->num_edges; e++) {
i32 surfedge_idx = face->first_edge + e;
if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
u32 v0_idx, v1_idx;
if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue;
pxl8_vec3 p0 = bsp->vertices[v0_idx].position;
pxl8_vec3 p1 = bsp->vertices[v1_idx].position;
pxl8_3d_draw_line(gfx, p0, p1, line_color);
if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
pxl8_mesh_clear(mesh);
}
current_material = material_id;
collect_face_to_mesh(bsp, face_id, mesh);
}
}
if (mesh->index_count > 0 && current_material < bsp->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
}
static u32 debug_count = 0;
if (debug_count++ < 5) {
pxl8_debug("BSP render: %u faces, mesh verts=%u indices=%u", total_faces, mesh->vertex_count, mesh->index_count);
if (mesh->vertex_count > 0) {
pxl8_vertex* v = &mesh->vertices[0];
pxl8_debug(" vert[0]: pos=(%.1f,%.1f,%.1f) uv=(%.2f,%.2f)",
v->position.x, v->position.y, v->position.z, v->u, v->v);
}
}
pxl8_bsp_pvs_destroy(&pvs);
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_mesh_destroy(mesh);
}

View file

@ -17,7 +17,7 @@ typedef struct pxl8_bsp_face {
u16 side;
u8 styles[4];
u16 texinfo_id;
u16 material_id;
pxl8_vec3 aabb_min;
pxl8_vec3 aabb_max;
@ -63,16 +63,6 @@ typedef struct pxl8_bsp_plane {
i32 type;
} pxl8_bsp_plane;
typedef struct pxl8_bsp_texinfo {
u32 miptex;
char name[16];
f32 u_offset;
pxl8_vec3 u_axis;
f32 v_offset;
pxl8_vec3 v_axis;
} pxl8_bsp_texinfo;
typedef struct pxl8_bsp_vertex {
pxl8_vec3 position;
@ -91,47 +81,52 @@ typedef struct pxl8_bsp_lightmap_sample {
u8 r;
} pxl8_bsp_lightmap_sample;
typedef struct pxl8_bsp_material_batch {
u16* face_indices;
u32 face_count;
u8 material_id;
pxl8_mesh* mesh;
} pxl8_bsp_material_batch;
typedef struct pxl8_bsp_pvs {
u8* data;
u32 size;
} pxl8_bsp_pvs;
typedef struct pxl8_bsp_portal {
f32 x0, z0;
f32 x1, z1;
u32 target_leaf;
} pxl8_bsp_portal;
typedef struct pxl8_bsp_cell_portals {
pxl8_bsp_portal portals[4];
u8 num_portals;
} pxl8_bsp_cell_portals;
typedef struct pxl8_bsp {
pxl8_bsp_cell_portals* cell_portals;
pxl8_bsp_edge* edges;
pxl8_bsp_face* faces;
pxl8_bsp_leaf* leafs;
u8* lightdata;
pxl8_bsp_lightmap* lightmaps;
u16* marksurfaces;
pxl8_bsp_material_batch* material_batches;
pxl8_gfx_material* materials;
pxl8_bsp_model* models;
pxl8_bsp_node* nodes;
pxl8_bsp_plane* planes;
u8* render_face_flags;
i32* surfedges;
pxl8_bsp_texinfo* texinfo;
u32* vertex_lights;
pxl8_bsp_vertex* vertices;
u8* visdata;
u32 lightdata_size;
u32 num_cell_portals;
u32 num_edges;
u32 num_faces;
u32 num_leafs;
u32 num_lightmaps;
u32 num_marksurfaces;
u32 num_material_batches;
u32 num_materials;
u32 num_models;
u32 num_nodes;
u32 num_planes;
u32 num_surfedges;
u32 num_texinfo;
u32 num_vertex_lights;
u32 num_vertices;
u32 visdata_size;
@ -155,9 +150,8 @@ pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id);
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color);
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
#ifdef __cplusplus
}

View file

@ -1,21 +1,40 @@
#include "pxl8_gen.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_rng.h"
#define CELL_SIZE 64.0f
#define PVS_MAX_DEPTH 64
#define WALL_HEIGHT 128.0f
typedef struct room_grid {
u8* cells;
i32 width;
i32 height;
} room_grid;
typedef struct bsp_build_context {
pxl8_bsp* bsp;
const room_grid* grid;
u32 node_count;
u32 plane_offset;
} bsp_build_context;
typedef struct light_source {
pxl8_vec3 position;
f32 intensity;
f32 radius;
} light_source;
static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
grid->width = width;
grid->height = height;
grid->cells = calloc(width * height, sizeof(u8));
grid->cells = pxl8_calloc(width * height, sizeof(u8));
return grid->cells != NULL;
}
@ -57,6 +76,333 @@ static void room_grid_fill(room_grid* grid, u8 value) {
}
}
static f32 compute_vertex_light(
pxl8_vec3 pos,
pxl8_vec3 normal,
const light_source* lights,
u32 num_lights,
f32 ambient
) {
f32 total = ambient;
for (u32 i = 0; i < num_lights; i++) {
pxl8_vec3 to_light = {
lights[i].position.x - pos.x,
lights[i].position.y - pos.y,
lights[i].position.z - pos.z
};
f32 dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z;
f32 dist = sqrtf(dist_sq);
if (dist > lights[i].radius) continue;
if (dist < 1.0f) dist = 1.0f;
f32 inv_dist = 1.0f / dist;
pxl8_vec3 light_dir = {
to_light.x * inv_dist,
to_light.y * inv_dist,
to_light.z * inv_dist
};
f32 ndotl = normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z;
if (ndotl < 0) ndotl = 0;
f32 attenuation = 1.0f - (dist / lights[i].radius);
if (attenuation < 0) attenuation = 0;
attenuation *= attenuation;
total += lights[i].intensity * ndotl * attenuation;
}
if (total > 1.0f) total = 1.0f;
return total;
}
static void compute_bsp_vertex_lighting(
pxl8_bsp* bsp,
const light_source* lights,
u32 num_lights,
f32 ambient
) {
if (!bsp || bsp->num_vertices == 0) return;
bsp->vertex_lights = pxl8_calloc(bsp->num_vertices, sizeof(u32));
if (!bsp->vertex_lights) return;
bsp->num_vertex_lights = bsp->num_vertices;
for (u32 f = 0; f < bsp->num_faces; f++) {
const pxl8_bsp_face* face = &bsp->faces[f];
pxl8_vec3 normal = {0, 1, 0};
if (face->plane_id < bsp->num_planes) {
normal = bsp->planes[face->plane_id].normal;
}
for (u32 e = 0; e < face->num_edges; e++) {
i32 surfedge_idx = face->first_edge + e;
if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
i32 edge_idx = bsp->surfedges[surfedge_idx];
u32 vert_idx;
if (edge_idx >= 0) {
if ((u32)edge_idx >= bsp->num_edges) continue;
vert_idx = bsp->edges[edge_idx].vertex[0];
} else {
edge_idx = -edge_idx;
if ((u32)edge_idx >= bsp->num_edges) continue;
vert_idx = bsp->edges[edge_idx].vertex[1];
}
if (vert_idx >= bsp->num_vertices) continue;
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 light = compute_vertex_light(pos, normal, lights, num_lights, ambient);
u8 light_byte = (u8)(light * 255.0f);
bsp->vertex_lights[vert_idx] = ((u32)light_byte << 24) | 0x00FFFFFF;
}
}
pxl8_debug("Computed vertex lighting: %u vertices, %u lights", bsp->num_vertices, num_lights);
}
static pxl8_bsp_cell_portals* build_pxl8_bsp_cell_portals(const room_grid* grid, f32 cell_size) {
i32 total_cells = grid->width * grid->height;
pxl8_bsp_cell_portals* portals = pxl8_calloc(total_cells, sizeof(pxl8_bsp_cell_portals));
if (!portals) return NULL;
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) != 0) continue;
i32 c = y * grid->width + x;
f32 cx = x * cell_size;
f32 cz = y * cell_size;
if (x > 0 && room_grid_get(grid, x - 1, y) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx;
p->z0 = cz;
p->x1 = cx;
p->z1 = cz + cell_size;
p->target_leaf = y * grid->width + (x - 1);
}
if (x < grid->width - 1 && room_grid_get(grid, x + 1, y) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx + cell_size;
p->z0 = cz + cell_size;
p->x1 = cx + cell_size;
p->z1 = cz;
p->target_leaf = y * grid->width + (x + 1);
}
if (y > 0 && room_grid_get(grid, x, y - 1) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx + cell_size;
p->z0 = cz;
p->x1 = cx;
p->z1 = cz;
p->target_leaf = (y - 1) * grid->width + x;
}
if (y < grid->height - 1 && room_grid_get(grid, x, y + 1) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx;
p->z0 = cz + cell_size;
p->x1 = cx + cell_size;
p->z1 = cz + cell_size;
p->target_leaf = (y + 1) * grid->width + x;
}
}
}
return portals;
}
typedef struct flood_entry {
u32 leaf;
u32 depth;
} flood_entry;
static void portal_flood_bfs(
u32 start_leaf,
const pxl8_bsp_cell_portals* portals,
const pxl8_bsp_leaf* leafs,
u8* pvs,
u32 num_leafs,
f32 cell_size,
i32 grid_width
) {
(void)cell_size;
(void)grid_width;
u32 pvs_bytes = (num_leafs + 7) / 8;
u8* visited = pxl8_calloc(pvs_bytes, 1);
flood_entry* queue = pxl8_malloc(num_leafs * sizeof(flood_entry));
if (!visited || !queue) {
pxl8_free(visited);
pxl8_free(queue);
return;
}
u32 head = 0, tail = 0;
pvs[start_leaf >> 3] |= (1 << (start_leaf & 7));
visited[start_leaf >> 3] |= (1 << (start_leaf & 7));
queue[tail++] = (flood_entry){start_leaf, 0};
while (head < tail) {
flood_entry e = queue[head++];
if (e.depth >= PVS_MAX_DEPTH) continue;
if (leafs[e.leaf].contents == -1) continue;
const pxl8_bsp_cell_portals* cp = &portals[e.leaf];
for (u8 i = 0; i < cp->num_portals; i++) {
u32 target = cp->portals[i].target_leaf;
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) continue;
visited[byte] |= (1 << bit);
if (leafs[target].contents == -1) continue;
pvs[byte] |= (1 << bit);
queue[tail++] = (flood_entry){target, e.depth + 1};
}
}
pxl8_free(visited);
pxl8_free(queue);
}
static u8* compute_leaf_pvs(u32 start_leaf, const pxl8_bsp_cell_portals* portals,
u32 num_leafs, const pxl8_bsp_leaf* leafs,
const room_grid* grid, f32 cell_size) {
u32 pvs_bytes = (num_leafs + 7) / 8;
u8* pvs = pxl8_calloc(pvs_bytes, 1);
if (!pvs) return NULL;
portal_flood_bfs(start_leaf, portals, leafs, pvs, num_leafs, cell_size, grid->width);
return pvs;
}
static u32 rle_compress_pvs(const u8* pvs, u32 pvs_bytes, u8* out) {
u32 out_pos = 0;
u32 i = 0;
while (i < pvs_bytes) {
if (pvs[i] != 0) {
out[out_pos++] = pvs[i++];
} else {
u32 count = 0;
while (i < pvs_bytes && pvs[i] == 0 && count < 255) {
count++;
i++;
}
out[out_pos++] = 0;
out[out_pos++] = (u8)count;
}
}
return out_pos;
}
static pxl8_result build_pvs_data(pxl8_bsp* bsp, const pxl8_bsp_cell_portals* portals,
const room_grid* grid, f32 cell_size) {
u32 num_leafs = bsp->num_leafs;
u32 pvs_bytes = (num_leafs + 7) / 8;
u32 max_visdata = num_leafs * pvs_bytes * 2;
u8* visdata = pxl8_malloc(max_visdata);
if (!visdata) return PXL8_ERROR_OUT_OF_MEMORY;
u32 visdata_pos = 0;
u8* compressed = pxl8_malloc(pvs_bytes * 2);
if (!compressed) {
pxl8_free(visdata);
return PXL8_ERROR_OUT_OF_MEMORY;
}
u32 debug_count = 0;
for (u32 leaf = 0; leaf < num_leafs; leaf++) {
if (bsp->leafs[leaf].contents == -1) {
bsp->leafs[leaf].visofs = -1;
continue;
}
u8* pvs = compute_leaf_pvs(leaf, portals, num_leafs, bsp->leafs, grid, cell_size);
if (!pvs) {
pxl8_free(compressed);
pxl8_free(visdata);
return PXL8_ERROR_OUT_OF_MEMORY;
}
if (debug_count < 3) {
u32 visible = 0;
for (u32 b = 0; b < pvs_bytes; b++) {
for (u32 i = 0; i < 8; i++) {
if (pvs[b] & (1 << i)) visible++;
}
}
pxl8_debug("Leaf %u PVS: %u cells visible (portals: %u)", leaf, visible, portals[leaf].num_portals);
debug_count++;
}
u32 compressed_size = rle_compress_pvs(pvs, pvs_bytes, compressed);
bsp->leafs[leaf].visofs = visdata_pos;
memcpy(visdata + visdata_pos, compressed, compressed_size);
visdata_pos += compressed_size;
pxl8_free(pvs);
}
pxl8_free(compressed);
bsp->visdata = pxl8_realloc(visdata, visdata_pos > 0 ? visdata_pos : 1);
bsp->visdata_size = visdata_pos;
pxl8_debug("Built PVS: %u leafs, %u bytes visdata", num_leafs, visdata_pos);
return PXL8_OK;
}
static i32 build_bsp_node(bsp_build_context* ctx, i32 x0, i32 y0, i32 x1, i32 y1, i32 depth) {
if (x1 - x0 == 1 && y1 - y0 == 1) {
i32 leaf_idx = y0 * ctx->grid->width + x0;
return -(leaf_idx + 1);
}
i32 node_idx = ctx->node_count++;
pxl8_bsp_node* node = &ctx->bsp->nodes[node_idx];
i32 plane_idx = ctx->plane_offset++;
pxl8_bsp_plane* plane = &ctx->bsp->planes[plane_idx];
node->plane_id = plane_idx;
if (depth % 2 == 0) {
i32 mid_x = (x0 + x1) / 2;
f32 split_pos = mid_x * CELL_SIZE;
plane->normal = (pxl8_vec3){1, 0, 0};
plane->dist = split_pos;
node->children[0] = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1);
node->children[1] = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1);
} else {
i32 mid_y = (y0 + y1) / 2;
f32 split_pos = mid_y * CELL_SIZE;
plane->normal = (pxl8_vec3){0, 0, 1};
plane->dist = split_pos;
node->children[0] = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1);
node->children[1] = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1);
}
return node_idx;
}
static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
i32 vertex_count = 0;
i32 face_count = 0;
@ -74,37 +420,47 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
}
}
face_count += floor_ceiling_count * 2;
face_count += floor_ceiling_count;
vertex_count = face_count * 4;
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices",
pxl8_debug("Level generation: %dx%d grid -> %d faces, %d vertices",
grid->width, grid->height, face_count, vertex_count);
bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex));
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face));
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane));
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
bsp->surfedges = calloc(vertex_count, sizeof(i32));
i32 total_cells = grid->width * grid->height;
u32 max_nodes = 2 * total_cells;
u32 total_planes = face_count + max_nodes;
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) {
bsp->vertices = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_vertex));
bsp->faces = pxl8_calloc(face_count, sizeof(pxl8_bsp_face));
bsp->planes = pxl8_calloc(total_planes, sizeof(pxl8_bsp_plane));
bsp->edges = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_edge));
bsp->surfedges = pxl8_calloc(vertex_count, sizeof(i32));
bsp->nodes = pxl8_calloc(max_nodes, sizeof(pxl8_bsp_node));
u32* face_cell = pxl8_calloc(face_count, sizeof(u32));
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges ||
!bsp->surfedges || !bsp->nodes || !face_cell) {
pxl8_free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY;
}
bsp->texinfo = NULL;
bsp->num_texinfo = 0;
bsp->materials = NULL;
bsp->num_materials = 0;
i32 vert_idx = 0;
i32 face_idx = 0;
i32 edge_idx = 0;
const f32 cell_size = 64.0f;
const f32 wall_height = 128.0f;
const f32 cell_size = CELL_SIZE;
const f32 wall_height = WALL_HEIGHT;
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size;
i32 cell_idx = y * grid->width + x;
if (room_grid_get(grid, x - 1, y) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
@ -112,13 +468,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
bsp->planes[face_idx].dist = -fx;
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
bsp->planes[face_idx].dist = fx;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -127,6 +483,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
@ -139,13 +496,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
bsp->planes[face_idx].dist = fx + cell_size;
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
bsp->planes[face_idx].dist = -(fx + cell_size);
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -154,6 +511,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
@ -166,13 +524,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
bsp->planes[face_idx].dist = -fy;
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
bsp->planes[face_idx].dist = fy;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -181,6 +539,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
@ -193,13 +552,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
bsp->planes[face_idx].dist = fy + cell_size;
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
bsp->planes[face_idx].dist = -(fy + cell_size);
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -208,6 +567,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
@ -222,6 +582,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size;
i32 cell_idx = y * grid->width + x;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
@ -234,32 +595,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
bsp->planes[face_idx].dist = -wall_height;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -268,6 +604,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
@ -278,28 +615,147 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->num_vertices = vertex_count;
bsp->num_faces = face_count;
bsp->num_planes = face_count;
bsp->num_edges = vertex_count;
bsp->num_surfedges = vertex_count;
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf));
bsp->marksurfaces = calloc(face_count, sizeof(u16));
bsp->leafs = pxl8_calloc(total_cells, sizeof(pxl8_bsp_leaf));
bsp->marksurfaces = pxl8_calloc(face_count, sizeof(u16));
if (!bsp->leafs || !bsp->marksurfaces) {
pxl8_free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY;
}
bsp->num_leafs = 1;
bsp->num_leafs = total_cells;
bsp->num_marksurfaces = face_count;
bsp->leafs[0].first_marksurface = 0;
bsp->leafs[0].num_marksurfaces = face_count;
bsp->leafs[0].contents = -2;
u32* faces_per_cell = pxl8_calloc(total_cells, sizeof(u32));
u32* cell_offset = pxl8_calloc(total_cells, sizeof(u32));
u32* cell_cursor = pxl8_calloc(total_cells, sizeof(u32));
if (!faces_per_cell || !cell_offset || !cell_cursor) {
pxl8_free(faces_per_cell);
pxl8_free(cell_offset);
pxl8_free(cell_cursor);
pxl8_free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY;
}
for (i32 i = 0; i < face_count; i++) {
bsp->marksurfaces[i] = i;
faces_per_cell[face_cell[i]]++;
}
u32 offset = 0;
for (i32 c = 0; c < total_cells; c++) {
cell_offset[c] = offset;
offset += faces_per_cell[c];
}
for (i32 i = 0; i < face_count; i++) {
u32 c = face_cell[i];
bsp->marksurfaces[cell_offset[c] + cell_cursor[c]++] = i;
}
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
i32 c = y * grid->width + x;
pxl8_bsp_leaf* leaf = &bsp->leafs[c];
f32 fx = (f32)x * cell_size;
f32 fz = (f32)y * cell_size;
leaf->mins[0] = (i16)fx;
leaf->mins[1] = 0;
leaf->mins[2] = (i16)fz;
leaf->maxs[0] = (i16)(fx + cell_size);
leaf->maxs[1] = (i16)wall_height;
leaf->maxs[2] = (i16)(fz + cell_size);
if (room_grid_get(grid, x, y) == 0) {
leaf->contents = -2;
leaf->first_marksurface = cell_offset[c];
leaf->num_marksurfaces = faces_per_cell[c];
} else {
leaf->contents = -1;
leaf->first_marksurface = 0;
leaf->num_marksurfaces = 0;
}
leaf->visofs = -1;
}
}
pxl8_free(faces_per_cell);
pxl8_free(cell_offset);
pxl8_free(cell_cursor);
pxl8_free(face_cell);
bsp_build_context ctx = {
.bsp = bsp,
.grid = grid,
.node_count = 0,
.plane_offset = face_count,
};
build_bsp_node(&ctx, 0, 0, grid->width, grid->height, 0);
bsp->num_nodes = ctx.node_count;
bsp->num_planes = ctx.plane_offset;
pxl8_debug("Built BSP tree: %u nodes, %u leafs, %u planes",
bsp->num_nodes, bsp->num_leafs, bsp->num_planes);
pxl8_bsp_cell_portals* portals = build_pxl8_bsp_cell_portals(grid, cell_size);
if (!portals) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
u32 walkable_cells = 0;
u32 total_portals = 0;
i32 first_walkable = -1;
for (i32 c = 0; c < total_cells; c++) {
if (bsp->leafs[c].contents == -2) {
walkable_cells++;
if (first_walkable < 0) first_walkable = c;
}
total_portals += portals[c].num_portals;
}
pxl8_debug("Portal stats: %u walkable cells, %u total portals (avg %.1f per cell)",
walkable_cells, total_portals, (f32)total_portals / walkable_cells);
if (first_walkable >= 0) {
u32 pvs_bytes = (total_cells + 7) / 8;
u8* visited = pxl8_calloc(pvs_bytes, 1);
u8* queue = pxl8_malloc(total_cells * sizeof(u32));
u32 head = 0, tail = 0;
((u32*)queue)[tail++] = first_walkable;
visited[first_walkable >> 3] |= (1 << (first_walkable & 7));
u32 reachable = 0;
while (head < tail) {
u32 c = ((u32*)queue)[head++];
reachable++;
for (u8 i = 0; i < portals[c].num_portals; i++) {
u32 n = portals[c].portals[i].target_leaf;
u32 nb = n >> 3, ni = n & 7;
if (!(visited[nb] & (1 << ni))) {
visited[nb] |= (1 << ni);
((u32*)queue)[tail++] = n;
}
}
}
pxl8_debug("Connectivity: %u/%u walkable cells reachable from leaf %d",
reachable, walkable_cells, first_walkable);
pxl8_free(visited);
pxl8_free(queue);
}
pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size);
if (pvs_result != PXL8_OK) {
pxl8_free(portals);
return pvs_result;
}
bsp->cell_portals = portals;
bsp->num_cell_portals = total_cells;
return PXL8_OK;
}
@ -348,6 +804,9 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
i32 room_count = 0;
i32 max_attempts = params->num_rooms * 10;
const f32 cell_size = CELL_SIZE;
const f32 light_height = 80.0f;
for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) {
i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
@ -394,7 +853,27 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
params->width, params->height, room_count);
pxl8_result result = grid_to_bsp(bsp, &grid);
free(grid.cells);
pxl8_free(grid.cells);
if (result != PXL8_OK) {
return result;
}
light_source lights[256];
u32 num_lights = 0;
for (i32 i = 0; i < room_count && num_lights < 256; i++) {
f32 cx = (rooms[i].x + rooms[i].w / 2.0f) * cell_size;
f32 cy = (rooms[i].y + rooms[i].h / 2.0f) * cell_size;
lights[num_lights++] = (light_source){
.position = {cx, light_height, cy},
.intensity = 0.8f,
.radius = 300.0f,
};
}
compute_bsp_vertex_lighting(bsp, lights, num_lights, 0.1f);
return result;
}
@ -417,88 +896,3 @@ pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
return PXL8_ERROR_INVALID_ARGUMENT;
}
}
static u32 hash2d(i32 x, i32 y) {
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
h ^= h >> 13;
h ^= h << 17;
h ^= h >> 5;
return h;
}
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
if (!buffer || !params) return;
for (i32 y = 0; y < params->height; y++) {
for (i32 x = 0; x < params->width; x++) {
f32 u = (f32)x / (f32)params->width;
f32 v = (f32)y / (f32)params->height;
u8 color = params->base_color;
// Tile-based pattern (floor style)
if (params->seed == 11111) {
i32 tile_x = (i32)floorf(u * 8.0f);
i32 tile_y = (i32)floorf(v * 8.0f);
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
color = params->base_color + quantized;
// Checkerboard dither
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
color = (color < 255) ? color + 1 : color;
}
}
// Large tile pattern (ceiling style)
else if (params->seed == 22222) {
i32 coarse_x = (i32)floorf(u * 2.0f);
i32 coarse_y = (i32)floorf(v * 2.0f);
u32 coarse_h = hash2d(coarse_x, coarse_y);
i32 subdivision = (coarse_h >> 8) & 0x3;
i32 tile_x, tile_y;
switch (subdivision) {
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
}
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
if (pattern < 0.25f) color = params->base_color;
else if (pattern < 0.50f) color = params->base_color + 1;
else if (pattern < 0.75f) color = params->base_color + 2;
else color = params->base_color + 3;
}
// Brick pattern (wall style)
else {
f32 brick_y = floorf(v * 4.0f);
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
i32 brick_x = (i32)floorf(u * 4.0f + offset);
brick_y = (i32)brick_y;
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
u32 h = hash2d(brick_x, (i32)brick_y);
f32 noise = (f32)(h & 0xFF) / 255.0f;
// Mortar lines
if (brick_u > 0.47f || brick_v > 0.47f) {
color = params->base_color - 2;
} else {
i32 shade = (i32)(noise * 3.0f);
color = params->base_color + shade;
}
}
buffer[y * params->width + x] = color;
}
}
}

View file

@ -21,24 +21,11 @@ typedef struct pxl8_procgen_params {
i32 num_rooms;
} pxl8_procgen_params;
typedef struct pxl8_procgen_tex_params {
char name[16];
u32 seed;
i32 width;
i32 height;
f32 scale;
f32 roughness;
u8 base_color;
u8 variation;
u8 max_color;
} pxl8_procgen_tex_params;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);
#ifdef __cplusplus
}

View file

@ -7,23 +7,21 @@
#include "pxl8_gen.h"
#include "pxl8_log.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
struct pxl8_world {
pxl8_bsp bsp;
bool loaded;
bool wireframe;
u32 wireframe_color;
};
pxl8_world* pxl8_world_create(void) {
pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world));
pxl8_world* world = (pxl8_world*)pxl8_calloc(1, sizeof(pxl8_world));
if (!world) {
pxl8_error("Failed to allocate world");
return NULL;
}
world->loaded = false;
world->wireframe_color = 15;
return world;
}
@ -35,7 +33,7 @@ void pxl8_world_destroy(pxl8_world* world) {
pxl8_bsp_destroy(&world->bsp);
}
free(world);
pxl8_free(world);
}
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
@ -98,12 +96,12 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
pxl8_bsp* bsp = &world->bsp;
u32 max_texinfo = count * 6;
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo));
if (!bsp->texinfo) {
u32 max_materials = count * 6;
bsp->materials = pxl8_calloc(max_materials, sizeof(pxl8_gfx_material));
if (!bsp->materials) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
bsp->num_texinfo = 0;
bsp->num_materials = 0;
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
pxl8_bsp_face* face = &bsp->faces[face_idx];
@ -136,45 +134,50 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
}
u32 texinfo_idx = bsp->num_texinfo;
u32 material_idx = bsp->num_materials;
bool found_existing = false;
for (u32 i = 0; i < bsp->num_texinfo; i++) {
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 &&
bsp->texinfo[i].miptex == matched->texture_id &&
bsp->texinfo[i].u_axis.x == u_axis.x &&
bsp->texinfo[i].u_axis.y == u_axis.y &&
bsp->texinfo[i].u_axis.z == u_axis.z &&
bsp->texinfo[i].v_axis.x == v_axis.x &&
bsp->texinfo[i].v_axis.y == v_axis.y &&
bsp->texinfo[i].v_axis.z == v_axis.z) {
texinfo_idx = i;
for (u32 i = 0; i < bsp->num_materials; i++) {
if (strcmp(bsp->materials[i].name, matched->name) == 0 &&
bsp->materials[i].texture_id == matched->texture_id &&
bsp->materials[i].u_axis.x == u_axis.x &&
bsp->materials[i].u_axis.y == u_axis.y &&
bsp->materials[i].u_axis.z == u_axis.z &&
bsp->materials[i].v_axis.x == v_axis.x &&
bsp->materials[i].v_axis.y == v_axis.y &&
bsp->materials[i].v_axis.z == v_axis.z) {
material_idx = i;
found_existing = true;
break;
}
}
if (!found_existing) {
if (bsp->num_texinfo >= max_texinfo) {
pxl8_error("Too many unique texinfo entries");
if (bsp->num_materials >= max_materials) {
pxl8_error("Too many unique material entries");
return PXL8_ERROR_OUT_OF_MEMORY;
}
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name));
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0';
bsp->texinfo[texinfo_idx].miptex = matched->texture_id;
bsp->texinfo[texinfo_idx].u_offset = 0.0f;
bsp->texinfo[texinfo_idx].v_offset = 0.0f;
bsp->texinfo[texinfo_idx].u_axis = u_axis;
bsp->texinfo[texinfo_idx].v_axis = v_axis;
pxl8_gfx_material* mat = &bsp->materials[material_idx];
memcpy(mat->name, matched->name, sizeof(mat->name));
mat->name[sizeof(mat->name) - 1] = '\0';
mat->texture_id = matched->texture_id;
mat->u_offset = 0.0f;
mat->v_offset = 0.0f;
mat->u_axis = u_axis;
mat->v_axis = v_axis;
mat->alpha = 255;
mat->dither = true;
mat->double_sided = true;
mat->dynamic_lighting = true;
bsp->num_texinfo++;
bsp->num_materials++;
}
face->texinfo_id = texinfo_idx;
face->material_id = material_idx;
}
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries",
count, bsp->num_faces, bsp->num_texinfo);
pxl8_info("Applied %u textures to %u faces, created %u materials",
count, bsp->num_faces, bsp->num_materials);
return PXL8_OK;
}
@ -398,9 +401,13 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
return;
}
if (world->wireframe) {
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color);
} else {
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
pxl8_bsp_render(gfx, &world->bsp, camera_pos);
}
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
if (!world || !world->loaded) return;
for (u32 i = 0; i < world->bsp.num_materials; i++) {
world->bsp.materials[i].wireframe = enabled;
}
}

View file

@ -32,6 +32,7 @@ bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radi
bool pxl8_world_is_loaded(const pxl8_world* world);
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
#ifdef __cplusplus
}