From f43273b3f0c589b063c5e4a772abae173def3679 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 15:35:05 +0000 Subject: [PATCH] fix(webgl): include texture size and cell dimensions in atlas cache equality configEquals omitted deviceMaxTextureSize, deviceCellWidth, and deviceCellHeight, which could cause the char atlas cache to reuse an atlas built for a different GPU texture limit or cell size. Fixes #5915 Co-authored-by: Daniel Imms --- addons/addon-webgl/src/CharAtlasUtils.test.ts | 87 +++++++++++++++++++ addons/addon-webgl/src/CharAtlasUtils.ts | 3 + 2 files changed, 90 insertions(+) create mode 100644 addons/addon-webgl/src/CharAtlasUtils.test.ts diff --git a/addons/addon-webgl/src/CharAtlasUtils.test.ts b/addons/addon-webgl/src/CharAtlasUtils.test.ts new file mode 100644 index 000000000..0a135746e --- /dev/null +++ b/addons/addon-webgl/src/CharAtlasUtils.test.ts @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2026 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { configEquals } from './CharAtlasUtils'; +import { ICharAtlasConfig } from './Types'; +import { NULL_COLOR } from 'common/Color'; +import { IColor } from 'common/Types'; + +function createTestConfig(overrides: Partial = {}): ICharAtlasConfig { + const color: IColor = { css: '#ffffff', rgba: 0xffffffff }; + const contrastCache = { + clear: () => {}, + setCss: () => {}, + getCss: () => undefined, + setColor: () => {}, + getColor: () => undefined + }; + const colors = { + foreground: color, + background: color, + cursor: NULL_COLOR, + cursorAccent: NULL_COLOR, + selectionForeground: undefined, + selectionBackgroundTransparent: NULL_COLOR, + selectionBackgroundOpaque: NULL_COLOR, + selectionInactiveBackgroundTransparent: NULL_COLOR, + selectionInactiveBackgroundOpaque: NULL_COLOR, + overviewRulerBorder: NULL_COLOR, + scrollbarSliderBackground: NULL_COLOR, + scrollbarSliderHoverBackground: NULL_COLOR, + scrollbarSliderActiveBackground: NULL_COLOR, + ansi: new Array(256).fill(color), + contrastCache, + halfContrastCache: contrastCache + }; + return { + customGlyphs: true, + devicePixelRatio: 1, + deviceMaxTextureSize: 4096, + letterSpacing: 0, + lineHeight: 1, + fontSize: 15, + fontFamily: 'monospace', + fontWeight: 'normal', + fontWeightBold: 'bold', + deviceCellWidth: 10, + deviceCellHeight: 20, + deviceCharWidth: 8, + deviceCharHeight: 16, + allowTransparency: false, + drawBoldTextInBrightColors: true, + minimumContrastRatio: 1, + colors, + ...overrides + }; +} + +describe('CharAtlasUtils', () => { + describe('configEquals', () => { + it('should return true for identical configs', () => { + const a = createTestConfig(); + const b = createTestConfig(); + assert.ok(configEquals(a, b)); + }); + + it('should return false when deviceMaxTextureSize differs', () => { + const a = createTestConfig(); + const b = createTestConfig({ deviceMaxTextureSize: 8192 }); + assert.ok(!configEquals(a, b)); + }); + + it('should return false when deviceCellWidth differs', () => { + const a = createTestConfig(); + const b = createTestConfig({ deviceCellWidth: 11 }); + assert.ok(!configEquals(a, b)); + }); + + it('should return false when deviceCellHeight differs', () => { + const a = createTestConfig(); + const b = createTestConfig({ deviceCellHeight: 21 }); + assert.ok(!configEquals(a, b)); + }); + }); +}); diff --git a/addons/addon-webgl/src/CharAtlasUtils.ts b/addons/addon-webgl/src/CharAtlasUtils.ts index 4ce103e98..d9ca69b39 100644 --- a/addons/addon-webgl/src/CharAtlasUtils.ts +++ b/addons/addon-webgl/src/CharAtlasUtils.ts @@ -59,6 +59,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean } } return a.devicePixelRatio === b.devicePixelRatio && + a.deviceMaxTextureSize === b.deviceMaxTextureSize && a.customGlyphs === b.customGlyphs && a.lineHeight === b.lineHeight && a.letterSpacing === b.letterSpacing && @@ -67,6 +68,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.fontWeight === b.fontWeight && a.fontWeightBold === b.fontWeightBold && a.allowTransparency === b.allowTransparency && + a.deviceCellWidth === b.deviceCellWidth && + a.deviceCellHeight === b.deviceCellHeight && a.deviceCharWidth === b.deviceCharWidth && a.deviceCharHeight === b.deviceCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&