From f56d0e05eac4725476941721656c477f124962a9 Mon Sep 17 00:00:00 2001 From: Ib Green Date: Thu, 30 Jul 2026 18:19:54 -0400 Subject: [PATCH 1/3] test: run WebGPU browser coverage in CI --- .github/workflows/test.yml | 2 ++ docs/whats-new.md | 2 ++ .../test/webgpu-layers.browser.spec.ts | 3 ++- .../wind-layer/wind-layers.browser.spec.ts | 3 ++- .../test/fast-text-layer.browser.spec.ts | 3 ++- .../layers/test/webgpu-layers.browser.spec.ts | 7 ++--- .../layers/trace-webgpu.browser.spec.ts | 3 ++- test/browser-test-device.ts | 18 +++++++++++++ vitest.config.ts | 27 ++++++++++--------- 9 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 test/browser-test-device.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f94beb95..9296158d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -260,6 +260,8 @@ jobs: - name: Run headless browser tests run: yarn test-headless + env: + DECK_GL_COMMUNITY_SOFTWARE_WEBGPU: 'true' website-build: runs-on: ubuntu-22.04 diff --git a/docs/whats-new.md b/docs/whats-new.md index 894d5a4c..bed53776 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -36,6 +36,8 @@ Scope tracked in the [v9.4 milestone](https://github.com/visgl/deck.gl-community including its picking-width shader customization. - `TraceGraphLayer`, `TracePreparedStateLayer`, and `TraceProcessLayer`: ported trace backgrounds, binary span blocks, outlines, labels, overflow labels, separators, and straight dependencies by reusing dual-backend community layers. - Trace counter sparklines now preserve their full geometry using portable `LineLayer` segments. +- Headless CI enables Chromium's software WebGPU adapter, so native WebGPU validation tests run + even on runners without hardware adapters instead of being skipped. - Every website gallery example and live layer-reference example now receives a standalone `DeviceTabsWidget` from the shared imperative host, with an independent device manager, WebGPU preference, WebGL2 fallback, renderer remounting, and preserved view state. diff --git a/modules/arrow-layers/test/webgpu-layers.browser.spec.ts b/modules/arrow-layers/test/webgpu-layers.browser.spec.ts index 5183761c..401e56d6 100644 --- a/modules/arrow-layers/test/webgpu-layers.browser.spec.ts +++ b/modules/arrow-layers/test/webgpu-layers.browser.spec.ts @@ -16,6 +16,7 @@ import { } from 'apache-arrow'; import {describe, expect, it} from 'vitest'; +import {releaseBrowserTestDevice} from '../../../test/browser-test-device'; import {GeoArrowPathLayer, GeoArrowSolidPolygonLayer} from '../src'; type BrowserGpu = {requestAdapter: () => Promise}; @@ -136,7 +137,7 @@ async function renderGeoArrowLayers(type: 'webgl' | 'webgpu'): Promise { } finally { nativeDevice?.removeEventListener('uncapturederror', captureValidationError); deck?.finalize(); - device?.destroy(); + releaseBrowserTestDevice(device); parent.remove(); } } diff --git a/modules/geo-layers/test/wind-layer/wind-layers.browser.spec.ts b/modules/geo-layers/test/wind-layer/wind-layers.browser.spec.ts index 8f7c557d..78ae2718 100644 --- a/modules/geo-layers/test/wind-layer/wind-layers.browser.spec.ts +++ b/modules/geo-layers/test/wind-layer/wind-layers.browser.spec.ts @@ -8,6 +8,7 @@ import {webgl2Adapter} from '@luma.gl/webgl'; import {webgpuAdapter} from '@luma.gl/webgpu'; import {describe, expect, it} from 'vitest'; +import {releaseBrowserTestDevice} from '../../../../test/browser-test-device'; import {GpuParticleSimulation} from '../../src/wind-layer/gpu-particle-simulation'; import { createWindField, @@ -213,7 +214,7 @@ async function renderWindLayers(type: 'webgl' | 'webgpu'): Promise { } finally { nativeDevice?.removeEventListener('uncapturederror', captureValidationError); deck?.finalize(); - device?.destroy(); + releaseBrowserTestDevice(device); parent.remove(); } } diff --git a/modules/infovis-layers/test/fast-text-layer.browser.spec.ts b/modules/infovis-layers/test/fast-text-layer.browser.spec.ts index 8e1ee775..f0905679 100644 --- a/modules/infovis-layers/test/fast-text-layer.browser.spec.ts +++ b/modules/infovis-layers/test/fast-text-layer.browser.spec.ts @@ -8,6 +8,7 @@ import {webgl2Adapter} from '@luma.gl/webgl'; import {webgpuAdapter} from '@luma.gl/webgpu'; import {describe, expect, it} from 'vitest'; +import {releaseBrowserTestDevice} from '../../../test/browser-test-device'; import {FastTextLayer} from '../src'; type BrowserGpu = {requestAdapter: () => Promise}; @@ -88,7 +89,7 @@ async function renderFastText(type: 'webgl' | 'webgpu', sdf: boolean): Promise { } finally { nativeDevice?.removeEventListener('uncapturederror', captureValidationError); deck?.finalize(); - device?.destroy(); + releaseBrowserTestDevice(device); parent.remove(); } } @@ -425,7 +426,7 @@ async function renderPortableLayers(type: 'webgl' | 'webgpu'): Promise { describe('community graphics backend compatibility', () => { it('renders custom shaders, paths, polygons, graph, timeline, and editing on WebGL2', async () => { await renderPortableLayers('webgl'); - }, 20_000); + }, 30_000); it('renders custom shaders, paths, polygons, graph, timeline, and editing on WebGPU', async ({ skip @@ -436,5 +437,5 @@ describe('community graphics backend compatibility', () => { } await renderPortableLayers('webgpu'); - }, 20_000); + }, 30_000); }); diff --git a/modules/trace-layers/src/layers/layers/trace-webgpu.browser.spec.ts b/modules/trace-layers/src/layers/layers/trace-webgpu.browser.spec.ts index ba694f4d..072bc254 100644 --- a/modules/trace-layers/src/layers/layers/trace-webgpu.browser.spec.ts +++ b/modules/trace-layers/src/layers/layers/trace-webgpu.browser.spec.ts @@ -8,6 +8,7 @@ import {webgl2Adapter} from '@luma.gl/webgl'; import {webgpuAdapter} from '@luma.gl/webgpu'; import {describe, expect, it} from 'vitest'; +import {releaseBrowserTestDevice} from '../../../../../test/browser-test-device'; import { buildJSONTrace, buildTraceGraphDataFromJSONTrace, @@ -212,7 +213,7 @@ async function renderTraceGraph( } finally { nativeGpuDevice?.removeEventListener('uncapturederror', handleValidationError); deck?.finalize(); - device?.destroy(); + releaseBrowserTestDevice(device); parent.remove(); } } diff --git a/test/browser-test-device.ts b/test/browser-test-device.ts new file mode 100644 index 00000000..ebe87895 --- /dev/null +++ b/test/browser-test-device.ts @@ -0,0 +1,18 @@ +// deck.gl-community +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import type {Device} from '@luma.gl/core'; + +/** + * Releases a graphics device created by a browser test. + * + * Deck finalization destroys each test's WebGPU resources. The Playwright browser process owns + * final WebGPU device teardown because explicitly destroying a Linux SwiftShader device can drop + * the external WebGPU instance shared by parallel Vitest browser files. + */ +export function releaseBrowserTestDevice(device: Device | undefined): void { + if (device?.type !== 'webgpu') { + device?.destroy(); + } +} diff --git a/vitest.config.ts b/vitest.config.ts index eaa01169..852449dd 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -83,19 +83,20 @@ const BROWSER_OPTIMIZE_DEPS_CONFIG = { const BROWSER_TEST_EXCLUDE = ['modules/**/dist/**', 'dev/**/dist/**']; const HEADLESS_BROWSER_PROVIDER = - process.env.GITHUB_ACTIONS === 'true' - ? playwright({launchOptions: {channel: 'chrome'}}) - : process.env.DECK_GL_COMMUNITY_SOFTWARE_WEBGPU === 'true' - ? playwright({ - launchOptions: { - args: [ - '--enable-unsafe-webgpu', - '--enable-unsafe-swiftshader', - '--use-angle=swiftshader', - '--enable-features=Vulkan,WebGPU' - ] - } - }) + process.env.DECK_GL_COMMUNITY_SOFTWARE_WEBGPU === 'true' + ? playwright({ + launchOptions: { + ...(process.env.GITHUB_ACTIONS === 'true' ? {channel: 'chrome'} : {}), + args: [ + '--enable-unsafe-webgpu', + '--enable-unsafe-swiftshader', + '--use-angle=swiftshader', + '--enable-features=Vulkan,WebGPU' + ] + } + }) + : process.env.GITHUB_ACTIONS === 'true' + ? playwright({launchOptions: {channel: 'chrome'}}) : playwright(); const CONFIG = defineConfig({ From 13e24a579c726878494454420489669955f16137 Mon Sep 17 00:00:00 2001 From: Ib Green Date: Thu, 30 Jul 2026 18:27:32 -0400 Subject: [PATCH 2/3] test: serialize software WebGPU browser files --- vitest.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index 852449dd..d32e59c9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -82,8 +82,10 @@ const BROWSER_OPTIMIZE_DEPS_CONFIG = { const BROWSER_TEST_EXCLUDE = ['modules/**/dist/**', 'dev/**/dist/**']; +const SOFTWARE_WEBGPU_ENABLED = process.env.DECK_GL_COMMUNITY_SOFTWARE_WEBGPU === 'true'; + const HEADLESS_BROWSER_PROVIDER = - process.env.DECK_GL_COMMUNITY_SOFTWARE_WEBGPU === 'true' + SOFTWARE_WEBGPU_ENABLED ? playwright({ launchOptions: { ...(process.env.GITHUB_ACTIONS === 'true' ? {channel: 'chrome'} : {}), @@ -168,6 +170,7 @@ const CONFIG = defineConfig({ browser: { enabled: true, headless: true, + fileParallelism: !SOFTWARE_WEBGPU_ENABLED, provider: HEADLESS_BROWSER_PROVIDER, instances: [{browser: 'chromium'}] } From 9ff19fcde07086c7c7ae4d540a1168e44276d71e Mon Sep 17 00:00:00 2001 From: Ib Green Date: Thu, 30 Jul 2026 18:33:07 -0400 Subject: [PATCH 3/3] test: select Dawn SwiftShader adapter explicitly --- vitest.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index d32e59c9..6ca36a00 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -92,8 +92,10 @@ const HEADLESS_BROWSER_PROVIDER = args: [ '--enable-unsafe-webgpu', '--enable-unsafe-swiftshader', + '--use-gl=angle', '--use-angle=swiftshader', - '--enable-features=Vulkan,WebGPU' + '--use-webgpu-adapter=swiftshader', + '--use-gpu-in-tests' ] } })