diff --git a/example/three/googleMapsAerial.js b/example/three/googleMapsAerial.js index 7603abc3a..14a511a65 100644 --- a/example/three/googleMapsAerial.js +++ b/example/three/googleMapsAerial.js @@ -1,5 +1,7 @@ import { GeoUtils, WGS84_ELLIPSOID, TilesRenderer } from '3d-tiles-renderer'; -import { TilesFadePlugin, TileCompressionPlugin, GLTFExtensionsPlugin, CesiumIonAuthPlugin, ReorientationPlugin } from '3d-tiles-renderer/plugins'; +import { CesiumIonAuthPlugin } from '3d-tiles-renderer/core/plugins'; +import { TilesFadePlugin, TileCompressionPlugin, GLTFExtensionsPlugin, ReorientationPlugin } from '3d-tiles-renderer/plugins'; +import { estimateBytesUsed } from '../../src/three/renderer/utils/MemoryUtils.js'; import { Scene, WebGLRenderer, @@ -9,14 +11,27 @@ import { } from 'three'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import { SparkGL } from '@ludicon/spark.js'; +import Stats from 'three/examples/jsm/libs/stats.module.js'; +import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; +import { createSparkPlugins } from '@ludicon/spark.js/three-gltf'; let camera, controls, scene, renderer, tiles; +let spark; +let stats; + +const params = { + enableSpark: true, + preferLowQuality: true, + generateMipmaps: false, + errorTarget: 16.0, + textureVRAM: 0, +}; const raycaster = new Raycaster(); raycaster.firstHitOnly = true; -init(); -animate(); +init().then( () => animate() ); function reinstantiateTiles() { @@ -35,7 +50,8 @@ function reinstantiateTiles() { tiles.registerPlugin( new GLTFExtensionsPlugin( { // Note the DRACO compression files need to be supplied via an explicit source. // We use unpkg here but in practice should be provided by the application. - dracoLoader: new DRACOLoader().setDecoderPath( 'https://unpkg.com/three@0.153.0/examples/jsm/libs/draco/gltf/' ) + dracoLoader: new DRACOLoader().setDecoderPath( 'https://unpkg.com/three@0.153.0/examples/jsm/libs/draco/gltf/' ), + plugins: params.enableSpark ? createSparkPlugins( spark, { preferLowQuality: params.preferLowQuality, generateMipmaps: params.generateMipmaps } ) : [] } ) ); tiles.registerPlugin( new ReorientationPlugin( { lat: 35.6586 * MathUtils.DEG2RAD, lon: 139.7454 * MathUtils.DEG2RAD } ) ); @@ -55,7 +71,7 @@ function reinstantiateTiles() { } -function init() { +async function init() { scene = new Scene(); @@ -79,8 +95,35 @@ function init() { controls.autoRotateSpeed = 0.5; controls.enablePan = false; + // initialize Spark + const gl = renderer.getContext(); + spark = await SparkGL.create( gl, { + preload: [ 'rgb' ], + cacheTempResources: true, + verbose: true, + } ); + reinstantiateTiles(); + // Initialize stats + stats = new Stats(); + stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom + document.body.appendChild( stats.dom ); + + // Create GUI + const gui = new GUI(); + gui.add( params, 'enableSpark' ).name( 'Enable Spark' ).onChange( () => { + reinstantiateTiles(); + } ); + gui.add( params, 'preferLowQuality' ).name( 'BC1 / ETC2' ).onChange( () => { + reinstantiateTiles(); + } ); + gui.add( params, 'generateMipmaps' ).name( 'Generate Mipmaps' ).onChange( () => { + reinstantiateTiles(); + } ); + gui.add( params, 'errorTarget', 4, 16, 0.1 ).name( 'Error Target' ); + gui.add( params, 'textureVRAM' ).name( 'Texture VRAM (MB)' ).listen().disable(); + onWindowResize(); window.addEventListener( 'resize', onWindowResize, false ); window.addEventListener( 'hashchange', initFromHash ); @@ -120,11 +163,14 @@ function animate() { requestAnimationFrame( animate ); + if ( stats ) stats.begin(); + if ( ! tiles ) return; controls.update(); // update options + tiles.errorTarget = params.errorTarget; tiles.setResolutionFromRenderer( camera, renderer ); tiles.setCamera( camera ); @@ -134,6 +180,8 @@ function animate() { render(); + if ( stats ) stats.end(); + } function render() { @@ -154,4 +202,8 @@ function render() { } + // Update GPU memory info + const approximateVRAM = estimateBytesUsed( scene ); + params.textureVRAM = ( approximateVRAM / ( 1024 * 1024 ) ).toFixed( 1 ); + } diff --git a/package-lock.json b/package-lock.json index 436ba0d23..2ce0bac05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@babylonjs/core": "^8.47.2", "@babylonjs/loaders": "^8.47.2", "@eslint/js": "^9.0.0", + "@ludicon/spark.js": "^0.1.0", "@react-three/drei": "^10.0.0", "@react-three/fiber": "^9.0.0", "@types/node": "^24.3.0", @@ -35,7 +36,7 @@ "leva": "^0.10.0", "lil-gui": "^0.21.0", "postprocessing": "^6.36.4", - "three": "^0.170.0", + "three": "^0.182.0", "typescript": "^5.6.0", "typescript-eslint": "^8.48.1", "vite": "^6.2.2", @@ -47,7 +48,7 @@ "@react-three/fiber": "^8.17.9 || ^9.0.0", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0", - "three": ">=0.167.0" + "three": ">=0.182.0" }, "peerDependenciesMeta": { "@babylonjs/core": { @@ -1579,6 +1580,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@ludicon/spark.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ludicon/spark.js/-/spark.js-0.1.0.tgz", + "integrity": "sha512-d/y+VRwWr2rzqFhv4yKYRU13tEymg9agSgI15hbGSIk0ToRZd8CNFhtcxJKZeWqNXfIW0WlCgNwclWIWmODD+w==", + "dev": true, + "license": "See LICENSE", + "peerDependencies": { + "three": ">=0.182.0" + }, + "peerDependenciesMeta": { + "three": { + "optional": true + } + } + }, "node_modules/@jsdoc/salty": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.10.tgz", @@ -7820,6 +7836,13 @@ "three": "*" } }, + "node_modules/stats-gl/node_modules/three": { + "version": "0.170.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz", + "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "dev": true, + "license": "MIT" + }, "node_modules/stats.js": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", @@ -8027,9 +8050,9 @@ } }, "node_modules/three": { - "version": "0.170.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz", - "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", + "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 7049a4207..9613c346b 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@babylonjs/core": "^8.47.2", "@babylonjs/loaders": "^8.47.2", "@eslint/js": "^9.0.0", + "@ludicon/spark.js": "^0.1.3", "@react-three/drei": "^10.0.0", "@react-three/fiber": "^9.0.0", "@types/node": "^24.3.0", @@ -108,7 +109,7 @@ "leva": "^0.10.0", "lil-gui": "^0.21.0", "postprocessing": "^6.36.4", - "three": "^0.170.0", + "three": "^0.182.0", "typescript": "^5.6.0", "typescript-eslint": "^8.48.1", "vite": "^6.2.2", @@ -120,7 +121,7 @@ "@react-three/fiber": "^8.17.9 || ^9.0.0", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0", - "three": ">=0.167.0" + "three": ">=0.182.0" }, "peerDependenciesMeta": { "@react-three/fiber": { diff --git a/src/three/renderer/utils/MemoryUtils.js b/src/three/renderer/utils/MemoryUtils.js index dffb42a02..e123e2e10 100644 --- a/src/three/renderer/utils/MemoryUtils.js +++ b/src/three/renderer/utils/MemoryUtils.js @@ -1,5 +1,5 @@ import { estimateBytesUsed as _estimateBytesUsed } from 'three/examples/jsm/utils/BufferGeometryUtils.js'; -import { TextureUtils } from 'three'; +import { TextureUtils, ExternalTexture } from 'three'; export function getTextureByteLength( tex ) { @@ -9,6 +9,13 @@ export function getTextureByteLength( tex ) { } + // IC: The code below assumes tex was created from an ImageBitmap + if ( tex instanceof ExternalTexture && tex.userData?.byteLength ) { + + return tex.userData.byteLength; + + } + const { format, type, image } = tex; const { width, height } = image;