From c86cc9e864623cd3cda1fb2c271591a107a94edf Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Fri, 22 May 2026 13:44:24 +0100 Subject: [PATCH 1/7] 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/7] 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/7] 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/7] 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/7] 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/7] 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; From 9ec31c75907aae1969f59f56331e3997a46f5de7 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 9 Jun 2026 21:28:19 +0100 Subject: [PATCH 7/7] GMLReal: optimise banker's round --- src/real_type.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/real_type.h b/src/real_type.h index ec56fdf7..ed042f81 100644 --- a/src/real_type.h +++ b/src/real_type.h @@ -8,6 +8,7 @@ #ifdef USE_FLOAT_REALS typedef float GMLReal; +typedef int32_t GMLReal_int; #define GMLReal_sin sinf #define GMLReal_cos cosf @@ -31,6 +32,7 @@ typedef float GMLReal; #else typedef double GMLReal; +typedef int64_t GMLReal_int; #define GMLReal_sin sin #define GMLReal_cos cos @@ -56,12 +58,12 @@ typedef double GMLReal; // Round-half-to-even (banker's rounding). // While the original runner uses "llrint(double)", we use our own banker's rounding implementation to avoid quirks in specific platforms (like the PlayStation 2) having different llrint rounding implementations. static inline GMLReal GMLReal_bankersRound(GMLReal v) { - if (isnan(v) || isinf(v)) return v; GMLReal f = GMLReal_floor(v); GMLReal frac = v - f; - if (0.5 > frac) return f; - if (frac > 0.5) return f + 1.0; - // Exactly halfway: round to the even neighbor. - int64_t fi = (int64_t) f; - return (fi & 1) == 0 ? f : f + 1.0; + + if (frac == 0.5) { + GMLReal_int fi = (GMLReal_int) f; + return ((fi & 1) == 0) ? f : (f + 1.0); + } + return (frac > 0.5) ? (f + 1.0) : f; }