UNPKG

@lightningjs/renderer

Version:
275 lines (253 loc) 7.63 kB
/* * 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 { mock } from 'vitest-mock-extended'; import { CoreTextNode, type CoreTextNodeProps } from '../../CoreTextNode.js'; import type { Stage } from '../../Stage.js'; import type { TextRenderer } from '../../text-rendering/TextRenderer.js'; import { createBound } from '../../lib/utils.js'; import type { CoreRenderer } from '../CoreRenderer.js'; import { WebGlRenderer } from './WebGlRenderer.js'; import { WebGlShaderProgram } from './WebGlShaderProgram.js'; const makeStage = (): Stage => mock<Stage>({ strictBound: createBound(0, 0, 1920, 1080), preloadBound: createBound(0, 0, 1920, 1080), defaultTexture: { state: 'loaded', }, pixelRatio: 2, renderer: mock<CoreRenderer>() as CoreRenderer, }); const makeTextProps = (): CoreTextNodeProps => ({ alpha: 1, autosize: false, boundsMargin: null, clipping: false, color: 0xffffffff, colorBl: 0xffffffff, colorBottom: 0xffffffff, colorBr: 0xffffffff, colorLeft: 0xffffffff, colorRight: 0xffffffff, colorTl: 0xffffffff, colorTop: 0xffffffff, colorTr: 0xffffffff, h: 20, mount: 0, mountX: 0, mountY: 0, parent: null, pivot: 0, pivotX: 0, pivotY: 0, rotation: 0, rtt: false, scale: 1, scaleX: 1, scaleY: 1, shader: null, src: '', texture: null, textureOptions: {}, w: 100, x: 0, y: 0, zIndex: 0, text: 'Test', textAlign: 'left', contain: 'none', fontFamily: 'Arial', fontStyle: 'normal', fontSize: 16, letterSpacing: 0, lineHeight: 1, maxHeight: 0, maxLines: 0, maxWidth: 0, offsetY: 0, overflowSuffix: '...', verticalAlign: 'top', wordBreak: 'break-word', textRendererOverride: null, forceLoad: false, }); 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 SDF shader props while using the main framebuffer resolution', () => { const { program, bindTextures, bindBufferCollection, uniform1f, uniform2f, } = createProgram(); const onSdfBind = vi.fn(); const renderOp = { isCoreNode: false, isSdfRenderOp: true, shader: { shaderType: { onSdfBind } }, sdfShaderProps: { size: 16, distanceRange: 4 }, 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); expect(onSdfBind).toHaveBeenCalledWith(renderOp.sdfShaderProps); }); it('keeps SDF binding active when rendering into a parent framebuffer', () => { const { program, uniform1f, uniform2f } = createProgram(); const onSdfBind = vi.fn(); const renderOp = { isCoreNode: false, isSdfRenderOp: true, shader: { shaderType: { onSdfBind } }, sdfShaderProps: { size: 18, distanceRange: 6 }, 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); expect(onSdfBind).toHaveBeenCalledWith(renderOp.sdfShaderProps); }); it('uses parent RTT dimensions for SDF text render ops', () => { const { program, uniform1f, uniform2f } = createProgram(); const onSdfBind = vi.fn(); const sdfShaderProps = { color: 0xffffffff, distanceRange: 1, size: 16, transform: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]), }; const renderOp = { framebufferDimensions: { w: 320, h: 180 }, isCoreNode: true, isSdfRenderOp: true, parentFramebufferDimensions: { w: 640, h: 360 }, parentHasRenderTexture: true, quadBufferCollection: {}, renderOpTextures: [], rtt: false, sdfShaderProps, shader: { shaderType: { onSdfBind, }, }, 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); expect(onSdfBind).toHaveBeenCalledWith(sdfShaderProps); }); }); describe('WebGlRenderer.canReuseRenderOp', () => { it('reuses SDF text render ops with matching parent RTT dimensions', () => { const renderer = Object.create(WebGlRenderer.prototype) as WebGlRenderer; const node = makeSdfTextNode(); node.props.shader = { shaderKey: 'default', } as any; renderer.curRenderOp = node; expect(renderer.reuseRenderOp(node)).toBe(true); }); });