Skip to content

Commit cb24815

Browse files
authored
Batch toggle (#815)
* Add a batched mesh toggle * Add draw call count * Google tiles improvement * Update * use instanceCount * add commented field * tex fixes * Use the new members * Update * Remove unused field * Use copyTextureToTexture3D * Handle already-deleted instances * Init render target * remove empty line * fix merge issue * Rename function * Fix disposal * Adjust expanding batched mesh * Update example * Update three.js package.json * Remove commented lint
1 parent 01c677f commit cb24815

File tree

9 files changed

+242
-246
lines changed

9 files changed

+242
-246
lines changed

example/googleMapsExample.js

Lines changed: 57 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import {
1616
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
1717
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
1818
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
19-
import { estimateBytesUsed } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
2019
import Stats from 'three/examples/jsm/libs/stats.module.js';
2120
import { CameraTransitionManager } from './src/camera/CameraTransitionManager.js';
2221
import { TileCompressionPlugin } from './src/plugins/TileCompressionPlugin.js';
2322
import { UpdateOnChangePlugin } from './src/plugins/UpdateOnChangePlugin.js';
2423
import { TilesFadePlugin } from './src/plugins/fade/TilesFadePlugin.js';
24+
import { BatchedTilesPlugin } from './src/plugins/batched/BatchedTilesPlugin.js';
2525

2626
let controls, scene, renderer, tiles, transition;
2727
let statsContainer, stats;
@@ -35,8 +35,10 @@ const params = {
3535
enableCacheDisplay: false,
3636
enableRendererStats: false,
3737
apiKey: apiKey,
38+
useBatchedMesh: Boolean( new URLSearchParams( window.location.hash.replace( /^#/, '' ) ).get( 'batched' ) ),
39+
errorTarget: 40,
3840

39-
'reload': reinstantiateTiles,
41+
reload: reinstantiateTiles,
4042

4143
};
4244

@@ -59,7 +61,17 @@ function reinstantiateTiles() {
5961
tiles.registerPlugin( new GoogleCloudAuthPlugin( { apiToken: params.apiKey, autoRefreshToken: true } ) );
6062
tiles.registerPlugin( new TileCompressionPlugin() );
6163
tiles.registerPlugin( new UpdateOnChangePlugin() );
62-
tiles.registerPlugin( new TilesFadePlugin() );
64+
65+
if ( params.useBatchedMesh ) {
66+
67+
tiles.registerPlugin( new BatchedTilesPlugin( { renderer } ) );
68+
69+
} else {
70+
71+
tiles.registerPlugin( new TilesFadePlugin() );
72+
73+
}
74+
6375
tiles.group.rotation.x = - Math.PI / 2;
6476

6577
// Note the DRACO compression files need to be supplied via an explicit source.
@@ -138,13 +150,19 @@ function init() {
138150

139151
} );
140152

141-
const mapsOptions = gui.addFolder( 'Google Tiles' );
153+
const mapsOptions = gui.addFolder( 'Google Photorealistic Tiles' );
142154
mapsOptions.add( params, 'apiKey' );
155+
mapsOptions.add( params, 'useBatchedMesh' ).listen();
143156
mapsOptions.add( params, 'reload' );
144157

145158
const exampleOptions = gui.addFolder( 'Example Options' );
146159
exampleOptions.add( params, 'enableCacheDisplay' );
147160
exampleOptions.add( params, 'enableRendererStats' );
161+
exampleOptions.add( params, 'errorTarget', 5, 100, 1 ).onChange( () => {
162+
163+
tiles.getPluginByName( 'UPDATE_ON_CHANGE_PLUGIN' ).needsUpdate = true;
164+
165+
} );
148166

149167
statsContainer = document.createElement( 'div' );
150168
document.getElementById( 'info' ).appendChild( statsContainer );
@@ -207,22 +225,34 @@ function updateHash() {
207225
cartographicResult.lon *= MathUtils.RAD2DEG;
208226

209227
// update hash
210-
const params = new URLSearchParams();
211-
params.set( 'lat', cartographicResult.lat.toFixed( 4 ) );
212-
params.set( 'lon', cartographicResult.lon.toFixed( 4 ) );
213-
params.set( 'height', cartographicResult.height.toFixed( 2 ) );
214-
params.set( 'az', orientationResult.azimuth.toFixed( 2 ) );
215-
params.set( 'el', orientationResult.elevation.toFixed( 2 ) );
216-
params.set( 'roll', orientationResult.roll.toFixed( 2 ) );
217-
window.history.replaceState( undefined, undefined, `#${ params }` );
228+
const urlParams = new URLSearchParams();
229+
urlParams.set( 'lat', cartographicResult.lat.toFixed( 4 ) );
230+
urlParams.set( 'lon', cartographicResult.lon.toFixed( 4 ) );
231+
urlParams.set( 'height', cartographicResult.height.toFixed( 2 ) );
232+
urlParams.set( 'az', orientationResult.azimuth.toFixed( 2 ) );
233+
urlParams.set( 'el', orientationResult.elevation.toFixed( 2 ) );
234+
urlParams.set( 'roll', orientationResult.roll.toFixed( 2 ) );
235+
236+
if ( params.useBatchedMesh ) {
237+
238+
urlParams.set( 'batched', 1 );
239+
240+
}
241+
window.history.replaceState( undefined, undefined, `#${ urlParams }` );
218242

219243
}
220244

221245
function initFromHash() {
222246

223247
const hash = window.location.hash.replace( /^#/, '' );
224-
const params = new URLSearchParams( hash );
225-
if ( ! params.has( 'lat' ) && ! params.has( 'lon' ) ) {
248+
const urlParams = new URLSearchParams( hash );
249+
if ( urlParams.has( 'batched' ) ) {
250+
251+
params.useBatchedMesh = Boolean( urlParams.get( 'batched' ) );
252+
253+
}
254+
255+
if ( ! urlParams.has( 'lat' ) && ! urlParams.has( 'lon' ) ) {
226256

227257
return;
228258

@@ -233,16 +263,16 @@ function initFromHash() {
233263

234264
// get the position fields
235265
const camera = transition.camera;
236-
const lat = parseFloat( params.get( 'lat' ) );
237-
const lon = parseFloat( params.get( 'lon' ) );
238-
const height = parseFloat( params.get( 'height' ) ) || 1000;
266+
const lat = parseFloat( urlParams.get( 'lat' ) );
267+
const lon = parseFloat( urlParams.get( 'lon' ) );
268+
const height = parseFloat( urlParams.get( 'height' ) ) || 1000;
239269

240-
if ( params.has( 'az' ) && params.has( 'el' ) ) {
270+
if ( urlParams.has( 'az' ) && urlParams.has( 'el' ) ) {
241271

242272
// get the az el fields for rotation if present
243-
const az = parseFloat( params.get( 'az' ) );
244-
const el = parseFloat( params.get( 'el' ) );
245-
const roll = parseFloat( params.get( 'roll' ) ) || 0;
273+
const az = parseFloat( urlParams.get( 'az' ) );
274+
const el = parseFloat( urlParams.get( 'el' ) );
275+
const roll = parseFloat( urlParams.get( 'roll' ) ) || 0;
246276

247277
// extract the east-north-up frame into matrix world
248278
WGS84_ELLIPSOID.getRotationMatrixFromAzElRoll(
@@ -287,6 +317,7 @@ function animate() {
287317

288318
// update tiles
289319
camera.updateMatrixWorld();
320+
tiles.errorTarget = params.errorTarget;
290321
tiles.update();
291322

292323
renderer.render( scene, camera );
@@ -299,48 +330,23 @@ function animate() {
299330
function updateHtml() {
300331

301332
// render html text updates
302-
const cacheFullness = tiles.lruCache.itemList.length / tiles.lruCache.maxSize;
303333
let str = '';
304334

305335
if ( params.enableCacheDisplay ) {
306336

337+
const lruCache = tiles.lruCache;
338+
const cacheFullness = lruCache.cachedBytes / lruCache.maxBytesSize;
307339
str += `Downloading: ${ tiles.stats.downloading } Parsing: ${ tiles.stats.parsing } Visible: ${ tiles.visibleTiles.size }<br/>`;
308-
309-
const geomSet = new Set();
310-
tiles.traverse( tile => {
311-
312-
const scene = tile.cached.scene;
313-
if ( scene ) {
314-
315-
scene.traverse( c => {
316-
317-
if ( c.geometry ) {
318-
319-
geomSet.add( c.geometry );
320-
321-
}
322-
323-
} );
324-
325-
}
326-
327-
} );
328-
329-
let count = 0;
330-
geomSet.forEach( g => {
331-
332-
count += estimateBytesUsed( g );
333-
334-
} );
335-
str += `Cache: ${ ( 100 * cacheFullness ).toFixed( 2 ) }% ~${ ( count / 1000 / 1000 ).toFixed( 2 ) }mb<br/>`;
340+
str += `Cache: ${ ( 100 * cacheFullness ).toFixed( 2 ) }% ~${ ( lruCache.cachedBytes / 1000 / 1000 ).toFixed( 2 ) }mb<br/>`;
336341

337342
}
338343

339344
if ( params.enableRendererStats ) {
340345

341346
const memory = renderer.info.memory;
347+
const render = renderer.info.render;
342348
const programCount = renderer.info.programs.length;
343-
str += `Geometries: ${ memory.geometries } Textures: ${ memory.textures } Programs: ${ programCount }`;
349+
str += `Geometries: ${ memory.geometries } Textures: ${ memory.textures } Programs: ${ programCount } Draw Calls: ${ render.calls }`;
344350

345351
}
346352

example/src/plugins/UpdateOnChangePlugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export class UpdateOnChangePlugin {
55

66
constructor() {
77

8+
this.name = 'UPDATE_ON_CHANGE_PLUGIN';
89
this.tiles = null;
910
this.needsUpdate = false;
1011
this.cameraMatrices = new Map();

example/src/plugins/batched/ArrayTextureCopyMaterial.js

Lines changed: 0 additions & 51 deletions
This file was deleted.

example/src/plugins/batched/BatchedTilesPlugin.js

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { WebGLArrayRenderTarget, MeshBasicMaterial, Group, DataTexture, REVISION } from 'three';
22
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
33
import { ExpandingBatchedMesh } from './ExpandingBatchedMesh.js';
4-
import { ArrayTextureCopyMaterial } from './ArrayTextureCopyMaterial.js';
54
import { convertMapToArrayTexture, isColorWhite } from './utilities.js';
65

76
const _textureRenderQuad = new FullScreenQuad( new MeshBasicMaterial() );
8-
const _layerCopyQuad = new FullScreenQuad( new ArrayTextureCopyMaterial() );
97
const _whiteTex = new DataTexture( new Uint8Array( [ 255, 255, 255, 255 ] ), 1, 1 );
108
_whiteTex.needsUpdate = true;
119

@@ -133,11 +131,11 @@ export class BatchedTilesPlugin {
133131
const texture = material.map;
134132
if ( texture ) {
135133

136-
this.renderTextureToLayer( texture, instanceId );
134+
this.assignTextureToLayer( texture, instanceId );
137135

138136
} else {
139137

140-
this.renderTextureToLayer( _whiteTex, instanceId );
138+
this.assignTextureToLayer( _whiteTex, instanceId );
141139

142140
}
143141

@@ -205,7 +203,7 @@ export class BatchedTilesPlugin {
205203
}
206204

207205
// init the batched mesh
208-
const { instanceCount, vertexCount, indexCount, tiles } = this;
206+
const { instanceCount, vertexCount, indexCount, tiles, renderer } = this;
209207
const material = this.material ? this.material : new target.material.constructor();
210208
const batchedMesh = new ExpandingBatchedMesh( instanceCount, instanceCount * vertexCount, instanceCount * indexCount, material );
211209
batchedMesh.name = 'BatchTilesPlugin';
@@ -215,19 +213,20 @@ export class BatchedTilesPlugin {
215213

216214
// init the array texture render target
217215
const map = target.material.map;
218-
const textureOptions = {
216+
const textureOptions = {
219217
colorSpace: map.colorSpace,
220218
wrapS: map.wrapS,
221219
wrapT: map.wrapT,
222220
wrapR: map.wrapS,
223221
// TODO: Generating mipmaps for the volume every time a new texture is added is extremely slow
224222
// generateMipmaps: map.generateMipmaps,
225223
// minFilter: map.minFilter,
226-
// magFilter: map.magFilter,
224+
magFilter: map.magFilter,
227225
};
228226

229227
const arrayTarget = new WebGLArrayRenderTarget( map.image.width, map.image.height, instanceCount );
230228
Object.assign( arrayTarget.texture, textureOptions );
229+
renderer.initRenderTarget( arrayTarget );
231230

232231
// init the material
233232
material.map = arrayTarget.texture;
@@ -239,7 +238,7 @@ export class BatchedTilesPlugin {
239238
}
240239

241240
// render the given into the given layer
242-
renderTextureToLayer( texture, layer ) {
241+
assignTextureToLayer( texture, layer ) {
243242

244243
this.expandArrayTargetIfNeeded();
245244

@@ -278,20 +277,9 @@ export class BatchedTilesPlugin {
278277
const newArrayTarget = new WebGLArrayRenderTarget( arrayTarget.width, arrayTarget.height, targetDepth );
279278
Object.assign( newArrayTarget.texture, textureOptions );
280279

281-
// render each old layer into the new texture target
282-
const currentRenderTarget = renderer.getRenderTarget();
283-
for ( let i = 0; i < arrayTarget.depth; i ++ ) {
284-
285-
_layerCopyQuad.material.map = arrayTarget.texture;
286-
_layerCopyQuad.material.layer = i;
287-
renderer.setRenderTarget( newArrayTarget, i );
288-
_layerCopyQuad.render( renderer );
289-
290-
}
291-
292-
// reset the state
293-
renderer.setRenderTarget( currentRenderTarget );
294-
_layerCopyQuad.material.map = null;
280+
// copy the contents
281+
renderer.initRenderTarget( newArrayTarget );
282+
renderer.copyTextureToTexture( arrayTarget.texture, newArrayTarget.texture );
295283

296284
// replace the old array target
297285
arrayTarget.dispose();
@@ -324,23 +312,25 @@ export class BatchedTilesPlugin {
324312

325313
dispose() {
326314

327-
if ( this.arrayTarget ) {
315+
const { arrayTarget, tiles, batchedMesh } = this;
316+
if ( arrayTarget ) {
328317

329-
this.arrayTarget.dispose();
318+
arrayTarget.dispose();
330319

331320
}
332321

333-
if ( this.batchedMesh ) {
322+
if ( batchedMesh ) {
334323

335-
this.batchedMesh.material.dispose();
336-
this.batchedMesh.dispose();
337-
this.batchedMesh.removeFromParent();
324+
batchedMesh.material.dispose();
325+
batchedMesh.geometry.dispose();
326+
batchedMesh.dispose();
327+
batchedMesh.removeFromParent();
338328

339329
}
340330

341-
this.tiles.removeEventListener( 'load-model', this._onLoadModel );
342-
this.tiles.removeEventListener( 'dispose-model', this._onDisposeModel );
343-
this.tiles.removeEventListener( 'tile-visibility-change', this._onVisibilityChange );
331+
tiles.removeEventListener( 'load-model', this._onLoadModel );
332+
tiles.removeEventListener( 'dispose-model', this._onDisposeModel );
333+
tiles.removeEventListener( 'tile-visibility-change', this._onVisibilityChange );
344334

345335
}
346336

0 commit comments

Comments
 (0)