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