Skip to main content

repose_render_wgpu/
callback.rs

1//! The core crate stores `Arc<dyn Any>` in `SceneNode::Callback`; this crate
2//! provides the concrete `Callback` wrapper and the `WgpuCallback` trait that
3//! the renderer downcasts to.
4//!
5//! ```ignore
6//! use repose_core::prelude::*; // remember_mutable, request_frame, Modifier, View, PaintCallbackInfo
7//! use repose_render_wgpu::{Callback, WgpuCallback, CallbackResources};
8//! use repose_ui::Embedded; // or repose_canvas::Embedded
9//!
10//! struct MyTriangle { angle: f32 }
11//! impl WgpuCallback for MyTriangle {
12//!     fn prepare(&self, device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, screen: &repose_render_wgpu::ScreenDescriptor, resources: &mut CallbackResources) {
13//!         // resources.get_or_insert_with::<Pipelines>() -> update uniform buffer with self.angle
14//!     }
15//!     fn paint(&self, info: PaintCallbackInfo, rpass: &mut repose_render_wgpu::CallbackRenderPass<'_>, resources: &CallbackResources) {
16//!         // info.viewport is layout rect (physical px), renderer already set viewport -> info.viewport
17//!         // resources.get::<Pipelines>().unwrap().paint(rpass)
18//!     }
19//! }
20//!
21//! fn MyView() -> View {
22//!     let angle = remember_mutable(|| 0f32);
23//!     // Signal is !Send -> snapshot Copy value into callback (not the Signal itself)
24//!     let payload = { let a = *angle.get(); Callback::new(MyTriangle{ angle: a }) };
25//!     Embedded(
26//!         Modifier::new().size(300.0,300.0)
27//!             .on_pointer_move({ let a=angle.clone(); move |ev: PointerEvent| { a.update(|v| *v+=ev.position.x*0.01); request_frame() } }),
28//!         payload,
29//!     )
30//! }
31//! // For offscreen textures (bevy render target) use register_native_texture -> Image.
32//! ```
33
34use std::any::TypeId;
35use std::collections::HashMap;
36use std::sync::Arc;
37
38use repose_core::{PaintCallbackInfo, PaintCallbackPayload, Rect};
39
40/// Type-map for callback-shared wgpu resources (pipelines, buffers, etc.).
41#[cfg(not(all(
42    target_arch = "wasm32",
43    any(
44        not(feature = "fragile-send-sync-non-atomic-wasm"),
45        target_feature = "atomics"
46    )
47)))]
48type AnyBox = Box<dyn std::any::Any + Send + Sync>;
49#[cfg(all(
50    target_arch = "wasm32",
51    any(
52        not(feature = "fragile-send-sync-non-atomic-wasm"),
53        target_feature = "atomics"
54    )
55))]
56type AnyBox = Box<dyn std::any::Any>;
57
58#[cfg(not(all(
59    target_arch = "wasm32",
60    any(
61        not(feature = "fragile-send-sync-non-atomic-wasm"),
62        target_feature = "atomics"
63    )
64)))]
65pub trait MaybeSendSync: Send + Sync + 'static {}
66#[cfg(not(all(
67    target_arch = "wasm32",
68    any(
69        not(feature = "fragile-send-sync-non-atomic-wasm"),
70        target_feature = "atomics"
71    )
72)))]
73impl<T: Send + Sync + 'static> MaybeSendSync for T {}
74
75#[cfg(all(
76    target_arch = "wasm32",
77    any(
78        not(feature = "fragile-send-sync-non-atomic-wasm"),
79        target_feature = "atomics"
80    )
81))]
82pub trait MaybeSendSync: 'static {}
83#[cfg(all(
84    target_arch = "wasm32",
85    any(
86        not(feature = "fragile-send-sync-non-atomic-wasm"),
87        target_feature = "atomics"
88    )
89))]
90impl<T: 'static> MaybeSendSync for T {}
91
92#[derive(Default)]
93pub struct CallbackResources {
94    map: HashMap<TypeId, AnyBox>,
95}
96
97impl CallbackResources {
98    pub fn insert<T: MaybeSendSync>(&mut self, value: T) {
99        self.map.insert(TypeId::of::<T>(), Box::new(value));
100    }
101
102    pub fn get<T: MaybeSendSync>(&self) -> Option<&T> {
103        self.map
104            .get(&TypeId::of::<T>())
105            .and_then(|b| b.downcast_ref::<T>())
106    }
107
108    pub fn get_mut<T: MaybeSendSync>(&mut self) -> Option<&mut T> {
109        self.map
110            .get_mut(&TypeId::of::<T>())
111            .and_then(|b| b.downcast_mut::<T>())
112    }
113
114    pub fn get_or_insert_with<T: MaybeSendSync + Default>(&mut self) -> &mut T {
115        let id = TypeId::of::<T>();
116        self.map.entry(id).or_insert_with(|| Box::new(T::default()));
117        self.map.get_mut(&id).unwrap().downcast_mut::<T>().unwrap()
118    }
119
120    pub fn remove<T: 'static>(&mut self) -> Option<T> {
121        self.map
122            .remove(&TypeId::of::<T>())
123            .and_then(|b| b.downcast::<T>().ok())
124            .map(|b| *b)
125    }
126
127    pub fn contains<T: 'static>(&self) -> bool {
128        self.map.contains_key(&TypeId::of::<T>())
129    }
130
131    pub fn len(&self) -> usize {
132        self.map.len()
133    }
134
135    pub fn is_empty(&self) -> bool {
136        self.map.is_empty()
137    }
138
139    pub fn clear(&mut self) {
140        self.map.clear();
141    }
142}
143
144#[derive(Clone, Copy, Debug)]
145pub struct ScreenDescriptor {
146    pub size_in_pixels: [u32; 2],
147    pub pixels_per_point: f32,
148    /// Target surface format (e.g. `Bgra8UnormSrgb`), so `prepare` can create pipelines.
149    pub target_format: wgpu::TextureFormat,
150    /// MSAA sample count of the surface render pass (1 or 4).
151    pub sample_count: u32,
152}
153
154pub struct CallbackRenderPass<'pass, 'encoder> {
155    pass: &'pass mut wgpu::RenderPass<'encoder>,
156    target_format: wgpu::TextureFormat,
157    sample_count: u32,
158}
159
160impl<'pass, 'encoder> CallbackRenderPass<'pass, 'encoder> {
161    pub(crate) fn new(
162        pass: &'pass mut wgpu::RenderPass<'encoder>,
163        target_format: wgpu::TextureFormat,
164        sample_count: u32,
165    ) -> Self {
166        Self {
167            pass,
168            target_format,
169            sample_count,
170        }
171    }
172
173    pub fn target_format(&self) -> wgpu::TextureFormat {
174        self.target_format
175    }
176
177    pub fn sample_count(&self) -> u32 {
178        self.sample_count
179    }
180
181    pub fn set_pipeline(&mut self, pipeline: &wgpu::RenderPipeline) {
182        self.pass.set_pipeline(pipeline);
183    }
184
185    pub fn set_bind_group(&mut self, index: u32, bind_group: &wgpu::BindGroup, offsets: &[u32]) {
186        self.pass.set_bind_group(index, bind_group, offsets);
187    }
188
189    pub fn set_vertex_buffer(&mut self, slot: u32, buffer: wgpu::BufferSlice<'_>) {
190        self.pass.set_vertex_buffer(slot, buffer);
191    }
192
193    pub fn set_index_buffer(&mut self, buffer: wgpu::BufferSlice<'_>, format: wgpu::IndexFormat) {
194        self.pass.set_index_buffer(buffer, format);
195    }
196
197    pub fn set_viewport(
198        &mut self,
199        x: f32,
200        y: f32,
201        width: f32,
202        height: f32,
203        min_depth: f32,
204        max_depth: f32,
205    ) {
206        self.pass
207            .set_viewport(x, y, width, height, min_depth, max_depth);
208    }
209
210    pub fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
211        self.pass.set_scissor_rect(x, y, width, height);
212    }
213
214    pub fn set_stencil_reference(&mut self, reference: u32) {
215        self.pass.set_stencil_reference(reference);
216    }
217
218    pub fn draw(&mut self, vertices: std::ops::Range<u32>, instances: std::ops::Range<u32>) {
219        self.pass.draw(vertices, instances);
220    }
221
222    pub fn draw_indexed(
223        &mut self,
224        indices: std::ops::Range<u32>,
225        base_vertex: i32,
226        instances: std::ops::Range<u32>,
227    ) {
228        self.pass.draw_indexed(indices, base_vertex, instances);
229    }
230}
231
232/// Trait for custom wgpu rendering inside a `repose` layout rect.
233pub trait WgpuCallback: Send + Sync + 'static {
234    fn resource_key(&self) -> Option<&str> {
235        None
236    }
237
238    fn resource_type_id(&self) -> TypeId {
239        TypeId::of::<Self>()
240    }
241
242    /// Called before the main `repose` render pass, with access to `device`/`queue`/`encoder`
243    /// for buffer uploads. Can return extra command buffers to be submitted.
244    fn prepare(
245        &self,
246        _device: &wgpu::Device,
247        _queue: &wgpu::Queue,
248        _encoder: &mut wgpu::CommandEncoder,
249        _screen_descriptor: &ScreenDescriptor,
250        _resources: &mut CallbackResources,
251    ) -> Vec<wgpu::CommandBuffer> {
252        Vec::new()
253    }
254
255    /// Called after all `prepare` calls, before `paint`. For cross-callback sync.
256    fn finish_prepare(
257        &self,
258        _device: &wgpu::Device,
259        _queue: &wgpu::Queue,
260        _encoder: &mut wgpu::CommandEncoder,
261        _screen_descriptor: &ScreenDescriptor,
262        _resources: &mut CallbackResources,
263    ) -> Vec<wgpu::CommandBuffer> {
264        Vec::new()
265    }
266
267    fn paint(
268        &self,
269        info: PaintCallbackInfo,
270        render_pass: &mut CallbackRenderPass<'_, '_>,
271        resources: &CallbackResources,
272    );
273}
274
275pub struct Callback(pub Box<dyn WgpuCallback>);
276
277impl Callback {
278    #[deprecated(note = "rect is ignored; use Callback::new(callback) - layout supplies rect")]
279    pub fn new_paint_callback(
280        _rect: Rect,
281        callback: impl WgpuCallback + 'static,
282    ) -> PaintCallbackPayload {
283        Arc::new(Self(Box::new(callback)))
284    }
285
286    /// Create payload without caring about rect (rect is supplied via `SceneNode`).
287    // Public API used downstream; keep the `new` name despite the Arc return.
288    #[allow(clippy::new_ret_no_self)]
289    pub fn new(callback: impl WgpuCallback + 'static) -> PaintCallbackPayload {
290        Arc::new(Self(Box::new(callback)))
291    }
292
293    /// Idiomatic helper: create an `Embedded` view directly from a `WgpuCallback`.
294    /// `Modifier` supplies the layout rect.
295    pub fn embedded_view(
296        modifier: repose_core::Modifier,
297        callback: impl WgpuCallback + 'static,
298    ) -> repose_core::View {
299        let payload = Self::new(callback);
300        let mut m = modifier.paint_callback(payload);
301        let has_size = m.size.is_some()
302            || m.width.is_some()
303            || m.height.is_some()
304            || m.fill_max.is_some()
305            || m.fill_max_w.is_some()
306            || m.fill_max_h.is_some();
307        if !has_size {
308            m = m.size(repose_core::Dp(100.0), repose_core::Dp(100.0));
309        }
310        repose_core::View::new(0, repose_core::ViewKind::Box).modifier(m)
311    }
312}