From abed93d63a993340ac5b41ce0d95d51ac3ee4e00 Mon Sep 17 00:00:00 2001 From: francois-unity <105223066+francois-unity@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:30:15 -0400 Subject: [PATCH] gpui_wgpu: build headless renderers on a shared context WgpuHeadlessRenderer::with_context(&WgpuContext) renders on an existing context's device and queue, so a test suite that opens many headless windows creates one device instead of one per window. Each renderer keeps its own sprite atlas and render target. new_with_requirements now delegates to it. --- .../gpui_wgpu/src/wgpu_renderer/headless.rs | 10 +++- crates/gpui_wgpu/tests/headless_primitives.rs | 52 ++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/crates/gpui_wgpu/src/wgpu_renderer/headless.rs b/crates/gpui_wgpu/src/wgpu_renderer/headless.rs index 75dac8b5d3..655b468e6c 100644 --- a/crates/gpui_wgpu/src/wgpu_renderer/headless.rs +++ b/crates/gpui_wgpu/src/wgpu_renderer/headless.rs @@ -184,9 +184,15 @@ impl WgpuHeadlessRenderer { pub fn new_with_requirements( requirements: Option<&crate::WgpuDeviceRequirements>, ) -> anyhow::Result { - let context = WgpuContext::new_headless(requirements)?; + Self::with_context(&WgpuContext::new_headless(requirements)?) + } + + /// Like [`Self::new`], on an existing context's device and queue instead + /// of a new device, so a suite that opens many windows creates one. Each + /// renderer still gets its own sprite atlas and render target. + pub fn with_context(context: &WgpuContext) -> anyhow::Result { let renderer = WgpuRenderer::new_headless( - &context, + context, Size { width: DevicePixels(1), height: DevicePixels(1), diff --git a/crates/gpui_wgpu/tests/headless_primitives.rs b/crates/gpui_wgpu/tests/headless_primitives.rs index 179779652b..620200c15a 100644 --- a/crates/gpui_wgpu/tests/headless_primitives.rs +++ b/crates/gpui_wgpu/tests/headless_primitives.rs @@ -9,7 +9,7 @@ use gpui::{ Quad, RenderImageParams, RenderSvgParams, ScaledFilter, ScaledPixels, Scene, ShaderBool, Shadow, Size, Underline, checkerboard, linear_color_stop, linear_gradient, solid_background, }; -use gpui_ce_wgpu::WgpuHeadlessRenderer; +use gpui_ce_wgpu::{WgpuContext, WgpuHeadlessRenderer}; use smallvec::smallvec; use std::borrow::Cow; @@ -58,6 +58,56 @@ fn the_headless_context_handle_lends_the_renderers_own_device() { assert!(!handle.device_lost()); } +#[test] +fn renderers_on_one_context_share_its_device_and_render_alike() { + let context = WgpuContext::new_headless(None).expect("headless context"); + let mut first = WgpuHeadlessRenderer::with_context(&context).expect("first renderer"); + let mut second = WgpuHeadlessRenderer::with_context(&context).expect("second renderer"); + let mut own_device = WgpuHeadlessRenderer::new().expect("renderer with its own device"); + + let first_handle = first.gpu_context_info().expect("a live device"); + let second_handle = second.gpu_context_info().expect("a live device"); + assert!(first_handle.is_same_device(&second_handle)); + assert!(std::ptr::eq(second_handle.device(), &*context.device)); + assert!(std::ptr::eq(second_handle.queue(), &*context.queue)); + assert!(!first_handle.is_same_device(&own_device.gpu_context_info().expect("a live device"))); + + let scene = triangle_scene(); + let expected = own_device + .render_scene_to_image(&scene, TARGET) + .expect("frame on its own device"); + for renderer in [&mut first, &mut second] { + let image = renderer + .render_scene_to_image(&scene, TARGET) + .expect("frame on the shared device"); + assert!( + image == expected, + "a shared device renders a different frame" + ); + } +} + +fn triangle_scene() -> Scene { + let mut scene = Scene::default(); + scene.insert_primitive(Quad { + bounds: bounds(0.0, 0.0, 310.0, 100.0), + content_mask: full_mask(), + background: solid_background(gpui::black()), + ..Default::default() + }); + let mut builder = gpui::PathBuilder::fill(); + builder.move_to(gpui::point(gpui::px(20.0), gpui::px(20.0))); + builder.line_to(gpui::point(gpui::px(80.0), gpui::px(20.0))); + builder.line_to(gpui::point(gpui::px(50.0), gpui::px(80.0))); + builder.close(); + let mut path = builder.build().expect("triangle").scale(1.0); + path.content_mask = full_mask(); + path.color = solid_background(gpui::white()); + scene.insert_primitive(path); + scene.finish(); + scene +} + fn bounds(x: f32, y: f32, w: f32, h: f32) -> Bounds { Bounds { origin: Point {