Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/core/src/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ export class Engine extends EventDispatcher {
private _postProcessPasses = new Array<PostProcessPass>();
private _activePostProcessPasses = new Array<PostProcessPass>();

/** Flush the render target pool's free list when the canvas resizes; canvas-sized entries are now stale. */
private _onCanvasResize = (): void => {
this._renderTargetPool.gc();
};

private _animate = () => {
if (this._vSyncCount) {
const raf = this.xrManager?._getRequestAnimationFrame() || requestAnimationFrame;
Expand Down Expand Up @@ -256,6 +261,7 @@ export class Engine extends EventDispatcher {

this._batcherManager = new BatcherManager(this);
this._renderTargetPool = new RenderTargetPool(this);
canvas._sizeUpdateFlagManager.addListener(this._onCanvasResize);
this.inputManager = new InputManager(this, configuration.input);

const { xrDevice } = configuration;
Expand Down Expand Up @@ -324,6 +330,8 @@ export class Engine extends EventDispatcher {
const time = this._time;
time._update();

this._renderTargetPool.tick(time.frameCount);

const deltaTime = time.deltaTime;
this._frameInProcess = true;

Expand Down Expand Up @@ -502,6 +510,8 @@ export class Engine extends EventDispatcher {
this._destroyed = true;
this._waitingDestroy = false;

this._canvas._sizeUpdateFlagManager.removeListener(this._onCanvasResize);

this._sceneManager._destroyAllScene();
this._resourceManager._destroy();

Expand Down
50 changes: 23 additions & 27 deletions packages/core/src/RenderPipeline/BasicRenderPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,8 @@ export class BasicRenderPipeline {
depthFormat = null;
}
const viewport = camera.pixelViewport;
const internalColorTarget = PipelineUtils.recreateRenderTargetIfNeeded(
engine,
this._internalColorTarget,
const pool = engine._renderTargetPool;
this._internalColorTarget = pool.allocateRenderTarget(
viewport.width,
viewport.height,
camera._getInternalColorTextureFormat(),
Expand All @@ -183,9 +182,7 @@ export class BasicRenderPipeline {

if (this._shouldCopyBackgroundColor) {
const colorTexture = camera.renderTarget?.getColorTexture(0);
const copyBackgroundTexture = PipelineUtils.recreateTextureIfNeeded(
engine,
this._copyBackgroundTexture,
this._copyBackgroundTexture = pool.allocateTexture(
viewport.width,
viewport.height,
colorTexture?.format ?? TextureFormat.R8G8B8A8,
Expand All @@ -194,35 +191,34 @@ export class BasicRenderPipeline {
TextureWrapMode.Clamp,
TextureFilterMode.Bilinear
);
this._copyBackgroundTexture = copyBackgroundTexture;
}
}

this._internalColorTarget = internalColorTarget;
} else {
const internalColorTarget = this._internalColorTarget;
const copyBackgroundTexture = this._copyBackgroundTexture;
// Scalable ambient obscurance pass
// Before opaque pass so materials can sample ambient occlusion in BRDF
const saoPass = this._saoPass;
try {
if (ambientOcclusionEnabled && supportDepthTexture && saoPass.isSupported) {
saoPass.onConfig(camera, this._depthOnlyPass.renderTarget);
saoPass.onRender(context);
} else {
this._saoPass.release();
}

this._drawRenderPass(context, camera, finalClearFlags, cubeFace, mipLevel);
} finally {
// Always return the per-frame leases, even if a pass threw, so the next camera with
// matching shape can reuse them and the pool never strands an internal RT/texture.
const pool = engine._renderTargetPool;
if (internalColorTarget) {
pool.freeRenderTarget(internalColorTarget);
if (this._internalColorTarget) {
pool.freeRenderTarget(this._internalColorTarget);
this._internalColorTarget = null;
}
if (copyBackgroundTexture) {
pool.freeTexture(copyBackgroundTexture);
if (this._copyBackgroundTexture) {
pool.freeTexture(this._copyBackgroundTexture);
this._copyBackgroundTexture = null;
}
}

// Scalable ambient obscurance pass
// Before opaque pass so materials can sample ambient occlusion in BRDF
const saoPass = this._saoPass;
if (ambientOcclusionEnabled && supportDepthTexture && saoPass.isSupported) {
saoPass.onConfig(camera, this._depthOnlyPass.renderTarget);
saoPass.onRender(context);
} else {
this._saoPass.release();
}

this._drawRenderPass(context, camera, finalClearFlags, cubeFace, mipLevel);
}

private _drawRenderPass(
Expand Down
166 changes: 111 additions & 55 deletions packages/core/src/RenderPipeline/RenderTargetPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,73 @@ import { RenderTarget, Texture2D, TextureFilterMode, TextureFormat, TextureWrapM
* @internal
*/
export class RenderTargetPool {
private static _destroyRenderTargetResource(rt: RenderTarget): void {
const colorTexture = rt.getColorTexture(0);
const depthTexture = rt.depthTexture;
rt.destroy(true);
colorTexture?.destroy(true);
if (depthTexture && depthTexture !== colorTexture) {
depthTexture.destroy(true);
}
}

private static _matchRenderTarget(
renderTarget: RenderTarget,
width: number,
height: number,
colorFormat: TextureFormat | null,
depthFormat: TextureFormat | null,
needDepthTexture: boolean,
mipmap: boolean,
isSRGBColorSpace: boolean,
antiAliasing: number
): boolean {
if (renderTarget.width !== width || renderTarget.height !== height || renderTarget.antiAliasing !== antiAliasing) {
return false;
}

const colorTexture = renderTarget.getColorTexture(0) as Texture2D;
if (colorFormat != null) {
if (
!colorTexture ||
colorTexture.format !== colorFormat ||
colorTexture.mipmapCount > 1 !== mipmap ||
colorTexture.isSRGBColorSpace !== isSRGBColorSpace
) {
return false;
}
} else if (colorTexture) {
return false;
}

const depthTexture = renderTarget.depthTexture;
if (needDepthTexture) {
if (depthFormat) {
if (!depthTexture || (depthTexture as Texture2D).format !== depthFormat) {
return false;
}
} else if (depthTexture) {
return false;
}
} else {
if (renderTarget._depthFormat !== depthFormat) {
return false;
}
}

return true;
}

/** An entry idle for at least this many frames is destroyed by `tick()`. */
maxFreeAgeFrames: number = 60;

private _engine: Engine;

private _freeRenderTargets: RenderTarget[] = [];
private _freeRenderTargetFrames: number[] = [];

private _freeTextures: Texture2D[] = [];
private _engine: Engine;
private _freeTextureFrames: number[] = [];

constructor(engine: Engine) {
this._engine = engine;
Expand Down Expand Up @@ -41,8 +105,7 @@ export class RenderTargetPool {
antiAliasing
)
) {
freeRenderTargets[i] = freeRenderTargets[freeRenderTargets.length - 1];
freeRenderTargets.length--;
this._removeFreeRenderTargetAt(i);
const colorTexture = renderTarget.getColorTexture(0) as Texture2D;
if (colorTexture) {
colorTexture.wrapModeU = colorTexture.wrapModeV = wrapMode;
Expand Down Expand Up @@ -103,8 +166,7 @@ export class RenderTargetPool {
texture.mipmapCount > 1 === mipmap &&
texture.isSRGBColorSpace === isSRGBColorSpace
) {
freeTextures[i] = freeTextures[freeTextures.length - 1];
freeTextures.length--;
this._removeFreeTextureAt(i);
texture.wrapModeU = texture.wrapModeV = wrapMode;
texture.filterMode = filterMode;
return texture;
Expand All @@ -122,78 +184,72 @@ export class RenderTargetPool {
freeRenderTarget(renderTarget: RenderTarget): void {
if (!renderTarget || renderTarget.destroyed) return;
this._freeRenderTargets.push(renderTarget);
this._freeRenderTargetFrames.push(this._engine.time.frameCount);
}

freeTexture(texture: Texture2D): void {
if (!texture || texture.destroyed) return;
this._freeTextures.push(texture);
this._freeTextureFrames.push(this._engine.time.frameCount);
}

tick(currentFrame: number): void {
const maxAge = this.maxFreeAgeFrames;
const rtFrames = this._freeRenderTargetFrames;
for (let i = rtFrames.length - 1; i >= 0; i--) {
if (currentFrame - rtFrames[i] >= maxAge) {
this._destroyFreeRenderTargetAt(i);
}
}
const texFrames = this._freeTextureFrames;
for (let i = texFrames.length - 1; i >= 0; i--) {
if (currentFrame - texFrames[i] >= maxAge) {
this._destroyFreeTextureAt(i);
}
}
}

gc(): void {
const freeRenderTargets = this._freeRenderTargets;
for (let i = 0, n = freeRenderTargets.length; i < n; i++) {
const renderTarget = freeRenderTargets[i];
const colorTexture = renderTarget.getColorTexture(0);
const depthTexture = renderTarget.depthTexture;
renderTarget.destroy(true);
colorTexture?.destroy(true);
if (depthTexture && depthTexture !== colorTexture) {
depthTexture.destroy(true);
}
RenderTargetPool._destroyRenderTargetResource(freeRenderTargets[i]);
}
freeRenderTargets.length = 0;
this._freeRenderTargetFrames.length = 0;

const freeTextures = this._freeTextures;
for (let i = 0, n = freeTextures.length; i < n; i++) {
freeTextures[i].destroy(true);
}
freeTextures.length = 0;
this._freeTextureFrames.length = 0;
}

private static _matchRenderTarget(
renderTarget: RenderTarget,
width: number,
height: number,
colorFormat: TextureFormat | null,
depthFormat: TextureFormat | null,
needDepthTexture: boolean,
mipmap: boolean,
isSRGBColorSpace: boolean,
antiAliasing: number
): boolean {
if (renderTarget.width !== width || renderTarget.height !== height || renderTarget.antiAliasing !== antiAliasing) {
return false;
}
private _removeFreeRenderTargetAt(index: number): void {
const last = this._freeRenderTargets.length - 1;
this._freeRenderTargets[index] = this._freeRenderTargets[last];
this._freeRenderTargetFrames[index] = this._freeRenderTargetFrames[last];
this._freeRenderTargets.length = last;
this._freeRenderTargetFrames.length = last;
}

const colorTexture = renderTarget.getColorTexture(0) as Texture2D;
if (colorFormat != null) {
if (
!colorTexture ||
colorTexture.format !== colorFormat ||
colorTexture.mipmapCount > 1 !== mipmap ||
colorTexture.isSRGBColorSpace !== isSRGBColorSpace
) {
return false;
}
} else if (colorTexture) {
return false;
}
private _removeFreeTextureAt(index: number): void {
const last = this._freeTextures.length - 1;
this._freeTextures[index] = this._freeTextures[last];
this._freeTextureFrames[index] = this._freeTextureFrames[last];
this._freeTextures.length = last;
this._freeTextureFrames.length = last;
}

const depthTexture = renderTarget.depthTexture;
if (needDepthTexture) {
if (depthFormat) {
if (!depthTexture || (depthTexture as Texture2D).format !== depthFormat) {
return false;
}
} else if (depthTexture) {
return false;
}
} else {
if (renderTarget._depthFormat !== depthFormat) {
return false;
}
}
private _destroyFreeRenderTargetAt(index: number): void {
const rt = this._freeRenderTargets[index];
this._removeFreeRenderTargetAt(index);
RenderTargetPool._destroyRenderTargetResource(rt);
}

return true;
private _destroyFreeTextureAt(index: number): void {
const tex = this._freeTextures[index];
this._removeFreeTextureAt(index);
tex.destroy(true);
}
}
Loading
Loading