@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
121 lines (100 loc) • 3.55 kB
text/typescript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import {
EventDispatcher,
WebGLRenderTarget,
type RenderTargetOptions,
type WebGLRenderer,
} from 'three';
import type MemoryUsage from '../core/MemoryUsage';
import { type GetMemoryUsageContext } from '../core/MemoryUsage';
import NestedMap from '../utils/NestedMap';
import TextureGenerator from '../utils/TextureGenerator';
export interface RenderTargetPoolEvents {
cleanup: unknown;
}
const createPool = <T>(): T[] => [];
/**
* A pool that manages {@link RenderTarget}s.
*/
export default class RenderTargetPool
extends EventDispatcher<RenderTargetPoolEvents>
implements MemoryUsage
{
public readonly isMemoryUsage = true as const;
// Note that we cannot share render targets between instances are they are tied to a single WebGLRenderer.
private readonly _globalPool: NestedMap<
WebGLRenderer,
RenderTargetOptions,
WebGLRenderTarget[]
> = new NestedMap();
private readonly _renderTargets: Map<WebGLRenderTarget, RenderTargetOptions> = new Map();
private readonly _cleanupTimeoutMs: number;
private _timeout: NodeJS.Timeout | null = null;
private _maxPoolSize: number;
public constructor(cleanupTimeoutMs: number, maxPoolSize: number) {
super();
this._cleanupTimeoutMs = cleanupTimeoutMs;
this._maxPoolSize = maxPoolSize;
}
public getMemoryUsage(context: GetMemoryUsageContext): void {
if (this._globalPool.size === 0) {
return;
}
this._globalPool.forEach((targets, renderer) => {
if (renderer === context.renderer) {
targets.forEach(target => TextureGenerator.getMemoryUsage(context, target));
}
});
}
public acquire(
renderer: WebGLRenderer,
width: number,
height: number,
options: RenderTargetOptions,
): WebGLRenderTarget {
const pool = this._globalPool.getOrCreate(renderer, options, createPool);
if (pool.length > 0) {
const cached = pool.pop() as WebGLRenderTarget;
cached.setSize(width, height);
return cached;
}
const result = new WebGLRenderTarget(width, height, options);
this._renderTargets.set(result, options);
return result;
}
public get count(): number {
return this._renderTargets.size;
}
public release(obj: WebGLRenderTarget, renderer: WebGLRenderer): void {
const options = this._renderTargets.get(obj);
if (options) {
const pool = this._globalPool.getOrCreate(renderer, options, createPool);
if (pool.length < this._maxPoolSize) {
pool.push(obj);
} else {
obj.dispose();
this._renderTargets.delete(obj);
}
}
if (this._timeout) {
clearTimeout(this._timeout);
}
this._timeout = setTimeout(() => this.cleanup(), this._cleanupTimeoutMs);
}
public cleanup(): void {
this._timeout = null;
this._globalPool.forEach(list => {
list.forEach(renderTarget => {
renderTarget.dispose();
this._renderTargets.delete(renderTarget);
});
});
this._globalPool.clear();
this.dispatchEvent({ type: 'cleanup' });
}
}
export const GlobalRenderTargetPool = new RenderTargetPool(50, 16);