From aed29ca2fef72a0c9663624f3bccbe27c21a0cc8 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Sat, 21 Mar 2026 10:12:14 +0100 Subject: [PATCH] SVGRenderer: Fix performance regression. (#33220) --- examples/jsm/renderers/Projector.js | 56 +++++++++------------------ examples/jsm/renderers/SVGRenderer.js | 31 +++------------ 2 files changed, 24 insertions(+), 63 deletions(-) diff --git a/examples/jsm/renderers/Projector.js b/examples/jsm/renderers/Projector.js index d0f7b21c720425..2a87bf0142267e 100644 --- a/examples/jsm/renderers/Projector.js +++ b/examples/jsm/renderers/Projector.js @@ -310,18 +310,18 @@ class Projector { const v2 = _vertexPool[ b ]; const v3 = _vertexPool[ c ]; - // Get homogeneous clip space positions (before perspective divide) - _clipPos1.copy( v1.positionWorld ).applyMatrix4( _viewProjectionMatrix ); - _clipPos2.copy( v2.positionWorld ).applyMatrix4( _viewProjectionMatrix ); - _clipPos3.copy( v3.positionWorld ).applyMatrix4( _viewProjectionMatrix ); - - // Check if triangle needs clipping - const nearDist1 = _clipPos1.z + _clipPos1.w; - const nearDist2 = _clipPos2.z + _clipPos2.w; - const nearDist3 = _clipPos3.z + _clipPos3.w; - const farDist1 = - _clipPos1.z + _clipPos1.w; - const farDist2 = - _clipPos2.z + _clipPos2.w; - const farDist3 = - _clipPos3.z + _clipPos3.w; + // Derive near/far clip distances from NDC z and stored clip-space w + // (projectVertex already computed positionScreen = clipPos / w, with w preserved) + const w1 = v1.positionScreen.w; + const w2 = v2.positionScreen.w; + const w3 = v3.positionScreen.w; + + const nearDist1 = w1 * ( v1.positionScreen.z + 1 ); + const nearDist2 = w2 * ( v2.positionScreen.z + 1 ); + const nearDist3 = w3 * ( v3.positionScreen.z + 1 ); + const farDist1 = w1 * ( 1 - v1.positionScreen.z ); + const farDist2 = w2 * ( 1 - v2.positionScreen.z ); + const farDist3 = w3 * ( 1 - v3.positionScreen.z ); // Check if completely outside if ( ( nearDist1 < 0 && nearDist2 < 0 && nearDist3 < 0 ) || @@ -385,7 +385,10 @@ class Projector { } - // Triangle needs clipping + // Triangle needs clipping - reconstruct clip-space positions from NDC + w + _clipPos1.set( v1.positionScreen.x * w1, v1.positionScreen.y * w1, v1.positionScreen.z * w1, w1 ); + _clipPos2.set( v2.positionScreen.x * w2, v2.positionScreen.y * w2, v2.positionScreen.z * w2, w2 ); + _clipPos3.set( v3.positionScreen.x * w3, v3.positionScreen.y * w3, v3.positionScreen.z * w3, w3 ); _clipInputVertices[ 0 ] = _clipPos1; _clipInputVertices[ 1 ] = _clipPos2; _clipInputVertices[ 2 ] = _clipPos3; @@ -577,7 +580,7 @@ class Projector { if ( sortObjects === true ) { - painterSortStable( _renderData.objects, 0, _renderData.objects.length ); + _renderData.objects.sort( painterSort ); } @@ -843,7 +846,7 @@ class Projector { if ( sortElements === true ) { - painterSortStable( _renderData.elements, 0, _renderData.elements.length ); + _renderData.elements.sort( painterSort ); } @@ -987,29 +990,6 @@ class Projector { } - function painterSortStable( array, start, length ) { - - // A stable insertion sort for sorting render items - // This avoids the GC overhead of Array.prototype.sort() - - for ( let i = start + 1; i < start + length; i ++ ) { - - const item = array[ i ]; - let j = i - 1; - - while ( j >= start && painterSort( array[ j ], item ) > 0 ) { - - array[ j + 1 ] = array[ j ]; - j --; - - } - - array[ j + 1 ] = item; - - } - - } - // Sutherland-Hodgman triangle clipping in homogeneous clip space // Returns count of vertices in clipped polygon (0 if completely clipped, 3+ if partially clipped) // Result vertices are in _clipInput array diff --git a/examples/jsm/renderers/SVGRenderer.js b/examples/jsm/renderers/SVGRenderer.js index 6013aeb3809b16..6582913778087c 100644 --- a/examples/jsm/renderers/SVGRenderer.js +++ b/examples/jsm/renderers/SVGRenderer.js @@ -300,29 +300,6 @@ class SVGRenderer { } - function arraySortStable( array, start, length ) { - - // A stable insertion sort for sorting the render list - // This avoids the GC overhead of Array.prototype.sort() - - for ( let i = start + 1; i < start + length; i ++ ) { - - const item = array[ i ]; - let j = i - 1; - - while ( j >= start && renderSort( array[ j ], item ) > 0 ) { - - array[ j + 1 ] = array[ j ]; - j --; - - } - - array[ j + 1 ] = item; - - } - - } - /** * Performs a manual clear with the defined clear color. */ @@ -416,9 +393,13 @@ class SVGRenderer { } ); - if ( this.sortElements ) { + _renderListPool.length = _renderListCount; + + if ( this.sortElements && _svgObjectCount > 0 ) { - arraySortStable( _renderListPool, 0, _renderListCount ); + // Elements are already sorted by the Projector. + // Only re-sort when SVGObjects need depth-interleaving. + _renderListPool.sort( renderSort ); }