threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
162 lines • 7.29 kB
JavaScript
import { createRenderTargetKey } from './RenderTarget';
import { ClampToEdgeWrapping, DepthFormat, DepthTexture, EventDispatcher, LinearFilter, LinearMipMapLinearFilter, NoColorSpace, RGBAFormat, UnsignedByteType, UnsignedIntType, WebGLCubeRenderTarget, WebGLMultipleRenderTargets, WebGLRenderTarget, } from 'three';
export class RenderTargetManager extends EventDispatcher {
constructor() {
super();
this._trackedTargets = [];
this._trackedTempTargets = [];
this._releasedTempTargets = {};
this.maxTempPerKey = 5;
this._processNewTarget = this._processNewTarget.bind(this);
this._processNewTempTarget = this._processNewTempTarget.bind(this);
this.trackTarget = this.trackTarget.bind(this);
this.disposeTarget = this.disposeTarget.bind(this);
this.createTarget = this.createTarget.bind(this);
this.createTargetCustom = this.createTargetCustom.bind(this);
}
trackTarget(target) {
this._trackedTargets.push(target);
}
removeTrackedTarget(target) {
const ind = this._trackedTargets.indexOf(target);
if (ind >= 0)
this._trackedTargets.splice(ind, 1);
}
createTarget({ sizeMultiplier = undefined, samples = 0, colorSpace = NoColorSpace, type = UnsignedByteType, format = RGBAFormat, stencilBuffer = false, depthBuffer = true, depthTexture = false, depthTextureType = UnsignedIntType, depthTextureFormat = DepthFormat, size = undefined, textureCount = 1, ...op } = {}, trackTarget = true) {
if (!this.isWebGL2)
samples = 0;
if (sizeMultiplier !== undefined && size !== undefined)
console.error('Both sizeMultiplier and size are defined. sizeMultiplier will be ignored.');
size = size || this.renderSize.clone().multiplyScalar(this.renderScale * (sizeMultiplier = sizeMultiplier || 1));
size.width = Math.floor(size.width);
size.height = Math.floor(size.height);
const depthTex = depthTexture ? new DepthTexture(size.width, size.height, depthTextureType) : null;
if (depthTex)
depthTex.format = depthTextureFormat;
const target = this.createTargetCustom(textureCount > 1 ? {
width: size.width,
height: size.height,
count: textureCount,
} : size, { samples, colorSpace, type, format, depthBuffer, depthTexture: depthTex, stencilBuffer }, textureCount > 1 ? WebGLMultipleRenderTargets : WebGLRenderTarget);
this._processNewTarget(target, sizeMultiplier, trackTarget);
this._setTargetOptions(target, op);
return target;
}
/**
* Dispose and remove tracked target. Release target in-case of temporary target.
* To just dispose from the GPU memory and keep reference, call `target.dispose()` or `target.dispose(false)`
* @param target
* @param remove
*/
disposeTarget(target, remove = true) {
if (!target)
return;
if (target.isTemporary)
return this.releaseTempTarget(target);
if (remove)
this.removeTrackedTarget(target);
// @ts-expect-error internal, not in types
target.dispose(false); // false is not required but still passing so that it doesnt cause infinite loop in future.
}
getTempTarget(op = {}) {
const key = createRenderTargetKey(op);
let target;
if (this._releasedTempTargets[key]?.length)
target = this._releasedTempTargets[key].pop();
if (!target) {
target = this.createTarget(op);
this._processNewTempTarget(target, key);
}
else {
this._setTargetOptions(target, op);
}
return target;
}
releaseTempTarget(target) {
const key = target.targetKey;
if (!key || !target.isTemporary) {
throw 'Not a temp target';
}
if (this._releasedTempTargets[key].length > this.maxTempPerKey) {
this.removeTrackedTarget(target);
target.dispose();
}
else
this._releasedTempTargets[key].push(target);
}
createTargetCustom({ width, height, count, }, options = {}, clazz) {
let size = [width, height];
if (count && count > 1)
size.push(count);
if (clazz?.prototype === WebGLCubeRenderTarget.prototype) { // todo: check for subclass also of WebGLCubeRenderTarget
if (width !== height)
throw 'Width and height of cube render target must be equal';
size = [width];
}
return this._createTargetClass(clazz ?? WebGLRenderTarget, size, {
format: RGBAFormat,
minFilter: LinearFilter,
magFilter: LinearFilter,
generateMipmaps: false,
type: UnsignedByteType,
colorSpace: NoColorSpace,
...options,
});
}
dispose(clear = true) {
this._trackedTargets.forEach(t => t.dispose());
Object.values(this._trackedTempTargets).forEach(t => t.dispose());
if (clear) {
this._trackedTargets = [];
this._releasedTempTargets = {};
this._trackedTempTargets = [];
}
}
/**
* Resizes all tracked targets with a sizeMultiplier based on the current renderSize and renderScale.
* This must be automatically called by the renderer on resize, and manually when sizeMultiplier of a target changes.
*/
resizeTrackedTargets() {
for (const v of this._trackedTargets)
this.resizeTrackedTarget(v);
}
resizeTrackedTarget(target) {
const multiplier = target.sizeMultiplier;
if (multiplier) {
const s = this.renderSize.clone().multiplyScalar(this.renderScale * multiplier);
target.setSize(Math.floor(s.width), Math.floor(s.height));
}
}
_processNewTempTarget(target, key) {
target.isTemporary = true;
target.targetKey = key;
if (this._releasedTempTargets[key] === undefined)
this._releasedTempTargets[key] = [];
this._trackedTempTargets.push(target);
return target;
}
_setTargetOptions(target, op) {
const tex = target.texture;
for (const t of Array.isArray(tex) ? tex : [tex])
this._setTargetTextureOptions(t, op);
}
_setTargetTextureOptions(texture, op) {
texture.minFilter = op.minFilter ?? LinearFilter;
texture.magFilter = op.magFilter ?? LinearFilter;
texture.wrapS = op.wrapS ?? ClampToEdgeWrapping;
texture.wrapT = op.wrapT ?? ClampToEdgeWrapping;
texture.generateMipmaps = op.generateMipmaps ?? false;
if (texture.generateMipmaps && texture.minFilter === LinearFilter)
texture.minFilter = LinearMipMapLinearFilter;
if (!texture.generateMipmaps && texture.minFilter === LinearMipMapLinearFilter)
texture.minFilter = LinearFilter;
}
_processNewTarget(target, sizeMultiplier, trackTarget) {
if (sizeMultiplier !== undefined)
target.sizeMultiplier = sizeMultiplier;
if (trackTarget)
this.trackTarget(target);
return target;
}
}
//# sourceMappingURL=RenderTargetManager.js.map