275 lines
8.2 KiB
C
275 lines
8.2 KiB
C
#include "texture.h"
|
|
#include "arena.h"
|
|
#include "memory.h"
|
|
#include "json.h"
|
|
#include "renderer.h"
|
|
#include "util.h"
|
|
#include "asset_cache.h"
|
|
#include "sys.h"
|
|
#include "scratch.h"
|
|
#include "resource.h"
|
|
#include "ase.h"
|
|
#include "log.h"
|
|
|
|
#define LOOKUP_TABLE_CAPACITY_FACTOR 2.0f
|
|
|
|
struct texture_task_params {
|
|
struct texture_task_params *next_free;
|
|
|
|
struct asset *asset;
|
|
u64 path_len;
|
|
char path_cstr[1024];
|
|
};
|
|
|
|
struct texture_task_params_store {
|
|
struct texture_task_params *head_free;
|
|
struct arena arena;
|
|
struct sys_mutex mutex;
|
|
};
|
|
|
|
/* ========================== *
|
|
* Global state
|
|
* ========================== */
|
|
|
|
GLOBAL struct {
|
|
struct texture_task_params_store params;
|
|
} L = { 0 }, DEBUG_LVAR(L_texture);
|
|
|
|
/* ========================== *
|
|
* Startup
|
|
* ========================== */
|
|
|
|
void texture_startup(void)
|
|
{
|
|
L.params.arena = arena_alloc(GIGABYTE(64));
|
|
L.params.mutex = sys_mutex_alloc();
|
|
}
|
|
|
|
/* ========================== *
|
|
* Load task param store
|
|
* ========================== */
|
|
|
|
INTERNAL struct texture_task_params *texture_task_params_alloc(void)
|
|
{
|
|
struct texture_task_params *p = NULL;
|
|
{
|
|
sys_mutex_lock(&L.params.mutex);
|
|
{
|
|
if (L.params.head_free) {
|
|
p = L.params.head_free;
|
|
L.params.head_free = p->next_free;
|
|
} else {
|
|
p = arena_push_zero(&L.params.arena, struct texture_task_params);
|
|
}
|
|
}
|
|
sys_mutex_unlock(&L.params.mutex);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
INTERNAL void texture_task_params_release(struct texture_task_params *p)
|
|
{
|
|
sys_mutex_lock(&L.params.mutex);
|
|
{
|
|
p->next_free = L.params.head_free;
|
|
L.params.head_free = p;
|
|
}
|
|
sys_mutex_unlock(&L.params.mutex);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Default texture load
|
|
* ========================== */
|
|
|
|
#define DEFAULT_TEXTURE_NAME "__texture_default"
|
|
|
|
INTERNAL struct image_rgba generate_purple_black_image(struct arena *arena, u32 width, u32 height)
|
|
{
|
|
u32 *pixels = arena_push_array(arena, u32, width * height);
|
|
|
|
/* Create texture containing alternating blocks of purple and black */
|
|
u32 color_size = 4;
|
|
u32 color_1 = 0xFFDC00FF;
|
|
u32 color_2 = 0xFF000000;
|
|
for (u32 x = 0; x < width; ++x) {
|
|
for (u32 y = 0; y < height; ++y) {
|
|
u32 pixel_index = x + width * y;
|
|
if ((y / color_size) % 2 == 0) {
|
|
if ((x / color_size) % 2 == 0) {
|
|
pixels[pixel_index] = color_1;
|
|
} else {
|
|
pixels[pixel_index] = color_2;
|
|
}
|
|
} else {
|
|
if ((x / color_size) % 2 == 0) {
|
|
pixels[pixel_index] = color_2;
|
|
} else {
|
|
pixels[pixel_index] = color_1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (struct image_rgba) {
|
|
.width = width,
|
|
.height = height,
|
|
.pixels = pixels
|
|
};
|
|
}
|
|
|
|
/* ========================== *
|
|
* Load
|
|
* ========================== */
|
|
|
|
INTERNAL void texture_load_asset_task(void *vparams)
|
|
{
|
|
__prof;
|
|
struct texture_task_params *params = (struct texture_task_params *)vparams;
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
struct string path = string_from_cstr_len(params->path_cstr, params->path_len);
|
|
struct asset *asset = params->asset;
|
|
|
|
logf_info("Loading texture \"%F\"", FMT_STR(path));
|
|
sys_timestamp_t start_ts = sys_timestamp();
|
|
|
|
b32 success = false;
|
|
struct string error_msg = STR("Unknown error");
|
|
|
|
ASSERT(string_ends_with(path, STR(".ase")));
|
|
if (resource_exists(path)) {
|
|
/* Decode */
|
|
struct resource texture_rs = resource_open(path);
|
|
struct ase_decode_image_result decoded = ase_decode_image(scratch.arena, texture_rs.bytes);
|
|
resource_close(texture_rs);
|
|
|
|
/* Failure paths */
|
|
if (!decoded.valid) {
|
|
if (decoded.error_msg.len > 0) {
|
|
error_msg = decoded.error_msg;
|
|
}
|
|
goto abort;
|
|
} else if (decoded.image.width > RENDERER_TEXTURE_MAX_WIDTH || decoded.image.height > RENDERER_TEXTURE_MAX_HEIGHT) {
|
|
error_msg = string_format(scratch.arena,
|
|
STR("Image with dimensions %F x %F exceeds maximum allowed dimensions (%F x %F)"),
|
|
FMT_UINT(decoded.image.width),
|
|
FMT_UINT(decoded.image.height),
|
|
FMT_UINT(RENDERER_TEXTURE_MAX_WIDTH),
|
|
FMT_UINT(RENDERER_TEXTURE_MAX_HEIGHT));
|
|
goto abort;
|
|
} else {
|
|
success = true;
|
|
}
|
|
|
|
struct image_rgba image_data = decoded.image;
|
|
|
|
/* Create renderer texture */
|
|
struct renderer_handle handle = renderer_texture_alloc(image_data);
|
|
|
|
/* Initialize texture & its data into store */
|
|
struct texture *texture = NULL;
|
|
{
|
|
struct asset_cache_store store = asset_cache_store_open();
|
|
texture = arena_push(store.arena, struct texture);
|
|
*texture = (struct texture) {
|
|
.size = V2(image_data.width, image_data.height),
|
|
.renderer_handle = handle
|
|
};
|
|
asset_cache_store_close(&store);
|
|
}
|
|
|
|
logf_info("Finished loading texture \"%F\" in %F seconds",
|
|
FMT_STR(path),
|
|
FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)));
|
|
|
|
asset_cache_mark_ready(asset, texture);
|
|
} else {
|
|
success = false;
|
|
error_msg = STR("Resource not found");
|
|
goto abort;
|
|
}
|
|
|
|
abort:
|
|
if (!success) {
|
|
logf_error("Error loading texture \"%F\": %F", FMT_STR(path), FMT_STR(error_msg));
|
|
|
|
/* Generate purple & black image */
|
|
struct renderer_handle handle = { 0 };
|
|
struct image_rgba default_image_data = generate_purple_black_image(scratch.arena, 64, 64);
|
|
handle = renderer_texture_alloc(default_image_data);
|
|
|
|
/* Store */
|
|
struct texture *texture = NULL;
|
|
{
|
|
struct asset_cache_store store = asset_cache_store_open();
|
|
texture = arena_push(store.arena, struct texture);
|
|
*texture = (struct texture) {
|
|
.renderer_handle = handle,
|
|
.size = V2(default_image_data.width, default_image_data.height)
|
|
};
|
|
asset_cache_store_close(&store);
|
|
}
|
|
|
|
asset_cache_mark_ready(asset, texture);
|
|
}
|
|
|
|
texture_task_params_release(params);
|
|
|
|
/* Decommit decoded texture data */
|
|
scratch_end_and_decommit(scratch);
|
|
}
|
|
|
|
struct asset *texture_load_asset(struct string path, b32 help)
|
|
{
|
|
__prof;
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct string key = string_cat(scratch.arena, path, STR("_tex"));
|
|
u64 hash = asset_cache_hash(key);
|
|
b32 is_first_touch;
|
|
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch);
|
|
|
|
if (is_first_touch) {
|
|
/* Assemble task params */
|
|
struct texture_task_params *params = texture_task_params_alloc();
|
|
if (path.len > (sizeof(params->path_cstr) - 1)) {
|
|
sys_panic(string_format(scratch.arena,
|
|
STR("Texture path \"%F\" too long!"),
|
|
FMT_STR(path)));
|
|
}
|
|
string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr));
|
|
params->path_len = path.len;
|
|
params->asset = asset;
|
|
|
|
/* Push task */
|
|
asset_cache_mark_loading(asset);
|
|
struct work_handle wh = { 0 };
|
|
if (help) {
|
|
wh = work_push_task_and_help(&texture_load_asset_task, params, WORK_PRIORITY_NORMAL);
|
|
} else {
|
|
wh = work_push_task(&texture_load_asset_task, params, WORK_PRIORITY_NORMAL);
|
|
}
|
|
asset_cache_set_work(asset, &wh);
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
return asset;
|
|
}
|
|
|
|
struct texture *texture_load_async(struct string path)
|
|
{
|
|
__prof;
|
|
struct asset *asset = texture_load_asset(path, false);
|
|
struct texture *tex = (struct texture *)asset_cache_get_store_data(asset);
|
|
return tex;
|
|
}
|
|
|
|
|
|
struct texture *texture_load(struct string path)
|
|
{
|
|
__prof;
|
|
struct asset *asset = texture_load_asset(path, true);
|
|
asset_cache_wait(asset);
|
|
struct texture *tex = (struct texture *)asset_cache_get_store_data(asset);
|
|
return tex;
|
|
}
|