Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Viewer suspend rendering when not visible #15835

Merged
95 changes: 49 additions & 46 deletions packages/tools/viewer-alpha/src/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ export type ViewerDetails = {
* Provides access to the currently loaded model.
*/
model: Nullable<AssetContainer>;

/**
* Suspends the render loop.
* @returns A token that should be disposed when the request for suspending rendering is no longer needed.
*/
suspendRendering(): IDisposable;
};

export type ViewerOptions = Partial<
Expand Down Expand Up @@ -359,6 +365,7 @@ export class Viewer implements IDisposable {
scene,
camera,
model: null,
suspendRendering: this._suspendRendering.bind(this),
};
}
this._details.scene.skipFrustumClipping = true;
Expand Down Expand Up @@ -608,52 +615,6 @@ export class Viewer implements IDisposable {
return this._details.model?.animationGroups[this._selectedAnimation] ?? null;
}

/**
* Suspends the render loop.
* @returns A token that should be disposed when the request for suspending rendering is no longer needed.
*/
public suspendRendering(): IDisposable {
this._renderLoopController?.dispose();
this._suspendRenderCount++;
let disposed = false;
return {
dispose: () => {
if (!disposed) {
disposed = true;
this._suspendRenderCount--;
if (this._suspendRenderCount === 0) {
this._beginRendering();
}
}
},
};
}

private _beginRendering(): void {
if (!this._renderLoopController) {
const render = () => {
this._details.scene.render();
if (this.isAnimationPlaying) {
this.onAnimationProgressChanged.notifyObservers();
this._autoRotationBehavior.resetLastInteractionTime();
}
};

this._engine.runRenderLoop(render);

let disposed = false;
this._renderLoopController = {
dispose: () => {
if (!disposed) {
disposed = true;
this._engine.stopRenderLoop(render);
this._renderLoopController = null;
}
},
};
}
}

/**
* Loads a 3D model from the specified URL.
* @remarks
Expand Down Expand Up @@ -929,6 +890,48 @@ export class Viewer implements IDisposable {
return true;
}

private _suspendRendering(): IDisposable {
this._renderLoopController?.dispose();
this._suspendRenderCount++;
let disposed = false;
return {
dispose: () => {
if (!disposed) {
disposed = true;
this._suspendRenderCount--;
if (this._suspendRenderCount === 0) {
this._beginRendering();
}
}
},
};
}

private _beginRendering(): void {
if (!this._renderLoopController) {
const render = () => {
this._details.scene.render();
if (this.isAnimationPlaying) {
this.onAnimationProgressChanged.notifyObservers();
sebavan marked this conversation as resolved.
Show resolved Hide resolved
this._autoRotationBehavior.resetLastInteractionTime();
}
};

this._engine.runRenderLoop(render);

let disposed = false;
this._renderLoopController = {
dispose: () => {
if (!disposed) {
disposed = true;
this._engine.stopRenderLoop(render);
this._renderLoopController = null;
}
},
};
}
}

private _updateCamera(interpolate = false): void {
// Enable camera's behaviors
this._details.camera.useFramingBehavior = true;
Expand Down
30 changes: 15 additions & 15 deletions packages/tools/viewer-alpha/src/viewerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ export async function createViewerForCanvas(canvas: HTMLCanvasElement, options?:
});
disposeActions.push(() => beforeRenderObserver.remove());

// If the canvas is not visible, suspend rendering.
let offscreenRenderingSuspension: Nullable<IDisposable> = null;
const interactionObserver = new IntersectionObserver((entries) => {
if (entries.length > 0) {
if (entries[entries.length - 1].isIntersecting) {
offscreenRenderingSuspension?.dispose();
offscreenRenderingSuspension = null;
} else {
offscreenRenderingSuspension = details.suspendRendering();
}
}
});
interactionObserver.observe(canvas);
disposeActions.push(() => interactionObserver.disconnect());

// Call the original onInitialized callback, if one was provided.
onInitialized?.(details);
};
Expand All @@ -77,21 +92,6 @@ export async function createViewerForCanvas(canvas: HTMLCanvasElement, options?:
const viewer = new Viewer(engine, finalOptions);
disposeActions.push(viewer.dispose.bind(viewer));

// If the canvas is not visible, suspend rendering.
let offscreenRenderingSuspension: Nullable<IDisposable> = null;
const interactionObserver = new IntersectionObserver((entries) => {
if (entries.length > 0) {
if (entries[entries.length - 1].isIntersecting) {
offscreenRenderingSuspension?.dispose();
offscreenRenderingSuspension = null;
} else {
offscreenRenderingSuspension = viewer.suspendRendering();
}
}
});
interactionObserver.observe(canvas);
disposeActions.push(() => interactionObserver.disconnect());

disposeActions.push(() => engine.dispose());

// Override the Viewer's dispose method to add in additional cleanup.
Expand Down