diff --git a/src/asset/CMakeLists.txt b/src/asset/CMakeLists.txt index 75531bb..709261f 100644 --- a/src/asset/CMakeLists.txt +++ b/src/asset/CMakeLists.txt @@ -8,4 +8,7 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE asset.c assetmanager.c -) \ No newline at end of file +) + +# Subdirs +add_subdirectory(type) \ No newline at end of file diff --git a/src/asset/asset.c b/src/asset/asset.c index 409fe32..fc85576 100644 --- a/src/asset/asset.c +++ b/src/asset/asset.c @@ -11,6 +11,10 @@ #include "assert/assert.h" #include "console/console.h" +assetdef_t ASSET_DEFINITIONS[ASSET_TYPE_COUNT] = { + [ASSET_TYPE_PALETTE_IMAGE] = { "DPI", assetPaletteImageLoad }, +}; + errorret_t assetInit(asset_t *asset, const char_t *filename) { assertNotNull(asset, "Asset cannot be NULL."); assertNotNull(filename, "Filename cannot be NULL."); @@ -27,10 +31,101 @@ errorret_t assetInit(asset_t *asset, const char_t *filename) { ASSET_REFERENCE_COUNT_MAX ); + // Test the file can be opened. In future I may make the handle stay open for + // a while to see if it increases performance and stops disk thrashing. asset->file = zip_fopen(ASSET_MANAGER.zip, filename, 0); if(asset->file == NULL) errorThrow("Failed to open asset file: %s", filename); + zip_fclose(asset->file); + asset->file = NULL; consolePrint("Initialized asset: %s", filename); - errorOk(); +} + +ref_t assetLock(asset_t *asset) { + assertNotNull(asset, "Asset cannot be NULL."); + + return refListLock(&asset->refList); +} + +void assetUnlock(asset_t *asset, const ref_t ref) { + assertNotNull(asset, "Asset cannot be NULL."); + assertTrue(ref > 0, "Reference must be greater than 0."); + + // Just unlock the reference in the reference list. + refListUnlock(&asset->refList, ref); +} + +errorret_t assetLoad(asset_t *asset) { + assertNotNull(asset, "Asset cannot be NULL."); + assertTrue( + asset->state == ASSET_STATE_NOT_LOADED, + "Asset is already loaded or loading." + ); + + // Mark as loading. + asset->state = ASSET_STATE_LOADING; + + // Open the file. + if(asset->file == NULL) { + asset->file = zip_fopen(ASSET_MANAGER.zip, asset->filename, 0); + if(asset->file == NULL) { + asset->state = ASSET_STATE_ERROR; + errorThrow("Failed to open asset file: %s", asset->filename); + } + } + + // Read header. + char_t header[ASSET_HEADER_SIZE + 1]; + memoryZero(header, ASSET_HEADER_SIZE + 1); + zip_int64_t bytesRead = zip_fread(asset->file, header, ASSET_HEADER_SIZE); + if(bytesRead != ASSET_HEADER_SIZE) { + asset->state = ASSET_STATE_ERROR; + zip_fclose(asset->file); + asset->file = NULL; + errorThrow("Failed to read asset header for: %s", asset->filename); + } + + // Check header. + if(strlen(header) != ASSET_HEADER_SIZE) { + asset->state = ASSET_STATE_ERROR; + zip_fclose(asset->file); + asset->file = NULL; + errorThrow("Invalid asset header for: %s", asset->filename); + } + + // Get the asset definition based on the header. + assetdef_t *def; + for(uint_fast8_t i = 0; i < ASSET_TYPE_COUNT; i++) { + if(strcmp(header, ASSET_DEFINITIONS[i].header) == 0) { + def = &ASSET_DEFINITIONS[i]; + asset->type = i; + break; + } + } + assertNotNull(def, "Failed to find asset definition for header."); + + // Load the asset + errorret_t ret = def->load(asset); + if(ret.code != ERROR_OK) { + asset->state = ASSET_STATE_ERROR; + zip_fclose(asset->file); + asset->file = NULL; + errorChain(ret); + } + + // Finished loading. + asset->state = ASSET_STATE_LOADED; + zip_fclose(asset->file); + asset->file = NULL; + errorOk(); +} + +void assetDispose(asset_t *asset) { + assertNotNull(asset, "Asset cannot be NULL."); + + if(asset->file) { + zip_fclose(asset->file); + asset->file = NULL; + } } \ No newline at end of file diff --git a/src/asset/asset.h b/src/asset/asset.h index d98c71a..aff1ca1 100644 --- a/src/asset/asset.h +++ b/src/asset/asset.h @@ -6,14 +6,19 @@ */ #pragma once -#include "assetpaletteimage.h" #include "error/error.h" #include "util/reflist.h" #include +#include "asset/type/assetpaletteimage.h" + #define ASSET_HEADER_SIZE 3 #define ASSET_REFERENCE_COUNT_MAX 8 +typedef struct { + void (*load)(); +} assetcallback_t; + typedef enum { ASSET_STATE_NOT_LOADED, ASSET_STATE_LOADING, @@ -24,18 +29,30 @@ typedef enum { typedef enum { ASSET_TYPE_UNKNOWN, ASSET_TYPE_PALETTE_IMAGE, + + ASSET_TYPE_COUNT } assettype_t; -typedef struct { - const char_t filename[FILENAME_MAX]; +typedef struct asset_s { + char_t filename[FILENAME_MAX]; ref_t refListArray[ASSET_REFERENCE_COUNT_MAX]; reflist_t refList; assetstate_t state; assettype_t type; - void *data; zip_file_t *file; + + union { + assetpaletteimage_t paletteImage; + }; } asset_t; +typedef struct { + const char_t header[ASSET_HEADER_SIZE + 1]; + errorret_t (*load)(asset_t *asset); +} assetdef_t; + +extern assetdef_t ASSET_DEFINITIONS[ASSET_TYPE_COUNT]; + /** * Initializes an asset structure. This should be called by the asset manager * only. @@ -44,4 +61,40 @@ typedef struct { * @param filename The filename of the asset. * @return An error code. */ -errorret_t assetInit(asset_t *asset, const char_t *filename); \ No newline at end of file +errorret_t assetInit(asset_t *asset, const char_t *filename); + +/** + * Requests a lock on the given asset. This will increase the reference count + * of the asset, and prevent it from being unloaded until all locks are + * released. + * + * @param asset The asset to lock. + * @return A unique reference ID for the lock. + */ +ref_t assetLock(asset_t *asset); + +/** + * Releases a lock on the given asset. This will decrease the reference count + * of the asset, and allow it to be unloaded if there are no more locks. + * + * @param asset The asset to unlock. + * @param ref The reference ID of the lock to release. + */ +void assetUnlock(asset_t *asset, const ref_t ref); + +/** + * Permission has been granted to load the asset data from disk. This should + * only be called by the asset manager. + * + * @param asset The asset to load. + * @return An error code. + */ +errorret_t assetLoad(asset_t *asset); + +/** + * Disposes of the asset, freeing any allocated memory and closing any open + * file handles. This should only be called by the asset manager. + * + * @param asset The asset to dispose of. + */ +void assetDispose(asset_t *asset); \ No newline at end of file diff --git a/src/asset/assetmanager.c b/src/asset/assetmanager.c index 29cb08b..2eca2d3 100644 --- a/src/asset/assetmanager.c +++ b/src/asset/assetmanager.c @@ -44,7 +44,18 @@ errorret_t assetManagerInit(void) { } void assetManagerUpdate(void) { - + // Update all assets + asset_t *asset = ASSET_MANAGER.assets; + while(asset < &ASSET_MANAGER.assets[ASSET_MANAGER.assetCount]) { + if(asset->state == ASSET_STATE_LOADING) { + // Check if the asset is loaded + if(asset->file != NULL) { + asset->state = ASSET_STATE_LOADED; + consolePrint("Asset loaded: %s", asset->filename); + } + } + ++asset; + } } errorret_t assetManagerGetAsset(const char_t *filename, asset_t **outAsset) { @@ -71,6 +82,7 @@ errorret_t assetManagerGetAsset(const char_t *filename, asset_t **outAsset) { // Pop an asset off the struct asset = &ASSET_MANAGER.assets[ASSET_MANAGER.assetCount++]; + *outAsset = asset; errorChain(assetInit(asset, filename)); errorOk(); } diff --git a/src/asset/assetmanager.h b/src/asset/assetmanager.h index c79bcbc..86e030d 100644 --- a/src/asset/assetmanager.h +++ b/src/asset/assetmanager.h @@ -18,7 +18,7 @@ static const char_t ASSET_MANAGER_SEARCH_PATHS[][FILENAME_MAX] = { "%s/%s", - "./%s", + "%s", "../%s", "../../%s", "data/%s", @@ -50,7 +50,8 @@ void assetManagerUpdate(void); /** * Get an asset by filename. This will return NULL if the asset does not exist. - * This will not lock the asset, you must do that separately. + * This will not lock the asset, you must do that separately. If you do not + * lock the asset there is no guarantee the asset pointer will remain valid. * * @param filename The filename of the asset to get. * @param outAsset The output asset pointer. diff --git a/src/asset/assetpaletteimage.h b/src/asset/assetpaletteimage.h deleted file mode 100644 index 208bea1..0000000 --- a/src/asset/assetpaletteimage.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -#define ASSET_PALETTE_IMAGE_SIZE_MAX (256*256) - -typedef struct { - uint32_t width; - uint32_t height; - uint8_t data[ASSET_PALETTE_IMAGE_SIZE_MAX]; -} assetpaletteimage_t; \ No newline at end of file diff --git a/src/asset/type/CMakeLists.txt b/src/asset/type/CMakeLists.txt new file mode 100644 index 0000000..588c9f4 --- /dev/null +++ b/src/asset/type/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + assetpaletteimage.c +) \ No newline at end of file diff --git a/src/asset/type/assetpaletteimage.c b/src/asset/type/assetpaletteimage.c new file mode 100644 index 0000000..2a95cd7 --- /dev/null +++ b/src/asset/type/assetpaletteimage.c @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "asset/asset.h" +#include "display/palette/palettelist.h" + +errorret_t assetPaletteImageLoad(asset_t *asset) { + // Read entire palettized image. + assetpaletteimageraw_t raw; + zip_int64_t bytesRead = zip_fread(asset->file, &raw, sizeof(raw)); + + if(bytesRead < sizeof(raw.width) + sizeof(raw.height)) { + errorThrow("Failed to read palette image dimensions."); + } + + if(raw.width <= 0 || raw.width > ASSET_PALETTE_IMAGE_WIDTH_MAX) { + errorThrow("Invalid palette image width."); + } + + if(raw.height <= 0 || raw.height > ASSET_PALETTE_IMAGE_HEIGHT_MAX) { + errorThrow("Invalid palette image height."); + } + + if(raw.paletteIndex >= PALETTE_LIST_COUNT) { + errorThrow("Invalid palette index."); + } + + printf("Palette image dimensions: %ux%u\n", raw.width, raw.height); + zip_int64_t expecting = ( + sizeof(raw.width) + + sizeof(raw.height) + + sizeof(raw.paletteIndex) + + (raw.width * raw.height * sizeof(uint8_t)) + ); + if(bytesRead != expecting) { + errorThrow("Incorrect palette filesize."); + } + + textureInit( + &asset->paletteImage.texture, + (int32_t)raw.width, + (int32_t)raw.height, + TEXTURE_FORMAT_PALETTE, + (texturedata_t){ + .palette = { + .palette = raw.paletteIndex, + .data = raw.palette + } + } + ); + + errorOk(); +} \ No newline at end of file diff --git a/src/asset/type/assetpaletteimage.h b/src/asset/type/assetpaletteimage.h new file mode 100644 index 0000000..0c639ba --- /dev/null +++ b/src/asset/type/assetpaletteimage.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "display/texture/texture.h" + +#define ASSET_PALETTE_IMAGE_WIDTH_MAX 256 +#define ASSET_PALETTE_IMAGE_HEIGHT_MAX 256 +#define ASSET_PALETTE_IMAGE_SIZE_MAX ( \ + ASSET_PALETTE_IMAGE_WIDTH_MAX * ASSET_PALETTE_IMAGE_HEIGHT_MAX \ +) + +typedef struct asset_s asset_t; + +#pragma pack(push, 1) +typedef struct { + uint32_t width; + uint32_t height; + uint8_t paletteIndex; + uint8_t palette[ASSET_PALETTE_IMAGE_SIZE_MAX]; +} assetpaletteimageraw_t; +#pragma pack(pop) + +typedef struct { + texture_t texture; +} assetpaletteimage_t; + +/** + * Loads a palette image asset from the given asset structure. The asset must + * be of type ASSET_TYPE_PALETTE_IMAGE and must be loaded. + * + * @param asset The asset to load the palette image from. + * @return An error code. + */ +errorret_t assetPaletteImageLoad(asset_t *asset); \ No newline at end of file diff --git a/src/display/scene/overworld/sceneoverworld.c b/src/display/scene/overworld/sceneoverworld.c index 86ee46e..6b2de0b 100644 --- a/src/display/scene/overworld/sceneoverworld.c +++ b/src/display/scene/overworld/sceneoverworld.c @@ -11,8 +11,10 @@ #include "display/framebuffer/framebuffer.h" #include "display/scene/scenemanager.h" #include "display/mesh/quad.h" +#include "asset/assetmanager.h" camera_t SCENE_OVERWORLD_CAMERA; +asset_t *testAsset; void sceneOverworldInit(void) { cameraInit(&SCENE_OVERWORLD_CAMERA); @@ -20,6 +22,10 @@ void sceneOverworldInit(void) { scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_OVERWORLD]; scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE; + + assetManagerGetAsset("entities.dpi", &testAsset); + ref_t lock = assetLock(testAsset); + assetLoad(testAsset); } void sceneOverworldUpdate(void) { @@ -35,7 +41,7 @@ void sceneOverworldRender(void) { // Draw overlay layer. spriteBatchPush( - NULL, + &testAsset->paletteImage.texture, 0.0f, 0.0f, 12.0f, 12.0f, 0xFF, 0x00, 0x00, 0xFF, 0.0f, 0.0f, 1.0f, 1.0f diff --git a/src/display/texture/texture.c b/src/display/texture/texture.c index 7afb522..5d50197 100644 --- a/src/display/texture/texture.c +++ b/src/display/texture/texture.c @@ -9,6 +9,7 @@ #include "assert/assert.h" #include "util/memory.h" #include "util/math.h" +#include "display/palette/palettelist.h" const texture_t *TEXTURE_BOUND = NULL; @@ -17,7 +18,7 @@ void textureInit( const int32_t width, const int32_t height, const textureformat_t format, - const void *data + const texturedata_t data ) { assertNotNull(texture, "Texture cannot be NULL"); assertTrue(width > 0 && height > 0, "width/height must be greater than 0"); @@ -40,15 +41,96 @@ void textureInit( #if DISPLAY_SDL2 glGenTextures(1, &texture->id); glBindTexture(GL_TEXTURE_2D, texture->id); - glTexImage2D( - GL_TEXTURE_2D, 0, format, width, height, 0, - format, GL_UNSIGNED_BYTE, data - ); + + switch(format) { + case TEXTURE_FORMAT_RGBA: + assertNotNull(data.rgba.colors, "RGBA texture data cannot be NULL"); + glTexImage2D( + GL_TEXTURE_2D, 0, format, width, height, 0, + format, GL_UNSIGNED_BYTE, (void*)data.rgba.colors + ); + break; + + case TEXTURE_FORMAT_ALPHA: + assertNotNull(data.alpha.data, "Alpha texture data cannot be NULL"); + glTexImage2D( + GL_TEXTURE_2D, 0, format, width, height, 0, + format, GL_UNSIGNED_BYTE, (void*)data.alpha.data + ); + break; + + case TEXTURE_FORMAT_PALETTE: + assertNotNull(data.palette.data, "Palette texture data cannot be NULL"); + assertTrue( + data.palette.palette < PALETTE_LIST_COUNT, + "Palette index out of range" + ); + + const palette_t *pal = PALETTE_LIST[data.palette.palette]; + assertNotNull(pal, "Palette cannot be NULL"); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + assertUnreachable("GL Error before setting color table"); + } + + // Do we support paletted textures? + bool_t havePalTex = false; + GLint mask = 0; + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask); + if(mask & GL_CONTEXT_CORE_PROFILE_BIT) { + GLint n=0; glGetIntegerv(GL_NUM_EXTENSIONS, &n); + for (GLint i=0; icolorCount, + "Palette index out of range" + ); + formatted[i] = pal->colors[index]; + } + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, (void*)formatted + ); + } else { + // Supported. + glColorTableEXT( + GL_TEXTURE_2D, GL_RGBA, pal->colorCount, GL_RGBA, + GL_UNSIGNED_BYTE, (const void*)pal->colors + ); + glTexImage2D( + GL_TEXTURE_2D, + 0, GL_COLOR_INDEX8_EXT, + width, height, + 0, GL_COLOR_INDEX8_EXT, + GL_UNSIGNED_BYTE, (void*)data.palette.data + ); + } + + break; + + default: + assertUnreachable("Unknown texture format"); + break; + } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glBindTexture(GL_TEXTURE_2D, 0); #endif diff --git a/src/display/texture/texture.h b/src/display/texture/texture.h index d73884f..cfe9e4d 100644 --- a/src/display/texture/texture.h +++ b/src/display/texture/texture.h @@ -13,6 +13,7 @@ typedef enum { #if DISPLAY_SDL2 TEXTURE_FORMAT_RGBA = GL_RGBA, TEXTURE_FORMAT_ALPHA = GL_ALPHA, + TEXTURE_FORMAT_PALETTE = GL_COLOR_INDEX8_EXT, #endif } textureformat_t; @@ -25,6 +26,21 @@ typedef struct { int32_t height; } texture_t; +typedef union { + struct { + color_t *colors; + } rgba; + + struct { + uint8_t palette; + uint8_t *data; + } palette; + + struct { + uint8_t *data; + } alpha; +} texturedata_t; + extern const texture_t *TEXTURE_BOUND; /** @@ -34,14 +50,14 @@ extern const texture_t *TEXTURE_BOUND; * @param width The width of the texture. * @param height The height of the texture. * @param format The format of the texture (e.g., GL_RGBA, GL_ALPHA). - * @param data The pixel data for the texture. + * @param data The data for the texture, the format changes per format. */ void textureInit( texture_t *texture, const int32_t width, const int32_t height, const textureformat_t format, - const void *data + const texturedata_t data ); /** diff --git a/src/engine/engine.c b/src/engine/engine.c index 7951623..562a84d 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -15,7 +15,6 @@ engine_t ENGINE; -asset_t *testAsset; errorret_t engineInit(void) { memoryZero(&ENGINE, sizeof(engine_t)); @@ -28,8 +27,6 @@ errorret_t engineInit(void) { errorChain(displayInit()); rpgInit(); - errorChain(assetManagerGetAsset("entities.dpi", &testAsset)); - errorOk(); } diff --git a/src/util/reflist.c b/src/util/reflist.c index 2d20a85..cafe895 100644 --- a/src/util/reflist.c +++ b/src/util/reflist.c @@ -30,7 +30,7 @@ ref_t refListLock(reflist_t *list) { assertFalse(refListIsFull(list), "Reference list is full"); ref_t ref = list->refNext++; - assertTrue(ref > 0, "Reference ID overflow"); + assertTrue(ref >= 0, "Reference ID overflow"); assertTrue(ref < UINT16_MAX, "Reference ID too large."); bool_t empty = list->onNotEmpty && refListIsEmpty(list);