#include "asset_cache.h" #include "model.h" #include "scene/animation_impl.h" #include "scene/model_impl.h" #include "scene/node_impl.h" #include "scene/scene_memory.h" #include "texture.h" #include #include #include #include #include #include #include static void log_model_load_failure(const LoadModelCmd* cmd) { assert(cmd); switch (cmd->origin) { case AssetFromFile: log_error("Failed to load model: %s", mstring_cstr(&cmd->filepath)); break; case AssetFromMemory: log_error("Failed to load model: %p", cmd->data); break; } } static void log_texture_load_failure(const LoadTextureCmd* cmd) { assert(cmd); switch (cmd->origin) { case AssetFromFile: switch (cmd->type) { case LoadTexture: log_error( "Failed to load texture: %s", mstring_cstr(&cmd->data.texture.filepath)); break; case LoadCubemap: log_error( "Failed to load cubemap texture: %s", mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x)); break; } break; case AssetFromMemory: switch (cmd->type) { case LoadTexture: log_error("Failed to load texture: %p", cmd->data.texture.data); break; case LoadCubemap: log_error( "Failed to load texture: %p", cmd->data.cubemap.buffers.data_pos_x); break; } break; } } static Hash calc_model_hash(const LoadModelCmd* cmd) { assert(cmd); switch (cmd->origin) { case AssetFromFile: return cstring_hash(mstring_cstr(&cmd->filepath)); case AssetFromMemory: return (Hash)cmd->data; } FAIL("Unhandled model asset origin"); return 0; } static Hash calc_texture_hash(const LoadTextureCmd* cmd) { assert(cmd); switch (cmd->origin) { case AssetFromFile: switch (cmd->type) { case LoadTexture: return cstring_hash(mstring_cstr(&cmd->data.texture.filepath)); case LoadCubemap: return cstring_hash( mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x)) ^ cstring_hash( mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_x)) ^ cstring_hash( mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_y)) ^ cstring_hash( mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_y)) ^ cstring_hash( mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_z)) ^ cstring_hash( mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_z)); } break; case AssetFromMemory: switch (cmd->type) { case LoadTexture: return (Hash)cmd->data.texture.data; case LoadCubemap: return (Hash)cmd->data.cubemap.buffers.data_pos_x ^ (Hash)cmd->data.cubemap.buffers.data_neg_x ^ (Hash)cmd->data.cubemap.buffers.data_pos_y ^ (Hash)cmd->data.cubemap.buffers.data_neg_y ^ (Hash)cmd->data.cubemap.buffers.data_pos_z ^ (Hash)cmd->data.cubemap.buffers.data_neg_z; } break; } FAIL("Unhandled texture asset origin"); return 0; } static Asset* lookup_cache(AssetCache* cache, Hash hash) { assert(cache); mempool_foreach(&cache->assets, asset, { if (asset->hash == hash) { return asset; } }); return 0; } static void log_model_cache_hit(const LoadModelCmd* cmd, Hash hash) { assert(cmd); switch (cmd->origin) { case AssetFromFile: LOGD( "Found asset [%s] in cache with hash [%lu]", mstring_cstr(&cmd->filepath), hash); break; case AssetFromMemory: LOGD("Found asset [%p] in cache with hash [%lu]", cmd->data, hash); break; } } static void log_model_loaded(const LoadModelCmd* cmd) { assert(cmd); switch (cmd->origin) { case AssetFromFile: LOGD("Loaded asset from file: [%s]", mstring_cstr(&cmd->filepath)); break; case AssetFromMemory: LOGD("Loaded asset from memory: [%p]", cmd->data); break; } } static Model* clone_model(const Model* model) { assert(model); // Only the Anima needs to be cloned since everything else in the model is // static. // // The Anima can be partially shallow-cloned. Skeletons and animations are // static and can be shared with the original Anima. Other members are // deep-cloned. Skeletons in particular point back to their Anima, so need to // be deep-cloned. const SceneNode* root = mem_get_node(model->root); if (gfx_get_node_type(root) == AnimaNode) { const Anima* anima = gfx_get_node_anima(root); Anima* anima_copy = mem_alloc_anima(); *anima_copy = *anima; // Shallow copy. SceneNode* root_copy = gfx_clone_scene_shallow(root); root_copy->anima = mem_get_anima_index(anima_copy); anima_copy->parent = mem_get_node_index(root_copy); Model* copy = mem_alloc_model(); copy->root = mem_get_node_index(root_copy); return copy; } else { return (Model*)model; // Static model, can't be mutated. } } void gfx_init_asset_cache(AssetCache* cache) { assert(cache); mempool_make(&cache->assets); // Allocate a dummy asset at index 0 to guarantee that no assets allocated by // the caller map to index 0. const Asset* dummy = mempool_alloc(&cache->assets); assert(mempool_get_block_index(&cache->assets, dummy) == 0); } void gfx_destroy_asset_cache(AssetCache* cache) { assert(cache); mempool_del(&cache->assets); } Model* gfx_load_model(Gfx* gfx, const LoadModelCmd* cmd) { assert(gfx); AssetCache* cache = gfx_get_asset_cache(gfx); // First search for the asset in the cache. const uint64_t hash = calc_model_hash(cmd); Asset* asset = lookup_cache(cache, hash); if (asset) { log_model_cache_hit(cmd, hash); return clone_model(asset->model); } // Asset not found in the cache. // Load it, insert it into the cache, and return it. Model* model = gfx_model_load(gfx, cmd); if (model) { *(Asset*)mempool_alloc(&cache->assets) = (Asset){ .type = ModelAsset, .hash = hash, .model = model, }; log_model_loaded(cmd); return clone_model(model); } else { log_model_load_failure(cmd); return 0; } } const Texture* gfx_load_texture(Gfx* gfx, const LoadTextureCmd* cmd) { assert(gfx); assert(cmd); AssetCache* cache = gfx_get_asset_cache(gfx); // First search for the asset in the cache. const uint64_t hash = calc_texture_hash(cmd); Asset* asset = lookup_cache(cache, hash); if (asset) { return asset->texture; } // Asset not found in the cache. // Load it, insert it into the cache, and return it. GfxCore* gfxcore = gfx_get_core(gfx); const Texture* texture = gfx_texture_load(gfxcore, cmd); if (texture) { *(Asset*)mempool_alloc(&cache->assets) = (Asset){ .type = TextureAsset, .hash = hash, .texture = texture, }; } else { log_texture_load_failure(cmd); } return texture; }