Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -376,6 +446,7 @@ jobs:
- build
- test
- test-windows
- test-linux
permissions:
contents: write
id-token: write
Expand Down
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions packages/native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/native/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ export declare class TestGpuixRenderer {
getScrollOffset(elementId: number): Array<number> | 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
/**
Expand Down
53 changes: 23 additions & 30 deletions packages/native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")))]
Expand Down Expand Up @@ -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;

Expand All @@ -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.
Expand All @@ -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 {
Expand All @@ -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")))]
Expand All @@ -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() {
Expand Down
15 changes: 11 additions & 4 deletions packages/native/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ enum UiCommand {
input: KeyInput,
response: SyncSender<std::result::Result<(), String>>,
},
#[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<std::result::Result<(), String>>,
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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",
))
}
}
Expand Down
12 changes: 7 additions & 5 deletions packages/native/src/test_renderer.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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| {
Expand Down