diff --git a/examples/tests/viewport-strictbounds.ts b/examples/tests/viewport-strictbounds.ts new file mode 100644 index 00000000..eeb0cd66 --- /dev/null +++ b/examples/tests/viewport-strictbounds.ts @@ -0,0 +1,92 @@ +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export async function automation(settings: ExampleSettings) { + const page = await test(settings); + page(1); + await settings.snapshot(); + + page(2); + await settings.snapshot(); + + page(3); + await settings.snapshot(); +} + +export default async function test({ renderer, testRoot }: ExampleSettings) { + // Create a container node + const containerNode = renderer.createNode({ + x: 10, + y: 100, + width: 1000, + height: 600, + color: 0xff0000ff, // Red + parent: testRoot, + }); + + const status = renderer.createTextNode({ + text: 'Strict Bound: ', + fontSize: 30, + x: 10, + y: 50, + parent: testRoot, + }); + + const amountOfNodes = 11; + const childNodeWidth = 1700 / amountOfNodes; + + // Create 11 child nodes + for (let i = 0; i < amountOfNodes; i++) { + const childNode = renderer.createNode({ + x: i * childNodeWidth + i * 100, + y: 100, + width: childNodeWidth, + height: 300, + color: 0x00ff00ff, // Green + parent: containerNode, + }); + + const nodeTest = renderer.createTextNode({ + x: 10, + y: 130, + text: `Node ${i}`, + color: 0x000000ff, + parent: childNode, + }); + } + + renderer.on('idle', () => { + status.text = 'Strict Bound: ' + String(containerNode.strictBounds); + }); + + window.onkeydown = (e) => { + if (e.key === 'ArrowRight') { + containerNode.x -= 100; + } + + if (e.key === 'ArrowLeft') { + containerNode.x += 100; + } + + if (e.key === ' ') { + containerNode.strictBounds = !containerNode.strictBounds; + } + }; + + const page = (i = 0) => { + switch (i) { + case 1: + containerNode.x = -590; + break; + + case 2: + containerNode.x = -1390; + break; + + case 3: + containerNode.strictBounds = true; + break; + } + }; + + return page; +} diff --git a/src/core/CoreNode.test.ts b/src/core/CoreNode.test.ts index 1f0b286b..1d53881a 100644 --- a/src/core/CoreNode.test.ts +++ b/src/core/CoreNode.test.ts @@ -61,6 +61,7 @@ describe('set color()', () => { zIndex: 0, zIndexLocked: 0, preventCleanup: false, + strictBounds: false, }; it('should set all color subcomponents.', () => { diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index f95ff7a9..c9d127dd 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -665,6 +665,21 @@ export interface CoreNodeProps { * are provided. Only works when createImageBitmap is supported on the browser. */ srcY?: number; + /** + * By enabling Strict bounds the renderer will not process & render child nodes of a node that is out of the visible area + * + * @remarks + * When enabled out of bound nodes, i.e. nodes that are out of the visible area, will + * **NOT** have their children processed and renderer anymore. This means the children of a out of bound + * node will not receive update processing such as positioning updates and will not be drawn on screen. + * As such the rest of the branch of the update tree that sits below this node will not be processed anymore + * + * This is a big performance gain but may be disabled in cases where the width of the parent node is + * unknown and the render must process the child nodes regardless of the viewport status of the parent node + * + * @default false + */ + strictBounds: boolean; } /** @@ -1101,7 +1116,10 @@ export class CoreNode extends EventEmitter { parent.setUpdateType(UpdateType.ZIndexSortedChildren); } - if (this.renderState === CoreNodeRenderState.OutOfBounds) { + if ( + this.props.strictBounds === true && + this.renderState === CoreNodeRenderState.OutOfBounds + ) { return; } @@ -2134,6 +2152,20 @@ export class CoreNode extends EventEmitter { return this.props.textureOptions; } + get strictBounds(): boolean { + return this.props.strictBounds; + } + + set strictBounds(v) { + if (v === this.props.strictBounds) { + return; + } + + this.props.strictBounds = v; + this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children); + this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children; + } + setRTTUpdates(type: number) { this.hasRTTupdates = true; this.parent?.setRTTUpdates(type); diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 4d0cf235..0f60f7d6 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -19,7 +19,11 @@ import { startLoop, getTimeStamp } from './platform.js'; import { assertTruthy, setPremultiplyMode } from '../utils.js'; import { AnimationManager } from './animations/AnimationManager.js'; -import { CoreNode, type CoreNodeProps } from './CoreNode.js'; +import { + CoreNode, + CoreNodeRenderState, + type CoreNodeProps, +} from './CoreNode.js'; import { CoreTextureManager } from './CoreTextureManager.js'; import { TrFontManager } from './text-rendering/TrFontManager.js'; import { CoreShaderManager, type ShaderMap } from './CoreShaderManager.js'; @@ -240,6 +244,7 @@ export class Stage { src: null, scale: 1, preventCleanup: false, + strictBounds: false, }); this.root = rootNode; @@ -397,7 +402,11 @@ export class Stage { continue; } - if (child.worldAlpha === 0) { + if ( + child.worldAlpha === 0 || + (child.strictBounds === true && + child.renderState === CoreNodeRenderState.OutOfBounds) + ) { continue; } @@ -617,6 +626,7 @@ export class Stage { data: data, preventCleanup: props.preventCleanup ?? false, imageType: props.imageType, + strictBounds: props.strictBounds ?? false, }; } } diff --git a/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-1.png b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-1.png new file mode 100644 index 00000000..6cb74d62 Binary files /dev/null and b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-1.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-2.png b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-2.png new file mode 100644 index 00000000..117308e3 Binary files /dev/null and b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-2.png differ diff --git a/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-3.png b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-3.png new file mode 100644 index 00000000..bff8d478 Binary files /dev/null and b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-3.png differ