-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Object3D: matrixNeedsUpdate and matrix/orientation optimizations #28534
base: dev
Are you sure you want to change the base?
Object3D: matrixNeedsUpdate and matrix/orientation optimizations #28534
Conversation
📦 Bundle sizeFull ESM build, minified and gzipped.
🌳 Bundle size after tree-shakingMinimal build including a renderer, camera, empty scene, and dependencies.
|
e95a463
to
dd76004
Compare
listenToProperties( position, [ 'x', 'y', 'z' ], undefined, onWrite ); | ||
listenToProperties( rotation, [ 'x', 'y', 'z', 'order' ], onRotationRead, onRotationWrite ); | ||
listenToProperties( quaternion, [ 'x', 'y', 'z', 'w' ], onQuaternionRead, onQuaternionWrite ); | ||
listenToProperties( scale, [ 'x', 'y', 'z' ], undefined, onWrite ); | ||
listenToMethods( matrix, onMatrixChange ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
listenToProperties
adds an on-read and on-write callback to named keys (not a deopt), which we use here to set matrixNeedsUpdate
when transform properties are modified. On-read is used to convert quaternion/rotation like before, but only if their counterpart is dirty when read, preventing many unneeded conversions on mutation (which can be many times). Notably, if users stick to quaternion
, they will avoid this conversion entirely. This is nice so we don't have to modify math classes like before.
listenToMethods
I have as a special case for the local matrix whose members would be deopt if I just listened to all elements in its array. This is because array accessors are not named and unstable, so v8 would need to create a layer of indirection to make them stable. We want matrixWorldNeedsUpdate
to be set whenever the local matrix updates to preserve existing behavior, so I instead handle the cases where local matrix is modified by its methods (which are named keys), and don't handle the case where matrix.elements
is mutated directly (matrixWorldNeedsUpdate
has to then be set by the user). This keeps us on the fast path for common usage without breaking APIs who expected a 1-1 relationship between local and world matrix updates.
this.matrix = camera.matrixWorld; | ||
this.matrixAutoUpdate = false; | ||
this.matrixWorld = camera.matrixWorld; | ||
this.matrixWorldAutoUpdate = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This relied on the fact that world matrices were always updated even transiently. Since #28533, we can do this instead.
@CodyJasonBennett might be a breaking change in Euler changing members from _x, _y, _z, _order to x ,y ,z , order for apps that store Euler in JSON format. It saves the data to _x , _y format but with this PR it will expect x , y, z, order. What's your take on it ? |
I know {
"isEuler": true,
"_x": 0,
"_y": 0,
"_z": 0,
"_order": "XYZ"
} Regardless of approach -- including caching or any user-land optimizations (e.g. WASM or potentially all of the above) -- we need to address the overhead from |
Related issue: #21387, #25115
Description
Implements
matrixNeedsUpdate
set from mutations toposition
/quaternion
/rotation
/scale
properties inObject3D
, reducing the update depth and frequency of local matrix updates and consequent world matrix updates to descendants (related: #14138, #28533). I've also removed the_onChangeCallback
system in math classes and synchronizedrotation
andquaternion
locally inObject3D
once on read (versus unconditionally on write), where it's now possible to skip this conversion if users keep to one orientation property at a time. This is not something I've seen regarded anywhere than maybe #14044 (comment), but should be a nice benefit for both maintenance and performance outside of matrices. Most of the diff in this PR is from removing prior hacks.World matrices are also no longer updated unconditionally, as we will only set
matrixWorldNeedsUpdate
when the local matrix (or parent world matrix) updates. This removes the primary use ofmatrixWorldAutoUpdate
for those looking to squeeze out performance, as these optimizations are performed automatically. It's possible to further improveupdateWorldMatrix
in this regard so it can skip updates to ancestors -- #28533 (comment). The introduction ofmatrixNeedsUpdate
is similarly useful for polling in user-land, which I think is a reasonable compromise for those hoping to detect updates in the scene-graph (e.g. updating bounding volumes or even using the scene as a virtual API forBatchedMesh
).Future work could include optimizing the cost of matrix composition itself (#14138 (comment)). Although position is a simple mutation, scale would be more involved by dividing/multiplying new/old scales, and quaternion the worst-case-scenario with a complete update to the upper-left 3x3 matrix. It's possible this can be performed in user-land, or we can reduce this check to
matrix.setPosition
ormatrix.compose
depending on whether orientation/scale change. This is not something I'll consider further, as I believe this is best handled in user-land where we can compile WASM etc., but I've outlined a solution below.Partial Matrix Composition Example