From c86cc9e864623cd3cda1fb2c271591a107a94edf Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Fri, 22 May 2026 13:44:24 +0100 Subject: [PATCH 1/6] collision: implement AABB cache skips a lot of repetitive computation for bounding boxes --- src/collision.h | 39 ++++++++++++++++++++++----------------- src/collision_types.h | 7 +++++++ src/instance.c | 1 + src/instance.h | 5 +++++ src/spatial_grid.c | 2 ++ 5 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 src/collision_types.h diff --git a/src/collision.h b/src/collision.h index 38832d1b..1c2d5673 100644 --- a/src/collision.h +++ b/src/collision.h @@ -17,11 +17,6 @@ static inline bool Collision_matchesTarget(DataWin* dataWin, Instance* inst, int return VM_isObjectOrDescendant(dataWin, inst->objectIndex, target); } -typedef struct { - GMLReal left, right, top, bottom; - bool valid; -} InstanceBBox; - // Returns the collision sprite for an instance (mask sprite if set, else display sprite) static inline Sprite* Collision_getSprite(DataWin* dataWin, Instance* inst) { int32_t sprIdx = (inst->maskIndex >= 0) ? inst->maskIndex : inst->spriteIndex; @@ -31,8 +26,15 @@ static inline Sprite* Collision_getSprite(DataWin* dataWin, Instance* inst) { // Computes the axis-aligned bounding box for an instance using its collision sprite static inline InstanceBBox Collision_computeBBox(DataWin* dataWin, Instance* inst) { + // Fast path: return cached AABB when nothing bbox-affecting has changed. + if (__builtin_expect(inst->bboxCacheValid, 1)) return inst->cachedBBox; + Sprite* spr = Collision_getSprite(dataWin, inst); - if (spr == nullptr) return (InstanceBBox){0, 0, 0, 0, false}; + if (spr == nullptr) { + inst->cachedBBox = (InstanceBBox){0, 0, 0, 0, false}; + inst->bboxCacheValid = true; + return inst->cachedBBox; + } GMLReal marginL = (GMLReal) spr->marginLeft; GMLReal marginR = (GMLReal) (spr->marginRight + 1); @@ -41,6 +43,7 @@ static inline InstanceBBox Collision_computeBBox(DataWin* dataWin, Instance* ins GMLReal originX = (GMLReal) spr->originX; GMLReal originY = (GMLReal) spr->originY; + InstanceBBox result; if (GMLReal_fabs(inst->imageAngle) > 0.0001) { // Compute rotated AABB: transform the 4 corners of the unrotated bbox GMLReal rad = inst->imageAngle * M_PI / 180.0; @@ -68,26 +71,28 @@ static inline InstanceBBox Collision_computeBBox(DataWin* dataWin, Instance* ins if (cy[c] > maxY) maxY = cy[c]; } - return (InstanceBBox){ + result = (InstanceBBox){ .left = inst->x + minX, .right = inst->x + maxX, .top = inst->y + minY, .bottom = inst->y + maxY, .valid = true }; - } + } else { + GMLReal left = inst->x + inst->imageXscale * (marginL - originX); + GMLReal right = inst->x + inst->imageXscale * (marginR - originX); + GMLReal top = inst->y + inst->imageYscale * (marginT - originY); + GMLReal bottom = inst->y + inst->imageYscale * (marginB - originY); - // No rotation fast path - GMLReal left = inst->x + inst->imageXscale * (marginL - originX); - GMLReal right = inst->x + inst->imageXscale * (marginR - originX); - GMLReal top = inst->y + inst->imageYscale * (marginT - originY); - GMLReal bottom = inst->y + inst->imageYscale * (marginB - originY); + if (left > right) { GMLReal tmp = left; left = right; right = tmp; } + if (top > bottom) { GMLReal tmp = top; top = bottom; bottom = tmp; } - // Normalize if negative scale - if (left > right) { GMLReal tmp = left; left = right; right = tmp; } - if (top > bottom) { GMLReal tmp = top; top = bottom; bottom = tmp; } + result = (InstanceBBox){left, right, top, bottom, true}; + } - return (InstanceBBox){left, right, top, bottom, true}; + inst->cachedBBox = result; + inst->bboxCacheValid = true; + return result; } static inline bool Collision_hasFrameMasks(Sprite* sprite) { diff --git a/src/collision_types.h b/src/collision_types.h new file mode 100644 index 00000000..b98483bd --- /dev/null +++ b/src/collision_types.h @@ -0,0 +1,7 @@ +#pragma once +#include "real_type.h" // Where GMLReal is defined + +typedef struct { + GMLReal left, right, top, bottom; + bool valid; +} InstanceBBox; diff --git a/src/instance.c b/src/instance.c index 7f93a16e..3dfbfea9 100644 --- a/src/instance.c +++ b/src/instance.c @@ -47,6 +47,7 @@ Instance* Instance_create(uint32_t instanceId, int32_t objectIndex, GMLReal x, G inst->gravityDirection = 270.0f; inst->pathIndex = -1; inst->pathScale = 1.0f; + inst->bboxCacheValid = false; // Initialize alarms to -1 (inactive) repeat(GML_ALARM_COUNT, i) { diff --git a/src/instance.h b/src/instance.h index 2b175c97..8da60fd8 100644 --- a/src/instance.h +++ b/src/instance.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" +#include "collision_types.h" #include #include "rvalue.h" #include "gml_array.h" @@ -62,6 +63,10 @@ struct Instance { float pathXStart; // origin for relative paths float pathYStart; + // AABB cache for fast path reuse + InstanceBBox cachedBBox; + bool bboxCacheValid; + int32_t alarm[GML_ALARM_COUNT]; }; diff --git a/src/spatial_grid.c b/src/spatial_grid.c index d2029a47..d643807c 100644 --- a/src/spatial_grid.c +++ b/src/spatial_grid.c @@ -103,6 +103,8 @@ void SpatialGrid_markInstanceAsDirty(SpatialGrid* grid, Instance* dirtyInstance) return; } + dirtyInstance->bboxCacheValid = false; // <-- ADD THIS + if (dirtyInstance->spatialGridDirty) return; From dcf50726fcbf12474478eb1527a2c668472c1f8a Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Fri, 22 May 2026 14:42:59 +0100 Subject: [PATCH 2/6] restore comments --- src/collision.h | 2 ++ src/collision_types.h | 2 +- src/spatial_grid.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/collision.h b/src/collision.h index 1c2d5673..cc4f3c37 100644 --- a/src/collision.h +++ b/src/collision.h @@ -79,11 +79,13 @@ static inline InstanceBBox Collision_computeBBox(DataWin* dataWin, Instance* ins .valid = true }; } else { + // No rotation fast path GMLReal left = inst->x + inst->imageXscale * (marginL - originX); GMLReal right = inst->x + inst->imageXscale * (marginR - originX); GMLReal top = inst->y + inst->imageYscale * (marginT - originY); GMLReal bottom = inst->y + inst->imageYscale * (marginB - originY); + // Normalize if negative scale if (left > right) { GMLReal tmp = left; left = right; right = tmp; } if (top > bottom) { GMLReal tmp = top; top = bottom; bottom = tmp; } diff --git a/src/collision_types.h b/src/collision_types.h index b98483bd..d3de55b3 100644 --- a/src/collision_types.h +++ b/src/collision_types.h @@ -1,5 +1,5 @@ #pragma once -#include "real_type.h" // Where GMLReal is defined +#include "real_type.h" typedef struct { GMLReal left, right, top, bottom; diff --git a/src/spatial_grid.c b/src/spatial_grid.c index d643807c..2baccb78 100644 --- a/src/spatial_grid.c +++ b/src/spatial_grid.c @@ -103,7 +103,7 @@ void SpatialGrid_markInstanceAsDirty(SpatialGrid* grid, Instance* dirtyInstance) return; } - dirtyInstance->bboxCacheValid = false; // <-- ADD THIS + dirtyInstance->bboxCacheValid = false; if (dirtyInstance->spatialGridDirty) return; From e77f8be726e87ca0cf480d0b15b519dbc8e10393 Mon Sep 17 00:00:00 2001 From: Cobalt <65132371+cobaltgit@users.noreply.github.com> Date: Fri, 22 May 2026 18:58:42 +0100 Subject: [PATCH 3/6] collision: remove branch prediction hint --- src/collision.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collision.h b/src/collision.h index cc4f3c37..28e7366a 100644 --- a/src/collision.h +++ b/src/collision.h @@ -27,7 +27,7 @@ static inline Sprite* Collision_getSprite(DataWin* dataWin, Instance* inst) { // Computes the axis-aligned bounding box for an instance using its collision sprite static inline InstanceBBox Collision_computeBBox(DataWin* dataWin, Instance* inst) { // Fast path: return cached AABB when nothing bbox-affecting has changed. - if (__builtin_expect(inst->bboxCacheValid, 1)) return inst->cachedBBox; + if (inst->bboxCacheValid) return inst->cachedBBox; Sprite* spr = Collision_getSprite(dataWin, inst); if (spr == nullptr) { From 30445121d81ce516b45c8bd57e72128a3077887e Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 23 May 2026 08:23:14 +0100 Subject: [PATCH 4/6] collision: tidy up bbox edge assignments --- src/collision.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/collision.h b/src/collision.h index 378b69c7..591082ff 100644 --- a/src/collision.h +++ b/src/collision.h @@ -73,9 +73,9 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) if (cy[c] > maxY) maxY = cy[c]; } - left = inst->x + minX; - right = inst->x + maxX; - top = inst->y + minY; + left = inst->x + minX; + right = inst->x + maxX; + top = inst->y + minY; bottom = inst->y + maxY; } else { // No rotation fast path From 34ab67e5afd2dd8287d2944878c0f0187e041a6f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 24 May 2026 12:15:49 +0100 Subject: [PATCH 5/6] collision: use fmin/fmax for calculating corners --- src/collision.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/collision.h b/src/collision.h index 591082ff..e4aa2574 100644 --- a/src/collision.h +++ b/src/collision.h @@ -65,13 +65,10 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) cx[2] = cs * lx0 + sn * ly1; cy[2] = -sn * lx0 + cs * ly1; cx[3] = cs * lx1 + sn * ly1; cy[3] = -sn * lx1 + cs * ly1; - GMLReal minX = cx[0], maxX = cx[0], minY = cy[0], maxY = cy[0]; - for (int c = 1; 4 > c; c++) { - if (minX > cx[c]) minX = cx[c]; - if (cx[c] > maxX) maxX = cx[c]; - if (minY > cy[c]) minY = cy[c]; - if (cy[c] > maxY) maxY = cy[c]; - } + GMLReal minX = fmin(fmin(cx[0], cx[1]), fmin(cx[2], cx[3])); + GMLReal maxX = fmax(fmax(cx[0], cx[1]), fmax(cx[2], cx[3])); + GMLReal minY = fmin(fmin(cy[0], cy[1]), fmin(cy[2], cy[3])); + GMLReal maxY = fmax(fmax(cy[0], cy[1]), fmax(cy[2], cy[3])); left = inst->x + minX; right = inst->x + maxX; @@ -85,10 +82,16 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) bottom = inst->y + inst->imageYscale * (marginB - originY); // Normalize if negative scale - if (left > right) { GMLReal tmp = left; left = right; right = tmp; } - if (top > bottom) { GMLReal tmp = top; top = bottom; bottom = tmp; } + GMLReal tmp_left = fmin(left, right); + GMLReal tmp_right = fmax(left, right); + GMLReal tmp_top = fmin(top, bottom); + GMLReal tmp_bottom = fmax(top, bottom); + left = tmp_left; + right = tmp_right; + top = tmp_top; + bottom = tmp_bottom; } - + if (runner->collisionCompatibilityMode) { left = GMLReal_bankersRound(left); top = GMLReal_bankersRound(top); From 8e0e381736cfc1c22124a44e18acaa75a28ee767 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 24 May 2026 12:24:19 +0100 Subject: [PATCH 6/6] Collision_computeBBox: use GMLReal_fmin/max whoops, forgot about that. sorry PS2 users --- src/collision.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/collision.h b/src/collision.h index e4aa2574..7a87da79 100644 --- a/src/collision.h +++ b/src/collision.h @@ -65,10 +65,10 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) cx[2] = cs * lx0 + sn * ly1; cy[2] = -sn * lx0 + cs * ly1; cx[3] = cs * lx1 + sn * ly1; cy[3] = -sn * lx1 + cs * ly1; - GMLReal minX = fmin(fmin(cx[0], cx[1]), fmin(cx[2], cx[3])); - GMLReal maxX = fmax(fmax(cx[0], cx[1]), fmax(cx[2], cx[3])); - GMLReal minY = fmin(fmin(cy[0], cy[1]), fmin(cy[2], cy[3])); - GMLReal maxY = fmax(fmax(cy[0], cy[1]), fmax(cy[2], cy[3])); + GMLReal minX = GMLReal_fmin(GMLReal_fmin(cx[0], cx[1]), GMLReal_fmin(cx[2], cx[3])); + GMLReal maxX = GMLReal_fmax(GMLReal_fmax(cx[0], cx[1]), GMLReal_fmax(cx[2], cx[3])); + GMLReal minY = GMLReal_fmin(GMLReal_fmin(cy[0], cy[1]), GMLReal_fmin(cy[2], cy[3])); + GMLReal maxY = GMLReal_fmax(GMLReal_fmax(cy[0], cy[1]), GMLReal_fmax(cy[2], cy[3])); left = inst->x + minX; right = inst->x + maxX; @@ -82,10 +82,10 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) bottom = inst->y + inst->imageYscale * (marginB - originY); // Normalize if negative scale - GMLReal tmp_left = fmin(left, right); - GMLReal tmp_right = fmax(left, right); - GMLReal tmp_top = fmin(top, bottom); - GMLReal tmp_bottom = fmax(top, bottom); + GMLReal tmp_left = GMLReal_fmin(left, right); + GMLReal tmp_right = GMLReal_fmax(left, right); + GMLReal tmp_top = GMLReal_fmin(top, bottom); + GMLReal tmp_bottom = GMLReal_fmax(top, bottom); left = tmp_left; right = tmp_right; top = tmp_top;