From 9dba9df5a69a2bb8a6f23ded72293ffd589f5bc2 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 15 Oct 2026 15:47:22 +0200 Subject: [PATCH 1/2] feat(native): enable the GPU test renderer and captureScreenshot on Linux via the test-support feature --- .github/workflows/ci.yml | 75 +++++++++++++++++++++++++++- packages/native/Cargo.lock | 2 + packages/native/index.d.ts | 2 +- packages/native/src/lib.rs | 53 +++++++++----------- packages/native/src/renderer.rs | 15 ++++-- packages/native/src/test_renderer.rs | 12 +++-- 6 files changed, 117 insertions(+), 42 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d736f1d1c..29d8afcd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,11 +32,13 @@ jobs: bunTarget: bun-darwin-arm64 build: bun run build # ── Linux (Vulkan/wgpu) ──────────────────────────────────── - # build:release disables test-support until wgpu image readback lands. + # test-support (TestGpuixRenderer + render_to_image) is on by + # default; the wgpu offscreen image readback it relies on has + # landed, so Linux ships the same test renderer as macOS/Windows. - host: ubuntu-latest target: x86_64-unknown-linux-gnu bunTarget: bun-linux-x64 - build: bun run build:release + build: bun run build # ── Windows (Direct3D) ───────────────────────────────────── # Test support includes TestGpuixRenderer and render_to_image. - host: windows-latest @@ -333,6 +335,74 @@ jobs: run: bun run test working-directory: examples + test-linux: + name: test-linux + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # wgpu (Vulkan) needs an ICD on a headless runner; the test renderer + # renders offscreen through the same wgpu path as production. + - name: Install Linux dependencies + run: | + sudo apt-get update && sudo apt-get install -y \ + libvulkan1 \ + vulkan-validationlayers \ + libfontconfig1 \ + libwayland-client0 \ + libx11-6 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Install dependencies + run: bun install + + - name: Download Linux artifact + uses: actions/download-artifact@v4 + with: + name: bindings-x86_64-unknown-linux-gnu + path: packages/native/ + + - name: Load native binding with Bun + run: bun -e "import('./packages/native/index.js').then(() => console.log('Linux native binding OK (bun)'))" + + - name: Load native binding with Node + run: node -e "import('./packages/native/index.js').then(() => console.log('Linux native binding OK (node)'))" + + - name: Verify Linux test renderer + run: bun -e "import { statSync } from 'node:fs'; const { TestGpuixRenderer } = await import('./packages/native/index.js'); const renderer = new TestGpuixRenderer(320, 200); renderer.applyBatch(JSON.stringify([['createElement',1,'text'],['setText',1,'Linux test renderer'],['setRoot',1]])); renderer.flush(); renderer.captureScreenshot('linux-test-renderer.png'); if (renderer.getAllText()[0] !== 'Linux test renderer' || statSync('linux-test-renderer.png').size === 0) throw new Error('TestGpuixRenderer did not render')" + + - name: Build native shared TypeScript + run: bun run build:js + working-directory: packages/native + + - name: Run React tests + run: bun run test + working-directory: packages/react + + - name: Build React package + run: bun run build + working-directory: packages/react + + - name: Run Solid tests + run: bun run test + working-directory: packages/solid + + - name: Build Solid package + run: bun run build + working-directory: packages/solid + + - name: Run example tests + run: bun run test + working-directory: examples + publish-cli: name: publish-cli if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -376,6 +446,7 @@ jobs: - build - test - test-windows + - test-linux permissions: contents: write id-token: write diff --git a/packages/native/Cargo.lock b/packages/native/Cargo.lock index ade6700cd..45b771928 100644 --- a/packages/native/Cargo.lock +++ b/packages/native/Cargo.lock @@ -2401,6 +2401,7 @@ dependencies = [ "gpui_util", "gpui_wgpu", "http_client", + "image", "libc", "log", "notify-rust", @@ -2545,6 +2546,7 @@ dependencies = [ "etagere", "gpui", "gpui_util", + "image", "itertools 0.14.0", "log", "parking_lot", diff --git a/packages/native/index.d.ts b/packages/native/index.d.ts index 2f865d1cc..ad0c51fd5 100644 --- a/packages/native/index.d.ts +++ b/packages/native/index.d.ts @@ -399,7 +399,7 @@ export declare class TestGpuixRenderer { getScrollOffset(elementId: number): Array | null /** * Capture a screenshot of the current rendered state and save as PNG. - * Supported on macOS through Metal and Windows through DirectX. + * Supported on macOS through Metal, Windows through DirectX, and Linux through a wgpu offscreen readback. */ captureScreenshot(path: string): void /** diff --git a/packages/native/src/lib.rs b/packages/native/src/lib.rs index c5f4fa121..8ee8e1763 100644 --- a/packages/native/src/lib.rs +++ b/packages/native/src/lib.rs @@ -2,10 +2,7 @@ #[cfg(all( not(all(target_arch = "wasm32", target_os = "unknown")), - not(all( - feature = "test-support", - any(target_os = "macos", target_os = "windows") - )) + not(feature = "test-support") ))] use napi::bindgen_prelude::*; #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] @@ -35,7 +32,7 @@ mod updater; #[cfg(all( feature = "test-support", - any(target_os = "macos", target_os = "windows") + not(all(target_arch = "wasm32", target_os = "unknown")) ))] mod test_renderer; @@ -45,15 +42,16 @@ pub use style::*; #[cfg(any( test, - not(all( - feature = "test-support", - any(target_os = "macos", target_os = "windows") - )) + all( + not(all(target_arch = "wasm32", target_os = "unknown")), + not(feature = "test-support") + ) ))] const TEST_GPUIX_RENDERER_UNAVAILABLE: &str = concat!( - "TestGpuixRenderer is macOS and Windows only. ", - "Linux builds have no test-support because wgpu cannot read a rendered image back yet. ", - "GpuixRenderer still works on Linux." + "TestGpuixRenderer requires the `test-support` feature. ", + "Rebuild the native package with the `test-support` feature (a default feature) ", + "to get the real GPU test renderer. ", + "GpuixRenderer works on all desktop platforms including Linux." ); /// True only when this binary compiled the real GPU test renderer. @@ -62,26 +60,20 @@ const TEST_GPUIX_RENDERER_UNAVAILABLE: &str = concat!( pub fn has_test_gpuix_renderer() -> bool { cfg!(all( feature = "test-support", - any(target_os = "macos", target_os = "windows") + not(all(target_arch = "wasm32", target_os = "unknown")) )) } #[cfg(all( not(all(target_arch = "wasm32", target_os = "unknown")), - not(all( - feature = "test-support", - any(target_os = "macos", target_os = "windows") - )) + not(feature = "test-support") ))] #[napi] pub struct TestGpuixRenderer; #[cfg(all( not(all(target_arch = "wasm32", target_os = "unknown")), - not(all( - feature = "test-support", - any(target_os = "macos", target_os = "windows") - )) + not(feature = "test-support") ))] #[napi] impl TestGpuixRenderer { @@ -96,12 +88,16 @@ mod tests { use super::*; #[test] - fn unavailable_message_explains_linux() { - assert!(TEST_GPUIX_RENDERER_UNAVAILABLE.contains("macOS and Windows only")); + fn unavailable_message_explains_test_support() { + assert!(TEST_GPUIX_RENDERER_UNAVAILABLE.contains( + "requires the `test-support` feature" + )); + assert!(TEST_GPUIX_RENDERER_UNAVAILABLE.contains( + "Rebuild the native package with the `test-support` feature" + )); assert!(TEST_GPUIX_RENDERER_UNAVAILABLE.contains( - "Linux builds have no test-support because wgpu cannot read a rendered image back yet" + "GpuixRenderer works on all desktop platforms including Linux" )); - assert!(TEST_GPUIX_RENDERER_UNAVAILABLE.contains("GpuixRenderer still works on Linux")); } #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] @@ -111,17 +107,14 @@ mod tests { has_test_gpuix_renderer(), cfg!(all( feature = "test-support", - any(target_os = "macos", target_os = "windows") + not(all(target_arch = "wasm32", target_os = "unknown")) )) ); } #[cfg(all( not(all(target_arch = "wasm32", target_os = "unknown")), - not(all( - feature = "test-support", - any(target_os = "macos", target_os = "windows") - )) + not(feature = "test-support") ))] #[test] fn stub_constructor_explains_why() { diff --git a/packages/native/src/renderer.rs b/packages/native/src/renderer.rs index cc780fcf0..0b931fb33 100644 --- a/packages/native/src/renderer.rs +++ b/packages/native/src/renderer.rs @@ -489,7 +489,7 @@ enum UiCommand { input: KeyInput, response: SyncSender>, }, - #[cfg(all(target_os = "windows", feature = "test-support"))] + #[cfg(all(any(target_os = "windows", target_os = "linux"), feature = "test-support"))] CaptureScreenshot { path: String, response: SyncSender>, @@ -857,7 +857,7 @@ async fn run_ui_commands( .ok(); result } - #[cfg(all(target_os = "windows", feature = "test-support"))] + #[cfg(all(any(target_os = "windows", target_os = "linux"), feature = "test-support"))] UiCommand::CaptureScreenshot { path, response } => { let error_response = response.clone(); let result = window.update(cx, move |_view, window, cx| { @@ -2753,14 +2753,21 @@ impl GpuixRenderer { return recv_ui_response(receiver, "screenshot capture")?.map_err(Error::from_reason); } + #[cfg(all(target_os = "linux", feature = "test-support"))] + { + let (response, receiver) = sync_channel(1); + self.send_ui_command(UiCommand::CaptureScreenshot { path, response })?; + return recv_ui_response(receiver, "screenshot capture")?.map_err(Error::from_reason); + } + #[cfg(not(all( feature = "test-support", - any(target_os = "macos", target_os = "windows") + any(target_os = "macos", target_os = "windows", target_os = "linux") )))] { let _ = path; Err(Error::from_reason( - "captureScreenshot needs a test-support build on macOS or Windows", + "captureScreenshot needs a test-support build on macOS, Windows, or Linux", )) } } diff --git a/packages/native/src/test_renderer.rs b/packages/native/src/test_renderer.rs index fe530846d..2e82d22a2 100644 --- a/packages/native/src/test_renderer.rs +++ b/packages/native/src/test_renderer.rs @@ -1,7 +1,8 @@ /// TestGpuixRenderer — GPU-backed GPUI test renderer exposed to Node.js via napi. /// -/// Uses gpui::VisualTestAppContext with the native Metal or DirectX renderer -/// and TestDispatcher for deterministic scheduling. Runs the SAME GpuixView, +/// Uses gpui::VisualTestAppContext with the native GPU renderer (Metal on +/// macOS, DirectX on Windows, Vulkan on Linux) and TestDispatcher for +/// deterministic scheduling. Runs the SAME GpuixView, /// build_element(), apply_styles(), and event handlers as production. /// /// Windows are positioned offscreen at (-10000, -10000) — invisible but @@ -190,8 +191,9 @@ fn install_test_image_http_client(cx: &mut gpui::App) { // ── TestGpuixRenderer ──────────────────────────────────────────────── /// GPU-backed GPUI test renderer. Uses VisualTestAppContext with the native -/// Metal or DirectX renderer and TestDispatcher for deterministic scheduling. -/// Same GpuixView and rendering pipeline as production. +/// GPU renderer (Metal on macOS, DirectX on Windows, Vulkan on Linux) and +/// TestDispatcher for deterministic scheduling. Same GpuixView and rendering +/// pipeline as production. /// /// Usage from JS: /// const r = new TestGpuixRenderer() @@ -1021,7 +1023,7 @@ impl TestGpuixRenderer { } /// Capture a screenshot of the current rendered state and save as PNG. - /// Supported on macOS through Metal and Windows through DirectX. + /// Supported on macOS through Metal, Windows through DirectX, and Linux through a wgpu offscreen readback. #[napi] pub fn capture_screenshot(&self, path: String) -> Result<()> { with_test_state(|cx, window, view| { From 3c382d143cfda274393889b3949e2743aec41aad Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 19 Oct 2026 18:59:06 +0200 Subject: [PATCH 2/2] build(zed): point submodule URL from remorses/zed to knoopx/zed --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index a5311aa3c..5747f0668 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "zed"] path = zed - url = https://github.com/remorses/zed.git - branch = gpuix + url = https://github.com/knoopx/zed.git + branch = main