#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) { .width = image_data.width, .height = 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) { .width = default_image_data.width, .height = default_image_data.height, .renderer_handle = handle }; 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; }