diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c1459d4f39280..14508daec7f469 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - name: === E2E testing === run: npm run test-e2e - name: Upload output screenshots - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 if: always() with: name: Output screenshots-${{ matrix.os }}-${{ matrix.CI }} diff --git a/.github/workflows/read-size.yml b/.github/workflows/read-size.yml index 04e22904ec28c0..e039d1c3ce3e5e 100644 --- a/.github/workflows/read-size.yml +++ b/.github/workflows/read-size.yml @@ -61,7 +61,7 @@ jobs: # write the output in a json file to upload it as artifact node -pe "JSON.stringify({ filesize: $WEBGL_FILESIZE, gzip: $WEBGL_FILESIZE_GZIP, treeshaken: $WEBGL_TREESHAKEN, treeshakenGzip: $WEBGL_TREESHAKEN_GZIP, filesize2: $WEBGPU_FILESIZE, gzip2: $WEBGPU_FILESIZE_GZIP, treeshaken2: $WEBGPU_TREESHAKEN, treeshakenGzip2: $WEBGPU_TREESHAKEN_GZIP, filesize3: $WEBGPU_NODES_FILESIZE, gzip3: $WEBGPU_NODES_FILESIZE_GZIP, treeshaken3: $WEBGPU_NODES_TREESHAKEN, treeshakenGzip3: $WEBGPU_NODES_TREESHAKEN_GZIP, pr: $PR })" > sizes.json - name: Upload artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: name: sizes path: sizes.json diff --git a/examples/jsm/inspector/Inspector.js b/examples/jsm/inspector/Inspector.js index 65d4c771820e6d..482a7c137d328e 100644 --- a/examples/jsm/inspector/Inspector.js +++ b/examples/jsm/inspector/Inspector.js @@ -2,6 +2,7 @@ import { RendererInspector } from './RendererInspector.js'; import { Profiler } from './ui/Profiler.js'; import { Performance } from './tabs/Performance.js'; +import { Memory } from './tabs/Memory.js'; import { Console } from './tabs/Console.js'; import { Parameters } from './tabs/Parameters.js'; import { Settings } from './tabs/Settings.js'; @@ -58,6 +59,9 @@ class Inspector extends RendererInspector { const performance = new Performance(); profiler.addTab( performance ); + const memory = new Memory(); + profiler.addTab( memory ); + const timeline = new Timeline(); profiler.addTab( timeline ); @@ -79,6 +83,7 @@ class Inspector extends RendererInspector { this.canvasNodes = new Map(); this.profiler = profiler; this.performance = performance; + this.memory = memory; this.console = consoleTab; this.parameters = parameters; this.viewer = viewer; @@ -460,12 +465,14 @@ class Inspector extends RendererInspector { setText( 'fps-counter', this.fps.toFixed() ); this.performance.updateText( this, frame ); + this.memory.updateText( this ); } if ( this.displayCycle.graph.needsUpdate ) { this.performance.updateGraph( this, frame ); + this.memory.updateGraph( this ); } diff --git a/examples/jsm/inspector/tabs/Memory.js b/examples/jsm/inspector/tabs/Memory.js new file mode 100644 index 00000000000000..333c3751b7d56a --- /dev/null +++ b/examples/jsm/inspector/tabs/Memory.js @@ -0,0 +1,121 @@ +import { Tab } from '../ui/Tab.js'; +import { List } from '../ui/List.js'; +import { Graph } from '../ui/Graph.js'; +import { Item } from '../ui/Item.js'; +import { createValueSpan, setText, formatBytes } from '../ui/utils.js'; + +class Memory extends Tab { + + constructor( options = {} ) { + + super( 'Memory', options ); + + const memoryList = new List( 'Name', 'Count', 'Size' ); + memoryList.setGridStyle( 'minmax(200px, 2fr) 60px 100px' ); + memoryList.domElement.style.minWidth = '300px'; + + const scrollWrapper = document.createElement( 'div' ); + scrollWrapper.className = 'list-scroll-wrapper'; + scrollWrapper.appendChild( memoryList.domElement ); + this.content.appendChild( scrollWrapper ); + + // graph + + const graphContainer = document.createElement( 'div' ); + graphContainer.className = 'graph-container'; + + const graph = new Graph(); + graph.addLine( 'total', 'var( --color-yellow )' ); + graphContainer.append( graph.domElement ); + + // stats + + const graphStats = new Item( 'Graph Stats', '', '' ); + memoryList.add( graphStats ); + + const graphItem = new Item( graphContainer ); + graphItem.itemRow.childNodes[ 0 ].style.gridColumn = '1 / -1'; + graphStats.add( graphItem ); + + // info + + this.memoryStats = new Item( 'Renderer Info', '', createValueSpan() ); + this.memoryStats.domElement.firstChild.classList.add( 'no-hover' ); + memoryList.add( this.memoryStats ); + + this.attributes = new Item( 'Attributes', createValueSpan(), createValueSpan() ); + this.memoryStats.add( this.attributes ); + + this.geometries = new Item( 'Geometries', createValueSpan(), 'N/A' ); + this.memoryStats.add( this.geometries ); + + this.indexAttributes = new Item( 'Index Attributes', createValueSpan(), createValueSpan() ); + this.memoryStats.add( this.indexAttributes ); + + this.indirectStorageAttributes = new Item( 'Indirect Storage Attributes', createValueSpan(), createValueSpan() ); + this.memoryStats.add( this.indirectStorageAttributes ); + + this.programs = new Item( 'Programs', createValueSpan(), 'N/A' ); + this.memoryStats.add( this.programs ); + + this.renderTargets = new Item( 'Render Targets', createValueSpan(), 'N/A' ); + this.memoryStats.add( this.renderTargets ); + + this.storageAttributes = new Item( 'Storage Attributes', createValueSpan(), createValueSpan() ); + this.memoryStats.add( this.storageAttributes ); + + this.textures = new Item( 'Textures', createValueSpan(), createValueSpan() ); + this.memoryStats.add( this.textures ); + + this.graph = graph; + + } + + updateGraph( inspector ) { + + const renderer = inspector.getRenderer(); + if ( ! renderer ) return; + + const memory = renderer.info.memory; + + this.graph.addPoint( 'total', memory.total ); + + if ( this.graph.limit === 0 ) this.graph.limit = 1; + + this.graph.update(); + + } + + updateText( inspector ) { + + const renderer = inspector.getRenderer(); + if ( ! renderer ) return; + + const memory = renderer.info.memory; + + setText( this.memoryStats.data[ 2 ], formatBytes( memory.total ) ); + + setText( this.attributes.data[ 1 ], memory.attributes.toString() ); + setText( this.attributes.data[ 2 ], formatBytes( memory.attributesSize ) ); + setText( this.geometries.data[ 1 ], memory.geometries.toString() ); + + setText( this.indexAttributes.data[ 1 ], memory.indexAttributes.toString() ); + setText( this.indexAttributes.data[ 2 ], formatBytes( memory.indexAttributesSize ) ); + + setText( this.indirectStorageAttributes.data[ 1 ], memory.indirectStorageAttributes.toString() ); + setText( this.indirectStorageAttributes.data[ 2 ], formatBytes( memory.indirectStorageAttributesSize ) ); + + setText( this.programs.data[ 1 ], memory.programs.toString() ); + + setText( this.renderTargets.data[ 1 ], memory.renderTargets.toString() ); + + setText( this.storageAttributes.data[ 1 ], memory.storageAttributes.toString() ); + setText( this.storageAttributes.data[ 2 ], formatBytes( memory.storageAttributesSize ) ); + setText( this.textures.data[ 1 ], memory.textures.toString() ); + setText( this.textures.data[ 2 ], formatBytes( memory.texturesSize ) ); + + } + +} + +export { Memory }; diff --git a/examples/jsm/inspector/ui/utils.js b/examples/jsm/inspector/ui/utils.js index 4381bc1854c1e7..a33f0e3c77519d 100644 --- a/examples/jsm/inspector/ui/utils.js +++ b/examples/jsm/inspector/ui/utils.js @@ -54,3 +54,16 @@ export function splitCamelCase( str ) { return str.replace( /([a-z0-9])([A-Z])/g, '$1 $2' ).trim(); } + +export function formatBytes( bytes, decimals = 2 ) { + + if ( bytes === 0 ) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; + const i = Math.floor( Math.log( bytes ) / Math.log( k ) ); + + return parseFloat( ( bytes / Math.pow( k, i ) ).toFixed( dm ) ) + ' ' + sizes[ i ]; + +} diff --git a/examples/screenshots/webgpu_compute_particles_rain.jpg b/examples/screenshots/webgpu_compute_particles_rain.jpg index e782c45a6354ab..9e6dc17c7e7336 100644 Binary files a/examples/screenshots/webgpu_compute_particles_rain.jpg and b/examples/screenshots/webgpu_compute_particles_rain.jpg differ diff --git a/examples/screenshots/webgpu_compute_particles_snow.jpg b/examples/screenshots/webgpu_compute_particles_snow.jpg index c59959d8efa174..d7531389569d2b 100644 Binary files a/examples/screenshots/webgpu_compute_particles_snow.jpg and b/examples/screenshots/webgpu_compute_particles_snow.jpg differ diff --git a/examples/screenshots/webgpu_instancing_morph.jpg b/examples/screenshots/webgpu_instancing_morph.jpg index ceb1f40f0793c2..800ff9b5f6a4fd 100644 Binary files a/examples/screenshots/webgpu_instancing_morph.jpg and b/examples/screenshots/webgpu_instancing_morph.jpg differ diff --git a/examples/screenshots/webgpu_mesh_batch.jpg b/examples/screenshots/webgpu_mesh_batch.jpg index b60a8b40de2462..56498a574451ae 100644 Binary files a/examples/screenshots/webgpu_mesh_batch.jpg and b/examples/screenshots/webgpu_mesh_batch.jpg differ diff --git a/examples/screenshots/webgpu_performance_renderbundle.jpg b/examples/screenshots/webgpu_performance_renderbundle.jpg index 6f9a93c0813dd7..2cc95c71faf73a 100644 Binary files a/examples/screenshots/webgpu_performance_renderbundle.jpg and b/examples/screenshots/webgpu_performance_renderbundle.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing.jpg b/examples/screenshots/webgpu_postprocessing.jpg index a491a94f50fd6a..f76244f15a5335 100644 Binary files a/examples/screenshots/webgpu_postprocessing.jpg and b/examples/screenshots/webgpu_postprocessing.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_afterimage.jpg b/examples/screenshots/webgpu_postprocessing_afterimage.jpg index 03b706cae9fdcd..372caa887e2515 100644 Binary files a/examples/screenshots/webgpu_postprocessing_afterimage.jpg and b/examples/screenshots/webgpu_postprocessing_afterimage.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_outline.jpg b/examples/screenshots/webgpu_postprocessing_outline.jpg index 6506a811df873f..6db47a10a3b052 100644 Binary files a/examples/screenshots/webgpu_postprocessing_outline.jpg and b/examples/screenshots/webgpu_postprocessing_outline.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_ssaa.jpg b/examples/screenshots/webgpu_postprocessing_ssaa.jpg index beb4d0fbb4f0a7..8b11bde1605d5a 100644 Binary files a/examples/screenshots/webgpu_postprocessing_ssaa.jpg and b/examples/screenshots/webgpu_postprocessing_ssaa.jpg differ diff --git a/examples/screenshots/webgpu_sandbox.jpg b/examples/screenshots/webgpu_sandbox.jpg index 511cf317b30e75..cf7d879fd68daa 100644 Binary files a/examples/screenshots/webgpu_sandbox.jpg and b/examples/screenshots/webgpu_sandbox.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_array.jpg b/examples/screenshots/webgpu_shadowmap_array.jpg index f49862b92eac6c..f307f072c983d6 100644 Binary files a/examples/screenshots/webgpu_shadowmap_array.jpg and b/examples/screenshots/webgpu_shadowmap_array.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_csm.jpg b/examples/screenshots/webgpu_shadowmap_csm.jpg index 46bef94539e968..f2b7baf9794621 100644 Binary files a/examples/screenshots/webgpu_shadowmap_csm.jpg and b/examples/screenshots/webgpu_shadowmap_csm.jpg differ diff --git a/examples/screenshots/webgpu_test_memory.jpg b/examples/screenshots/webgpu_test_memory.jpg index 1f870fe923df49..bd0b04db7df307 100644 Binary files a/examples/screenshots/webgpu_test_memory.jpg and b/examples/screenshots/webgpu_test_memory.jpg differ diff --git a/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg b/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg index 6cf8505862485e..45f3893c5e836c 100644 Binary files a/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg and b/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg differ diff --git a/examples/screenshots/webgpu_tsl_galaxy.jpg b/examples/screenshots/webgpu_tsl_galaxy.jpg index 7bf9089f8df0b0..f92277d7b5764f 100644 Binary files a/examples/screenshots/webgpu_tsl_galaxy.jpg and b/examples/screenshots/webgpu_tsl_galaxy.jpg differ diff --git a/examples/webgpu_loader_gltf.html b/examples/webgpu_loader_gltf.html index ebfe7ce0685de9..1962f578f92a76 100644 --- a/examples/webgpu_loader_gltf.html +++ b/examples/webgpu_loader_gltf.html @@ -132,6 +132,27 @@ if ( currentModel ) { + currentModel.traverse( ( model ) => { + + if ( model.isMesh ) { + + model.geometry.dispose(); + model.material.dispose(); + + for ( const value of Object.values( model.material ) ) { + + if ( value && value.isTexture ) { + + value.dispose(); + + } + + } + + } + + } ); + scene.remove( currentModel ); currentModel = null; diff --git a/package-lock.json b/package-lock.json index 03e831f5ef7487..0e9acce63d3878 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@rollup/plugin-terser": "^0.4.0", "eslint": "^9.0.0", "eslint-config-mdcs": "^5.0.0", - "eslint-plugin-compat": "^6.0.0", + "eslint-plugin-compat": "^7.0.0", "eslint-plugin-html": "^8.1.3", "eslint-plugin-jsdoc": "^62.0.0", "globals": "^17.0.0", @@ -1763,16 +1763,15 @@ "license": "MIT" }, "node_modules/eslint-plugin-compat": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.2.0.tgz", - "integrity": "sha512-Ihz4zAeHKzyksDDUTObrYQxaqnV/pFlAiZoWkMuWM9XGf4O191ReQFYv516zcs9QVJ2vX+MMpqr1yEfTkXVETQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-7.0.0.tgz", + "integrity": "sha512-P5AKzirW/Z6T5XzN+4N7O2HKVyFwfDD9NaUdBNrG9c4652Ef2W77whXDtzJXxPuRXppljJavzBvTLcYpcY0UXw==", "dev": true, "license": "MIT", "dependencies": { "@mdn/browser-compat-data": "^6.1.1", "ast-metadata-inferer": "^0.8.1", "browserslist": "^4.25.2", - "caniuse-lite": "^1.0.30001687", "find-up": "^5.0.0", "globals": "^15.7.0", "lodash.memoize": "^4.1.2", @@ -1782,7 +1781,7 @@ "node": ">=18.x" }, "peerDependencies": { - "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + "eslint": "^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-compat/node_modules/@mdn/browser-compat-data": { @@ -1819,9 +1818,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "62.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.7.0.tgz", - "integrity": "sha512-jootujJOIGMkCLN+/WgDFKtaclCt2MEEy9cZ1RyK19Az1JvVI3awbeMXNlJ6y4h8RWIJpcXqmxsu4t9NThYbNw==", + "version": "62.7.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.7.1.tgz", + "integrity": "sha512-4Zvx99Q7d1uggYBUX/AIjvoyqXhluGbbKrRmG8SQTLprPFg6fa293tVJH1o1GQwNe3lUydd8ZHzn37OaSncgSQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2193,9 +2192,9 @@ } }, "node_modules/globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index e6aac6c4429477..4fa6bd4d905da1 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@rollup/plugin-terser": "^0.4.0", "eslint": "^9.0.0", "eslint-config-mdcs": "^5.0.0", - "eslint-plugin-compat": "^6.0.0", + "eslint-plugin-compat": "^7.0.0", "eslint-plugin-html": "^8.1.3", "eslint-plugin-jsdoc": "^62.0.0", "globals": "^17.0.0", diff --git a/src/renderers/common/Attributes.js b/src/renderers/common/Attributes.js index 7f356cf855b2b6..5be5dc85b66f68 100644 --- a/src/renderers/common/Attributes.js +++ b/src/renderers/common/Attributes.js @@ -15,8 +15,9 @@ class Attributes extends DataMap { * Constructs a new attribute management component. * * @param {Backend} backend - The renderer's backend. + * @param {Info} info - Renderer component for managing metrics and monitoring data. */ - constructor( backend ) { + constructor( backend, info ) { super(); @@ -27,6 +28,13 @@ class Attributes extends DataMap { */ this.backend = backend; + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + } /** @@ -43,6 +51,8 @@ class Attributes extends DataMap { this.backend.destroyAttribute( attribute ); + this.info.destroyAttribute( attribute ); + } return attributeData; @@ -65,18 +75,22 @@ class Attributes extends DataMap { if ( type === AttributeType.VERTEX ) { this.backend.createAttribute( attribute ); + this.info.createAttribute( attribute ); } else if ( type === AttributeType.INDEX ) { this.backend.createIndexAttribute( attribute ); + this.info.createIndexAttribute( attribute ); } else if ( type === AttributeType.STORAGE ) { this.backend.createStorageAttribute( attribute ); + this.info.createStorageAttribute( attribute ); } else if ( type === AttributeType.INDIRECT ) { this.backend.createIndirectStorageAttribute( attribute ); + this.info.createIndirectStorageAttribute( attribute ); } diff --git a/src/renderers/common/Info.js b/src/renderers/common/Info.js index 520debd9f6267d..c4453ee40b03b4 100644 --- a/src/renderers/common/Info.js +++ b/src/renderers/common/Info.js @@ -1,4 +1,12 @@ import { error } from '../../utils.js'; +import { + ByteType, UnsignedByteType, ShortType, UnsignedShortType, HalfFloatType, + IntType, UnsignedIntType, FloatType, + AlphaFormat, RedFormat, RedIntegerFormat, DepthFormat, DepthStencilFormat, + RGBFormat, + UnsignedShort4444Type, UnsignedShort5551Type, + UnsignedInt248Type, UnsignedInt5999Type, UnsignedInt101111Type +} from '../../constants.js'; /** * This renderer module provides a series of statistical information @@ -87,13 +95,45 @@ class Info { * @type {Object} * @readonly * @property {number} geometries - The number of active geometries. - * @property {number} frameCalls - The number of active textures. + * @property {number} textures - The number of active textures. + * @property {number} attributes - The number of active attributes. + * @property {number} indexAttributes - The number of active index attributes. + * @property {number} storageAttributes - The number of active storage attributes. + * @property {number} indirectStorageAttributes - The number of active indirect storage attributes. + * @property {number} programs - The number of active programs. + * @property {number} renderTargets - The number of active renderTargets. + * @property {number} total - The total memory size in bytes. + * @property {number} texturesSize - The memory size of active textures in bytes. + * @property {number} attributesSize - The memory size of active attributes in bytes. + * @property {number} indexAttributesSize - The memory size of active index attributes in bytes. + * @property {number} storageAttributesSize - The memory size of active storage attributes in bytes. + * @property {number} indirectStorageAttributesSize - The memory size of active indirect storage attributes in bytes. */ this.memory = { geometries: 0, - textures: 0 + textures: 0, + attributes: 0, + indexAttributes: 0, + storageAttributes: 0, + indirectStorageAttributes: 0, + programs: 0, + renderTargets: 0, + total: 0, + texturesSize: 0, + attributesSize: 0, + indexAttributesSize: 0, + storageAttributesSize: 0, + indirectStorageAttributesSize: 0 }; + /** + * Map for storing calculated byte sizes of tracked objects. + * + * @type {Map} + * @private + */ + this.memoryMap = new Map(); + } /** @@ -161,8 +201,225 @@ class Info { this.render.timestamp = 0; this.compute.timestamp = 0; - this.memory.geometries = 0; - this.memory.textures = 0; + + for ( const prop in this.memory ) { + + this.memory[ prop ] = 0; + + } + + this.memoryMap.clear(); + + } + + /** + * Tracks texture memory explicitly, updating counts and byte tracking. + * + * @param {Texture} texture + */ + createTexture( texture ) { + + const size = this._getTextureMemorySize( texture ); + this.memoryMap.set( texture, size ); + + this.memory.textures ++; + this.memory.total += size; + this.memory.texturesSize += size; + + } + + /** + * Tracks texture memory explicitly, updating counts and byte tracking. + * + * @param {Texture} texture + */ + destroyTexture( texture ) { + + const size = this.memoryMap.get( texture ) || 0; + this.memoryMap.delete( texture ); + + this.memory.textures --; + this.memory.total -= size; + this.memory.texturesSize -= size; + + } + + /** + * Tracks attribute memory explicitly, updating counts and byte tracking. + * + * @param {BufferAttribute} attribute + * @param {string} type - type of attribute + * @private + */ + _createAttribute( attribute, type ) { + + const size = this._getAttributeMemorySize( attribute ); + this.memoryMap.set( attribute, { size, type } ); + + this.memory[ type ] ++; + this.memory.total += size; + this.memory[ type + 'Size' ] += size; + + } + + /** + * Tracks a regular attribute memory explicitly. + * + * @param {BufferAttribute} attribute - The attribute to track. + */ + createAttribute( attribute ) { + + this._createAttribute( attribute, 'attributes' ); + + } + + /** + * Tracks an index attribute memory explicitly. + * + * @param {BufferAttribute} attribute - The index attribute to track. + */ + createIndexAttribute( attribute ) { + + this._createAttribute( attribute, 'indexAttributes' ); + + } + + /** + * Tracks a storage attribute memory explicitly. + * + * @param {BufferAttribute} attribute - The storage attribute to track. + */ + createStorageAttribute( attribute ) { + + this._createAttribute( attribute, 'storageAttributes' ); + + } + + /** + * Tracks an indirect storage attribute memory explicitly. + * + * @param {BufferAttribute} attribute - The indirect storage attribute to track. + */ + createIndirectStorageAttribute( attribute ) { + + this._createAttribute( attribute, 'indirectStorageAttributes' ); + + } + + /** + * Tracks attribute memory explicitly, updating counts and byte tracking. + * + * @param {BufferAttribute} attribute + */ + destroyAttribute( attribute ) { + + const data = this.memoryMap.get( attribute ); + + if ( data ) { + + this.memoryMap.delete( attribute ); + + this.memory[ data.type ] --; + this.memory.total -= data.size; + this.memory[ data.type + 'Size' ] -= data.size; + + } + + } + + /** + * Calculates the memory size of a texture in bytes. + * + * @param {Texture} texture - The texture to calculate the size for. + * @return {number} The calculated size in bytes. + * @private + */ + _getTextureMemorySize( texture ) { + + if ( texture.isCompressedTexture ) { + + return 1; // Fallback estimate since exact format decompressed isn't readily available without format maps + + } + + let bytesPerChannel = 1; + + if ( texture.type === ByteType || texture.type === UnsignedByteType ) bytesPerChannel = 1; + else if ( texture.type === ShortType || texture.type === UnsignedShortType || texture.type === HalfFloatType ) bytesPerChannel = 2; + else if ( texture.type === IntType || texture.type === UnsignedIntType || texture.type === FloatType ) bytesPerChannel = 4; + + let channels = 4; // RGBA default + + if ( texture.format === AlphaFormat || texture.format === RedFormat || texture.format === RedIntegerFormat || texture.format === DepthFormat || texture.format === DepthStencilFormat ) channels = 1; + else if ( texture.format === RGBFormat ) channels = 3; + + let bytesPerPixel = bytesPerChannel * channels; + + // Packed overrides + if ( texture.type === UnsignedShort4444Type || texture.type === UnsignedShort5551Type ) bytesPerPixel = 2; + else if ( texture.type === UnsignedInt248Type || texture.type === UnsignedInt5999Type || texture.type === UnsignedInt101111Type ) bytesPerPixel = 4; + + const width = texture.width || 1; + const height = texture.height || 1; + const depth = texture.isCubeTexture ? 6 : ( texture.depth || 1 ); + + let size = width * height * depth * bytesPerPixel; + const mipmaps = texture.mipmaps; + + if ( mipmaps && mipmaps.length > 0 ) { + + let mipmapSize = 0; + for ( let i = 0; i < mipmaps.length; i ++ ) { + + const mipmap = mipmaps[ i ]; + if ( mipmap.data ) { + + mipmapSize += mipmap.data.byteLength; + + } else { + + const mipWidth = mipmap.width || Math.max( 1, width >> i ); + const mipHeight = mipmap.height || Math.max( 1, height >> i ); + mipmapSize += mipWidth * mipHeight * depth * bytesPerPixel; + + } + + } + + size += mipmapSize; + + } else if ( texture.generateMipmaps ) { + + size = size * 1.333; // MiP chain approximation + + } + + return Math.round( size ); + + } + + /** + * Calculates the memory size of an attribute in bytes. + * + * @param {BufferAttribute} attribute - The attribute to calculate the size for. + * @return {number} The calculated size in bytes. + * @private + */ + _getAttributeMemorySize( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + if ( attribute.array ) { + + return attribute.array.byteLength; + + } else if ( attribute.count && attribute.itemSize ) { + + return attribute.count * attribute.itemSize * 4; // Assume Float32 + + } + + return 0; } diff --git a/src/renderers/common/Pipelines.js b/src/renderers/common/Pipelines.js index 89cb7db33206b6..35ff1b3f91a64f 100644 --- a/src/renderers/common/Pipelines.js +++ b/src/renderers/common/Pipelines.js @@ -16,8 +16,9 @@ class Pipelines extends DataMap { * * @param {Backend} backend - The renderer's backend. * @param {NodeManager} nodes - Renderer component for managing nodes related logic. + * @param {Info} info - Renderer component for managing metrics and monitoring data. */ - constructor( backend, nodes ) { + constructor( backend, nodes, info ) { super(); @@ -35,6 +36,13 @@ class Pipelines extends DataMap { */ this.nodes = nodes; + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + /** * A references to the bindings management component. * This reference will be set inside the `Bindings` @@ -349,6 +357,8 @@ class Pipelines extends DataMap { this.backend.createComputePipeline( pipeline, bindings ); + this.info.memory.programs ++; + } return pipeline; @@ -388,6 +398,8 @@ class Pipelines extends DataMap { this.backend.createRenderPipeline( renderObject, promises ); + this.info.memory.programs ++; + } return pipeline; @@ -433,6 +445,8 @@ class Pipelines extends DataMap { this.caches.delete( pipeline.cacheKey ); + this.info.memory.programs --; + } /** diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index eb40ac776b6f81..a497da24192255 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -787,11 +787,11 @@ class Renderer { this._nodes = new NodeManager( this, backend ); this._animation = new Animation( this, this._nodes, this.info ); - this._attributes = new Attributes( backend ); + this._attributes = new Attributes( backend, this.info ); this._background = new Background( this, this._nodes ); this._geometries = new Geometries( this._attributes, this.info ); this._textures = new Textures( this, backend, this.info ); - this._pipelines = new Pipelines( backend, this._nodes ); + this._pipelines = new Pipelines( backend, this._nodes, this.info ); this._bindings = new Bindings( backend, this._nodes, this._textures, this._attributes, this._pipelines, this.info ); this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._bindings, this.info ); this._renderLists = new RenderLists( this.lighting ); diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js index 37dd6b2eb60fb8..d4f370ea8db307 100644 --- a/src/renderers/common/Textures.js +++ b/src/renderers/common/Textures.js @@ -162,6 +162,8 @@ class Textures extends DataMap { renderTargetData.initialized = true; + this.info.memory.renderTargets ++; + // dispose renderTargetData.onDispose = () => { @@ -322,7 +324,7 @@ class Textures extends DataMap { // - this.info.memory.textures ++; + this.info.createTexture( texture ); // @@ -504,6 +506,8 @@ class Textures extends DataMap { this.delete( renderTarget ); this.backend.delete( renderTarget ); + this.info.memory.renderTargets --; + } } @@ -549,7 +553,7 @@ class Textures extends DataMap { this.delete( texture ); - this.info.memory.textures --; + this.info.destroyTexture( texture ); }