@lightningjs/renderer
Version:
Lightning 3 Renderer
198 lines (176 loc) • 5.92 kB
text/typescript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2026 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 { describe, expect, it, vi } from 'vitest';
import { CoreTextNode, type CoreTextNodeProps } from '../../CoreTextNode.js';
import type { Stage } from '../../Stage.js';
import type { TextRenderer } from '../../text-rendering/TextRenderer.js';
import { WebGlRenderer } from './WebGlRenderer.js';
import { WebGlShaderProgram } from './WebGlShaderProgram.js';
import {
makeMockStage,
makeTextProps as makeSharedTextProps,
} from '../../../../test/mockStage.js';
const makeStage = (): Stage => makeMockStage({ pixelRatio: 2 });
const makeTextProps = (): CoreTextNodeProps =>
makeSharedTextProps({ h: 20, w: 100 });
const makeSdfTextRenderer = (): TextRenderer =>
({
clearCache: vi.fn(),
type: 'sdf',
font: {
isFontLoaded: vi.fn().mockReturnValue(true),
loadFont: vi.fn(),
waitingForFont: vi.fn(),
stopWaitingForFont: vi.fn(),
},
init: vi.fn(),
renderText: vi.fn().mockReturnValue({
width: 100,
height: 20,
layout: { glyphs: [], width: 100, height: 20 },
}),
addQuads: vi.fn().mockReturnValue(new Float32Array(0)),
renderQuads: vi.fn(),
} as unknown as TextRenderer);
const makeSdfTextNode = () => {
const node = new CoreTextNode(
makeStage(),
makeTextProps(),
makeSdfTextRenderer(),
);
node.parentHasRenderTexture = true;
node.framebufferDimensions = { w: 320, h: 180 };
node.rttParent = { framebufferDimensions: { w: 640, h: 360 } } as any;
return node;
};
describe('WebGlShaderProgram.bindRenderOp', () => {
function createProgram() {
const program = Object.create(
WebGlShaderProgram.prototype,
) as WebGlShaderProgram;
const bindTextures = vi.fn();
const bindBufferCollection = vi.fn();
const uniform1f = vi.fn();
const uniform2f = vi.fn();
const glw = {
canvas: { width: 1920, height: 1080 },
uniform1f,
uniform2f,
};
(program as any).bindTextures = bindTextures;
(program as any).bindBufferCollection = bindBufferCollection;
(program as any).glw = glw;
(program as any).useTimeValue = false;
(program as any).useSystemAlpha = false;
(program as any).useSystemDimensions = false;
return {
program,
bindTextures,
bindBufferCollection,
uniform1f,
uniform2f,
};
}
it('binds an SdfRenderOp using the main framebuffer resolution', () => {
const {
program,
bindTextures,
bindBufferCollection,
uniform1f,
uniform2f,
} = createProgram();
const renderOp = {
isCoreNode: false,
shader: { uniforms: { hasStoredUniforms: false } },
renderOpTextures: [],
quadBufferCollection: {},
parentHasRenderTexture: false,
framebufferDimensions: null,
rtt: false,
stage: { pixelRatio: 1.5 },
time: 0,
worldAlpha: 1,
w: 100,
h: 20,
};
program.bindRenderOp(renderOp as any);
expect(bindTextures).toHaveBeenCalledWith(renderOp.renderOpTextures);
expect(bindBufferCollection).toHaveBeenCalledWith(
renderOp.quadBufferCollection,
);
expect(uniform1f).toHaveBeenCalledWith('u_pixelRatio', 1.5);
expect(uniform2f).toHaveBeenCalledWith('u_resolution', 1920, 1080);
});
it('uses the parent framebuffer resolution for an SdfRenderOp in RTT', () => {
const { program, uniform1f, uniform2f } = createProgram();
const renderOp = {
isCoreNode: false,
shader: { uniforms: { hasStoredUniforms: false } },
renderOpTextures: [],
quadBufferCollection: {},
parentHasRenderTexture: true,
framebufferDimensions: { w: 320, h: 180 },
rtt: false,
stage: { pixelRatio: 2 },
time: 0,
worldAlpha: 1,
w: 100,
h: 20,
};
program.bindRenderOp(renderOp as any);
expect(uniform1f).toHaveBeenCalledWith('u_pixelRatio', 1);
expect(uniform2f).toHaveBeenCalledWith('u_resolution', 320, 180);
});
it('uses parent RTT dimensions for core-node render ops', () => {
const { program, uniform1f, uniform2f } = createProgram();
const renderOp = {
framebufferDimensions: { w: 320, h: 180 },
isCoreNode: true,
parentFramebufferDimensions: { w: 640, h: 360 },
parentHasRenderTexture: true,
quadBufferCollection: {},
renderOpTextures: [],
rtt: false,
shader: {
uniforms: { hasStoredUniforms: false },
},
stage: { pixelRatio: 2 },
time: 0,
worldAlpha: 1,
w: 100,
h: 20,
} as any;
program.bindRenderOp(renderOp);
expect(uniform1f).toHaveBeenCalledWith('u_pixelRatio', 1.0);
expect(uniform2f).toHaveBeenCalledWith('u_resolution', 640, 360);
});
});
describe('WebGlRenderer.canReuseRenderOp', () => {
it('reuses SDF text render ops with matching parent RTT dimensions', () => {
const renderer = Object.create(WebGlRenderer.prototype) as WebGlRenderer;
renderer.stencilDepth = 0;
const node = makeSdfTextNode();
node.stencilDepth = 0;
node.props.shader = {
shaderKey: 'default',
} as any;
renderer.curRenderOp = node;
expect(renderer.reuseRenderOp(node)).toBe(true);
});
});