Skip to content

Commit

Permalink
Perf: Pause RAF loop when there are no updates (#99)
Browse files Browse the repository at this point in the history
- [x] Implement RAF Loop Pause optimization
- [x] Fix SDF text font loading race condition
- This results in text that is not rendered if an SDF font atlas texture
is not uploaded in time to the GPU before the RAF loop is paused.
- [x] Fix text doubling
- Some text appears to be double rendered which is causing text to
appear thicker and tests to fail.
- Turns out the `gl.clear()` command was still be affected by the last
scissor operation causing the entire canvas to not get cleared each
time.
  • Loading branch information
frank-weindel authored Dec 13, 2023
2 parents 3a2342e + a07aa68 commit 7f5913c
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 153 deletions.
5 changes: 4 additions & 1 deletion examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,11 @@ async function runAutomation(driverName: string, logFps: boolean) {
const snapshot = (window as any).snapshot as
| ((testName: string) => Promise<void>)
| undefined;
// Allow some time for all images to load and the RaF to unpause
// and render if needed.
await delay(200);
if (snapshot) {
console.error(`Calling snapshot(${testName})`);
console.log(`Calling snapshot(${testName})`);
await snapshot(testName);
} else {
console.error(
Expand Down
3 changes: 3 additions & 0 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ export class CoreNode extends EventEmitter implements ICoreNode {
}

private onTextureLoaded: TextureLoadedEventHandler = (target, dimensions) => {
// Texture was loaded. In case the RAF loop has already stopped, we request
// a render to ensure the texture is rendered.
this.stage.requestRender();
this.emit('loaded', {
type: 'texture',
dimensions,
Expand Down
3 changes: 3 additions & 0 deletions src/core/CoreTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
}
this.updateLocalTransform();

// Incase the RAF loop has been stopped already before text was loaded,
// we request a render so it can be drawn.
this.stage.requestRender();
this.emit('loaded', {
type: 'text',
dimensions: {
Expand Down
49 changes: 22 additions & 27 deletions src/core/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Scene } from './scene/Scene.js';

import { startLoop, getTimeStamp } from './platform.js';

import { WebGlCoreRenderer } from './renderers/webgl/WebGlCoreRenderer.js';
import { assertTruthy } from '../utils.js';
import { AnimationManager } from './animations/AnimationManager.js';
Expand Down Expand Up @@ -62,14 +58,15 @@ export class Stage extends EventEmitter {
public readonly textRenderers: Partial<TextRendererMap>;
public readonly shManager: CoreShaderManager;
public readonly renderer: WebGlCoreRenderer;
private scene: Scene;
public readonly root: CoreNode;

/// State
deltaTime = 0;
lastFrameTime = 0;
currentFrameTime = 0;
private fpsNumFrames = 0;
private fpsElapsedTime = 0;
private renderRequested = false;

/**
* Stage constructor
Expand Down Expand Up @@ -146,7 +143,7 @@ export class Stage extends EventEmitter {
shaderProps: null,
});

this.scene = new Scene(rootNode);
this.root = rootNode;

// execute platform start loop
if (autoStart) {
Expand All @@ -158,8 +155,8 @@ export class Stage extends EventEmitter {
* Update animations
*/
updateAnimations() {
const { scene, animationManager } = this;
if (!scene?.root) {
const { animationManager } = this;
if (!this.root) {
return;
}
this.lastFrameTime = this.currentFrameTime;
Expand All @@ -177,33 +174,32 @@ export class Stage extends EventEmitter {
* Check if the scene has updates
*/
hasSceneUpdates() {
const { scene } = this;

if (!scene?.root) {
return false;
}

return !!scene?.root?.updateType;
return !!this.root.updateType || this.renderRequested;
}

/**
* Start a new frame draw
*/
drawFrame() {
const { renderer, scene } = this;
const { renderer, renderRequested } = this;

// Update tree if needed
if (scene.root.updateType !== 0) {
scene.root.update(this.deltaTime);
if (this.root.updateType !== 0) {
this.root.update(this.deltaTime);
}

// test if we need to update the scene
renderer?.reset();

this.addQuads(scene.root);
this.addQuads(this.root);

renderer?.render();

// Reset renderRequested flag if it was set
if (renderRequested) {
this.renderRequested = false;
}

// If there's an FPS update interval, emit the FPS update event
// when the specified interval has elapsed.
const { fpsUpdateInterval } = this.options;
Expand Down Expand Up @@ -240,6 +236,13 @@ export class Stage extends EventEmitter {
}
}

/**
* Request a render pass without forcing an update
*/
requestRender() {
this.renderRequested = true;
}

/**
* Given a font name, and possible renderer override, return the best compatible text renderer.
*
Expand Down Expand Up @@ -304,12 +307,4 @@ export class Stage extends EventEmitter {
// the covariant state argument in the setter method map
return resolvedTextRenderer as unknown as TextRenderer;
}

//#region Properties

get root() {
return this.scene?.root || null;
}

//#endregion Properties
}
5 changes: 5 additions & 0 deletions src/core/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export const startLoop = (stage: Stage) => {
const runLoop = () => {
stage.updateAnimations();

if (!stage.hasSceneUpdates()) {
setTimeout(runLoop, 16.666666666666668);
return;
}

stage.drawFrame();
requestAnimationFrame(runLoop);
};
Expand Down
1 change: 1 addition & 0 deletions src/core/renderers/webgl/WebGlCoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export class WebGlCoreRenderer extends CoreRenderer {
this.curBufferIdx = 0;
this.curRenderOp = null;
this.renderOps.length = 0;
this.gl.disable(this.gl.SCISSOR_TEST);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
}

Expand Down
120 changes: 0 additions & 120 deletions src/core/scene/Scene.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ export class SdfTrFontFace<
},
);

// TODO: Add texture loaded support
// this.texture.on('loaded', () => {
// this.checkLoaded();
// });
this.texture.on('loaded', () => {
this.checkLoaded();
});

// Set this.data to the fetched data from dataUrl
fetch(atlasDataUrl)
Expand Down Expand Up @@ -120,7 +119,7 @@ export class SdfTrFontFace<

private checkLoaded(): void {
if (this.loaded) return;
if (/*this.texture.loaded && */ this.data) {
if (this.texture.state === 'loaded' && this.data) {
(this.loaded as boolean) = true;
this.emit('loaded');
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7f5913c

Please sign in to comment.