@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
163 lines (136 loc) • 4.54 kB
text/typescript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import type { BufferGeometry, IUniform, Material, Object3D, WebGLRenderer } from 'three';
import {
isBufferGeometry,
isMaterial,
isMeshBasicMaterial,
isShaderMaterial,
isTexture,
} from '../utils/predicates';
import TextureGenerator from '../utils/TextureGenerator';
export interface MemoryUsageReport {
cpuMemory: number;
gpuMemory: number;
}
export interface GetMemoryUsageContext {
renderer: WebGLRenderer;
objects: Map<number | string, MemoryUsageReport>;
}
/**
* Trait of objects that can report their memory usage.
*/
export interface MemoryUsage {
/** Readonly flag to indicate that his object implements {@link MemoryUsage}. */
isMemoryUsage: true;
/**
* Returns an approximation of the memory used by this object, in bytes.
* @param context - The graphics context.
*/
getMemoryUsage(context: GetMemoryUsageContext): void;
}
export function isMemoryUsage(obj: unknown): obj is MemoryUsage {
return (obj as MemoryUsage)?.isMemoryUsage ?? false;
}
export function aggregateMemoryUsage(context: GetMemoryUsageContext): MemoryUsageReport {
let cpuMemory = 0;
let gpuMemory = 0;
context.objects.forEach(v => {
cpuMemory += v.cpuMemory;
gpuMemory += v.gpuMemory;
});
return { gpuMemory, cpuMemory };
}
export const KILOBYTE = 1024;
export const MEGABYTE = 1024 * KILOBYTE;
export const GIGABYTE = 1024 * MEGABYTE;
/**
* Formats the byte count into a readable string.
* @param bytes - The number of bytes.
* @param locale - The locale parameter. Default is the current locale.
* @returns A formatted string using either the specified locale, or the current locale.
*/
export function format(bytes: number, locale: string | undefined = undefined): string {
const numberFormat = new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 1,
});
let unit: string;
let value: number;
if (bytes > GIGABYTE) {
value = bytes / GIGABYTE;
unit = 'GB';
} else if (bytes > MEGABYTE) {
value = bytes / MEGABYTE;
unit = 'MB';
} else if (bytes > KILOBYTE) {
value = bytes / KILOBYTE;
unit = 'KB';
} else {
value = bytes;
unit = 'B';
}
return `${numberFormat.format(value)} ${unit}`;
}
function iterateMaterials(obj: unknown, callback: (material: Material) => void): void {
const withMaterials = obj as { material: Material | Material[] };
if (withMaterials.material == null) {
return;
}
if (isMaterial(withMaterials.material)) {
callback(withMaterials.material);
} else if (Array.isArray(withMaterials.material)) {
for (const m of withMaterials.material) {
if (isMaterial(m)) {
callback(m);
}
}
}
}
export function getObject3DMemoryUsage(context: GetMemoryUsageContext, object3d: Object3D): void {
if ('geometry' in object3d && isBufferGeometry(object3d.geometry)) {
getGeometryMemoryUsage(context, object3d.geometry);
}
iterateMaterials(object3d, material => {
getMaterialMemoryUsage(context, material);
});
}
export function getUniformMemoryUsage(context: GetMemoryUsageContext, uniform: IUniform): void {
const value = uniform.value;
if (isTexture(value)) {
TextureGenerator.getMemoryUsage(context, value);
}
}
export function getMaterialMemoryUsage(context: GetMemoryUsageContext, material: Material): void {
if (isShaderMaterial(material)) {
for (const uniform of Object.values(material.uniforms)) {
getUniformMemoryUsage(context, uniform);
}
} else if (isMeshBasicMaterial(material)) {
if (material.map) {
TextureGenerator.getMemoryUsage(context, material.map);
}
// TODO other textures
}
// TODO other kinds of materials
}
export function getGeometryMemoryUsage(
context: GetMemoryUsageContext,
geometry: BufferGeometry,
): void {
let bytes = 0;
for (const attributeName of Object.keys(geometry.attributes)) {
bytes += geometry.getAttribute(attributeName).array.byteLength;
}
if (geometry.index) {
bytes += geometry.index.array.byteLength;
}
context.objects.set(geometry.id, {
cpuMemory: bytes,
gpuMemory: bytes,
});
}
export default MemoryUsage;