Why is the Geometry faster than BufferGeometry?

geometry

#1

Quote from docs / Geometry:

Geometries are easier to work with than BufferGeometries as they store attributes such as vertices, faces, colors and so on directly (rather than in buffers), however they are generally slower.

Because it’s easier, I’ve started the addon
Addon. Produces almost infinite many time-varying geometries with functions
with Geometry. I was told I should always take BufferGeometry. Now I have expanded the addon.

The surprise: Geometry is the fastest!
See http://sandbox.threejs.hofk.de/Geometry_BufferGeometry/speedtest.html
How can that be explained? I don’t no why.

May be my calcultion of vertices, positions, faces, normals are not efficient enought and the three.js internal calculations of Geometry are often much better.


#2

Hello hofk,

Buffergeometry saves its data into buffers: fast memory pools (for lack of better words) to reduce memory cost and cpu cycles. The performance improvement could be shown only when a certain number of vertices are used and it is GPU specific. It is recommended to use buffergeo, because in most cases (imported models) the end result performs better and it makes sense to have a standard/unique way to deal with geometries. Plus, three.js webGL 2.0 renderer makes usage of buffergeometries only.


#3

I’ve not looked into your code or performance test but Geometry allocates much memory than BufferGeometry. Especially for complex geometries, this is a real problem. Besides, entities that rely on Geometry are automatically converted to BufferGeometry by three.js. To avoid this overhead and all related problems, Geometry will sooner or later be deprecated.

Check out this video for more information: https://www.youtube.com/watch?v=jLNtjujPhzY&feature=youtu.be&t=1712


#4

Because I was not sure if my extensive code (THREEf.js) has influence on the test, I have made a very simple example. Except for the differences between Geometry and BufferGeometry, the code is the same.

Try out
http://sandbox.threejs.hofk.de/Geometry_BufferGeometry/speedtestSimple.html

But again, Geometry is faster.

Memory consumption and speed are usually in the reverse ratio.

So if Geometry is omitted, then you have to use even more complicated things than BufferGeometry instead of simpler Geometry (to get speed)?

If Geometry is eliminated, it will be much harder for beginners to enter three.js. Many simple examples currently use Geometry. I understand, however, that the focus is on the professional programmers.

I can not fully understand the internals of three.js, maybe there is a way to solve the speed problem?

The code:

segments 
<input type="text" size="5" id="hs" value="100" >  x  
<input type="text" size="5" id="rs" value="100" > :  
	
<input type="radio" name="geom" id="Geometry" checked="checked" > Geometry
<input type="radio" name="geom" id="BufferGeometry" > indexed BufferGeometry 

<input type="radio" name="color" id="multicolor" checked="checked" > multicolor	
<input type="radio" name="color" id="monochrome" > monochrome

<button type="button" id="show">  -> show new mesh </button> 

script src="three.min.86.js"
script src="OrbitControls.js"
script src=“THREEx.WindowResize.js”

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200000 );
camera.position.set( 400, 600, 1000 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xeeeeee, 1 );
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement ); 
THREEx.WindowResize( renderer, camera );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
var clock  = new THREE.Clock( true );
var t; // time
var	g; // Geometry or BufferGeometry
var mesh;
var hs; // height segment count ( y direction)
var rs; // radial segment count (here in test  x direction)
var hss; // hs + 1
var rss; // rs + 1
var vertexCount;
var vIdx;	// vertex Index
var faceCount;
var fIdx;	// face index
var j0, j1; // j index 
var a, b1, c1, c2; // vertex indices,  b2 equals c1
var ni, nj; // relative counter variable
var posIdx; // position Index
var x, y, z; // position coordinates
var materialSegment;
var multicolor = null;;

var showGeo = false;

// materials
var uvTex			= new THREE.TextureLoader().load( "uvgrid01.png" );
var waterlilyTex	= new THREE.TextureLoader().load( "waterlily.png" );
// var earthTex		= new THREE.TextureLoader().load( "earth_nasa_map_900.png" );
var side = THREE.DoubleSide;

var materials = [
																						// material index:
    new THREE.MeshBasicMaterial( { color: 0x000000,transparent: true, opacity: 0.6,	side: side } ),	//  0 transparent
	new THREE.MeshBasicMaterial( { map: uvTex, 							side: side } ),	//  1 uv grid
	new THREE.MeshPhongMaterial( { color: 0xff0000, emissive: 0xff0000, side: side } ),	//  2 red
	new THREE.MeshPhongMaterial( { color: 0x00ff00, emissive: 0x00ff00, side: side } ),	//  3 green
	new THREE.MeshPhongMaterial( { color: 0x0000ff, emissive: 0x0000ff, side: side } ),	//  4 blue
	new THREE.MeshPhongMaterial( { color: 0xffff00, emissive: 0xffff00, side: side } ),	//  5 yellow
	new THREE.MeshPhongMaterial( { color: 0xff00ff, emissive: 0xff00ff, side: side } ),	//  6 mgenta
	new THREE.MeshPhongMaterial( { color: 0x00ffff, emissive: 0x00ffff, side: side } ),	//  7 cyan	
	new THREE.MeshBasicMaterial( { map: waterlilyTex,					side: side } ),	//  8 photo waterlily (free)
	new THREE.MeshPhongMaterial( { color: 0x7755ff, emissive: 0x4433dd, side: side } ),	//  9 color
	new THREE.MeshPhongMaterial( { color: 0x444444, emissive: 0x333333, side: side } )		// 10 grey
	
];

// var material = new THREE.MeshBasicMaterial( { color: 0x880088, side: side, wireframe: true } );
// var material = new THREE.MeshBasicMaterial( { map: earthTex,	side: side } );

document.getElementById( "show" ).onclick = showNewMesh;

animate();

function showNewMesh() {

	if ( mesh ) {
	
		scene.remove( mesh );
		g.dispose();
		showGeo = false;
		
		multicolor = null;
				
	}
	
	hs = Math.floor( document.getElementById( "hs" ).value );
	rs = Math.floor( document.getElementById( "rs" ).value );
	
	hss = hs + 1;
	rss = rs + 1;
	
	vertexCount = hss * rss;
	faceCount =  hs * rs * 2;
	
	multicolor = document.getElementById( "multicolor" ).checked;
	
	// Geometry or BufferGeometry
	
	if ( document.getElementById( "Geometry" ).checked ) g = new THREE.Geometry();
	if ( document.getElementById( "BufferGeometry" ).checked ) g = new THREE.BufferGeometry();
		
	// mesh
	
	mesh = new THREE.Mesh( g, materials );
	scene.add( mesh );
	
	//configure
	
	if ( g.isGeometry ) {
		
		for ( var i = 0; i < vertexCount; i ++ ) { 
			
			g.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); 
		
		}
		
	}	
		
	if ( g.isBufferGeometry ) {
	
		var idxCount = 0;
		
		g.faceIndices = new Uint32Array( faceCount * 3 );
		g.vertices = new Float32Array( vertexCount * 3 );  
				
		g.setIndex( new THREE.BufferAttribute( g.faceIndices, 1 ) );	
		g.addAttribute( 'position', new THREE.BufferAttribute( g.vertices, 3 ).setDynamic( true ) );
		
		for ( var f = 0, p = 0; f < faceCount; f ++, p += 3 ) {
			
			g.addGroup( p, 3, 0 );  // write group for multi material
			
		}
		
	}
	
	for ( var j = 0; j < rs; j ++ ) {
	
		j0 = hss * j; 
		j1 = hss * ( j + 1 );
		
		for ( var i = 0; i < hs; i ++ ) {
			
			// 2 faces / segment,  3 vertex indices
			a =  j0 + i;
			b1 = j1 + i;			// right-bottom
			c1 = j1 + 1 + i;
			// b2 = j1 + 1 + i; =c1	// left-top 
			c2 = j0 + 1 + i;
			
			if ( g.isGeometry ) {
				
				g.faces.push( new THREE.Face3( a, b1, c1 ) ); // right-bottom
				g.faces.push( new THREE.Face3( a, c1, c2 ) ); // left-top
				
			}
			
			if ( g.isBufferGeometry ) {
			
				g.faceIndices[ idxCount     ] = a; // right-bottom
				g.faceIndices[ idxCount + 1 ] = b1;
				g.faceIndices[ idxCount + 2 ] = c1; 
				
				g.faceIndices[ idxCount + 3 ] = a; // left-top
				g.faceIndices[ idxCount + 4 ] = c1,
				g.faceIndices[ idxCount + 5 ] = c2; 
				
				idxCount += 6;
				
			}
						
		}
		
	}
	
	showGeo = true;	 // start animation
	
}

function move( t ) {
	
	if ( g.isGeometry ) g.verticesNeedUpdate  = true;
	if ( g.isBufferGeometry ) g.attributes.position.needsUpdate = true;
	
	for ( var j = 0; j < rss; j ++ ) {
		
		nj = j / rs;
		
		y = 400 * nj;							// calculate y
		
		for ( var i = 0; i < hss; i ++ ) {
			
			ni   = i / hs;
			
			x = 400 * ni;						// calculate x
			
			z =  400 * Math.sin( t + ni + nj );	// calculate z
			
			vIdx = hss * j + i; 
			
			if ( g.isGeometry ) g.vertices[ vIdx ].set( x, y, z );
			
			if ( g.isBufferGeometry ) {
			
				posIdx = vIdx * 3;
				
				g.vertices[ posIdx ]  = x;		
				g.vertices[ posIdx + 1 ]  = y;
				g.vertices[ posIdx + 2 ]  = z;
				
			}	
			
		}
		
	}
	
	if ( multicolor ) {
	
		g.groupsNeedUpdate = true;	// to change materialIndex for multi material
	
		for ( var j = 0; j < rs ; j ++ ) {
			
			for ( var i = 0; i < hs; i ++ ) {
				
				materialSegment = Math.floor( 5 * ( 1 + Math.cos( 0.2 * t + i * i + 2 * j ) ) ); // calculate material
				
				fIdx = 2 * hs * j + 2 * i;
				
				if ( g.isGeometry ) {
					
					g.faces[ fIdx ].materialIndex = materialSegment;
					g.faces[ fIdx + 1 ].materialIndex = materialSegment;
					
				}
				
				if ( g.isBufferGeometry ) {
					
					g.groups[ fIdx ].materialIndex = materialSegment;
					g.groups[ fIdx + 1 ].materialIndex = materialSegment;
					
				}
				
			}
			
		}
		
	}	
	
}

function animate() {
	
	requestAnimationFrame( animate );
	t = clock.getElapsedTime();
	
	if ( showGeo ) move( t );
	 
	renderer.render( scene, camera );
	controls.update();
	
}

#5

Geometry is converted to BufferGeometry internally anyway, basically Geometry just allows to construct geometries with easy-to-use arrays and Face3, Vector3 etc objects, what is btw. an awfull waste of memory and slow, then converted to BufferGeometry.


#6

I must admit, i find that the level of forum/stack overflow questions, is mostly appalling :frowning:

The surprise: Geometry is the fastest!

“Fastest” is absolute, what is the context here, faster in what, faster compared to what?

But again, Geometry is faster.

HOW is it faster? What is the benchmark, what parameters were used? I couldnt run the demo on my phone and now i’m annoyed that i had to open my laptop to find no frame rate being recorded, no initialization times being recorded, no memory usage / garbage collection etc. etc .

This example is pretty gnarly and there is a ton going on. Three converts Geometry to BufferGeometry, you just are adding the overhead.

To avoid this overhead and all related problems, Geometry will sooner or later be deprecated.

This though, would surprise me. Geometry is a structure that helps you do operations that would otherwise be very hard on the raw buffers BufferGeometry is made out of. For example, you can do a cross product or a dot product on a “normal”, with buffers, you just have three numbers.