Palettized image test.

This commit is contained in:
2025-09-02 18:57:28 -05:00
parent 71080682cc
commit 8de12da1ec
14 changed files with 394 additions and 39 deletions

View File

@@ -8,4 +8,7 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE
asset.c
assetmanager.c
)
)
# Subdirs
add_subdirectory(type)

View File

@@ -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;
}
}

View File

@@ -6,14 +6,19 @@
*/
#pragma once
#include "assetpaletteimage.h"
#include "error/error.h"
#include "util/reflist.h"
#include <zip.h>
#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);
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);

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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
)

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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; i<n; ++i) {
const char* e = (const char*)glGetStringi(GL_EXTENSIONS, i);
if (e && strcmp(e, "GL_EXT_paletted_texture")==0) { havePalTex = true; break; }
}
} else {
const char* ext = (const char*)glGetString(GL_EXTENSIONS);
havePalTex = ext && strstr(ext, "GL_EXT_paletted_texture");
}
if(!havePalTex) {
// Not supported, convert to RGBA using lookup
color_t formatted[width * height];
for(int32_t i = 0; i < width * height; i++) {
uint8_t index = data.palette.data[i];
assertTrue(
index < pal->colorCount,
"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

View File

@@ -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
);
/**

View File

@@ -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();
}

View File

@@ -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);