UNPKG

three

Version:

JavaScript 3D library

502 lines (376 loc) 13 kB
import { error } from '../../utils.js'; import { ByteType, UnsignedByteType, ShortType, UnsignedShortType, HalfFloatType, IntType, UnsignedIntType, FloatType, AlphaFormat, RedFormat, RedIntegerFormat, DepthFormat, DepthStencilFormat, RGFormat, RGIntegerFormat, RGBFormat, RGBIntegerFormat, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedInt248Type, UnsignedInt5999Type, UnsignedInt101111Type } from '../../constants.js'; /** * This renderer module provides a series of statistical information * about the GPU memory and the rendering process. Useful for debugging * and monitoring. */ class Info { /** * Constructs a new info component. */ constructor() { /** * Whether frame related metrics should automatically * be resetted or not. This property should be set to `false` * by apps which manage their own animation loop. They must * then call `renderer.info.reset()` once per frame manually. * * @type {boolean} * @default true */ this.autoReset = true; /** * The current frame ID. This ID is managed * by `NodeFrame`. * * @type {number} * @readonly * @default 0 */ this.frame = 0; /** * The number of render calls since the * app has been started. * * @type {number} * @readonly * @default 0 */ this.calls = 0; /** * Render related metrics. * * @type {Object} * @readonly * @property {number} calls - The number of render calls since the app has been started. * @property {number} frameCalls - The number of render calls of the current frame. * @property {number} drawCalls - The number of draw calls of the current frame. * @property {number} triangles - The number of rendered triangle primitives of the current frame. * @property {number} points - The number of rendered point primitives of the current frame. * @property {number} lines - The number of rendered line primitives of the current frame. * @property {number} timestamp - The timestamp of the frame. */ this.render = { calls: 0, frameCalls: 0, drawCalls: 0, triangles: 0, points: 0, lines: 0, timestamp: 0, }; /** * Compute related metrics. * * @type {Object} * @readonly * @property {number} calls - The number of compute calls since the app has been started. * @property {number} frameCalls - The number of compute calls of the current frame. * @property {number} timestamp - The timestamp of the frame when using `renderer.computeAsync()`. */ this.compute = { calls: 0, frameCalls: 0, timestamp: 0 }; /** * Memory related metrics. * * @type {Object} * @readonly * @property {number} geometries - The number of active geometries. * @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} readbackBuffers - The number of active readback buffers. * @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. * @property {number} readbackBuffersSize - The memory size of active readback buffers in bytes. * @property {number} programsSize - The memory size of active programs in bytes. */ this.memory = { geometries: 0, textures: 0, attributes: 0, indexAttributes: 0, storageAttributes: 0, indirectStorageAttributes: 0, readbackBuffers: 0, programs: 0, renderTargets: 0, total: 0, texturesSize: 0, attributesSize: 0, indexAttributesSize: 0, storageAttributesSize: 0, indirectStorageAttributesSize: 0, readbackBuffersSize: 0, programsSize: 0 }; /** * Map for storing calculated byte sizes of tracked objects. * * @type {Map<Object, number>} * @private */ this.memoryMap = new Map(); } /** * This method should be executed per draw call and updates the corresponding metrics. * * @param {Object3D} object - The 3D object that is going to be rendered. * @param {number} count - The vertex or index count. * @param {number} instanceCount - The instance count. */ update( object, count, instanceCount ) { this.render.drawCalls ++; if ( object.isMesh || object.isSprite ) { this.render.triangles += instanceCount * ( count / 3 ); } else if ( object.isPoints ) { this.render.points += instanceCount * count; } else if ( object.isLineSegments ) { this.render.lines += instanceCount * ( count / 2 ); } else if ( object.isLine ) { this.render.lines += instanceCount * ( count - 1 ); } else { error( 'WebGPUInfo: Unknown object type.' ); } } /** * Resets frame related metrics. */ reset() { this.render.drawCalls = 0; this.render.frameCalls = 0; this.compute.frameCalls = 0; this.render.triangles = 0; this.render.points = 0; this.render.lines = 0; } /** * Performs a complete reset of the object. */ dispose() { this.reset(); this.calls = 0; this.render.calls = 0; this.compute.calls = 0; this.render.timestamp = 0; this.compute.timestamp = 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; } } /** * Tracks a readback buffer memory explicitly. * * @param {ReadbackBuffer} readbackBuffer - The readback buffer to track. */ createReadbackBuffer( readbackBuffer ) { const maxByteLength = readbackBuffer.maxByteLength; this.memoryMap.set( readbackBuffer, { size: maxByteLength, type: 'readbackBuffers' } ); this.memory.readbackBuffers ++; this.memory.total += maxByteLength; this.memory.readbackBuffersSize += maxByteLength; } /** * Tracks a readback buffer memory explicitly. * * @param {ReadbackBuffer} readbackBuffer - The readback buffer to track. */ destroyReadbackBuffer( readbackBuffer ) { const { size } = this.memoryMap.get( readbackBuffer ); this.memoryMap.delete( readbackBuffer ); this.memory.readbackBuffers --; this.memory.total -= size; this.memory.readbackBuffersSize -= size; } /** * Tracks program memory explicitly, updating counts and byte tracking. * * @param {ProgrammableStage} program - The program to track. */ createProgram( program ) { const size = program.code.length; // Approx size this.memoryMap.set( program, size ); this.memory.programs ++; this.memory.total += size; this.memory.programsSize += size; } /** * Tracks program memory explicitly, updating counts and byte tracking. * * @param {Object} program - The program to track. */ destroyProgram( program ) { const size = this.memoryMap.get( program ) || 0; this.memoryMap.delete( program ); this.memory.programs --; this.memory.total -= size; this.memory.programsSize -= 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 === RGFormat || texture.format === RGIntegerFormat ) channels = 2; else if ( texture.format === RGBFormat || texture.format === RGBIntegerFormat ) 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; } } export default Info;