Skip to content

Simplify the unified CPU sort params after the non-uniform scale fix - #9270

Merged
mvaligursky merged 3 commits into
mainfrom
mv-gsplat-sort-cleanup
Sep 1, 2026
Merged

mvaligursky merged 3 commits into
mainfrom
mv-gsplat-sort-cleanup

Conversation

@mvaligursky

Copy link
Copy Markdown
Contributor

Follow-up cleanup to #9268. No behavioural change to either sort path.

Changes:

  • Removed the dead arm of the transformedDirection ternary. The radial path dispatches to evaluateSortKeysRadial / computeEffectiveDistanceRangeRadial, neither of which reads transformedDirection — only transformedPosition and scale — so the invModelMat.transformVector(...).normalize() result was never consumed. transformedDirection now always carries the exact per-axis weights, and only scale branches on the sort mode. This drops the two-convention coupling where the meaning of a posted field depended on a flag interpreted in another file.
  • Renamed uniformScale to scale, since it is 1 on the linear path now that the scale is folded into the per-axis weights.
  • setSortParams is passed the already-cached radialSort instead of re-reading this.scene.gsplat.radialSorting, so the params and the mode flag cannot be built from different reads.

Performance:

  • The radial path loses a 4x4 inverse-transform plus a normalize per splat placement per frame, and gains three dot products. Effectively a wash — lastState.splats counts placements, not gaussians.

Note on #9268: worth recording that the CPU worker's per-splat AABB range and the GPU path's computeDistanceRange now compute the same quantity. Before #9268 they disagreed under non-uniform scale, since the worker projected the local AABB with the erroneous 1/sᵢ weights while the GPU path already transformed corners with the full matrix.

The radial sort path never reads `transformedDirection`, so the ternary
introduced in #9268 had a dead arm. Always post the exact per-axis
weights and branch only on the scale, which the radial path still needs
to convert local distances to world units.

Also rename `uniformScale` to `scale`, since it is 1 on the linear path,
and pass the already-cached `radialSort` to `setSortParams` rather than
re-reading the scene flag.
@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2391.7 KB (−0.1 KB, −0.00%) 615.1 KB (−0.0 KB, −0.01%) 477.6 KB (+0.3 KB, +0.07%)
playcanvas.min.mjs 2389.1 KB (−0.1 KB, −0.00%) 613.8 KB (−0.0 KB, −0.00%) 476.7 KB (+0.3 KB, +0.06%)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Cleans up the unified CPU gsplat sort parameter construction after the non-uniform scale fix, aiming to simplify semantics and reduce redundant state reads while keeping sort behavior unchanged.

Changes:

  • Makes transformedDirection consistently represent the per-axis weights (and renames uniformScale → scale).
  • Passes the cached radialSort flag through to setSortParams to avoid mismatched reads.
  • Removes the now-dead radial/linear meaning split in the transformedDirection construction (though there’s still avoidable work in radial mode; see comments).
Suppressed comments (2)

src/scene/gsplat-unified/gsplat-manager.js:837

  • transformedDirection is still computed and a Vec3 allocated even when radialSort is enabled, but the worker’s radial path never reads transformedDirection (it only uses transformedPosition and scale). This adds unnecessary dot products and allocation in radial mode.
            const transformedDirection = new Vec3(
                modelMat.getX(splatAxis).dot(cameraDirection),
                modelMat.getY(splatAxis).dot(cameraDirection),
                modelMat.getZ(splatAxis).dot(cameraDirection)
            );

src/scene/gsplat-unified/gsplat-manager.js:857

  • modelMat: modelMat.data.slice() is never read by the sort worker (no modelMat usage in gsplat-unified-sort-worker.js), but it allocates a new array and increases per-frame message payload for every splat placement. Removing it should reduce GC and postMessage overhead without changing behavior.
                scale,
                modelMat: modelMat.data.slice(),
                aabbMin: [aabbMin.x, aabbMin.y, aabbMin.z],

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

);
// the radial path multiplies local-space distances by this to get world units; the
// linear path has the scale folded into the per-axis weights below, so it needs none
const scale = radialSort ? modelMat.getScale().x : 1;
@mvaligursky
mvaligursky merged commit 9f46c0b into main Sep 1, 2026
10 checks passed
@mvaligursky
mvaligursky deleted the mv-gsplat-sort-cleanup branch September 1, 2026 10:11

This branch was successfully deployed

2 active deployments
Preview – engine — c5a2668a Deployed Sep 1, 2026 by vercel[bot]
Preview – engine-api-docs — c5a2668a Deployed Sep 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants