Skip to content
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
93 changes: 93 additions & 0 deletions examples/tests/stress-multi-level-clipping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 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 INode } from '@lightningjs/renderer';
import logo from '../assets/lightning.png';
import type { ExampleSettings } from '../common/ExampleSettings.js';
import robotImg from '../assets/robot/robot.png';

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

export default async function ({
renderer,
testRoot,
perfMultiplier,
}: ExampleSettings) {
// create nodes
const numOuterNodes = 100 * perfMultiplier;
const nodes: INode[] = [];
let totalNodes = 0;

const bg = renderer.createNode({
width: 1920,
height: 1080,
color: 0xff1e293b,
parent: testRoot,
});

for (let i = 0; i < numOuterNodes; i++) {
const container = renderer.createNode({
x: Math.random() * 1920,
y: Math.random() * 1080,
width: 100,
height: 100,
clipping: true,
parent: bg,
});
const node = renderer.createNode({
mount: 0.5,
x: 50,
y: 50,
width: 200,
height: 200,
src: robotImg,
parent: container,
});

nodes.push(container);
totalNodes += 2;
}

console.log(
`Created ${numOuterNodes} clipping outer nodes with an image node nested inside. Total nodes: ${totalNodes}`,
);

// create animations
const animate = () => {
nodes.forEach((node) => {
node
.animate(
{
x: randomIntBetween(20, 1740),
y: randomIntBetween(20, 900),
},
{
duration: 3000,
easing: 'ease-out',
loop: true,
stopMethod: 'reverse',
},
)
.start();
});
};

animate();
}
51 changes: 30 additions & 21 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import type {
NodeTextureLoadedPayload,
} from '../common/CommonTypes.js';
import { EventEmitter } from '../common/EventEmitter.js';
import { intersectRect, type Rect } from './lib/utils.js';
import { copyRect, intersectRect, type RectWithValid } from './lib/utils.js';
import { Matrix3d } from './lib/Matrix3d.js';

export interface CoreNodeProps {
Expand Down Expand Up @@ -150,7 +150,13 @@ export class CoreNode extends EventEmitter implements ICoreNode {
public globalTransform?: Matrix3d;
public scaleRotateTransform?: Matrix3d;
public localTransform?: Matrix3d;
public clippingRect: Rect | null = null;
public clippingRect: RectWithValid = {
x: 0,
y: 0,
width: 0,
height: 0,
valid: false,
};
public isRenderable = false;
public worldAlpha = 1;
public premultipliedColorTl = 0;
Expand Down Expand Up @@ -295,7 +301,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
* @todo: test for correct calculation flag
* @param delta
*/
update(delta: number, parentClippingRect: Rect | null = null): void {
update(delta: number, parentClippingRect: RectWithValid): void {
if (this.updateType & UpdateType.ScaleRotate) {
this.updateScaleRotateTransform();
this.setUpdateType(UpdateType.Local);
Expand Down Expand Up @@ -477,28 +483,31 @@ export class CoreNode extends EventEmitter implements ICoreNode {
*
* Finally, the node's parentClippingRect and clippingRect properties are updated.
*/
calculateClippingRect(parentClippingRect: Rect | null = null) {
calculateClippingRect(parentClippingRect: RectWithValid) {
assertTruthy(this.globalTransform);
const { clippingRect, props, globalTransform: gt } = this;
const { clipping } = props;

const gt = this.globalTransform;
const isRotated = gt.tb !== 0 || gt.tc !== 0;

let clippingRect: Rect | null =
this.props.clipping && !isRotated
? {
x: gt.tx,
y: gt.ty,
width: this.width * gt.ta,
height: this.height * gt.td,
}
: null;
if (parentClippingRect && clippingRect) {
clippingRect = intersectRect(parentClippingRect, clippingRect);
} else if (parentClippingRect) {
clippingRect = parentClippingRect;
}

this.clippingRect = clippingRect;
if (clipping && !isRotated) {
clippingRect.x = gt.tx;
clippingRect.y = gt.ty;
clippingRect.width = this.width * gt.ta;
clippingRect.height = this.height * gt.td;
clippingRect.valid = true;
} else {
clippingRect.valid = false;
}

if (parentClippingRect.valid && clippingRect.valid) {
// Intersect parent clipping rect with node clipping rect
intersectRect(parentClippingRect, clippingRect, clippingRect);
} else if (parentClippingRect.valid) {
// Copy parent clipping rect
copyRect(parentClippingRect, clippingRect);
clippingRect.valid = true;
}
}

calculateZIndex(): void {
Expand Down
4 changes: 2 additions & 2 deletions src/core/CoreTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import type {
NodeTextFailedPayload,
NodeTextLoadedPayload,
} from '../common/CommonTypes.js';
import type { Rect } from './lib/utils.js';
import type { Rect, RectWithValid } from './lib/utils.js';
import { assertTruthy } from '../utils.js';

export interface CoreTextNodeProps extends CoreNodeProps, TrProps {
Expand Down Expand Up @@ -318,7 +318,7 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
this.textRenderer.set.debug(this.trState, value);
}

override update(delta: number, parentClippingRect: Rect | null = null) {
override update(delta: number, parentClippingRect: RectWithValid) {
super.update(delta, parentClippingRect);

assertTruthy(this.globalTransform);
Expand Down
2 changes: 1 addition & 1 deletion src/core/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class Stage extends EventEmitter {

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

// test if we need to update the scene
Expand Down
44 changes: 43 additions & 1 deletion src/core/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export interface Rect {
height: number;
}

export interface RectWithValid extends Rect {
valid: boolean;
}

export interface Bound {
x1: number;
y1: number;
Expand Down Expand Up @@ -132,19 +136,39 @@ export function intersectBound<T extends Bound = Bound>(
return createBound(0, 0, 0, 0, intersection);
}

export function intersectRect(a: Rect, b: Rect): Rect {
export function intersectRect(a: Rect, b: Rect): Rect;
export function intersectRect<T extends Rect = Rect>(
a: Rect,
b: Rect,
out: T,
): T;
export function intersectRect(a: Rect, b: Rect, out?: Rect): Rect {
const x = Math.max(a.x, b.x);
const y = Math.max(a.y, b.y);
const width = Math.min(a.x + a.width, b.x + b.width) - x;
const height = Math.min(a.y + a.height, b.y + b.height) - y;
if (width > 0 && height > 0) {
if (out) {
out.x = x;
out.y = y;
out.width = width;
out.height = height;
return out;
}
return {
x,
y,
width,
height,
};
}
if (out) {
out.x = 0;
out.y = 0;
out.width = 0;
out.height = 0;
return out;
}
return {
x: 0,
y: 0,
Expand All @@ -153,6 +177,24 @@ export function intersectRect(a: Rect, b: Rect): Rect {
};
}

export function copyRect(a: Rect): Rect;
export function copyRect<T extends Rect = Rect>(a: Rect, out: T): T;
export function copyRect(a: Rect, out?: Rect): Rect {
if (out) {
out.x = a.x;
out.y = a.y;
out.width = a.width;
out.height = a.height;
return out;
}
return {
x: a.x,
y: a.y,
width: a.width,
height: a.height,
};
}

export function compareRect(a: Rect | null, b: Rect | null): boolean {
if (a === b) {
return true;
Expand Down
4 changes: 2 additions & 2 deletions src/core/renderers/CoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import type { CoreShaderManager } from '../CoreShaderManager.js';
import type { TextureOptions } from '../CoreTextureManager.js';
import type { Stage } from '../Stage.js';
import type { Rect } from '../lib/utils.js';
import type { Rect, RectWithValid } from '../lib/utils.js';
import type { Texture } from '../textures/Texture.js';
import { CoreContextTexture } from './CoreContextTexture.js';
import type { CoreRenderOp } from './CoreRenderOp.js';
Expand All @@ -39,7 +39,7 @@ export interface QuadOptions {
shader: CoreShader | null;
shaderProps: Record<string, unknown> | null;
alpha: number;
clippingRect: Rect | null;
clippingRect: RectWithValid;
tx: number;
ty: number;
ta: number;
Expand Down
6 changes: 3 additions & 3 deletions src/core/renderers/webgl/WebGlCoreRenderOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type { WebGlCoreCtxTexture } from './WebGlCoreCtxTexture.js';
import type { WebGlCoreRendererOptions } from './WebGlCoreRenderer.js';
import type { BufferCollection } from './internal/BufferCollection.js';
import type { Dimensions } from '../../../common/CommonTypes.js';
import type { Rect } from '../../lib/utils.js';
import type { Rect, RectWithValid } from '../../lib/utils.js';
import type { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';

const MAX_TEXTURES = 8; // TODO: get from gl
Expand All @@ -45,7 +45,7 @@ export class WebGlCoreRenderOp extends CoreRenderOp {
readonly shader: WebGlCoreShader,
readonly shaderProps: Record<string, unknown>,
readonly alpha: number,
readonly clippingRect: Rect | null,
readonly clippingRect: RectWithValid,
readonly dimensions: Dimensions,
readonly bufferIdx: number,
readonly zIndex: number,
Expand Down Expand Up @@ -82,7 +82,7 @@ export class WebGlCoreRenderOp extends CoreRenderOp {
const quadIdx = (this.bufferIdx / 24) * 6 * 2;

// Clipping
if (this.clippingRect) {
if (this.clippingRect.valid) {
const { x, y, width, height } = this.clippingRect;
const pixelRatio = options.pixelRatio;
const canvasHeight = options.canvas.height;
Expand Down
3 changes: 2 additions & 1 deletion src/core/renderers/webgl/WebGlCoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
compareRect,
getNormalizedRgbaComponents,
type Rect,
type RectWithValid,
} from '../../lib/utils.js';
import type { Dimensions } from '../../../common/CommonTypes.js';
import { WebGlCoreShader } from './WebGlCoreShader.js';
Expand Down Expand Up @@ -412,7 +413,7 @@ export class WebGlCoreRenderer extends CoreRenderer {
shaderProps: Record<string, unknown>,
alpha: number,
dimensions: Dimensions,
clippingRect: Rect | null,
clippingRect: RectWithValid,
bufferIdx: number,
) {
const curRenderOp = new WebGlCoreRenderOp(
Expand Down
3 changes: 2 additions & 1 deletion src/core/text-rendering/renderers/CanvasTextRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
getNormalizedAlphaComponent,
type BoundWithValid,
createBound,
type RectWithValid,
} from '../../lib/utils.js';
import type { ImageTexture } from '../../textures/ImageTexture.js';
import type { TrFontFace } from '../font-face-types/TrFontFace.js';
Expand Down Expand Up @@ -524,7 +525,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
override renderQuads(
state: CanvasTextRendererState,
transform: Matrix3d,
clippingRect: Rect | null,
clippingRect: RectWithValid,
alpha: number,
): void {
const { stage } = this;
Expand Down
Loading