118 lines
5.1 KiB
TypeScript
118 lines
5.1 KiB
TypeScript
/**
|
|
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*
|
|
* WebGL glyph + texture-atlas integrity tests.
|
|
*
|
|
* These tests verify that *glyphs* render correctly (not just that cell colors
|
|
* are right) under conditions that stress the texture atlas: multiple atlas
|
|
* pages, page merges and atlas clears. The goal is to catch the "garbled glyph"
|
|
* corruption class reported in microsoft/vscode#322756.
|
|
*
|
|
* Note: CI runs WebGL on Chromium + SwiftShader (software WebGL). That catches
|
|
* renderer/atlas *logic* corruption (stale texture pages, missing model clears),
|
|
* but not GPU-driver-specific glitches. These tests target the former.
|
|
*/
|
|
|
|
import test, { expect } from '@playwright/test';
|
|
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
|
import {
|
|
assertTextureAtlasPresent,
|
|
captureRowSignatures,
|
|
expectSignatureDiffers,
|
|
expectSignatureMatches,
|
|
floodDistinctGlyphs,
|
|
loadWebglStrict,
|
|
setMaxAtlasPages,
|
|
waitForRender,
|
|
writeAndWaitForRender
|
|
} from '../../../test/playwright/RendererTestUtils';
|
|
|
|
let ctx: ITestContext;
|
|
test.beforeAll(async ({ browser }) => {
|
|
ctx = await createTestContext(browser);
|
|
});
|
|
test.afterAll(async () => await ctx.page.close());
|
|
|
|
test.describe('WebGL glyph + atlas integrity', () => {
|
|
// Additive scope: only run on Chromium (SwiftShader). Firefox/WebKit WebGL
|
|
// tests are known-broken in CI (xtermjs#5854) and out of scope here.
|
|
test.skip(({ browserName }) => browserName !== 'chromium', 'WebGL atlas tests run on Chromium/SwiftShader only');
|
|
|
|
test.beforeEach(async () => {
|
|
// Fresh terminal (and therefore a predictable atlas) per test.
|
|
await openTerminal(ctx, { cols: 80, rows: 24 });
|
|
await loadWebglStrict(ctx);
|
|
});
|
|
|
|
test('renders distinct, non-blank glyphs', async () => {
|
|
await writeAndWaitForRender(ctx, 'A W .');
|
|
await assertTextureAtlasPresent(ctx);
|
|
|
|
const [a, w, dot, blank] = await captureRowSignatures(ctx, 1, [1, 3, 5, 40]);
|
|
// A written cell must differ from an empty cell (glyph actually drawn).
|
|
expectSignatureDiffers(a, blank, 24, 'glyph "A" should not be blank');
|
|
expectSignatureDiffers(w, blank, 24, 'glyph "W" should not be blank');
|
|
// Different glyphs must look different (signature has discriminating power).
|
|
expectSignatureDiffers(a, w, 24, 'glyphs "A" and "W" should render differently');
|
|
expectSignatureDiffers(a, dot, 24, 'glyphs "A" and "." should render differently');
|
|
});
|
|
|
|
test('glyphs on the first atlas page stay correct after more pages are allocated', async () => {
|
|
// Reference: a known row of glyphs that lands on the first atlas page.
|
|
await writeAndWaitForRender(ctx, 'REFERENCE_LINE_0123456789\r\n');
|
|
const cols = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
const reference = await captureRowSignatures(ctx, 1, cols);
|
|
|
|
// Flood thousands of distinct glyphs (one screenful rendered at a time, so
|
|
// they actually rasterize) to force multiple atlas pages.
|
|
const pages = await floodDistinctGlyphs(ctx, 4);
|
|
expect(pages, 'flooding distinct glyphs should allocate more than one atlas page').toBeGreaterThan(1);
|
|
|
|
// Bring the reference line back into view and re-check its glyphs.
|
|
await ctx.proxy.scrollToTop();
|
|
await waitForRender(ctx);
|
|
const after = await captureRowSignatures(ctx, 1, cols);
|
|
for (let i = 0; i < cols.length; i++) {
|
|
expectSignatureMatches(after[i], reference[i], 14, `reference glyph at col ${cols[i]} corrupted after multi-page allocation`);
|
|
}
|
|
});
|
|
|
|
test('glyphs stay correct after a texture-atlas page merge', async () => {
|
|
// Force merges to happen almost immediately.
|
|
await writeAndWaitForRender(ctx, 'MERGE_REF_LINE_0123456789\r\n');
|
|
await setMaxAtlasPages(ctx, 4);
|
|
|
|
const cols = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
const reference = await captureRowSignatures(ctx, 1, cols);
|
|
|
|
// Flood distinct glyphs (rendered per screenful) to exceed the tiny page cap
|
|
// and trigger merges.
|
|
const pages = await floodDistinctGlyphs(ctx, 12);
|
|
expect(pages, 'flood should have allocated multiple atlas pages (and merged)').toBeGreaterThan(1);
|
|
|
|
await ctx.proxy.scrollToTop();
|
|
await waitForRender(ctx);
|
|
const after = await captureRowSignatures(ctx, 1, cols);
|
|
for (let i = 0; i < cols.length; i++) {
|
|
expectSignatureMatches(after[i], reference[i], 14, `reference glyph at col ${cols[i]} corrupted after atlas page merge`);
|
|
}
|
|
});
|
|
|
|
test('glyphs re-render correctly after clearTextureAtlas() on the same terminal', async () => {
|
|
await writeAndWaitForRender(ctx, 'CLEAR_REF_LINE_0123456789');
|
|
const cols = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
const reference = await captureRowSignatures(ctx, 1, cols);
|
|
|
|
await ctx.proxy.clearTextureAtlas();
|
|
// Force a full redraw so glyphs are re-rasterized into the cleared atlas.
|
|
await ctx.proxy.refresh(0, (await ctx.proxy.rows) - 1);
|
|
await waitForRender(ctx);
|
|
|
|
const after = await captureRowSignatures(ctx, 1, cols);
|
|
for (let i = 0; i < cols.length; i++) {
|
|
expectSignatureMatches(after[i], reference[i], 14, `glyph at col ${cols[i]} corrupted after clearTextureAtlas()`);
|
|
}
|
|
});
|
|
});
|