the content bounds of a display object and the local bounds of its children (not the whole subtree) when transformed into its own frame of reference. Optimizing Bounds • World Bounds:  The transformation of Calculations in PixiJS Scenes local bounds into the scene graph’s root frame of reference. This ”root” frame of Shukant K. Pal reference is also known as the ”world” and the aforementioned bounds are called February 2020 ”world bounds”. • Node depth: The depth of a node in the This paper describes techniques that can be scene graph denoted by α. used to optimize the bounds calculation of pixi.js display objects. • Transformation matrix T (α0 , αP ): Con verts a point from the reference frame of a node at depth α0 to that of its ances 1 Introduction tor at depth αP = α0 − ∆α. The world transformation matrix is always of the form 1.1 Terminology T (α0 , 0) = Tworld (α0 ). • Target object: Any pixi.js display object on which an operation occurs or property 1.2 Why? accessed is called the ”target”. To generate the local bounds of an object, the • All nodes below a target node are collec the transforms of all nodes in its subtree have to tively a part of the subtree (which includes be recalculated. Similarly, to calculate the world the target as well). bounds of an object, the transforms of all ances tors must be updated before the target’s subtree. • Frame of reference: The frame of reference If the depth of an object in the scene graph for any display object is a 2D coordinate is α and the number of objects in its subtree system uniquely associated with it. is N , then the number of transforms calculated • Content Bounds: The bounds of a display in a getLocalBounds() call is N and in a object when no transformation is applied. getBounds() call is N + α. It can easily be This is defined in the target’s own frame of calculated that in a tree with depth α and n reference. The space taken by children is times more nodes each successive depth, calling ignored when calculating content bounds. getBounds(false) on each node will result in α × nα transform updates. • Local Bounds:  The union of the content Of course, pixi.js deals with this, by updat bounds of a displayobject and the content ing the whole scene graph once before a render bounds of nodes in its subtree after trans pass (with O(N ) transform updates). Internally, forming them into the target’s frame of ref the renderer uses getBounds(true) to pre erence. Due to the transformation required vent any additional transform updates. This vul in child nodes, the local bounds depend on nerability results in the following restrictions: the local transform of each node in the sub tree. An alternate, recursive, and simpler 1. getBounds(true) must always be used definition of local bounds is the union of in the render pass to prevent any unneces 1 sary transform updates. This has not been The current behaviour of getLocalBounds documented properly. is to cut the scene graph temporarily at the tar get node and update the worldtransforms of its 2. getLocalBounds() will dirty the trans subtree as if the target itself was the root. This forms of all the nodes in the subtree. This overwrites each worldtransform and makes it means that an updateTransform must dirty. A call later to getBounds on the target always be called after it. If not, then child or any one of its ancestors will trigger a full re nodes will render using the wrong trans calculation of the world transforms in the whole form. subtree. This behaviour can be corrected by allowing 3. Applications with large scene graphs will the coexistence of nonlocal transforms that are suffer from full transform updates each tagged with a depthdelta: frame if they require localbounds of nodes anywhere in their code. 1. The difference in depth of the node itself and the ancestor whose reference frame is 1.3 Goals the destination of the transformation ma trix (= α0 − αP ). The following optimizations are the endgoal of this paper: or 1. Localbounds calculations of a node will not result in ”dirty transforms” in its sub 2. ”∞” indicating that the nonlocal trans tree. form is, in fact, the actual world trans form. This is required because it is very 2. Localbounds of any object are only recal expensive to track the absolute depth of culated when a) its transform has changed a node outside of a getLocalBounds or or b) a node in its subtree has changed getBounds call. transforms or c) content bounds in its sub tree has changed. Similarly, worldbounds of an object should be recalculated on the 2.0.1 Virtuality of nonlocal, nonworld same conditions or if one of the ances transforms tor’s transform has changed. This must be Nonlocal, nonworld transforms may be de achieved by preventing transform updates scribed as ”virtual” because they do not have from making the bounds dirty if the trans any purpose outside of a getLocalBounds call form hasn’t changed. (i.e. they are an implementation detail and do not ”exist” for the enduser). Furthermore, a virtual transform will always have a finite depth 2 Coexistence of nonlocal delta ∆α = α0 − αP associated with it. transformation matrices 2.0.2 Equivalence of virtual and world The current implementation of PIXI.Transform transform holds only two transformations matrices  a lo cal transform Tlocal and its corresponding world Both virtual and world transforms are nonlocal. transform Tworld (α0 ) (for a node at depth α0 ). The difference is just that the world transform However, there actually exist α0 nonlocal trans is always cached in PIXI.Transform, while forms for a node that may be required when cal virtual transforms are temporarily created when culating the local bounds of one of its ancestors. calculating localbounds of an ancestor. 2 2.1 Implementation 3 Optimized virtual trans • A PIXI.Transform object will have form and lazy bounds cal a map associating the depthdelta to culations its corresponding virtual transform (say virtualTransforms). 3.1 Derivation of a virtual trans • The exhibited worldTransform will form based off of the corre be either the actual world transform sponding world transform or one of the virtual transforms in A world transform T world (α0 ) can be decom virtualTransforms. By default, the posed into an ordered product of local transforms actual world transform shall be exhibited. of ancestors at each level: Only under certain circumstances shall a virtual transform be exhibited, and the enduser will never see a virtual transform Tworld (α0 ) = Tlocal (1) × Tlocal (2)... × Tlocal (α0 ) being exhibited. (2) Similarly, the transformation matrix used to • A transformupdate associated with a calculate the bounds of a a node at depth α0 getLocalBounds call will indicate the in the frame of reference of a node at depth αP depthdelta (if any) of the transform up (for example, when calculating local bounds of date. If it exists, then the virtual trans an ancestor) will be: form associated with that depthdelta will be calculated (by the same recursive algo rithm used for the world transform.) T (α0 , αP ) = Tlocal (αP +1)×Tlocal (αP +2)...×Tlocal (α0 ) (3) On comparing with equation (1), it can be Tvirtual (α) = Tvirtual (α − 1) × Tlocal (1) seen that: • At the beginning of a getLocalBounds T (α0 , αP ) = inv(Tworld (αP )) × Tworld (α0 ) (4) call, the transform update will use depth deltas that associated with the refer This optimization relies on the fact that the ence frame of the target. This will predicate Tworld (αP ) is equal in the target and make transforms in the subtree exhibit child node transforms. This assumption is valid worldTransforms of the newly created if we are simultaneously updating worlds trans virtual transforms. At the end of a forms while calculating virtual transforms. getLocalBounds call, all virtual trans This also assumes that the world transform forms associated with the target node shall of the target node is invertible. The condition for be deleted and the actual world transforms this is that the determinant of the world trans be restored. form Tworld (αP ) is nonzero, which can safely be assumed for transforms in a pixi.js scene graph. 2.2 Reflection This is an alternate method of calculating a virtual transform without having to recursively This section dealt with the first goal of this pa calculate the corresponding virtual transform of per. By temporarily creating virtual transforms, its direct parent. The benefit derived is that a we prevent the actual world transforms from be virtual transform is not needed by a node’s chil ing corrupted in getLocalBounds. dren and, hence, the virtual transform can be 3 deleted right after a calculateBounds rather 3.4 Preventing redundant trans than an (additional) recursive deletion at the end form calculations by pre of the target’s getLocalBounds. evaluating changes in local transforms 3.2 Preventing dirty bounds with The localbounds of an object depend only on the local transforms in its subtree (ex out dirty transforms cluding itself). A preliminary pass similar to updateTransform that checks for any changes The current implementation of updateTransformin transforms without actually calculating them will dirty boundsID even if the transform has will prevent redundant transform updates. not changed. This can be prevented by only making the bounds dirty of the worldID of the transform has changed. 3.5 Reflection To do this, updateTransform will re This section dealt with preventing transform turn the a value indicating the difference updates from causing a bounds recalculation in boundsID it has caused. This differ when transforms haven’t changed and intrinsic, ence will then be added by the parent’s content bounds are still the same. This solves updateTransform method. The target node goal no. 2 for world bounds. on which updateTransform was called will This still leaves a weakness that if a trans manually then propagate this to its ancestors. form of an ancestor has changed, then the trans forms of the whole subtree will be updated and, henceforth, the local bounds will be recalculated again. This is prevented by the preliminary 3.3 Bounds dependencies check discussed in 3.4 Suppose transforms throughout the scene graph have not changed, then still the bounds of a tar 4 Implementation summary get object depend on the changes in the content bounds of nodes in its subtree. Unlike transforms 4.1 getLocalBounds whose dependence is topdown, this dependency is bottomup (i.e. changes in children affect the After implementing all the changes recom parent, not vice versa). mended in this paper, a getLocalBounds call would operate as follows: Since updateTransform is a recursive function, it can serve to detect changes in • A checkBounds method will be used to both types of dependencies. Hence, to de detect any local boundsaffecting changes tect whether any content bounds have changed in the subtree (from section 3.4). for a node in the subtree, each node will have a subtreeBoundsID which will be the – Each node’s local transform will sum of its own boundsID and its children’s be updated and the difference in subtreeBoundsID. If the contentbounds of transform_ currentLocalID a node in the subtree have changed, then will be added to boundsID. its boundsID and hence subtreeBoundsID – A contentbounds change should have must have changed too. Similar is the situation already changed boundsID. if a transform has changed. Changes will prop – Changes in boundsID will be prop agate up the subtree because of the summation agated up the subtree via summation at each level. in subtreeBoundsID. 4 • If the resulting subtreeBoundsID is dif 1. A recursivePostUpdateTransform ferent in the target, this would indicate is obligatory to check for worldtransform that the stored localbounds are dirty. changes in ancestors. • A updateTransform call will occur. Each node in the subtree will calcu 2. A transformupdate does no harm (be late a virtual transform that results cause world transforms are cached in the target’s reference frame. This for each node, and will only be virtual transform shall be exhibited in recalculated if parent is dirty, see node.transform.worldTransform. Transform.updateTransform). The calculations of these virtual trans forms can be optimized in accordance It would look something like as follows: with section 3.1 by passing down the world transform of the target node at • A recursivePostUpdateTransform each level (each node will calculate the occurs followed with an updateTransform. world transform and then apply equation 4 to calculate the corresponding virtual • If the subtreeBoundsID has not transform. This requires that the target changed, then bounds do not have to be node update its own world transform via recalculated. recursivePostUpdateTransform()). • This updateTransform will dirty • Otherwise, run calculateBounds as boundsID again if the world trans normal. form has changed. These changes will be propagated up to the target’s subtreeBoundsID again. 4.3 Virtual transform tagging • A calculateBounds call will occur. Instead of tagging virtual transforms with a depthdelta, it may be more appropriate to tag • The subtree will be recursed again to them with the target node. This is useful if, in restore the actual world transforms and the future, virtual transforms may be cached like delete the temporary virtual transforms. the worldtransform. This can be optimized by making a wrap per around calculateBounds that will automatically delete any existing virtual transform (because of the section 3.1 op 5 Future work timization). Localbounds of the scene graph can be (op tionally) cached before each render pass like the 4.2 getBounds world bounds in the current implementation. This operation is similar to getLocalBounds, This can be done by updating transforms bottom except here: up and calculating local bounds at each level. 5
Enter the password to open this PDF file:











