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

Improve Canvas Text Renderer Performance #319

Merged
merged 5 commits into from
Jul 5, 2024
Merged
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
1 change: 1 addition & 0 deletions examples/tests/text-baseline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ function generateBaselineTest(

const baselineNode = renderer.createTextNode({
...nodeProps,
parent: renderer.root,
});
const dimensions = await waitForLoadedDimensions(baselineNode);

Expand Down
156 changes: 156 additions & 0 deletions examples/tests/text-canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2024 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { ExampleSettings } from '../common/ExampleSettings.js';

const Colors = {
Black: 0x000000ff,
Red: 0xff0000ff,
Green: 0x00ff00ff,
Blue: 0x0000ffff,
Magenta: 0xff00ffff,
Gray: 0x7f7f7fff,
White: 0xffffffff,
};

const randomIntBetween = (from: number, to: number) =>
Math.floor(Math.random() * (to - from + 1) + from);

/**
* Tests that Single-Channel Signed Distance Field (SSDF) fonts are rendered
* correctly.
*
* Text that is thinner than the certified snapshot may indicate that the
* SSDF font atlas texture was premultiplied before being uploaded to the GPU.
*
* @param settings
* @returns
*/
export default async function test(settings: ExampleSettings) {
const { renderer, testRoot } = settings;

// Set a smaller snapshot area
// testRoot.width = 200;
// testRoot.height = 200;
// testRoot.color = 0xffffffff;

const nodes: any[] = [];

const renderNode = (t: string) => {
const node = renderer.createTextNode({
x: Math.random() * 1900,
y: Math.random() * 1080,
text: 'CANVAS ' + t,
fontFamily: 'sans-serif',
parent: testRoot,
fontSize: 80,
});

nodes.push(node);

// pick random color from Colors
node.color =
Object.values(Colors)[
randomIntBetween(0, Object.keys(Colors).length - 1)
] || 0xff0000ff;
};

const spawn = (amount = 100) => {
for (let i = 0; i < amount; i++) {
renderNode(i.toString());
}
};

const despawn = (amount = 100) => {
for (let i = 0; i < amount; i++) {
const node = nodes.pop();
node.destroy();
}
};

const move = () => {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
node.x = randomIntBetween(0, 1600);
node.y = randomIntBetween(0, 880);
}
};

const newColor = () => {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
node.color =
Object.values(Colors)[
randomIntBetween(0, Object.keys(Colors).length - 1)
] || 0x000000ff;
}
};

let animating = false;
const animate = () => {
animating = !animating;

const animateNode = (node: any) => {
nodes.forEach((node) => {
node
.animate(
{
x: randomIntBetween(20, 1740),
y: randomIntBetween(20, 900),
rotation: Math.random() * Math.PI,
},
{
duration: 3000,
easing: 'ease-out',
},
)
.start();
});
};

const animateRun = () => {
if (animating) {
for (let i = 0; i < nodes.length; i++) {
animateNode(nodes[i]);
}
setTimeout(animateRun, 3050);
}
};

animateRun();
};

window.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp') {
spawn();
} else if (event.key === 'ArrowDown') {
despawn();
} else if (event.key === 'ArrowLeft') {
move();
} else if (event.key === 'ArrowRight') {
move();
} else if (event.key === '1') {
newColor();
} else if (event.key === ' ') {
animate();
}
});

spawn();
}
208 changes: 208 additions & 0 deletions examples/tests/viewport-events-canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2024 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { ExampleSettings } from '../common/ExampleSettings.js';

export default async function ({ renderer, testRoot }: ExampleSettings) {
const instructionText = renderer.createTextNode({
text: 'Press space to start animation, arrow keys to move, enter to reset',
fontSize: 30,
x: 10,
y: 960,
fontFamily: 'Ubuntu-ssdf',
parent: testRoot,
});

const redStatus = renderer.createTextNode({
text: 'Red Status: ',
fontSize: 30,
x: 10,
y: 50,
fontFamily: 'Ubuntu-ssdf',
parent: testRoot,
});

const blueStatus = renderer.createTextNode({
text: 'Blue Status: ',
fontSize: 30,
x: 10,
y: 10,
fontFamily: 'Ubuntu-ssdf',
parent: testRoot,
});

const boundaryRect = renderer.createNode({
x: 1920 / 2 - (1920 * 0.75) / 2,
y: 1080 / 2 - (1080 * 0.75) / 2,
width: 1440,
height: 810,
color: 0x000000ff,
clipping: true,
parent: testRoot,
});

const redText = renderer.createTextNode({
x: 500,
y: 305,
alpha: 1,
width: 200,
height: 200,
color: 0xff0000ff,
pivot: 0,
text: 'red',
fontSize: 80,
fontFamily: 'sans-serif',
parent: boundaryRect,
});

redText.on('outOfBounds', () => {
console.log('red text out of bounds');
redStatus.text = 'Red Status: text out of bounds';
redStatus.color = 0xff0000ff;
});

redText.on('inViewport', () => {
console.log('red text in view port');
redStatus.text = 'Red Status: text in view port';
redStatus.color = 0x00ff00ff;
});

redText.on('inBounds', () => {
console.log('red text inside render bounds');
redStatus.text = 'Red Status: text in bounds';
redStatus.color = 0xffff00ff;
});

const blueText = renderer.createTextNode({
x: 1920 / 2 - 200,
y: 100,
alpha: 1,
width: 200,
height: 200,
color: 0x0000ffff,
pivot: 0,
text: 'blue',
fontSize: 80,
fontFamily: 'sans-serif',
parent: testRoot,
});

blueText.on('outOfBounds', () => {
console.log('blue text ouf ot bounds');
blueStatus.text = 'Blue Status: blue text out of bounds';
blueStatus.color = 0xff0000ff;
});

blueText.on('inViewport', () => {
console.log('blue text in view port');
blueStatus.text = 'Blue Status: blue text in view port';
blueStatus.color = 0x00ff00ff;
});

blueText.on('inBounds', () => {
console.log('blue text inside render bounds');
blueStatus.text = 'Blue Status: blue text in bounds';
blueStatus.color = 0xffff00ff;
});

let runAnimation = false;
const animate = async () => {
redText
.animate(
{
x: -500,
},
{
duration: 4000,
},
)
.start();

await blueText
.animate(
{
x: -1200,
},
{
duration: 4000,
},
)
.start()
.waitUntilStopped();

redText.x = 1920 + 400;
blueText.x = 1920 + 400;

redText
.animate(
{
x: 520,
},
{
duration: 4000,
},
)
.start();

await blueText
.animate(
{
x: 1920 / 2 - 200,
},
{
duration: 4000,
},
)
.start()
.waitUntilStopped();

if (runAnimation) {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(animate, 2000);
}
};

const moveModifier = 10;
window.onkeydown = (e) => {
if (e.key === ' ') {
runAnimation = !runAnimation;

if (runAnimation) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
animate();
}
}

if (e.key === 'ArrowRight') {
redText.x += moveModifier;
blueText.x += moveModifier;
}

if (e.key === 'ArrowLeft') {
redText.x -= moveModifier;
blueText.x -= moveModifier;
}

if (e.key === 'Enter') {
runAnimation = false;
redText.x = 520;
blueText.x = 1920 / 2 - 200;
}
};
}
2 changes: 1 addition & 1 deletion src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ export type CoreNodeAnimatableProps = {
export class CoreNode extends EventEmitter {
readonly children: CoreNode[] = [];
protected _id: number = getNewId();
protected props: Required<CoreNodeWritableProps>;
readonly props: Required<CoreNodeWritableProps>;

public updateType = UpdateType.All;

Expand Down
Loading
Loading