Skip to content
Open
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
4 changes: 3 additions & 1 deletion src/core/Autosizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export enum AutosizeUpdateType {
const applyDimensions = (node: CoreNode, w: number, h: number) => {
node.props.w = w;
node.props.h = h;
node.setUpdateType(UpdateType.Local);
// Direct props.w/h write bypasses the w/h setters, so raise RecalcUniforms
// here too — dimensions feed shader uniforms.
node.setUpdateType(UpdateType.Local | UpdateType.RecalcUniforms);
};

const getFilteredChildren = (
Expand Down
83 changes: 83 additions & 0 deletions src/core/CoreNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1180,4 +1180,87 @@ describe('set color()', () => {
expect(node.clippingRect.h).toBe(30);
});
});

describe('RecalcUniforms scoping', () => {
const makeAttachedNode = () => {
// Fresh stage per node: earlier tests can mutate the shared stage
// mock's bound objects through by-reference strictBound assignment
// in createRenderBounds.
const localStage = mock<Stage>({
strictBound: createBound(0, 0, 200, 200),
preloadBound: createBound(0, 0, 200, 200),
defaultTexture: {
state: 'loaded',
},
renderer: mock<CoreRenderer>() as CoreRenderer,
});
const parent = new CoreNode(localStage, defaultProps());
parent.globalTransform = Matrix3d.identity();
parent.worldAlpha = 1;

const node = new CoreNode(
localStage,
defaultProps({ parent, w: 100, h: 100 }),
);
node.alpha = 1;
return node;
};

it('should not set RecalcUniforms on pure translation', () => {
const node = makeAttachedNode();
node.update(0, clippingRect);

node.x = 50;
node.y = 25;

expect(node.updateType & UpdateType.Local).toBe(UpdateType.Local);
expect(node.updateType & UpdateType.RecalcUniforms).toBe(0);
});

it('should set RecalcUniforms when w changes', () => {
const node = makeAttachedNode();
node.update(0, clippingRect);

node.w = 150;

expect(node.updateType & UpdateType.RecalcUniforms).toBe(
UpdateType.RecalcUniforms,
);
});

it('should set RecalcUniforms when h changes', () => {
const node = makeAttachedNode();
node.update(0, clippingRect);

node.h = 75;

expect(node.updateType & UpdateType.RecalcUniforms).toBe(
UpdateType.RecalcUniforms,
);
});

it('should run the shader updater on resize but not on translation', () => {
const node = makeAttachedNode();
const shader = {
shaderKey: 'test',
update: vi.fn(),
attachNode: vi.fn(),
time: undefined,
};
// Assignment raises RecalcUniforms | IsRenderable via the setter
node.shader = shader as never;
node.update(0, clippingRect);
expect(shader.update).toHaveBeenCalledTimes(1);

// Pure translation: no uniform recompute
node.x = 50;
node.update(0, clippingRect);
expect(shader.update).toHaveBeenCalledTimes(1);

// Resize: uniforms depend on dimensions, must recompute
node.w = 150;
node.update(0, clippingRect);
expect(shader.update).toHaveBeenCalledTimes(2);
});
});
});
16 changes: 13 additions & 3 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1355,7 +1355,13 @@ export class CoreNode extends EventEmitter {
this.calculateRenderCoords();
this.updateBoundingRect();

updateType |= UpdateType.RenderState | UpdateType.RecalcUniforms;
// RecalcUniforms is intentionally NOT set here: shader uniforms are a
// function of resolvedProps + w/h only (that is exactly the shader
// value-key cache key), so pure transform changes (translate, scale,
// rotate) cannot affect them. The flag is raised where w/h actually
// change: the w/h setters, Autosizer.applyDimensions, text layout
// application, and the shader setter itself.
updateType |= UpdateType.RenderState;

//only propagate children updates if not autosizing
if ((updateType & UpdateType.Autosize) === 0) {
Expand Down Expand Up @@ -2232,7 +2238,9 @@ export class CoreNode extends EventEmitter {
const props = this.props;
if (props.w !== value) {
props.w = value;
let updateType = UpdateType.Local;
// Dimensions feed shader uniforms (e.g. factored corner radius), so a
// resize must recompute them; see the Global-update branch in update().
let updateType = UpdateType.Local | UpdateType.RecalcUniforms;

if (
props.texture !== null &&
Expand Down Expand Up @@ -2262,7 +2270,9 @@ export class CoreNode extends EventEmitter {
const props = this.props;
if (props.h !== value) {
props.h = value;
let updateType = UpdateType.Local;
// Dimensions feed shader uniforms (e.g. factored corner radius), so a
// resize must recompute them; see the Global-update branch in update().
let updateType = UpdateType.Local | UpdateType.RecalcUniforms;

if (
props.texture !== null &&
Expand Down
8 changes: 6 additions & 2 deletions src/core/CoreTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,12 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
this.props.w = width;
this.props.h = height;

// Text dimensions may have changed, recalculate transforms and bounds
this.setUpdateType(UpdateType.Local | UpdateType.RenderBounds);
// Text dimensions may have changed, recalculate transforms and bounds.
// RecalcUniforms because the direct props.w/h write above bypasses the
// w/h setters and dimensions feed shader uniforms.
this.setUpdateType(
UpdateType.Local | UpdateType.RenderBounds | UpdateType.RecalcUniforms,
);

// Handle SDF renderer (uses layout caching)
if (textRendererType === 'sdf') {
Expand Down
Loading