Skip to content

Commit 5f5a566

Browse files
authored
Merge pull request #1 from nestrilabs/fix-gpui-issue
feat: Expose wgpu to the client
2 parents 5dd9082 + 1cbfe27 commit 5f5a566

3 files changed

Lines changed: 169 additions & 17 deletions

File tree

crates/gpui/src/scene.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ use crate::{
99
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
1010
};
1111
use std::{
12+
any::Any,
1213
fmt::Debug,
1314
iter::Peekable,
1415
ops::{Add, Range, Sub},
1516
slice,
17+
sync::Arc,
1618
};
1719

1820
#[allow(non_camel_case_types, unused)]
@@ -36,6 +38,7 @@ pub struct Scene {
3638
pub subpixel_sprites: Vec<SubpixelSprite>,
3739
pub polychrome_sprites: Vec<PolychromeSprite>,
3840
pub surfaces: Vec<PaintSurface>,
41+
pub custom_render_passes: Vec<CustomRenderPassPrimitive>,
3942
}
4043

4144
#[expect(missing_docs)]
@@ -52,6 +55,7 @@ impl Scene {
5255
self.subpixel_sprites.clear();
5356
self.polychrome_sprites.clear();
5457
self.surfaces.clear();
58+
self.custom_render_passes.clear();
5559
}
5660

5761
pub fn len(&self) -> usize {
@@ -119,6 +123,10 @@ impl Scene {
119123
surface.order = order;
120124
self.surfaces.push(surface.clone());
121125
}
126+
Primitive::CustomRenderPass(custom) => {
127+
custom.order = order;
128+
self.custom_render_passes.push(custom.clone());
129+
}
122130
}
123131
self.paint_operations
124132
.push(PaintOperation::Primitive(primitive));
@@ -146,6 +154,7 @@ impl Scene {
146154
self.polychrome_sprites
147155
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
148156
self.surfaces.sort_by_key(|surface| surface.order);
157+
self.custom_render_passes.sort_by_key(|custom| custom.order);
149158
}
150159

151160
#[cfg_attr(
@@ -173,6 +182,8 @@ impl Scene {
173182
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
174183
surfaces_start: 0,
175184
surfaces_iter: self.surfaces.iter().peekable(),
185+
custom_render_passes_start: 0,
186+
custom_render_passes_iter: self.custom_render_passes.iter().peekable(),
176187
}
177188
}
178189
}
@@ -195,6 +206,7 @@ pub(crate) enum PrimitiveKind {
195206
SubpixelSprite,
196207
PolychromeSprite,
197208
Surface,
209+
CustomRenderPass,
198210
}
199211

200212
pub(crate) enum PaintOperation {
@@ -214,6 +226,7 @@ pub enum Primitive {
214226
SubpixelSprite(SubpixelSprite),
215227
PolychromeSprite(PolychromeSprite),
216228
Surface(PaintSurface),
229+
CustomRenderPass(CustomRenderPassPrimitive),
217230
}
218231

219232
#[expect(missing_docs)]
@@ -228,6 +241,7 @@ impl Primitive {
228241
Primitive::SubpixelSprite(sprite) => &sprite.bounds,
229242
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
230243
Primitive::Surface(surface) => &surface.bounds,
244+
Primitive::CustomRenderPass(custom) => &custom.bounds,
231245
}
232246
}
233247

@@ -241,6 +255,7 @@ impl Primitive {
241255
Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
242256
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
243257
Primitive::Surface(surface) => &surface.content_mask,
258+
Primitive::CustomRenderPass(custom) => &custom.content_mask,
244259
}
245260
}
246261
}
@@ -269,6 +284,8 @@ struct BatchIterator<'a> {
269284
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
270285
surfaces_start: usize,
271286
surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
287+
custom_render_passes_start: usize,
288+
custom_render_passes_iter: Peekable<slice::Iter<'a, CustomRenderPassPrimitive>>,
272289
}
273290

274291
impl<'a> Iterator for BatchIterator<'a> {
@@ -302,6 +319,10 @@ impl<'a> Iterator for BatchIterator<'a> {
302319
self.surfaces_iter.peek().map(|s| s.order),
303320
PrimitiveKind::Surface,
304321
),
322+
(
323+
self.custom_render_passes_iter.peek().map(|c| c.order),
324+
PrimitiveKind::CustomRenderPass,
325+
),
305326
];
306327
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
307328

@@ -447,6 +468,20 @@ impl<'a> Iterator for BatchIterator<'a> {
447468
self.surfaces_start = surfaces_end;
448469
Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
449470
}
471+
PrimitiveKind::CustomRenderPass => {
472+
let custom_render_passes_start = self.custom_render_passes_start;
473+
let mut custom_render_passes_end = custom_render_passes_start + 1;
474+
self.custom_render_passes_iter.next();
475+
while self
476+
.custom_render_passes_iter
477+
.next_if(|custom| (custom.order, batch_kind) < max_order_and_kind)
478+
.is_some()
479+
{
480+
custom_render_passes_end += 1;
481+
}
482+
self.custom_render_passes_start = custom_render_passes_end;
483+
Some(PrimitiveBatch::CustomRenderPasses(custom_render_passes_start..custom_render_passes_end))
484+
}
450485
}
451486
}
452487
}
@@ -479,6 +514,7 @@ pub enum PrimitiveBatch {
479514
range: Range<usize>,
480515
},
481516
Surfaces(Range<usize>),
517+
CustomRenderPasses(Range<usize>),
482518
}
483519

484520
#[derive(Default, Debug, Copy, Clone)]
@@ -726,6 +762,37 @@ impl From<PaintSurface> for Primitive {
726762
}
727763
}
728764

765+
/// A trait implemented by renderer-specific context types passed to custom render callbacks.
766+
/// The callback can downcast to the concrete type using `Any` methods.
767+
pub trait AnyRenderContext: std::any::Any {
768+
/// Returns a reference to the underlying `Any` for downcasting.
769+
fn as_any(&self) -> &dyn std::any::Any;
770+
/// Returns a mutable reference to the underlying `Any` for downcasting.
771+
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
772+
}
773+
774+
/// A primitive that invokes a custom render callback during the wgpu render pass.
775+
/// The callback receives a type-erased context which the renderer populates with
776+
/// platform-specific GPU resources. This keeps gpui independent of wgpu while
777+
/// allowing full GPU access to consumers.
778+
///
779+
/// The callback should downcast the context to the concrete type provided by the
780+
/// renderer (e.g., `CustomRenderPassContext` in `gpui_wgpu`).
781+
#[derive(Clone)]
782+
#[expect(missing_docs)]
783+
pub struct CustomRenderPassPrimitive {
784+
pub order: DrawOrder,
785+
pub bounds: Bounds<ScaledPixels>,
786+
pub content_mask: ContentMask<ScaledPixels>,
787+
pub callback: Arc<dyn Fn(&mut dyn AnyRenderContext) + Send + Sync>,
788+
}
789+
790+
impl From<CustomRenderPassPrimitive> for Primitive {
791+
fn from(custom: CustomRenderPassPrimitive) -> Self {
792+
Primitive::CustomRenderPass(custom)
793+
}
794+
}
795+
729796
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
730797
#[expect(missing_docs)]
731798
pub struct PathId(pub usize);

crates/gpui/src/window.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@ use crate::Inspector;
33
use crate::{
44
Action, AnyDrag, AnyElement, AnyImageCache, AnyTooltip, AnyView, App, AppContext, Arena, Asset,
55
AsyncWindowContext, AvailableSpace, Background, BorderStyle, Bounds, BoxShadow, Capslock,
6-
Context, Corners, CursorHideMode, CursorStyle, Decorations, DevicePixels,
7-
DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
8-
EntityId, EventEmitter, FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs,
9-
Hsla, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke,
10-
KeystrokeEvent, LayoutId, LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite,
11-
MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
12-
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
13-
Priority, PromptButton, PromptLevel, Quad, Render, RenderGlyphParams, RenderImage,
14-
RenderImageParams, RenderSvgParams, Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR,
15-
SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ScaledPixels, Scene, Shadow, SharedString, Size,
16-
StrikethroughStyle, Style, SubpixelSprite, SubscriberSet, Subscription, SystemWindowTab,
17-
SystemWindowTabController, TabStopMap, TaffyLayoutEngine, Task, TextRenderingMode, TextStyle,
18-
TextStyleRefinement, ThermalState, TransformationMatrix, Underline, UnderlineStyle,
19-
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
20-
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, px, rems, size,
21-
transparent_black,
6+
Context, Corners, CursorStyle, CustomRenderPassPrimitive, Decorations, DevicePixels, DispatchActionListener,
7+
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
8+
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero,
9+
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
10+
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
11+
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
12+
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, Priority, PromptButton,
13+
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
14+
Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y,
15+
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubpixelSprite,
16+
SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap,
17+
TaffyLayoutEngine, Task, TextRenderingMode, TextStyle, TextStyleRefinement, ThermalState,
18+
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
19+
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
20+
point, prelude::*, px, rems, size, transparent_black,
2221
};
2322
use anyhow::{Context as _, Result, anyhow};
2423
use collections::{FxHashMap, FxHashSet};
@@ -3858,6 +3857,30 @@ impl Window {
38583857
});
38593858
}
38603859

3860+
/// Schedule a custom render pass to be executed during the wgpu rendering phase.
3861+
///
3862+
/// The callback receives a type-erased context (`&mut dyn Any`) which the wgpu renderer
3863+
/// populates with GPU resources. On Linux/wgpu, this is a `CustomRenderPassContext`
3864+
/// containing `&wgpu::Device`, `&wgpu::Queue`, `&wgpu::TextureView`, and `wgpu::TextureFormat`.
3865+
///
3866+
/// This method should only be called as part of the paint phase of element drawing.
3867+
pub fn paint_custom_render_pass(
3868+
&mut self,
3869+
bounds: Bounds<Pixels>,
3870+
callback: Arc<dyn Fn(&mut dyn crate::AnyRenderContext) + Send + Sync>,
3871+
) {
3872+
self.invalidator.debug_assert_paint();
3873+
3874+
let bounds = self.snap_bounds(bounds);
3875+
let content_mask = self.snapped_content_mask();
3876+
self.next_frame.scene.insert_primitive(CustomRenderPassPrimitive {
3877+
order: 0,
3878+
bounds,
3879+
content_mask,
3880+
callback,
3881+
});
3882+
}
3883+
38613884
/// Removes an image from the sprite atlas.
38623885
pub fn drop_image(&mut self, data: Arc<RenderImage>) -> Result<()> {
38633886
for frame_index in 0..data.frame_count() {

crates/gpui_wgpu/src/wgpu_renderer.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext};
22
use bytemuck::{Pod, Zeroable};
33
use gpui::{
4-
AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
4+
AnyRenderContext, AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
55
PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite,
66
Underline, get_gamma_correction_ratios,
77
};
@@ -69,6 +69,27 @@ struct PathRasterizationVertex {
6969
bounds: Bounds<ScaledPixels>,
7070
}
7171

72+
/// Context passed to custom render pass callbacks. Contains the GPU resources
73+
/// needed to render directly to the window surface. All fields are owned
74+
/// (reference-counted) so the context is `'static`.
75+
pub struct CustomRenderPassContext {
76+
pub device: Arc<wgpu::Device>,
77+
pub queue: Arc<wgpu::Queue>,
78+
pub view: wgpu::TextureView,
79+
pub format: wgpu::TextureFormat,
80+
pub bounds: Bounds<ScaledPixels>,
81+
}
82+
83+
impl AnyRenderContext for CustomRenderPassContext {
84+
fn as_any(&self) -> &dyn std::any::Any {
85+
self
86+
}
87+
88+
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
89+
self
90+
}
91+
}
92+
7293
pub struct WgpuSurfaceConfig {
7394
pub size: Size<DevicePixels>,
7495
pub transparent: bool,
@@ -1305,6 +1326,47 @@ impl WgpuRenderer {
13051326
// Not implemented for Linux/wgpu
13061327
true
13071328
}
1329+
PrimitiveBatch::CustomRenderPasses(range) => {
1330+
let custom_passes = &scene.custom_render_passes[range];
1331+
if custom_passes.is_empty() {
1332+
continue;
1333+
}
1334+
1335+
drop(pass);
1336+
1337+
let resources = self.resources();
1338+
let device = Arc::clone(&resources.device);
1339+
let queue = Arc::clone(&resources.queue);
1340+
let view = frame_view.clone();
1341+
let format = self.surface_config.format;
1342+
1343+
for custom in custom_passes {
1344+
let mut ctx = CustomRenderPassContext {
1345+
device: Arc::clone(&device),
1346+
queue: Arc::clone(&queue),
1347+
view: view.clone(),
1348+
format,
1349+
bounds: custom.bounds,
1350+
};
1351+
(custom.callback)(&mut ctx);
1352+
}
1353+
1354+
pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1355+
label: Some("main_pass_after_custom"),
1356+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1357+
view: &frame_view,
1358+
resolve_target: None,
1359+
ops: wgpu::Operations {
1360+
load: wgpu::LoadOp::Load,
1361+
store: wgpu::StoreOp::Store,
1362+
},
1363+
depth_slice: None,
1364+
})],
1365+
depth_stencil_attachment: None,
1366+
..Default::default()
1367+
});
1368+
true
1369+
}
13081370
};
13091371
if !ok {
13101372
overflow = true;

0 commit comments

Comments
 (0)