#include "resources.h" #include "render.h" #include "stdbool.h" #include "utils/hash_map.h" #include "utils/dictionary.h" #include "utils/debug.h" #include "utils/strutil.h" char const *RESOURCE_DIRECTORY = "resources"; typedef enum ResourceType { RESOURCE_MODEL, RESOURCE_TEXTURE, RESOURCE_SHADER, RESOURCE_MAX } ResourceType; typedef struct ResourceContainer { char const *path; char const *name; unsigned use_counter; bool is_loaded; ResourceType type; union { Model model; Texture texture; Shader shader; }; } ResourceContainer; typedef void (*LoadResourceFn)(ResourceContainer *resource); typedef void (*UnloadResourceFn)(ResourceContainer *resource); static void Internal_LoadModelResource(ResourceContainer *resource); static void Internal_UnloadModelResource(ResourceContainer *resource); static void Internal_LoadTextureResource(ResourceContainer *resource); static void Internal_UnloadTextureResource(ResourceContainer *resource); static void Internal_LoadShaderResource(ResourceContainer *resource); static void Internal_UnloadShaderResource(ResourceContainer *resource); LoadResourceFn g_load_functions[] = {Internal_LoadModelResource, Internal_LoadTextureResource, Internal_LoadShaderResource}; UnloadResourceFn g_unload_functions[] = {Internal_UnloadModelResource, Internal_UnloadTextureResource, Internal_UnloadShaderResource}; static HashMap g_resource_map; static FilePathList g_resource_files; //! hash function for hashmaps with char* as key type. static uintptr_t HashMapHashString(char const **str_ptr) { return strhash(*str_ptr); } //! Use file extensions to figure out the static ResourceType ResourceGetTypeFromPath(char const *path) { // Texture files if(IsFileExtension(path, ".png")) return RESOURCE_TEXTURE; if(IsFileExtension(path, ".jpg")) return RESOURCE_TEXTURE; if(IsFileExtension(path, ".jpeg")) return RESOURCE_TEXTURE; // Model files if(IsFileExtension(path, ".glb")) return RESOURCE_MODEL; if(IsFileExtension(path, ".gltf")) return RESOURCE_MODEL; // Shader files if(IsFileExtension(path, ".fg")) return RESOURCE_SHADER; if(IsFileExtension(path, ".vs")) return RESOURCE_SHADER; return RESOURCE_MAX; } //! Find which resource directory to use: Relative to working directory, or relative to application directory //! Working directory resources are only supposed to be used for debugging. static inline void Internal_InitializeResourceDirectory() { // if there is a resource directory in the working directory, prioritize that for debugging if(DirectoryExists(RESOURCE_DIRECTORY)) { ChangeDirectory(TextFormat("%s/%s", GetWorkingDirectory(), RESOURCE_DIRECTORY)); LOG_WARNING("Using working dir resources, this is intended for debug purposes only."); return; } // check application installation directory for a resource directory // this is the default running environment char const *installation_resource_dir = TextFormat("%s/%s", GetApplicationDirectory(), RESOURCE_DIRECTORY); if(DirectoryExists(installation_resource_dir)) { ChangeDirectory(installation_resource_dir); return; } UNREACHABLE("Failed to find a valid resource directory"); } static inline void Internal_IndexResourceDirectory() { g_resource_files = LoadDirectoryFilesEx(GetWorkingDirectory(), NULL, true); g_resource_map = hash_map_from_types(char const *, ResourceContainer, HashMapHashString); ResourceContainer placeholder; for(size_t i = 0; i < g_resource_files.count; ++i) { placeholder = (ResourceContainer){ .is_loaded = false, .use_counter = 0, .path = g_resource_files.paths[i], .name = GetFileNameWithoutExt(g_resource_files.paths[i]), .type = ResourceGetTypeFromPath(g_resource_files.paths[i]) }; // only index resources that the engine knows how to load // shaders are made up of two files, so their paths are treated differently if(placeholder.type == RESOURCE_SHADER) { // if the shader has already been added, don't accidentally add it twice. ResourceContainer *found_container = hash_map_get_as(ResourceContainer, &g_resource_map, &placeholder.name); if(found_container == NULL) { placeholder.path = GetDirectoryPath(placeholder.path); LOG_INFO("Internal_IndexResourceDirectory: Indexing %s as %s", placeholder.path, placeholder.name); hash_map_insert(&g_resource_map, &placeholder.name, &placeholder); } } else if(placeholder.type != RESOURCE_MAX) { // regular resources LOG_INFO("Internal_IndexResourceDirectory: Indexing %s as %s", placeholder.path, placeholder.name); hash_map_insert(&g_resource_map, &placeholder.name, &placeholder); } } } void InitializeResourceSubsystem() { Internal_InitializeResourceDirectory(); Internal_IndexResourceDirectory(); } void CleanResourceSubsystem() { List resources = hash_map_values(&g_resource_map); list_foreach(ResourceContainer *,resource, &resources) if(resource->is_loaded) g_unload_functions[resource->type](resource); hash_map_empty(&g_resource_map); UnloadDirectoryFiles(g_resource_files); } bool GetModelResource(char const *path, ModelResource *out) { ResourceContainer *container = hash_map_get_as(ResourceContainer, &g_resource_map, &path); *out = ResourceEmpty(Model); // assert some assumptions about the found resource ASSERT_RETURN(container != NULL, false, "GetTextureResource: Resource %s not in index.", path); ASSERT_RETURN(container->type == RESOURCE_MODEL, false, "GetTextureResource: Resource %s is not a Texture.", path); ++container->use_counter; *out = (ModelResource) { .handle = container, .resource = &container->model }; return true; } bool GetTextureResource(char const *path, TextureResource *out) { ResourceContainer *container = hash_map_get_as(ResourceContainer, &g_resource_map, &path); *out = ResourceEmpty(Texture); // assert some assumptions about the found resource ASSERT_RETURN(container != NULL, false, "GetTextureResource: Resource %s not in index.", path); ASSERT_RETURN(container->type == RESOURCE_TEXTURE, false, "GetTextureResource: Resource %s is not a Texture.", path); *out = (TextureResource) { .handle = container, .resource = &container->texture }; return true; } bool GetShaderResource(char const *path, ShaderResource *out) { ResourceContainer *container = hash_map_get_as(ResourceContainer, &g_resource_map, &path); *out = ResourceEmpty(Shader); ASSERT_RETURN(container != NULL, false, "GetShaderResource: Resource %s not in index.", path); ASSERT_RETURN(container->type == RESOURCE_SHADER, false, "GetShaderResource: Resource %s is not a Shader.", path); *out = (ShaderResource) { .handle = container, .resource = &container->shader }; return true; } bool IsResourceLoaded(ResourceHandle handle) { return handle->is_loaded; } void LoadResource(ResourceHandle handle) { ASSERT_RETURN(handle != NULL,, "LoadResource: Resource handle invalid"); g_load_functions[handle->type](handle); handle->is_loaded = true; ++handle->use_counter; } void ReleaseResource(ResourceHandle handle) { ASSERT_RETURN(handle != NULL,, "ReleaseResource: Resource handle invalid"); ASSERT_RETURN_WARN(handle->is_loaded,, "ReleaseResource: Resource %s is not loaded.", handle->path); g_unload_functions[handle->type](handle); handle->is_loaded = false; --handle->use_counter; } static void Internal_LoadModelResource(ResourceContainer *resource) { LOG_INFO("Loading model from: %s", resource->path); resource->model = LoadModel(resource->path); } static void Internal_UnloadModelResource(ResourceContainer *resource) { UnloadModel(resource->model); resource->model = (Model){0}; } static void Internal_LoadTextureResource(ResourceContainer *resource) { resource->texture = LoadTexture(resource->path); } static void Internal_UnloadTextureResource(ResourceContainer *resource) { UnloadTexture(resource->texture); resource->texture = (Texture){0}; } static void Internal_LoadShaderResource(ResourceContainer *resource) { resource->shader = LoadShader(TextFormat("%s/%s.vs", resource->path, resource->name), TextFormat("%s/%s.fs", resource->path, resource->name)); ASSERT_RETURN(IsShaderReady(resource->shader),, "Internal_LoadShaderResource: Shader failed to load."); Internal_OnShaderLoaded((ShaderResource){.resource = &resource->shader, .handle = resource}); } static void Internal_UnloadShaderResource(ResourceContainer *resource) { UnloadShader(resource->shader); resource->shader = (Shader){0}; Internal_OnShaderUnloaded((ShaderResource){.resource = &resource->shader, .handle = resource}); }