Skip to content

Commit eb54ccc

Browse files
authored
Render to Texture refactors (#441)
Fixes #440 Todo: - [x] Investigate why source -> destination texture relation adheres to original RTT node render state - [x] Cache Parent RTT node for faster update behaviour
2 parents efa9ed9 + b43e90a commit eb54ccc

File tree

9 files changed

+253
-85
lines changed

9 files changed

+253
-85
lines changed

examples/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ async function runAutomation(
382382
// Override Math.random() as stable random number generator
383383
// - Each test gets the same sequence of random numbers
384384
// - This only is in effect when tests are run in automation mode
385-
const rand = mt19937.factory({ seed: 1234 });
385+
// eslint-disable-next-line @typescript-eslint/unbound-method
386+
const factory = mt19937.factory || mt19937.default.factory;
387+
const rand = factory({ seed: 1234 });
386388
Math.random = function () {
387389
return rand() / rand.MAX;
388390
};

examples/tests/rtt-dimension.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import type { ExampleSettings } from '../common/ExampleSettings.js';
22
import rocko from '../assets/rocko.png';
33

44
export async function automation(settings: ExampleSettings) {
5-
await test(settings);
6-
await settings.snapshot();
5+
const page = await test(settings);
6+
7+
const maxPages = 6;
8+
for (let i = 0; i < maxPages; i++) {
9+
page(i);
10+
await settings.snapshot();
11+
}
712
}
813

914
export default async function test({ renderer, testRoot }: ExampleSettings) {
@@ -251,4 +256,44 @@ export default async function test({ renderer, testRoot }: ExampleSettings) {
251256
rttNode.height = rttNode.height === 200 ? 300 : 200;
252257
}
253258
});
259+
260+
// Define the page function to configure different test scenarios
261+
const page = (i = 0) => {
262+
switch (i) {
263+
case 1:
264+
rttNode.rtt = false;
265+
rttNode2.rtt = false;
266+
rttNode3.rtt = false;
267+
break;
268+
269+
case 2:
270+
rttNode.rtt = true;
271+
rttNode2.rtt = true;
272+
rttNode3.rtt = true;
273+
break;
274+
275+
case 4:
276+
// Modify child texture properties in nested RTT node
277+
rocko4.x = 0;
278+
break;
279+
280+
case 5:
281+
nestedRTTNode1.rtt = false;
282+
break;
283+
284+
case 6:
285+
nestedRTTNode1.rtt = true;
286+
break;
287+
288+
default:
289+
// Reset to initial state
290+
rttNode.rtt = true;
291+
rttNode2.rtt = true;
292+
rttNode3.rtt = true;
293+
nestedRTTNode1.rtt = true;
294+
break;
295+
}
296+
};
297+
298+
return page;
254299
}

src/core/CoreNode.ts

Lines changed: 125 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,21 @@ export class CoreNode extends EventEmitter {
783783
if (this.textureOptions.preload) {
784784
texture.ctxTexture.load();
785785
}
786+
787+
texture.on('loaded', this.onTextureLoaded);
788+
texture.on('failed', this.onTextureFailed);
789+
texture.on('freed', this.onTextureFreed);
790+
791+
// If the parent is a render texture, the initial texture status
792+
// will be set to freed until the texture is processed by the
793+
// Render RTT nodes. So we only need to listen fo changes and
794+
// no need to check the texture.state until we restructure how
795+
// textures are being processed.
796+
if (this.parentHasRenderTexture) {
797+
this.notifyParentRTTOfUpdate();
798+
return;
799+
}
800+
786801
if (texture.state === 'loaded') {
787802
assertTruthy(texture.dimensions);
788803
this.onTextureLoaded(texture, texture.dimensions);
@@ -792,9 +807,6 @@ export class CoreNode extends EventEmitter {
792807
} else if (texture.state === 'freed') {
793808
this.onTextureFreed(texture);
794809
}
795-
texture.on('loaded', this.onTextureLoaded);
796-
texture.on('failed', this.onTextureFailed);
797-
texture.on('freed', this.onTextureFreed);
798810
});
799811
}
800812

@@ -822,9 +834,8 @@ export class CoreNode extends EventEmitter {
822834
this.stage.requestRender();
823835

824836
// If parent has a render texture, flag that we need to update
825-
// @todo: Reserve type for RTT updates
826837
if (this.parentHasRenderTexture) {
827-
this.setRTTUpdates(1);
838+
this.notifyParentRTTOfUpdate();
828839
}
829840

830841
this.emit('loaded', {
@@ -839,13 +850,23 @@ export class CoreNode extends EventEmitter {
839850
};
840851

841852
private onTextureFailed: TextureFailedEventHandler = (_, error) => {
853+
// If parent has a render texture, flag that we need to update
854+
if (this.parentHasRenderTexture) {
855+
this.notifyParentRTTOfUpdate();
856+
}
857+
842858
this.emit('failed', {
843859
type: 'texture',
844860
error,
845861
} satisfies NodeTextureFailedPayload);
846862
};
847863

848864
private onTextureFreed: TextureFreedEventHandler = () => {
865+
// If parent has a render texture, flag that we need to update
866+
if (this.parentHasRenderTexture) {
867+
this.notifyParentRTTOfUpdate();
868+
}
869+
849870
this.emit('freed', {
850871
type: 'texture',
851872
} satisfies NodeTextureFreedPayload);
@@ -866,24 +887,10 @@ export class CoreNode extends EventEmitter {
866887
const parent = this.props.parent;
867888
if (!parent) return;
868889

869-
// Inform the parent if it doesn’t already have a child update
870890
if ((parent.updateType & UpdateType.Children) === 0) {
891+
// Inform the parent if it doesn’t already have a child update
871892
parent.setUpdateType(UpdateType.Children);
872893
}
873-
874-
if (this.parentHasRenderTexture === false) return;
875-
876-
if (this.rtt === false) {
877-
if ((parent.updateType & UpdateType.RenderTexture) === 0) {
878-
this.setRTTUpdates(type);
879-
parent.setUpdateType(UpdateType.RenderTexture);
880-
}
881-
}
882-
883-
// If this node has outstanding RTT updates, propagate them
884-
if (this.hasRTTupdates) {
885-
this.setRTTUpdates(type);
886-
}
887894
}
888895

889896
sortChildren() {
@@ -988,24 +995,11 @@ export class CoreNode extends EventEmitter {
988995
const parent = this.props.parent;
989996
let renderState = null;
990997

991-
if (this.updateType & UpdateType.ParentRenderTexture) {
992-
let p = this.parent;
993-
while (p) {
994-
if (p.rtt) {
995-
this.parentHasRenderTexture = true;
996-
}
997-
p = p.parent;
998-
}
999-
}
1000-
1001-
// If we have render texture updates and not already running a full update
1002-
if (
1003-
this.updateType ^ UpdateType.All &&
1004-
this.updateType & UpdateType.RenderTexture
1005-
) {
1006-
for (let i = 0, length = this.children.length; i < length; i++) {
1007-
this.children[i]?.setUpdateType(UpdateType.All);
1008-
}
998+
// Handle specific RTT updates at this node level
999+
if (this.updateType & UpdateType.RenderTexture && this.rtt) {
1000+
// Only the RTT node itself triggers `renderToTexture`
1001+
this.hasRTTupdates = true;
1002+
this.stage.renderer?.renderToTexture(this);
10091003
}
10101004

10111005
if (this.updateType & UpdateType.Global) {
@@ -1130,11 +1124,7 @@ export class CoreNode extends EventEmitter {
11301124
return;
11311125
}
11321126

1133-
if (
1134-
this.updateType & UpdateType.Children &&
1135-
this.children.length > 0 &&
1136-
this.rtt === false
1137-
) {
1127+
if (this.updateType & UpdateType.Children && this.children.length > 0) {
11381128
for (let i = 0, length = this.children.length; i < length; i++) {
11391129
const child = this.children[i] as CoreNode;
11401130

@@ -1148,6 +1138,13 @@ export class CoreNode extends EventEmitter {
11481138
}
11491139
}
11501140

1141+
// If the node has an RTT parent and requires a texture re-render, inform the RTT parent
1142+
// if (this.parentHasRenderTexture && this.updateType & UpdateType.RenderTexture) {
1143+
// @TODO have a more scoped down updateType for RTT updates
1144+
if (this.parentHasRenderTexture && this.updateType > 0) {
1145+
this.notifyParentRTTOfUpdate();
1146+
}
1147+
11511148
// Sorting children MUST happen after children have been updated so
11521149
// that they have the oppotunity to update their calculated zIndex.
11531150
if (this.updateType & UpdateType.ZIndexSortedChildren) {
@@ -1168,6 +1165,31 @@ export class CoreNode extends EventEmitter {
11681165
this.childUpdateType = 0;
11691166
}
11701167

1168+
private notifyParentRTTOfUpdate() {
1169+
if (this.parent === null) {
1170+
return;
1171+
}
1172+
1173+
let rttNode: CoreNode | null = this.parent;
1174+
// Traverse up to find the RTT root node
1175+
while (rttNode && !rttNode.rtt) {
1176+
rttNode = rttNode.parent;
1177+
}
1178+
1179+
if (!rttNode) {
1180+
return;
1181+
}
1182+
1183+
// If an RTT node is found, mark it for re-rendering
1184+
rttNode.hasRTTupdates = true;
1185+
rttNode.setUpdateType(UpdateType.RenderTexture);
1186+
1187+
// if rttNode is nested, also make it update its RTT parent
1188+
if (rttNode.parentHasRenderTexture === true) {
1189+
rttNode.notifyParentRTTOfUpdate();
1190+
}
1191+
}
1192+
11711193
//check if CoreNode is renderable based on props
11721194
hasRenderableProperties(): boolean {
11731195
if (this.props.texture) {
@@ -1940,8 +1962,9 @@ export class CoreNode extends EventEmitter {
19401962
UpdateType.Children | UpdateType.ZIndexSortedChildren,
19411963
);
19421964

1965+
// If the new parent has an RTT enabled, apply RTT inheritance
19431966
if (newParent.rtt || newParent.parentHasRenderTexture) {
1944-
this.setRTTUpdates(UpdateType.All);
1967+
this.applyRTTInheritance(newParent);
19451968
}
19461969
}
19471970
this.updateScaleRotateTransform();
@@ -1963,43 +1986,77 @@ export class CoreNode extends EventEmitter {
19631986
}
19641987

19651988
set rtt(value: boolean) {
1966-
if (this.props.rtt === true) {
1967-
this.props.rtt = value;
1968-
1969-
// unload texture if we used to have a render texture
1970-
if (value === false && this.texture !== null) {
1971-
this.unloadTexture();
1972-
this.setUpdateType(UpdateType.All);
1973-
for (let i = 0, length = this.children.length; i < length; i++) {
1974-
this.children[i]!.parentHasRenderTexture = false;
1975-
}
1976-
this.stage.renderer?.removeRTTNode(this);
1977-
return;
1978-
}
1989+
if (this.props.rtt === value) {
1990+
return;
19791991
}
1992+
this.props.rtt = value;
19801993

1981-
// if the new value is false and we didnt have rtt previously, we don't need to do anything
1982-
if (value === false) {
1983-
return;
1994+
if (value) {
1995+
this.initRenderTexture();
1996+
this.markChildrenWithRTT();
1997+
} else {
1998+
this.cleanupRenderTexture();
19841999
}
19852000

1986-
// load texture
2001+
this.setUpdateType(UpdateType.RenderTexture);
2002+
2003+
if (this.parentHasRenderTexture) {
2004+
this.notifyParentRTTOfUpdate();
2005+
}
2006+
}
2007+
private initRenderTexture() {
19872008
this.texture = this.stage.txManager.loadTexture('RenderTexture', {
19882009
width: this.width,
19892010
height: this.height,
19902011
});
19912012
this.textureOptions.preload = true;
2013+
this.stage.renderer?.renderToTexture(this); // Only this RTT node
2014+
}
19922015

1993-
this.props.rtt = true;
1994-
this.hasRTTupdates = true;
1995-
this.setUpdateType(UpdateType.All);
2016+
private cleanupRenderTexture() {
2017+
this.unloadTexture();
2018+
this.clearRTTInheritance();
19962019

1997-
for (let i = 0, length = this.children.length; i < length; i++) {
1998-
this.children[i]!.setUpdateType(UpdateType.All);
2020+
this.stage.renderer?.removeRTTNode(this);
2021+
this.hasRTTupdates = false;
2022+
this.texture = null;
2023+
}
2024+
2025+
private markChildrenWithRTT(node: CoreNode | null = null) {
2026+
const parent = node || this;
2027+
2028+
for (const child of parent.children) {
2029+
child.setUpdateType(UpdateType.All);
2030+
child.parentHasRenderTexture = true;
2031+
child.markChildrenWithRTT();
2032+
}
2033+
}
2034+
2035+
// Apply RTT inheritance when a node has an RTT-enabled parent
2036+
private applyRTTInheritance(parent: CoreNode) {
2037+
if (parent.rtt) {
2038+
// Only the RTT node should be added to `renderToTexture`
2039+
parent.setUpdateType(UpdateType.RenderTexture);
19992040
}
20002041

2001-
// Store RTT nodes in a separate list
2002-
this.stage.renderer?.renderToTexture(this);
2042+
// Propagate `parentHasRenderTexture` downwards
2043+
this.markChildrenWithRTT(parent);
2044+
}
2045+
2046+
// Clear RTT inheritance when detaching from an RTT chain
2047+
private clearRTTInheritance() {
2048+
// if this node is RTT itself stop the propagation important for nested RTT nodes
2049+
// for the initial RTT node this is already handled in `set rtt`
2050+
if (this.rtt) {
2051+
return;
2052+
}
2053+
2054+
for (const child of this.children) {
2055+
// force child to update everything as the RTT inheritance has changed
2056+
child.parentHasRenderTexture = false;
2057+
child.setUpdateType(UpdateType.All);
2058+
child.clearRTTInheritance();
2059+
}
20032060
}
20042061

20052062
get shader(): BaseShaderController {
@@ -2158,11 +2215,6 @@ export class CoreNode extends EventEmitter {
21582215
this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children;
21592216
}
21602217

2161-
setRTTUpdates(type: number) {
2162-
this.hasRTTupdates = true;
2163-
this.parent?.setRTTUpdates(type);
2164-
}
2165-
21662218
animate(
21672219
props: Partial<CoreNodeAnimateProps>,
21682220
settings: Partial<AnimationSettings>,

0 commit comments

Comments
 (0)