Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@lightningjs/renderer

Package Overview
Dependencies
Maintainers
0
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lightningjs/renderer - npm Package Compare versions

Comparing version 2.6.2 to 2.7.0

7

dist/src/core/CoreNode.d.ts

@@ -681,2 +681,3 @@ import type { TextureOptions } from './CoreTextureManager.js';

update(delta: number, parentClippingRect: RectWithValid): void;
private notifyParentRTTOfUpdate;
hasRenderableProperties(): boolean;

@@ -777,2 +778,7 @@ checkRenderBounds(): CoreNodeRenderState;

set rtt(value: boolean);
private initRenderTexture;
private cleanupRenderTexture;
private markChildrenWithRTT;
private applyRTTInheritance;
private clearRTTInheritance;
get shader(): BaseShaderController;

@@ -809,3 +815,2 @@ set shader(value: BaseShaderController);

set strictBounds(v: boolean);
setRTTUpdates(type: number): void;
animate(props: Partial<CoreNodeAnimateProps>, settings: Partial<AnimationSettings>): IAnimationController;

@@ -812,0 +817,0 @@ flush(): void;

173

dist/src/core/CoreNode.js

@@ -223,2 +223,14 @@ /*

}
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);
// If the parent is a render texture, the initial texture status
// will be set to freed until the texture is processed by the
// Render RTT nodes. So we only need to listen fo changes and
// no need to check the texture.state until we restructure how
// textures are being processed.
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
return;
}
if (texture.state === 'loaded') {

@@ -235,5 +247,2 @@ assertTruthy(texture.dimensions);

}
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);
});

@@ -261,5 +270,4 @@ }

// If parent has a render texture, flag that we need to update
// @todo: Reserve type for RTT updates
if (this.parentHasRenderTexture) {
this.setRTTUpdates(1);
this.notifyParentRTTOfUpdate();
}

@@ -276,2 +284,6 @@ this.emit('loaded', {

onTextureFailed = (_, error) => {
// If parent has a render texture, flag that we need to update
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
this.emit('failed', {

@@ -283,2 +295,6 @@ type: 'texture',

onTextureFreed = () => {
// If parent has a render texture, flag that we need to update
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
this.emit('freed', {

@@ -302,18 +318,6 @@ type: 'texture',

return;
// Inform the parent if it doesn’t already have a child update
if ((parent.updateType & UpdateType.Children) === 0) {
// Inform the parent if it doesn’t already have a child update
parent.setUpdateType(UpdateType.Children);
}
if (this.parentHasRenderTexture === false)
return;
if (this.rtt === false) {
if ((parent.updateType & UpdateType.RenderTexture) === 0) {
this.setRTTUpdates(type);
parent.setUpdateType(UpdateType.RenderTexture);
}
}
// If this node has outstanding RTT updates, propagate them
if (this.hasRTTupdates) {
this.setRTTUpdates(type);
}
}

@@ -398,18 +402,8 @@ sortChildren() {

let renderState = null;
if (this.updateType & UpdateType.ParentRenderTexture) {
let p = this.parent;
while (p) {
if (p.rtt) {
this.parentHasRenderTexture = true;
}
p = p.parent;
}
// Handle specific RTT updates at this node level
if (this.updateType & UpdateType.RenderTexture && this.rtt) {
// Only the RTT node itself triggers `renderToTexture`
this.hasRTTupdates = true;
this.stage.renderer?.renderToTexture(this);
}
// If we have render texture updates and not already running a full update
if (this.updateType ^ UpdateType.All &&
this.updateType & UpdateType.RenderTexture) {
for (let i = 0, length = this.children.length; i < length; i++) {
this.children[i]?.setUpdateType(UpdateType.All);
}
}
if (this.updateType & UpdateType.Global) {

@@ -496,5 +490,3 @@ assertTruthy(this.localTransform);

}
if (this.updateType & UpdateType.Children &&
this.children.length > 0 &&
this.rtt === false) {
if (this.updateType & UpdateType.Children && this.children.length > 0) {
for (let i = 0, length = this.children.length; i < length; i++) {

@@ -509,2 +501,8 @@ const child = this.children[i];

}
// If the node has an RTT parent and requires a texture re-render, inform the RTT parent
// if (this.parentHasRenderTexture && this.updateType & UpdateType.RenderTexture) {
// @TODO have a more scoped down updateType for RTT updates
if (this.parentHasRenderTexture && this.updateType > 0) {
this.notifyParentRTTOfUpdate();
}
// Sorting children MUST happen after children have been updated so

@@ -527,2 +525,22 @@ // that they have the oppotunity to update their calculated zIndex.

}
notifyParentRTTOfUpdate() {
if (this.parent === null) {
return;
}
let rttNode = this.parent;
// Traverse up to find the RTT root node
while (rttNode && !rttNode.rtt) {
rttNode = rttNode.parent;
}
if (!rttNode) {
return;
}
// If an RTT node is found, mark it for re-rendering
rttNode.hasRTTupdates = true;
rttNode.setUpdateType(UpdateType.RenderTexture);
// if rttNode is nested, also make it update its RTT parent
if (rttNode.parentHasRenderTexture === true) {
rttNode.notifyParentRTTOfUpdate();
}
}
//check if CoreNode is renderable based on props

@@ -1136,4 +1154,5 @@ hasRenderableProperties() {

newParent.setUpdateType(UpdateType.Children | UpdateType.ZIndexSortedChildren);
// If the new parent has an RTT enabled, apply RTT inheritance
if (newParent.rtt || newParent.parentHasRenderTexture) {
this.setRTTUpdates(UpdateType.All);
this.applyRTTInheritance(newParent);
}

@@ -1155,20 +1174,19 @@ }

set rtt(value) {
if (this.props.rtt === true) {
this.props.rtt = value;
// unload texture if we used to have a render texture
if (value === false && this.texture !== null) {
this.unloadTexture();
this.setUpdateType(UpdateType.All);
for (let i = 0, length = this.children.length; i < length; i++) {
this.children[i].parentHasRenderTexture = false;
}
this.stage.renderer?.removeRTTNode(this);
return;
}
}
// if the new value is false and we didnt have rtt previously, we don't need to do anything
if (value === false) {
if (this.props.rtt === value) {
return;
}
// load texture
this.props.rtt = value;
if (value) {
this.initRenderTexture();
this.markChildrenWithRTT();
}
else {
this.cleanupRenderTexture();
}
this.setUpdateType(UpdateType.RenderTexture);
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
}
initRenderTexture() {
this.texture = this.stage.txManager.loadTexture('RenderTexture', {

@@ -1179,11 +1197,42 @@ width: this.width,

this.textureOptions.preload = true;
this.props.rtt = true;
this.hasRTTupdates = true;
this.setUpdateType(UpdateType.All);
for (let i = 0, length = this.children.length; i < length; i++) {
this.children[i].setUpdateType(UpdateType.All);
this.stage.renderer?.renderToTexture(this); // Only this RTT node
}
cleanupRenderTexture() {
this.unloadTexture();
this.clearRTTInheritance();
this.stage.renderer?.removeRTTNode(this);
this.hasRTTupdates = false;
this.texture = null;
}
markChildrenWithRTT(node = null) {
const parent = node || this;
for (const child of parent.children) {
child.setUpdateType(UpdateType.All);
child.parentHasRenderTexture = true;
child.markChildrenWithRTT();
}
// Store RTT nodes in a separate list
this.stage.renderer?.renderToTexture(this);
}
// Apply RTT inheritance when a node has an RTT-enabled parent
applyRTTInheritance(parent) {
if (parent.rtt) {
// Only the RTT node should be added to `renderToTexture`
parent.setUpdateType(UpdateType.RenderTexture);
}
// Propagate `parentHasRenderTexture` downwards
this.markChildrenWithRTT(parent);
}
// Clear RTT inheritance when detaching from an RTT chain
clearRTTInheritance() {
// if this node is RTT itself stop the propagation important for nested RTT nodes
// for the initial RTT node this is already handled in `set rtt`
if (this.rtt) {
return;
}
for (const child of this.children) {
// force child to update everything as the RTT inheritance has changed
child.parentHasRenderTexture = false;
child.setUpdateType(UpdateType.All);
child.clearRTTInheritance();
}
}
get shader() {

@@ -1316,6 +1365,2 @@ return this.props.shader;

}
setRTTUpdates(type) {
this.hasRTTupdates = true;
this.parent?.setRTTUpdates(type);
}
animate(props, settings) {

@@ -1322,0 +1367,0 @@ const animation = new CoreAnimation(this, props, settings);

@@ -23,4 +23,4 @@ /*

import { CanvasCoreTexture } from './CanvasCoreTexture.js';
import { getRadius } from './internal/C2DShaderUtils.js';
import { formatRgba, parseColor, } from './internal/ColorUtils.js';
import { getBorder, getRadius, strokeLine } from './internal/C2DShaderUtils.js';
import { formatRgba, parseColorRgba, parseColor, } from './internal/ColorUtils.js';
import { UnsupportedShader } from './shaders/UnsupportedShader.js';

@@ -93,3 +93,5 @@ export class CanvasCoreRenderer extends CoreRenderer {

const hasGradient = colorTl !== colorTr || colorTl !== colorBr;
const radius = quad.shader ? getRadius(quad) : 0;
const hasQuadShader = Boolean(quad.shader);
const radius = hasQuadShader ? getRadius(quad) : 0;
const border = hasQuadShader ? getBorder(quad) : undefined;
if (hasTransform || hasClipping || radius) {

@@ -160,2 +162,37 @@ ctx.save();

}
if (border && border.width) {
const borderWidth = border.width;
const borderInnerWidth = border.width / 2;
const borderColor = formatRgba(parseColorRgba(border.color ?? 0));
ctx.beginPath();
ctx.lineWidth = borderWidth;
ctx.strokeStyle = borderColor;
ctx.globalAlpha = alpha;
if (radius) {
ctx.roundRect(tx + borderInnerWidth, ty + borderInnerWidth, width - borderWidth, height - borderWidth, radius);
ctx.stroke();
}
else {
ctx.strokeRect(tx + borderInnerWidth, ty + borderInnerWidth, width - borderWidth, height - borderWidth);
}
ctx.globalAlpha = 1;
}
else if (hasQuadShader) {
const borderTop = getBorder(quad, 'Top');
const borderRight = getBorder(quad, 'Right');
const borderBottom = getBorder(quad, 'Bottom');
const borderLeft = getBorder(quad, 'Left');
if (borderTop) {
strokeLine(ctx, tx, ty, width, height, borderTop.width, borderTop.color, 'Top');
}
if (borderRight) {
strokeLine(ctx, tx, ty, width, height, borderRight.width, borderRight.color, 'Right');
}
if (borderBottom) {
strokeLine(ctx, tx, ty, width, height, borderBottom.width, borderBottom.color, 'Bottom');
}
if (borderLeft) {
strokeLine(ctx, tx, ty, width, height, borderLeft.width, borderLeft.color, 'Left');
}
}
if (hasTransform || hasClipping || radius) {

@@ -162,0 +199,0 @@ ctx.restore();

import type { QuadOptions } from '../../CoreRenderer.js';
import type { BorderEffectProps } from '../../webgl/shaders/effects/BorderEffect.js';
import type { RadiusEffectProps } from '../../webgl/shaders/effects/RadiusEffect.js';
type Direction = 'Top' | 'Right' | 'Bottom' | 'Left';
/**
* Extract `RoundedRectangle` shader radius to apply as a clipping
*/
export declare function getRadius(quad: QuadOptions): number;
export declare function getRadius(quad: QuadOptions): RadiusEffectProps['radius'];
/**
* Extract `RoundedRectangle` shader radius to apply as a clipping */
export declare function getBorder(quad: QuadOptions, direction?: '' | Direction): BorderEffectProps | undefined;
export declare function strokeLine(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, lineWidth: number | undefined, color: number | undefined, direction: Direction): void;
export {};

@@ -20,2 +20,3 @@ /*

import { ROUNDED_RECTANGLE_SHADER_TYPE, UnsupportedShader, } from '../shaders/UnsupportedShader.js';
import { formatRgba, parseColorRgba } from './ColorUtils.js';
/**

@@ -30,5 +31,72 @@ * Extract `RoundedRectangle` shader radius to apply as a clipping

}
else if (shType === 'DynamicShader') {
const effects = quad.shaderProps?.effects;
if (effects) {
const effect = effects.find((effect) => {
return effect.type === 'radius' && effect?.props?.radius;
});
return (effect && effect.type === 'radius' && effect.props.radius) || 0;
}
}
}
return 0;
}
/**
* Extract `RoundedRectangle` shader radius to apply as a clipping */
export function getBorder(quad, direction = '') {
if (quad.shader instanceof UnsupportedShader) {
const shType = quad.shader.shType;
if (shType === 'DynamicShader') {
const effects = quad.shaderProps?.effects;
if (effects && effects.length) {
const effect = effects.find((effect) => {
return (effect.type === `border${direction}` &&
effect.props &&
effect.props.width);
});
return effect && effect.props;
}
}
}
return undefined;
}
export function strokeLine(ctx, x, y, width, height, lineWidth = 0, color, direction) {
if (!lineWidth) {
return;
}
let sx, sy = 0;
let ex, ey = 0;
switch (direction) {
case 'Top':
sx = x;
sy = y;
ex = width + x;
ey = y;
break;
case 'Right':
sx = x + width;
sy = y;
ex = x + width;
ey = y + height;
break;
case 'Bottom':
sx = x;
sy = y + height;
ex = x + width;
ey = y + height;
break;
case 'Left':
sx = x;
sy = y;
ex = x;
ey = y + height;
break;
}
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = formatRgba(parseColorRgba(color ?? 0));
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.stroke();
}
//# sourceMappingURL=C2DShaderUtils.js.map

@@ -13,4 +13,8 @@ export interface IParsedColor {

/**
* Extract color components
*/
export declare function parseColorRgba(rgba: number): IParsedColor;
/**
* Format a parsed color into a rgba CSS color
*/
export declare function formatRgba({ a, r, g, b }: IParsedColor): string;

@@ -40,2 +40,15 @@ /*

/**
* Extract color components
*/
export function parseColorRgba(rgba) {
if (rgba === 0xffffffff) {
return WHITE;
}
const r = (rgba >>> 24) & 0xff;
const g = (rgba >>> 16) & 0xff & 0xff;
const b = (rgba >>> 8) & 0xff & 0xff;
const a = (rgba & 0xff & 0xff) / 255;
return { isWhite: false, r, g, b, a };
}
/**
* Format a parsed color into a rgba CSS color

@@ -42,0 +55,0 @@ */

@@ -26,5 +26,5 @@ /*

this.shType = shType;
if (shType !== ROUNDED_RECTANGLE_SHADER_TYPE) {
console.warn('Unsupported shader:', shType);
}
// if (shType !== ROUNDED_RECTANGLE_SHADER_TYPE) {
// console.warn('Unsupported shader:', shType);
// }
}

@@ -31,0 +31,0 @@ bindRenderOp() {

@@ -0,1 +1,19 @@

/*
* 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 { updateWebSafeRadius, validateArrayLength4 } from './EffectUtils.js';

@@ -2,0 +20,0 @@ import { ShaderEffect, } from './ShaderEffect.js';

@@ -97,2 +97,25 @@ import { CoreRenderer, type BufferInfo, type CoreRendererOptions, type QuadOptions } from '../CoreRenderer.js';

renderToTexture(node: CoreNode): void;
/**
* Inserts an RTT node into `this.rttNodes` while maintaining the correct rendering order based on hierarchy.
*
* Rendering order for RTT nodes is critical when nested RTT nodes exist in a parent-child relationship.
* Specifically:
* - Child RTT nodes must be rendered before their RTT-enabled parents to ensure proper texture composition.
* - If an RTT node is added and it has existing RTT children, it should be rendered after those children.
*
* This function addresses both cases by:
* 1. **Checking Upwards**: It traverses the node's hierarchy upwards to identify any RTT parent
* already in `rttNodes`. If an RTT parent is found, the new node is placed before this parent.
* 2. **Checking Downwards**: It traverses the node’s children recursively to find any RTT-enabled
* children that are already in `rttNodes`. If such children are found, the new node is inserted
* after the last (highest index) RTT child node.
*
* The final calculated insertion index ensures the new node is positioned in `rttNodes` to respect
* both parent-before-child and child-before-parent rendering rules, preserving the correct order
* for the WebGL renderer.
*
* @param node - The RTT-enabled CoreNode to be added to `rttNodes` in the appropriate hierarchical position.
*/
private insertRTTNodeInOrder;
private findMaxChildRTTIndex;
renderRTTNodes(): void;

@@ -99,0 +122,0 @@ removeRTTNode(node: CoreNode): void;

@@ -465,5 +465,68 @@ /*

}
// @todo: Better bottom up rendering order
this.rttNodes.unshift(node);
this.insertRTTNodeInOrder(node);
}
/**
* Inserts an RTT node into `this.rttNodes` while maintaining the correct rendering order based on hierarchy.
*
* Rendering order for RTT nodes is critical when nested RTT nodes exist in a parent-child relationship.
* Specifically:
* - Child RTT nodes must be rendered before their RTT-enabled parents to ensure proper texture composition.
* - If an RTT node is added and it has existing RTT children, it should be rendered after those children.
*
* This function addresses both cases by:
* 1. **Checking Upwards**: It traverses the node's hierarchy upwards to identify any RTT parent
* already in `rttNodes`. If an RTT parent is found, the new node is placed before this parent.
* 2. **Checking Downwards**: It traverses the node’s children recursively to find any RTT-enabled
* children that are already in `rttNodes`. If such children are found, the new node is inserted
* after the last (highest index) RTT child node.
*
* The final calculated insertion index ensures the new node is positioned in `rttNodes` to respect
* both parent-before-child and child-before-parent rendering rules, preserving the correct order
* for the WebGL renderer.
*
* @param node - The RTT-enabled CoreNode to be added to `rttNodes` in the appropriate hierarchical position.
*/
insertRTTNodeInOrder(node) {
let insertIndex = this.rttNodes.length; // Default to the end of the array
// 1. Traverse upwards to ensure the node is placed before its RTT parent (if any).
let currentNode = node;
while (currentNode) {
if (!currentNode.parent) {
break;
}
const parentIndex = this.rttNodes.indexOf(currentNode.parent);
if (parentIndex !== -1) {
// Found an RTT parent in the list; set insertIndex to place node before the parent
insertIndex = parentIndex;
break;
}
currentNode = currentNode.parent;
}
// 2. Traverse downwards to ensure the node is placed after any RTT children.
// Look through each child recursively to see if any are already in rttNodes.
const maxChildIndex = this.findMaxChildRTTIndex(node);
if (maxChildIndex !== -1) {
// Adjust insertIndex to be after the last child RTT node
insertIndex = Math.max(insertIndex, maxChildIndex + 1);
}
// 3. Insert the node at the calculated position
this.rttNodes.splice(insertIndex, 0, node);
}
// Helper function to find the highest index of any RTT children of a node within rttNodes
findMaxChildRTTIndex(node) {
let maxIndex = -1;
const traverseChildren = (currentNode) => {
const currentIndex = this.rttNodes.indexOf(currentNode);
if (currentIndex !== -1) {
maxIndex = Math.max(maxIndex, currentIndex);
}
// Recursively check all children of the current node
for (const child of currentNode.children) {
traverseChildren(child);
}
};
// Start traversal directly with the provided node
traverseChildren(node);
return maxIndex;
}
renderRTTNodes() {

@@ -498,9 +561,2 @@ const { glw } = this;

}
child.update(this.stage.deltaTime, {
x: 0,
y: 0,
width: 0,
height: 0,
valid: false,
});
this.stage.addQuads(child);

@@ -507,0 +563,0 @@ child.hasRTTupdates = false;

{
"name": "@lightningjs/renderer",
"version": "2.6.2",
"version": "2.7.0",
"description": "Lightning 3 Renderer",

@@ -64,3 +64,2 @@ "type": "module",

],
"packageManager": "pnpm@8.9.2",
"engines": {

@@ -67,0 +66,0 @@ "npm": ">= 10.0.0",

@@ -786,2 +786,17 @@ /*

}
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);
// If the parent is a render texture, the initial texture status
// will be set to freed until the texture is processed by the
// Render RTT nodes. So we only need to listen fo changes and
// no need to check the texture.state until we restructure how
// textures are being processed.
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
return;
}
if (texture.state === 'loaded') {

@@ -796,5 +811,2 @@ assertTruthy(texture.dimensions);

}
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);
});

@@ -827,5 +839,4 @@ }

// If parent has a render texture, flag that we need to update
// @todo: Reserve type for RTT updates
if (this.parentHasRenderTexture) {
this.setRTTUpdates(1);
this.notifyParentRTTOfUpdate();
}

@@ -845,2 +856,7 @@

private onTextureFailed: TextureFailedEventHandler = (_, error) => {
// If parent has a render texture, flag that we need to update
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
this.emit('failed', {

@@ -853,2 +869,7 @@ type: 'texture',

private onTextureFreed: TextureFreedEventHandler = () => {
// If parent has a render texture, flag that we need to update
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
this.emit('freed', {

@@ -874,20 +895,6 @@ type: 'texture',

// Inform the parent if it doesn’t already have a child update
if ((parent.updateType & UpdateType.Children) === 0) {
// Inform the parent if it doesn’t already have a child update
parent.setUpdateType(UpdateType.Children);
}
if (this.parentHasRenderTexture === false) return;
if (this.rtt === false) {
if ((parent.updateType & UpdateType.RenderTexture) === 0) {
this.setRTTUpdates(type);
parent.setUpdateType(UpdateType.RenderTexture);
}
}
// If this node has outstanding RTT updates, propagate them
if (this.hasRTTupdates) {
this.setRTTUpdates(type);
}
}

@@ -997,22 +1004,9 @@

if (this.updateType & UpdateType.ParentRenderTexture) {
let p = this.parent;
while (p) {
if (p.rtt) {
this.parentHasRenderTexture = true;
}
p = p.parent;
}
// Handle specific RTT updates at this node level
if (this.updateType & UpdateType.RenderTexture && this.rtt) {
// Only the RTT node itself triggers `renderToTexture`
this.hasRTTupdates = true;
this.stage.renderer?.renderToTexture(this);
}
// If we have render texture updates and not already running a full update
if (
this.updateType ^ UpdateType.All &&
this.updateType & UpdateType.RenderTexture
) {
for (let i = 0, length = this.children.length; i < length; i++) {
this.children[i]?.setUpdateType(UpdateType.All);
}
}
if (this.updateType & UpdateType.Global) {

@@ -1140,7 +1134,3 @@ assertTruthy(this.localTransform);

if (
this.updateType & UpdateType.Children &&
this.children.length > 0 &&
this.rtt === false
) {
if (this.updateType & UpdateType.Children && this.children.length > 0) {
for (let i = 0, length = this.children.length; i < length; i++) {

@@ -1159,2 +1149,9 @@ const child = this.children[i] as CoreNode;

// If the node has an RTT parent and requires a texture re-render, inform the RTT parent
// if (this.parentHasRenderTexture && this.updateType & UpdateType.RenderTexture) {
// @TODO have a more scoped down updateType for RTT updates
if (this.parentHasRenderTexture && this.updateType > 0) {
this.notifyParentRTTOfUpdate();
}
// Sorting children MUST happen after children have been updated so

@@ -1180,2 +1177,27 @@ // that they have the oppotunity to update their calculated zIndex.

private notifyParentRTTOfUpdate() {
if (this.parent === null) {
return;
}
let rttNode: CoreNode | null = this.parent;
// Traverse up to find the RTT root node
while (rttNode && !rttNode.rtt) {
rttNode = rttNode.parent;
}
if (!rttNode) {
return;
}
// If an RTT node is found, mark it for re-rendering
rttNode.hasRTTupdates = true;
rttNode.setUpdateType(UpdateType.RenderTexture);
// if rttNode is nested, also make it update its RTT parent
if (rttNode.parentHasRenderTexture === true) {
rttNode.notifyParentRTTOfUpdate();
}
}
//check if CoreNode is renderable based on props

@@ -1953,4 +1975,5 @@ hasRenderableProperties(): boolean {

// If the new parent has an RTT enabled, apply RTT inheritance
if (newParent.rtt || newParent.parentHasRenderTexture) {
this.setRTTUpdates(UpdateType.All);
this.applyRTTInheritance(newParent);
}

@@ -1977,23 +2000,21 @@ }

set rtt(value: boolean) {
if (this.props.rtt === true) {
this.props.rtt = value;
if (this.props.rtt === value) {
return;
}
this.props.rtt = value;
// unload texture if we used to have a render texture
if (value === false && this.texture !== null) {
this.unloadTexture();
this.setUpdateType(UpdateType.All);
for (let i = 0, length = this.children.length; i < length; i++) {
this.children[i]!.parentHasRenderTexture = false;
}
this.stage.renderer?.removeRTTNode(this);
return;
}
if (value) {
this.initRenderTexture();
this.markChildrenWithRTT();
} else {
this.cleanupRenderTexture();
}
// if the new value is false and we didnt have rtt previously, we don't need to do anything
if (value === false) {
return;
this.setUpdateType(UpdateType.RenderTexture);
if (this.parentHasRenderTexture) {
this.notifyParentRTTOfUpdate();
}
// load texture
}
private initRenderTexture() {
this.texture = this.stage.txManager.loadTexture('RenderTexture', {

@@ -2004,15 +2025,51 @@ width: this.width,

this.textureOptions.preload = true;
this.stage.renderer?.renderToTexture(this); // Only this RTT node
}
this.props.rtt = true;
this.hasRTTupdates = true;
this.setUpdateType(UpdateType.All);
private cleanupRenderTexture() {
this.unloadTexture();
this.clearRTTInheritance();
for (let i = 0, length = this.children.length; i < length; i++) {
this.children[i]!.setUpdateType(UpdateType.All);
this.stage.renderer?.removeRTTNode(this);
this.hasRTTupdates = false;
this.texture = null;
}
private markChildrenWithRTT(node: CoreNode | null = null) {
const parent = node || this;
for (const child of parent.children) {
child.setUpdateType(UpdateType.All);
child.parentHasRenderTexture = true;
child.markChildrenWithRTT();
}
}
// Store RTT nodes in a separate list
this.stage.renderer?.renderToTexture(this);
// Apply RTT inheritance when a node has an RTT-enabled parent
private applyRTTInheritance(parent: CoreNode) {
if (parent.rtt) {
// Only the RTT node should be added to `renderToTexture`
parent.setUpdateType(UpdateType.RenderTexture);
}
// Propagate `parentHasRenderTexture` downwards
this.markChildrenWithRTT(parent);
}
// Clear RTT inheritance when detaching from an RTT chain
private clearRTTInheritance() {
// if this node is RTT itself stop the propagation important for nested RTT nodes
// for the initial RTT node this is already handled in `set rtt`
if (this.rtt) {
return;
}
for (const child of this.children) {
// force child to update everything as the RTT inheritance has changed
child.parentHasRenderTexture = false;
child.setUpdateType(UpdateType.All);
child.clearRTTInheritance();
}
}
get shader(): BaseShaderController {

@@ -2174,7 +2231,2 @@ return this.props.shader;

setRTTUpdates(type: number) {
this.hasRTTupdates = true;
this.parent?.setRTTUpdates(type);
}
animate(

@@ -2181,0 +2233,0 @@ props: Partial<CoreNodeAnimateProps>,

@@ -33,5 +33,6 @@ /*

import { CanvasCoreTexture } from './CanvasCoreTexture.js';
import { getRadius } from './internal/C2DShaderUtils.js';
import { getBorder, getRadius, strokeLine } from './internal/C2DShaderUtils.js';
import {
formatRgba,
parseColorRgba,
parseColor,

@@ -137,3 +138,5 @@ type IParsedColor,

const hasGradient = colorTl !== colorTr || colorTl !== colorBr;
const radius = quad.shader ? getRadius(quad) : 0;
const hasQuadShader = Boolean(quad.shader);
const radius = hasQuadShader ? getRadius(quad) : 0;
const border = hasQuadShader ? getBorder(quad) : undefined;

@@ -216,2 +219,88 @@ if (hasTransform || hasClipping || radius) {

if (border && border.width) {
const borderWidth = border.width;
const borderInnerWidth = border.width / 2;
const borderColor = formatRgba(parseColorRgba(border.color ?? 0));
ctx.beginPath();
ctx.lineWidth = borderWidth;
ctx.strokeStyle = borderColor;
ctx.globalAlpha = alpha;
if (radius) {
ctx.roundRect(
tx + borderInnerWidth,
ty + borderInnerWidth,
width - borderWidth,
height - borderWidth,
radius,
);
ctx.stroke();
} else {
ctx.strokeRect(
tx + borderInnerWidth,
ty + borderInnerWidth,
width - borderWidth,
height - borderWidth,
);
}
ctx.globalAlpha = 1;
} else if (hasQuadShader) {
const borderTop = getBorder(quad, 'Top');
const borderRight = getBorder(quad, 'Right');
const borderBottom = getBorder(quad, 'Bottom');
const borderLeft = getBorder(quad, 'Left');
if (borderTop) {
strokeLine(
ctx,
tx,
ty,
width,
height,
borderTop.width,
borderTop.color,
'Top',
);
}
if (borderRight) {
strokeLine(
ctx,
tx,
ty,
width,
height,
borderRight.width,
borderRight.color,
'Right',
);
}
if (borderBottom) {
strokeLine(
ctx,
tx,
ty,
width,
height,
borderBottom.width,
borderBottom.color,
'Bottom',
);
}
if (borderLeft) {
strokeLine(
ctx,
tx,
ty,
width,
height,
borderLeft.width,
borderLeft.color,
'Left',
);
}
}
if (hasTransform || hasClipping || radius) {

@@ -218,0 +307,0 @@ ctx.restore();

@@ -21,2 +21,5 @@ /*

import type { QuadOptions } from '../../CoreRenderer.js';
import type { BorderEffectProps } from '../../webgl/shaders/effects/BorderEffect.js';
import type { RadiusEffectProps } from '../../webgl/shaders/effects/RadiusEffect.js';
import type { EffectDescUnion } from '../../webgl/shaders/effects/ShaderEffect.js';
import {

@@ -26,7 +29,10 @@ ROUNDED_RECTANGLE_SHADER_TYPE,

} from '../shaders/UnsupportedShader.js';
import { formatRgba, parseColorRgba } from './ColorUtils.js';
type Direction = 'Top' | 'Right' | 'Bottom' | 'Left';
/**
* Extract `RoundedRectangle` shader radius to apply as a clipping
*/
export function getRadius(quad: QuadOptions): number {
export function getRadius(quad: QuadOptions): RadiusEffectProps['radius'] {
if (quad.shader instanceof UnsupportedShader) {

@@ -36,2 +42,14 @@ const shType = quad.shader.shType;

return (quad.shaderProps?.radius as number) ?? 0;
} else if (shType === 'DynamicShader') {
const effects = quad.shaderProps?.effects as
| EffectDescUnion[]
| undefined;
if (effects) {
const effect = effects.find((effect: EffectDescUnion) => {
return effect.type === 'radius' && effect?.props?.radius;
});
return (effect && effect.type === 'radius' && effect.props.radius) || 0;
}
}

@@ -41,1 +59,84 @@ }

}
/**
* Extract `RoundedRectangle` shader radius to apply as a clipping */
export function getBorder(
quad: QuadOptions,
direction: '' | Direction = '',
): BorderEffectProps | undefined {
if (quad.shader instanceof UnsupportedShader) {
const shType = quad.shader.shType;
if (shType === 'DynamicShader') {
const effects = quad.shaderProps?.effects as
| EffectDescUnion[]
| undefined;
if (effects && effects.length) {
const effect = effects.find((effect: EffectDescUnion) => {
return (
effect.type === `border${direction}` &&
effect.props &&
effect.props.width
);
});
return effect && effect.props;
}
}
}
return undefined;
}
export function strokeLine(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
lineWidth = 0,
color: number | undefined,
direction: Direction,
) {
if (!lineWidth) {
return;
}
let sx,
sy = 0;
let ex,
ey = 0;
switch (direction) {
case 'Top':
sx = x;
sy = y;
ex = width + x;
ey = y;
break;
case 'Right':
sx = x + width;
sy = y;
ex = x + width;
ey = y + height;
break;
case 'Bottom':
sx = x;
sy = y + height;
ex = x + width;
ey = y + height;
break;
case 'Left':
sx = x;
sy = y;
ex = x;
ey = y + height;
break;
}
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = formatRgba(parseColorRgba(color ?? 0));
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.stroke();
}

@@ -51,2 +51,16 @@ /*

/**
* Extract color components
*/
export function parseColorRgba(rgba: number): IParsedColor {
if (rgba === 0xffffffff) {
return WHITE;
}
const r = (rgba >>> 24) & 0xff;
const g = (rgba >>> 16) & 0xff & 0xff;
const b = (rgba >>> 8) & 0xff & 0xff;
const a = (rgba & 0xff & 0xff) / 255;
return { isWhite: false, r, g, b, a };
}
/**
* Format a parsed color into a rgba CSS color

@@ -53,0 +67,0 @@ */

@@ -30,5 +30,5 @@ /*

this.shType = shType;
if (shType !== ROUNDED_RECTANGLE_SHADER_TYPE) {
console.warn('Unsupported shader:', shType);
}
// if (shType !== ROUNDED_RECTANGLE_SHADER_TYPE) {
// console.warn('Unsupported shader:', shType);
// }
}

@@ -35,0 +35,0 @@

@@ -19,3 +19,2 @@ /*

*/
import type { DynamicShaderProps } from '../DynamicShader.js';
import { updateWebSafeRadius, validateArrayLength4 } from './EffectUtils.js';

@@ -26,3 +25,2 @@ import {

type ShaderEffectUniforms,
type ShaderEffectValueMap,
} from './ShaderEffect.js';

@@ -29,0 +27,0 @@

import type { EffectMap } from '../../../../CoreShaderManager.js';
import type { ExtractProps } from '../../../../CoreTextureManager.js';
import type { WebGlContextWrapper } from '../../../../lib/WebGlContextWrapper.js';
import type {

@@ -5,0 +4,0 @@ AlphaShaderProp,

@@ -91,3 +91,6 @@ /*

this._state = 'failed';
this.textureSource.setState('failed', new Error('Could not create WebGL Texture'));
this.textureSource.setState(
'failed',
new Error('Could not create WebGL Texture'),
);
console.error('Could not create WebGL Texture');

@@ -94,0 +97,0 @@ return;

@@ -629,9 +629,84 @@ /*

// @todo: Better bottom up rendering order
this.rttNodes.unshift(node);
this.insertRTTNodeInOrder(node);
}
/**
* Inserts an RTT node into `this.rttNodes` while maintaining the correct rendering order based on hierarchy.
*
* Rendering order for RTT nodes is critical when nested RTT nodes exist in a parent-child relationship.
* Specifically:
* - Child RTT nodes must be rendered before their RTT-enabled parents to ensure proper texture composition.
* - If an RTT node is added and it has existing RTT children, it should be rendered after those children.
*
* This function addresses both cases by:
* 1. **Checking Upwards**: It traverses the node's hierarchy upwards to identify any RTT parent
* already in `rttNodes`. If an RTT parent is found, the new node is placed before this parent.
* 2. **Checking Downwards**: It traverses the node’s children recursively to find any RTT-enabled
* children that are already in `rttNodes`. If such children are found, the new node is inserted
* after the last (highest index) RTT child node.
*
* The final calculated insertion index ensures the new node is positioned in `rttNodes` to respect
* both parent-before-child and child-before-parent rendering rules, preserving the correct order
* for the WebGL renderer.
*
* @param node - The RTT-enabled CoreNode to be added to `rttNodes` in the appropriate hierarchical position.
*/
private insertRTTNodeInOrder(node: CoreNode) {
let insertIndex = this.rttNodes.length; // Default to the end of the array
// 1. Traverse upwards to ensure the node is placed before its RTT parent (if any).
let currentNode: CoreNode = node;
while (currentNode) {
if (!currentNode.parent) {
break;
}
const parentIndex = this.rttNodes.indexOf(currentNode.parent);
if (parentIndex !== -1) {
// Found an RTT parent in the list; set insertIndex to place node before the parent
insertIndex = parentIndex;
break;
}
currentNode = currentNode.parent;
}
// 2. Traverse downwards to ensure the node is placed after any RTT children.
// Look through each child recursively to see if any are already in rttNodes.
const maxChildIndex = this.findMaxChildRTTIndex(node);
if (maxChildIndex !== -1) {
// Adjust insertIndex to be after the last child RTT node
insertIndex = Math.max(insertIndex, maxChildIndex + 1);
}
// 3. Insert the node at the calculated position
this.rttNodes.splice(insertIndex, 0, node);
}
// Helper function to find the highest index of any RTT children of a node within rttNodes
private findMaxChildRTTIndex(node: CoreNode): number {
let maxIndex = -1;
const traverseChildren = (currentNode: CoreNode) => {
const currentIndex = this.rttNodes.indexOf(currentNode);
if (currentIndex !== -1) {
maxIndex = Math.max(maxIndex, currentIndex);
}
// Recursively check all children of the current node
for (const child of currentNode.children) {
traverseChildren(child);
}
};
// Start traversal directly with the provided node
traverseChildren(node);
return maxIndex;
}
renderRTTNodes() {
const { glw } = this;
const { txManager } = this.stage;
// Render all associated RTT nodes to their textures

@@ -666,12 +741,6 @@ for (let i = 0; i < this.rttNodes.length; i++) {

const child = node.children[i];
if (!child) {
continue;
}
child.update(this.stage.deltaTime, {
x: 0,
y: 0,
width: 0,
height: 0,
valid: false,
});

@@ -678,0 +747,0 @@ this.stage.addQuads(child);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc