-
-
Notifications
You must be signed in to change notification settings - Fork 36.5k
add fast path for compute bounding sphere/box from attribute data for common case (~2.5x speedup) #34204
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
base: dev
Are you sure you want to change the base?
add fast path for compute bounding sphere/box from attribute data for common case (~2.5x speedup) #34204
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| /** | ||
| * Benchmark: BufferGeometry.computeBoundingSphere() fast path vs the previous | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, we have removed the earlier benchmark suite on purpose. They were not in used because developers often applied benchmarks via jsbench or jsfiddle which are easier to share and execute on various systems like mobile devices. I would prefer not to adding |
||
| * implementation (per-vertex Vector3.fromBufferAttribute access). | ||
| * | ||
| * Run with: node test/benchmark/computeBoundingSphere.js | ||
| */ | ||
|
|
||
| import { BufferGeometry } from '../../src/core/BufferGeometry.js'; | ||
| import { BufferAttribute } from '../../src/core/BufferAttribute.js'; | ||
| import { Box3 } from '../../src/math/Box3.js'; | ||
| import { Sphere } from '../../src/math/Sphere.js'; | ||
| import { Vector3 } from '../../src/math/Vector3.js'; | ||
|
|
||
| const _box = new Box3(); | ||
| const _vector = new Vector3(); | ||
|
|
||
| // faithful copy of the previous implementation (no morph targets) | ||
|
|
||
| function computeBoundingSphereOld( geometry ) { | ||
|
|
||
| const sphere = new Sphere(); | ||
| const position = geometry.attributes.position; | ||
| const center = sphere.center; | ||
|
|
||
| // old Box3.setFromBufferAttribute path | ||
|
|
||
| _box.makeEmpty(); | ||
|
|
||
| for ( let i = 0, il = position.count; i < il; i ++ ) { | ||
|
|
||
| _box.expandByPoint( _vector.fromBufferAttribute( position, i ) ); | ||
|
|
||
| } | ||
|
|
||
| _box.getCenter( center ); | ||
|
|
||
| let maxRadiusSq = 0; | ||
|
|
||
| for ( let i = 0, il = position.count; i < il; i ++ ) { | ||
|
|
||
| _vector.fromBufferAttribute( position, i ); | ||
|
|
||
| maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); | ||
|
|
||
| } | ||
|
|
||
| sphere.radius = Math.sqrt( maxRadiusSq ); | ||
|
|
||
| return sphere; | ||
|
|
||
| } | ||
|
|
||
| function makeGeometry( vertexCount ) { | ||
|
|
||
| const array = new Float32Array( vertexCount * 3 ); | ||
|
|
||
| for ( let i = 0; i < array.length; i ++ ) { | ||
|
|
||
| array[ i ] = Math.random() * 200 - 100; | ||
|
|
||
| } | ||
|
|
||
| const geometry = new BufferGeometry(); | ||
| geometry.setAttribute( 'position', new BufferAttribute( array, 3 ) ); | ||
|
|
||
| return geometry; | ||
|
|
||
| } | ||
|
|
||
| function bench( fn, iterations ) { | ||
|
|
||
| const times = []; | ||
|
|
||
| for ( let i = 0; i < iterations; i ++ ) { | ||
|
|
||
| const start = performance.now(); | ||
| fn(); | ||
| times.push( performance.now() - start ); | ||
|
|
||
| } | ||
|
|
||
| times.sort( ( a, b ) => a - b ); | ||
|
|
||
| return times[ Math.floor( times.length / 2 ) ]; // median | ||
|
|
||
| } | ||
|
|
||
| const sizes = [ 10_000, 100_000, 1_000_000, 5_000_000 ]; | ||
| const iterations = 20; | ||
|
|
||
| console.log( `median of ${ iterations } runs per case\n` ); | ||
| console.log( 'vertices'.padStart( 10 ), 'old (ms)'.padStart( 12 ), 'new (ms)'.padStart( 12 ), 'speedup'.padStart( 10 ) ); | ||
|
|
||
| for ( const size of sizes ) { | ||
|
|
||
| const geometry = makeGeometry( size ); | ||
|
|
||
| // correctness check | ||
|
|
||
| const oldSphere = computeBoundingSphereOld( geometry ); | ||
| geometry.computeBoundingSphere(); | ||
| const newSphere = geometry.boundingSphere; | ||
|
|
||
| if ( Math.abs( oldSphere.radius - newSphere.radius ) > 1e-10 || | ||
| oldSphere.center.distanceTo( newSphere.center ) > 1e-10 ) { | ||
|
|
||
| console.error( `MISMATCH at ${ size } vertices: old`, oldSphere, 'new', newSphere ); | ||
| process.exit( 1 ); | ||
|
|
||
| } | ||
|
|
||
| // warmup | ||
|
|
||
| for ( let i = 0; i < 3; i ++ ) { | ||
|
|
||
| computeBoundingSphereOld( geometry ); | ||
| geometry.computeBoundingSphere(); | ||
|
|
||
| } | ||
|
|
||
| const oldMs = bench( () => computeBoundingSphereOld( geometry ), iterations ); | ||
| const newMs = bench( () => geometry.computeBoundingSphere(), iterations ); | ||
|
|
||
| console.log( | ||
| String( size ).padStart( 10 ), | ||
| oldMs.toFixed( 3 ).padStart( 12 ), | ||
| newMs.toFixed( 3 ).padStart( 12 ), | ||
| ( oldMs / newMs ).toFixed( 2 ).padStart( 9 ) + 'x' | ||
| ); | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
How about we create a new method on buffer attribute level called
supportsDirectAccess()?This method would decide on attribute level if a direct access is allowed. The policy would be "non-normalized, non-interleaved, float storage".
So
BufferAttributeshould check:InterleavedBufferAttributeandGLBufferAttributeboth returnfalse.