Optimized

This commit is contained in:
2026-07-11 23:51:39 -05:00
parent b9195fbbad
commit 6a43363539
40 changed files with 386 additions and 190 deletions
+1
View File
@@ -43,4 +43,5 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
DUSK_THREAD_PTHREAD
)
+1
View File
@@ -75,6 +75,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=960
DUSK_DISPLAY_HEIGHT=544
DUSK_THREAD_PTHREAD
)
# Post-build: create SELF from the ELF binary (UNSAFE = homebrew, no signing)
+3 -4
View File
@@ -5,7 +5,6 @@
#include "easing.h"
#include "assert/assert.h"
#include "util/math.h"
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
easingLinear,
@@ -36,15 +35,15 @@ float_t easingLinear(const float_t t) {
}
float_t easingInSine(const float_t t) {
return 1.0f - cosf(t * MATH_PI * 0.5f);
return 1.0f - cosf(t * EASING_PI * 0.5f);
}
float_t easingOutSine(const float_t t) {
return sinf(t * MATH_PI * 0.5f);
return sinf(t * EASING_PI * 0.5f);
}
float_t easingInOutSine(const float_t t) {
return -(cosf(MATH_PI * t) - 1.0f) * 0.5f;
return -(cosf(EASING_PI * t) - 1.0f) * 0.5f;
}
float_t easingInQuad(const float_t t) {
+19
View File
@@ -84,6 +84,25 @@ extern assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT];
} \
}
/**
* Like @ref assetLoaderErrorChain, but also frees `_ptr` (via memoryFree)
* before chaining the error if `_expr` failed. Use this for any loader step
* that runs after a buffer has already been allocated, so a later I/O
* failure doesn't leak it.
*
* @param loading The asset loading slot.
* @param _ptr A heap pointer to free if `_expr` fails.
* @param _expr The error return value to check and chain if it's an error.
*/
#define assetLoaderErrorChainFree(loading, _ptr, _expr) {\
errorret_t _alecf = (_expr); \
if(errorIsNotOk(_alecf)) { \
memoryFree(_ptr); \
(loading)->entry->state = ASSET_ENTRY_STATE_ERROR; \
errorChain(_alecf); \
} \
}
/**
* Shorthand method to both throw an error (against the loader state) and to
* set the asset entry state to error.
@@ -27,11 +27,12 @@ errorret_t assetTilesetLoaderAsync(assetloading_t *loading) {
assetFileInit(file, loading->entry->name, NULL, NULL)
);
uint8_t *data = memoryAllocate(file->size);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, data, file->size));
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
uint8_t *data = memoryAllocate(file->size);
assetLoaderErrorChainFree(loading, data, assetFileRead(file, data, file->size));
assetLoaderErrorChainFree(loading, data, assetFileClose(file));
assetLoaderErrorChainFree(loading, data, assetFileDispose(file));
assertTrue(
file->lastRead == file->size,
"Failed to read entire tileset file."
@@ -102,6 +103,11 @@ errorret_t assetTilesetLoaderSync(assetloading_t *loading) {
out->uv[0] = endianLittleToHostFloat(*(float *)(data + 16));
out->uv[1] = endianLittleToHostFloat(*(float *)(data + 20));
if(out->uv[0] < 0.0f || out->uv[0] > 1.0f) {
memoryFree(data);
assetLoaderErrorThrow(loading, "Invalid u0 value in tileset");
}
if(out->uv[1] < 0.0f || out->uv[1] > 1.0f) {
memoryFree(data);
assetLoaderErrorThrow(loading, "Invalid v0 value in tileset");
+5 -4
View File
@@ -27,11 +27,12 @@ errorret_t assetMeshLoaderAsync(assetloading_t *loading) {
assetFileInit(file, loading->entry->name, NULL, NULL)
);
uint8_t *raw = memoryAllocate(file->size);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, raw, file->size));
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
uint8_t *raw = memoryAllocate(file->size);
assetLoaderErrorChainFree(loading, raw, assetFileRead(file, raw, file->size));
assetLoaderErrorChainFree(loading, raw, assetFileClose(file));
assetLoaderErrorChainFree(loading, raw, assetFileDispose(file));
assertTrue(file->lastRead == file->size, "Failed to read entire DMF file.");
if(raw[0] != 'D' || raw[1] != 'M' || raw[2] != 'F') {
+5 -4
View File
@@ -32,13 +32,14 @@ errorret_t assetJsonLoaderAsync(assetloading_t *loading) {
assetLoaderErrorThrow(loading, "JSON exceeds maximum allowed size");
}
assetLoaderErrorChain(loading, assetFileOpen(file));
size_t fileSize = (size_t)file->size;
uint8_t *buffer = memoryAllocate(fileSize);
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(loading, assetFileRead(file, buffer, fileSize));
assetLoaderErrorChainFree(loading, buffer, assetFileRead(file, buffer, fileSize));
assertTrue(file->lastRead == file->size, "Failed to read entire JSON file.");
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
assetLoaderErrorChainFree(loading, buffer, assetFileClose(file));
assetLoaderErrorChainFree(loading, buffer, assetFileDispose(file));
loading->loading.json.buffer = buffer;
loading->loading.json.size = fileSize;
@@ -482,6 +482,24 @@ errorret_t assetLocaleGetString(
assertTrue(pluralCount >= 0, "Plural index cannot be negative.");
assertNotNull(stringBuffer, "String buffer cannot be NULL.");
assertTrue(stringBufferSize > 0, "String buffer size must be > 0");
// Check the cache before rewinding/rescanning the whole file.
for(uint8_t i = 0; i < ASSET_LOCALE_STRING_CACHE_SIZE; i++) {
assetlocalecacheentry_t *entry = &file->stringCache[i];
if(
!entry->valid ||
entry->pluralCount != pluralCount ||
stringCompare(messageId, entry->messageId) != 0
) continue;
size_t valueLen = strlen(entry->value);
if(valueLen >= stringBufferSize) {
errorThrow("String buffer overflow");
}
memoryCopy(stringBuffer, entry->value, valueLen + 1);
errorOk();
}
assetfilelinereader_t reader;
bool_t msgidFound = false, msgidPluralFound = false, msgstrFound = false;
@@ -625,6 +643,20 @@ errorret_t assetLocaleGetString(
errorThrow("Failed to find msgstr for message ID: %s", messageId);
}
// Cache the resolved string for future lookups, if it fits.
if(
strlen(messageId) < ASSET_LOCALE_CACHE_MESSAGE_ID_MAX &&
strlen(stringBuffer) < ASSET_LOCALE_CACHE_VALUE_MAX
) {
assetlocalecacheentry_t *entry = &file->stringCache[file->stringCacheNext];
stringCopy(entry->messageId, messageId, sizeof(entry->messageId) - 1);
stringCopy(entry->value, stringBuffer, sizeof(entry->value) - 1);
entry->pluralCount = pluralCount;
entry->valid = true;
file->stringCacheNext =
(file->stringCacheNext + 1) % ASSET_LOCALE_STRING_CACHE_SIZE;
}
errorOk();
}
@@ -28,6 +28,27 @@ typedef struct {
/** Maximum number of distinct plural forms a locale file may declare. */
#define ASSET_LOCALE_FILE_PLURAL_FORM_COUNT 6
/** Max distinct resolved strings cached per open locale file. */
#define ASSET_LOCALE_STRING_CACHE_SIZE 16
/** Max message ID length (incl. null terminator) storable in the cache. */
#define ASSET_LOCALE_CACHE_MESSAGE_ID_MAX 128
/** Max resolved string length (incl. null terminator) storable in the cache. */
#define ASSET_LOCALE_CACHE_VALUE_MAX 256
/**
* A single cached (messageId, pluralCount) -> resolved string lookup, used by
* @ref assetLocaleGetString to avoid rescanning the whole PO file for
* repeated lookups of the same message.
*/
typedef struct {
bool_t valid;
char_t messageId[ASSET_LOCALE_CACHE_MESSAGE_ID_MAX];
int32_t pluralCount;
char_t value[ASSET_LOCALE_CACHE_VALUE_MAX];
} assetlocalecacheentry_t;
/**
* Comparison operator used in a plural-form expression.
*
@@ -98,6 +119,12 @@ typedef struct {
/** Form index used when no conditional clause matches. */
uint8_t pluralDefaultIndex;
/** Ring buffer of recently resolved (messageId, pluralCount) lookups. */
assetlocalecacheentry_t stringCache[ASSET_LOCALE_STRING_CACHE_SIZE];
/** Next slot in @ref stringCache to overwrite. */
uint8_t stringCacheNext;
} assetlocalefile_t;
/** Convenience alias - the loaded output type of a locale asset entry. */
+1 -1
View File
@@ -51,7 +51,7 @@ spritebatchsprite_t textGetSprite(
tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
}
assertTrue(
tileIndex >= 0 && tileIndex <= font->tileset->tileCount,
tileIndex >= 0 && tileIndex < font->tileset->tileCount,
"Character is out of bounds for font tiles"
);
-4
View File
@@ -105,10 +105,6 @@ entityid_t componentGetEntitiesWithComponent(
componentGetIndex(i,used) < ENTITY_COUNT_MAX*ENTITY_COMPONENT_COUNT_MAX,
"Component index OOB in entitiesWithComponent lookup"
);
assertTrue(
ENTITY_MANAGER.components[componentGetIndex(i,used)].type == type,
"Component type mismatch in entitiesWithComponent lookup"
);
outComponents[written] = used;
outEntities[written++] = i;
}
@@ -220,9 +220,15 @@ entityposition_t *entityPositionGet(
void entityPositionRebuild(entityposition_t *pos) {
glm_mat4_identity(pos->localTransform);
glm_translate(pos->localTransform, pos->position);
glm_rotate_x(pos->localTransform, pos->rotation[0], pos->localTransform);
glm_rotate_y(pos->localTransform, pos->rotation[1], pos->localTransform);
glm_rotate_z(pos->localTransform, pos->rotation[2], pos->localTransform);
if(pos->rotation[0] != 0.0f) {
glm_rotate_x(pos->localTransform, pos->rotation[0], pos->localTransform);
}
if(pos->rotation[1] != 0.0f) {
glm_rotate_y(pos->localTransform, pos->rotation[1], pos->localTransform);
}
if(pos->rotation[2] != 0.0f) {
glm_rotate_z(pos->localTransform, pos->rotation[2], pos->localTransform);
}
glm_scale(pos->localTransform, pos->scale);
entityPositionMarkDirty(pos);
}
+60
View File
@@ -14,6 +14,35 @@
physicsworld_t PHYSICS_WORLD;
/** Below this squared speed, a grounded body is considered at rest. */
#define PHYSICS_REST_VELOCITY_EPSILON_SQ 0.0001f
/**
* Computes a conservative (never-too-small) AABB half-extent for a shape,
* used as a cheap broad-phase pre-filter before the full narrow-phase test.
* Returns false for unbounded shapes (planes), which skip the pre-filter.
*/
static bool_t physicsShapeBroadphaseExtents(
const physicsshape_t shape, vec3 outHalfExtents
) {
switch(shape.type) {
case PHYSICS_SHAPE_CUBE:
glm_vec3_copy((float_t *)shape.data.cube.halfExtents, outHalfExtents);
return true;
case PHYSICS_SHAPE_SPHERE:
outHalfExtents[0] = outHalfExtents[1] = outHalfExtents[2] =
shape.data.sphere.radius;
return true;
case PHYSICS_SHAPE_CAPSULE:
outHalfExtents[0] = outHalfExtents[2] = shape.data.capsule.radius;
outHalfExtents[1] =
shape.data.capsule.radius + shape.data.capsule.halfHeight;
return true;
default:
return false;
}
}
void physicsWorldInit() {
memoryZero(&PHYSICS_WORLD, sizeof(physicsworld_t));
@@ -34,6 +63,8 @@ void physicsWorldStep(const float_t dt) {
/* Pre-fetch all position and physics pointers once. */
entityposition_t *positions[ENTITY_COUNT_MAX];
entityphysics_t *physBodies[ENTITY_COUNT_MAX];
vec3 boundsHalfExtents[ENTITY_COUNT_MAX];
bool_t boundsValid[ENTITY_COUNT_MAX];
for(entityid_t i = 0; i < physCount; i++) {
componentid_t posComp = entityGetComponent(
physEnts[i], COMPONENT_TYPE_POSITION
@@ -42,6 +73,9 @@ void physicsWorldStep(const float_t dt) {
? entityPositionGet(physEnts[i], posComp)
: NULL;
physBodies[i] = entityPhysicsGet(physEnts[i], physComps[i]);
boundsValid[i] = physicsShapeBroadphaseExtents(
physBodies[i]->shape, boundsHalfExtents[i]
);
}
/* Phase 1: integrate dynamic bodies (gravity + velocity → position).
@@ -51,8 +85,16 @@ void physicsWorldStep(const float_t dt) {
entityphysics_t *phys = physBodies[i];
if(phys->type != PHYSICS_BODY_DYNAMIC) continue;
// A body resting from the previous tick skips gravity/velocity
// integration this tick, but still takes part in phases 2/3 below so it
// correctly notices being pushed or its support disappearing.
bool_t wasResting = phys->onGround &&
glm_vec3_norm2(phys->velocity) < PHYSICS_REST_VELOCITY_EPSILON_SQ;
phys->onGround = false;
if(wasResting) continue;
phys->velocity[0] += PHYSICS_WORLD.gravity[0] * phys->gravityScale * dt;
phys->velocity[1] += PHYSICS_WORLD.gravity[1] * phys->gravityScale * dt;
phys->velocity[2] += PHYSICS_WORLD.gravity[2] * phys->gravityScale * dt;
@@ -76,6 +118,15 @@ void physicsWorldStep(const float_t dt) {
entityphysics_t *otherPhys = physBodies[j];
if(otherPhys->type == PHYSICS_BODY_DYNAMIC) continue;
if(boundsValid[i] && boundsValid[j]) {
vec3 bpNormal; float_t bpDepth;
if(!physicsTestAabbVsAabb(
pos, boundsHalfExtents[i],
positions[j]->position, boundsHalfExtents[j],
bpNormal, &bpDepth
)) continue;
}
vec3 normal; float_t depth;
if(!physicsTestShapeVsShape(
pos, phys->shape,
@@ -113,6 +164,15 @@ void physicsWorldStep(const float_t dt) {
float_t *posB = positions[j]->position;
if(boundsValid[i] && boundsValid[j]) {
vec3 bpNormal; float_t bpDepth;
if(!physicsTestAabbVsAabb(
posA, boundsHalfExtents[i],
posB, boundsHalfExtents[j],
bpNormal, &bpDepth
)) continue;
}
vec3 normal; float_t depth;
if(!physicsTestShapeVsShape(
posA, physA->shape, posB, physB->shape, normal, &depth
+28 -15
View File
@@ -19,6 +19,16 @@ scene_t SCENE;
errorret_t sceneInit(void) {
memoryZero(&SCENE, sizeof(scene_t));
// Fixed for the lifetime of the scene manager; computed once here.
glm_mat4_identity(SCENE.screenIdentity);
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
SCENE.screenView
);
errorOk();
}
@@ -58,22 +68,25 @@ errorret_t sceneUpdate(void) {
}
errorret_t sceneRender(void) {
// Setup screen matrices for 3D rendering.
glm_mat4_identity(SCENE.screenIdentity);
glm_ortho(
0.0f, (float_t)(SCREEN.width / SCREEN.scaleUi),
(float_t)(SCREEN.height / SCREEN.scaleUi), 0.0f,
0.1f, 100.0f,
SCENE.screenProj
);
// Recompute the UI ortho projection only when its inputs actually change.
if(
!SCENE.screenProjValid ||
SCENE.cachedScreenWidth != SCREEN.width ||
SCENE.cachedScreenHeight != SCREEN.height ||
SCENE.cachedScreenScaleUi != SCREEN.scaleUi
) {
glm_ortho(
0.0f, (float_t)(SCREEN.width / SCREEN.scaleUi),
(float_t)(SCREEN.height / SCREEN.scaleUi), 0.0f,
0.1f, 100.0f,
SCENE.screenProj
);
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
SCENE.screenView
);
SCENE.cachedScreenWidth = SCREEN.width;
SCENE.cachedScreenHeight = SCREEN.height;
SCENE.cachedScreenScaleUi = SCREEN.scaleUi;
SCENE.screenProjValid = true;
}
// Scene rendering
if(
+4
View File
@@ -16,6 +16,10 @@ typedef struct {
mat4 screenProj;
mat4 screenView;
mat4 screenIdentity;
int32_t cachedScreenWidth;
int32_t cachedScreenHeight;
int32_t cachedScreenScaleUi;
bool_t screenProjValid;
} scene_t;
extern scene_t SCENE;
+31 -2
View File
@@ -9,6 +9,7 @@
#include "assert/assert.h"
#include "asset/asset.h"
#include "util/memory.h"
#include "util/string.h"
#include "scriptproto.h"
#include "script/module/module.h"
@@ -120,13 +121,35 @@ static errorret_t scriptManagerFormatException(
return err;
}
/**
* Returns a cached jerry_value_t string key for a global function name,
* creating and caching it on first use. Avoids re-allocating a JS string
* every call for names looked up every frame/tick (e.g. "update").
*/
static jerry_value_t scriptManagerGetGlobalKey(const char_t *name) {
for(uint8_t i = 0; i < SCRIPT_MANAGER.globalKeyCacheCount; i++) {
if(stringCompare(name, SCRIPT_MANAGER.globalKeyCache[i].name) == 0) {
return SCRIPT_MANAGER.globalKeyCache[i].key;
}
}
jerry_value_t key = jerry_string_sz(name);
assertTrue(
SCRIPT_MANAGER.globalKeyCacheCount < SCRIPT_MANAGER_MAX_CACHED_GLOBAL_KEYS,
"Global function key cache is full."
);
SCRIPT_MANAGER.globalKeyCache[SCRIPT_MANAGER.globalKeyCacheCount].name = name;
SCRIPT_MANAGER.globalKeyCache[SCRIPT_MANAGER.globalKeyCacheCount].key = key;
SCRIPT_MANAGER.globalKeyCacheCount++;
return key;
}
errorret_t scriptManagerCallGlobal(const char_t *name) {
assertNotNull(name, "Function name cannot be NULL");
jerry_value_t global = jerry_current_realm();
jerry_value_t key = jerry_string_sz(name);
jerry_value_t key = scriptManagerGetGlobalKey(name);
jerry_value_t fn = jerry_object_get(global, key);
jerry_value_free(key);
jerry_value_free(global);
if(!jerry_value_is_function(fn)) {
@@ -176,6 +199,12 @@ errorret_t scriptManagerCallGlobal(const char_t *name) {
errorret_t scriptManagerDispose(void) {
scriptProtoDisposeAll();
for(uint8_t i = 0; i < SCRIPT_MANAGER.globalKeyCacheCount; i++) {
jerry_value_free(SCRIPT_MANAGER.globalKeyCache[i].key);
}
SCRIPT_MANAGER.globalKeyCacheCount = 0;
jerry_cleanup();
errorOk();
}
+8 -1
View File
@@ -12,8 +12,15 @@
#define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64
/** Max distinct global function names cached by @ref scriptManagerCallGlobal. */
#define SCRIPT_MANAGER_MAX_CACHED_GLOBAL_KEYS 8
typedef struct {
void* nothing;
struct {
const char_t *name;
jerry_value_t key;
} globalKeyCache[SCRIPT_MANAGER_MAX_CACHED_GLOBAL_KEYS];
uint8_t globalKeyCacheCount;
} scriptmanager_t;
extern scriptmanager_t SCRIPT_MANAGER;
+2
View File
@@ -23,6 +23,8 @@ void threadMutexLock(threadmutex_t *lock) {
bool_t threadMutexTryLock(threadmutex_t *lock) {
#ifdef DUSK_THREAD_PTHREAD
return pthread_mutex_trylock(&lock->mutex) == 0;
#else
return false;
#endif
}
-8
View File
@@ -9,8 +9,6 @@
#include "util/memory.h"
#include "assert/assert.h"
#include "console/console.h"
dusktime_t TIME;
void timeInit(void) {
@@ -48,12 +46,6 @@ void timeUpdate(void) {
TIME.delta = DUSK_TIME_STEP;
TIME.time += DUSK_TIME_STEP;
#endif
// Print time in UTC style string
dusktimeepoch_t epoch = timeGetEpoch();
char_t buffer[256];
timeEpochFormat(epoch, "%Y-%m-%d %H:%M:%S", buffer, sizeof(buffer));
// consolePrint("Real Time: %s", buffer);
}
dusktimeepoch_t timeGetEpoch(void) {
+1
View File
@@ -16,6 +16,7 @@ errorret_t uiConsoleDraw(void) {
float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight;
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
if(CONSOLE.line[i][0] == '\0') continue;
errorChain(textDraw(
(float_t)SCREEN.scanX,
(float_t)SCREEN.scanY + lineH * (float_t)i,
+5 -2
View File
@@ -58,8 +58,11 @@ errorret_t uiFPSDraw() {
));
errorChain(spriteBatchFlush());
int32_t versionWidth, versionHeight;
textMeasure(ENGINE.version, &FONT_DEFAULT, &versionWidth, &versionHeight);
// ENGINE.version and FONT_DEFAULT never change after boot; measure once.
static int32_t versionWidth = -1, versionHeight = 0;
if(versionWidth < 0) {
textMeasure(ENGINE.version, &FONT_DEFAULT, &versionWidth, &versionHeight);
}
errorChain(textDraw(
(float_t)(SCREEN.scanX + SCREEN.scanWidth - versionWidth),
(float_t)(SCREEN.scanY + SCREEN.scanHeight - versionHeight),
+5 -4
View File
@@ -29,6 +29,9 @@ void uiDropdownInit(
dropdown->options = options;
dropdown->optionCount = optionCount;
dropdown->selectedIndex = selectedIndex < optionCount ? selectedIndex : 0;
textMeasure(
label, &FONT_DEFAULT, &dropdown->labelWidth, &dropdown->labelHeight
);
}
uint8_t uiDropdownGetSelectedIndex(const uidropdown_t *dropdown) {
@@ -83,9 +86,6 @@ errorret_t uiDropdownDraw(
errorChain(textDraw(x, y, dropdown->label, color, &FONT_DEFAULT));
int32_t labelW, labelH;
textMeasure(dropdown->label, &FONT_DEFAULT, &labelW, &labelH);
char_t valueText[UI_DROPDOWN_VALUE_TEXT_MAX];
stringFormat(
valueText, UI_DROPDOWN_VALUE_TEXT_MAX - 1, "< %s >",
@@ -93,7 +93,8 @@ errorret_t uiDropdownDraw(
);
errorChain(textDraw(
x + (float_t)labelW + UI_DROPDOWN_GAP, y, valueText, color, &FONT_DEFAULT
x + (float_t)dropdown->labelWidth + UI_DROPDOWN_GAP, y, valueText, color,
&FONT_DEFAULT
));
errorOk();
+2
View File
@@ -17,6 +17,8 @@ typedef struct {
uint8_t optionCount;
uint8_t selectedIndex;
bool_t highlighted;
int32_t labelWidth;
int32_t labelHeight;
} uidropdown_t;
/**
+4 -8
View File
@@ -36,6 +36,7 @@ void uiSliderInitFloat(
slider->max.f = max;
slider->step.f = step;
slider->value.f = mathClamp(value, min, max);
textMeasure(label, &FONT_DEFAULT, &slider->labelWidth, &slider->labelHeight);
}
void uiSliderInitInt(
@@ -58,6 +59,7 @@ void uiSliderInitInt(
slider->max.i = max;
slider->step.i = step;
slider->value.i = mathClamp(value, min, max);
textMeasure(label, &FONT_DEFAULT, &slider->labelWidth, &slider->labelHeight);
}
float_t uiSliderGetFloat(const uislider_t *slider) {
@@ -151,11 +153,8 @@ errorret_t uiSliderDraw(
errorChain(textDraw(x, y, slider->label, color, &FONT_DEFAULT));
int32_t labelW, labelH;
textMeasure(slider->label, &FONT_DEFAULT, &labelW, &labelH);
float_t trackX = x + (float_t)labelW + UI_SLIDER_GAP;
float_t trackY = y + ((float_t)labelH - UI_SLIDER_TRACK_HEIGHT) * 0.5f;
float_t trackX = x + (float_t)slider->labelWidth + UI_SLIDER_GAP;
float_t trackY = y + ((float_t)slider->labelHeight - UI_SLIDER_TRACK_HEIGHT) * 0.5f;
spritebatchsprite_t trackSprite = {
.min = { trackX, trackY, 0.0f },
@@ -170,7 +169,6 @@ errorret_t uiSliderDraw(
}
};
errorChain(spriteBatchBuffer(&trackSprite, 1, &SHADER_UNLIT, trackMaterial));
errorChain(spriteBatchFlush());
float_t fillWidth = UI_SLIDER_TRACK_WIDTH * uiSliderGetRatio(slider);
if(fillWidth > 0.0f) {
@@ -187,7 +185,6 @@ errorret_t uiSliderDraw(
}
};
errorChain(spriteBatchBuffer(&fillSprite, 1, &SHADER_UNLIT, fillMaterial));
errorChain(spriteBatchFlush());
}
int32_t stepCount = uiSliderGetStepCount(slider);
@@ -218,7 +215,6 @@ errorret_t uiSliderDraw(
errorChain(
spriteBatchBuffer(&markerSprite, 1, &SHADER_UNLIT, markerMaterial)
);
errorChain(spriteBatchFlush());
}
}
+2
View File
@@ -41,6 +41,8 @@ typedef struct {
uislidervalue_t max;
uislidervalue_t step;
bool_t highlighted;
int32_t labelWidth;
int32_t labelHeight;
} uislider_t;
/**
+2 -4
View File
@@ -19,6 +19,7 @@ void uiTabInit(uitab_t *tab, const char_t *label) {
assertNotNull(label, "Label cannot be NULL");
memoryZero(tab, sizeof(uitab_t));
tab->label = label;
textMeasure(label, &FONT_DEFAULT, &tab->labelWidth, &tab->labelHeight);
}
bool_t uiTabIsActive(const uitab_t *tab) {
@@ -38,12 +39,9 @@ errorret_t uiTabDraw(
) {
assertNotNull(tab, "Tab cannot be NULL");
int32_t labelW, labelH;
textMeasure(tab->label, &FONT_DEFAULT, &labelW, &labelH);
spritebatchsprite_t sprite = {
.min = { x, y, 0.0f },
.max = { x + (float_t)labelW, y + (float_t)labelH, 0.0f },
.max = { x + (float_t)tab->labelWidth, y + (float_t)tab->labelHeight, 0.0f },
.uvMin = { 0.0f, 0.0f },
.uvMax = { 1.0f, 1.0f }
};
+2
View File
@@ -11,6 +11,8 @@
typedef struct {
const char_t *label;
bool_t active;
int32_t labelWidth;
int32_t labelHeight;
} uitab_t;
/**
-6
View File
@@ -28,9 +28,3 @@ void cryptCRC32Update(uint32_t *crc, const void *data, const size_t size) {
uint32_t cryptCRC32End(const uint32_t crc) {
return ~crc;
}
uint32_t cryptCRC32(const void *data, const size_t size) {
uint32_t crc = cryptCRC32Begin();
cryptCRC32Update(&crc, data, size);
return cryptCRC32End(crc);
}
-9
View File
@@ -8,15 +8,6 @@
#pragma once
#include "dusk.h"
/**
* Computes a CRC32 checksum over a block of data.
*
* @param data Pointer to the data to checksum.
* @param size Number of bytes to checksum.
* @return The CRC32 checksum.
*/
uint32_t cryptCRC32(const void *data, const size_t size);
/**
* Returns the initial CRC32 accumulator value.
*
-9
View File
@@ -103,15 +103,6 @@ int_t memoryCompare(
return memcmp(a, b, size);
}
void memoryReallocate(void **ptr, const size_t size) {
assertNotNull(ptr, "Cannot reallocate NULL pointer.");
assertTrue(size > 0, "Cannot reallocate to 0 bytes of memory.");
void *newPointer = memoryAllocate(size);
assertNotNull(newPointer, "Memory reallocation failed.");
memoryFree(*ptr);
*ptr = newPointer;
}
void memoryCopyInterleaved(
void *dest,
const size_t destStride,
-8
View File
@@ -114,14 +114,6 @@ int_t memoryCompare(
const size_t size
);
/**
* Reallocates memory.
*
* @param ptr The pointer to the memory to reallocate.
* @param size The new size of the memory.
*/
void memoryReallocate(void **ptr, const size_t size);
/**
* Reallocates memory, but copies existing data to the new memory.
*
+3
View File
@@ -6,12 +6,15 @@
*/
#include "random.h"
#include "assert/assert.h"
#include <stdlib.h>
float_t randomFloat(const float_t min, const float_t max) {
assertTrue(max > min, "randomFloat: max must be greater than min.");
return min + ((float_t)rand() / (float_t)RAND_MAX) * (max - min);
}
int_t randomInt(const int_t min, const int_t max) {
assertTrue(max > min, "randomInt: max must be greater than min.");
return min + (rand() % (max - min));
}
@@ -21,9 +21,7 @@ uint32_t frameBufferGLGetWidth(const framebuffer_t *framebuffer) {
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight);
return windowWidth;
return DISPLAY.cachedWidth;
#else
return DUSK_DISPLAY_WIDTH;
#endif
@@ -39,9 +37,7 @@ uint32_t frameBufferGLGetHeight(const framebuffer_t *framebuffer) {
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight);
return windowHeight;
return DISPLAY.cachedHeight;
#else
return DUSK_DISPLAY_HEIGHT;
#endif
+4 -6
View File
@@ -93,13 +93,11 @@ errorret_t meshFlushGL(
#else
glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId);
errorChain(errorGLCheck());
glBufferData(
glBufferSubData(
GL_ARRAY_BUFFER,
mesh->vertexCount * sizeof(meshvertex_t),
mesh->vertices,
// vertCount * sizeof(meshvertex_t),
// &mesh->vertices[vertOffset],
GL_DYNAMIC_DRAW
vertOffset * sizeof(meshvertex_t),
vertCount * sizeof(meshvertex_t),
&mesh->vertices[vertOffset]
);
errorChain(errorGLCheck());
#endif
+16 -1
View File
@@ -157,13 +157,28 @@ errorret_t shaderParamGetLocationGL(
#ifdef DUSK_OPENGL_LEGACY
assertUnreachable("Cannot get uniform locations on legacy opengl.");
#else
for(uint8_t i = 0; i < shader->uniformCacheCount; i++) {
if(stringCompare(name, shader->uniformCache[i].name) == 0) {
*location = shader->uniformCache[i].location;
errorOk();
}
}
*location = glGetUniformLocation(shader->shaderProgramId, name);
errorChain(errorGLCheck());
if(*location == -1) {
errorThrow("Uniform '%s' not found in shader.", name);
}
assertTrue(
shader->uniformCacheCount < SHADERGL_MAX_CACHED_UNIFORMS,
"Uniform cache is full; increase SHADERGL_MAX_CACHED_UNIFORMS."
);
shader->uniformCache[shader->uniformCacheCount].name = name;
shader->uniformCache[shader->uniformCacheCount].location = *location;
shader->uniformCacheCount++;
#endif
errorOk();
}
+9
View File
@@ -30,6 +30,9 @@ typedef struct {
#endif
} shaderdefinitiongl_t;
/** Number of distinct uniform names a single shader instance can cache. */
#define SHADERGL_MAX_CACHED_UNIFORMS 8
typedef struct shadergl_s {
const shaderdefinitiongl_t *definition;
@@ -41,6 +44,12 @@ typedef struct shadergl_s {
GLuint shaderProgramId;
GLuint vertexShaderId;
GLuint fragmentShaderId;
struct {
const char_t *name;
GLint location;
} uniformCache[SHADERGL_MAX_CACHED_UNIFORMS];
uint8_t uniformCacheCount;
#endif
} shadergl_t;
+23 -15
View File
@@ -13,6 +13,10 @@
.setMaterial = shaderUnlitSetMaterial,
};
#else
// Last-uploaded palette for the (single, global) unlit shader instance;
// skips rebuilding/re-uploading the color array when it hasn't changed.
static palette_t *SHADER_UNLIT_LAST_PALETTE = NULL;
errorret_t shaderUnlitSetTextureGL(
shadergl_t *shader,
const char_t *name,
@@ -52,23 +56,27 @@
glUniform1i(locType, 2);
errorChain(errorGLCheck());
shaderParamGetLocationGL(shader, "u_ColorCount", &locColorCount);
glUniform1i(locColorCount, texture->palette->count);
errorChain(errorGLCheck());
if(texture->palette != SHADER_UNLIT_LAST_PALETTE) {
shaderParamGetLocationGL(shader, "u_ColorCount", &locColorCount);
glUniform1i(locColorCount, texture->palette->count);
errorChain(errorGLCheck());
shaderParamGetLocationGL(shader, "u_Colors", &locColors);
GLuint paletteData[texture->palette->count];
for(size_t i = 0; i < texture->palette->count; i++) {
color_t color = texture->palette->colors[i];
paletteData[i] = (
((uint32_t)color.r << 24) |
((uint32_t)color.g << 16) |
((uint32_t)color.b << 8) |
((uint32_t)color.a << 0)
);
shaderParamGetLocationGL(shader, "u_Colors", &locColors);
GLuint paletteData[texture->palette->count];
for(size_t i = 0; i < texture->palette->count; i++) {
color_t color = texture->palette->colors[i];
paletteData[i] = (
((uint32_t)color.r << 24) |
((uint32_t)color.g << 16) |
((uint32_t)color.b << 8) |
((uint32_t)color.a << 0)
);
}
glUniform1uiv(locColors, texture->palette->count, paletteData);
errorChain(errorGLCheck());
SHADER_UNLIT_LAST_PALETTE = texture->palette;
}
glUniform1uiv(locColors, texture->palette->count, paletteData);
errorChain(errorGLCheck());
} else {
glUniform1i(locType, 1);
errorChain(errorGLCheck());
+58 -28
View File
@@ -10,7 +10,12 @@
#include "display/displaygl.h"
#include "error/errorgl.h"
static displaystate_t DISPLAY_STATE_CACHE = { 0 };
static bool_t DISPLAY_STATE_CACHE_VALID = false;
errorret_t displaySDL2Init(void) {
DISPLAY_STATE_CACHE_VALID = false;
uint32_t flags = SDL_INIT_VIDEO;
#ifdef DUSK_INPUT_GAMEPAD
flags |= SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK;
@@ -57,7 +62,9 @@ errorret_t displaySDL2Init(void) {
errorChain(errorGLCheck());
errorChain(displayOpenGLInit());
SDL_GetWindowSize(DISPLAY.window, &DISPLAY.cachedWidth, &DISPLAY.cachedHeight);
errorChain(errorGLCheck());
errorOk();
}
@@ -77,11 +84,19 @@ errorret_t displaySDL2Update(void) {
ENGINE.running = false;
break;
}
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED: {
DISPLAY.cachedWidth = event.window.data1;
DISPLAY.cachedHeight = event.window.data2;
break;
}
default: {
break;
}
}
break;
}
default: {
@@ -103,42 +118,57 @@ errorret_t displaySDL2Swap(void) {
}
errorret_t displaySDL2SetState(displaystate_t state) {
if(state.flags & DISPLAY_STATE_FLAG_CULL) {
glEnable(GL_CULL_FACE);
errorChain(errorGLCheck());
glCullFace(GL_BACK);
errorChain(errorGLCheck());
} else {
glDisable(GL_CULL_FACE);
errorChain(errorGLCheck());
uint8_t changed = DISPLAY_STATE_CACHE_VALID
? (state.flags ^ DISPLAY_STATE_CACHE.flags)
: 0xFF;
if(changed & DISPLAY_STATE_FLAG_CULL) {
if(state.flags & DISPLAY_STATE_FLAG_CULL) {
glEnable(GL_CULL_FACE);
errorChain(errorGLCheck());
glCullFace(GL_BACK);
errorChain(errorGLCheck());
} else {
glDisable(GL_CULL_FACE);
errorChain(errorGLCheck());
}
}
if(state.flags & DISPLAY_STATE_FLAG_DEPTH_TEST) {
glEnable(GL_DEPTH_TEST);
errorChain(errorGLCheck());
glDepthFunc(GL_LEQUAL);
errorChain(errorGLCheck());
glClearDepth(1.0f);
errorChain(errorGLCheck());
} else {
glDisable(GL_DEPTH_TEST);
errorChain(errorGLCheck());
if(changed & DISPLAY_STATE_FLAG_DEPTH_TEST) {
if(state.flags & DISPLAY_STATE_FLAG_DEPTH_TEST) {
glEnable(GL_DEPTH_TEST);
errorChain(errorGLCheck());
glDepthFunc(GL_LEQUAL);
errorChain(errorGLCheck());
glClearDepth(1.0f);
errorChain(errorGLCheck());
} else {
glDisable(GL_DEPTH_TEST);
errorChain(errorGLCheck());
}
}
if(state.flags & DISPLAY_STATE_FLAG_BLEND) {
glEnable(GL_BLEND);
errorChain(errorGLCheck());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
errorChain(errorGLCheck());
} else {
glDisable(GL_BLEND);
errorChain(errorGLCheck());
if(changed & DISPLAY_STATE_FLAG_BLEND) {
if(state.flags & DISPLAY_STATE_FLAG_BLEND) {
glEnable(GL_BLEND);
errorChain(errorGLCheck());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
errorChain(errorGLCheck());
} else {
glDisable(GL_BLEND);
errorChain(errorGLCheck());
}
}
DISPLAY_STATE_CACHE = state;
DISPLAY_STATE_CACHE_VALID = true;
errorOk();
}
void displaySDL2Dispose(void) {
DISPLAY_STATE_CACHE_VALID = false;
if(DISPLAY.glContext) {
SDL_GL_DeleteContext(DISPLAY.glContext);
DISPLAY.glContext = NULL;
+2
View File
@@ -13,6 +13,8 @@ typedef struct {
SDL_Window *window;
SDL_GLContext glContext;
bool_t usingShaderedPalettes;
int32_t cachedWidth;
int32_t cachedHeight;
} displaysdl2_t;
/**
-34
View File
@@ -326,39 +326,6 @@ static void test_memoryCompare(void **state) {
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_memoryReallocate(void **state) {
size_t initialSize = 16;
void *ptr = memoryAllocate(initialSize);
assert_non_null(ptr);
// Reallocate to a larger size
size_t newSize = 32;
memoryReallocate(&ptr, newSize);
assert_non_null(ptr);
// Reallocate to a smaller size
size_t smallerSize = 8;
memoryReallocate(&ptr, smallerSize);
assert_non_null(ptr);
// Cannot realloc to size 0
expect_assert_failure(memoryReallocate(&ptr, 0));
// Cannot realloc NULL pointer
expect_assert_failure(memoryReallocate(NULL, 16));
// Cannot reallocate more memory than possible
expect_assert_failure(memoryReallocate(&ptr, SIZE_MAX));
// All we really care about is that the pointer is valid after reallocations
memoryFree(ptr);
// Expect no leaks
assert_int_equal(memoryGetAllocatedCount(), 0);
}
static void test_memoryResize(void **state) {
@@ -514,7 +481,6 @@ int main(int argc, char **argv) {
cmocka_unit_test(test_memoryCopyRangeSafe),
cmocka_unit_test(test_memoryMove),
cmocka_unit_test(test_memoryCompare),
cmocka_unit_test(test_memoryReallocate),
cmocka_unit_test(test_memoryResize),
cmocka_unit_test(test_memoryCopyInterleaved),
};