ranim_render/
primitives.rs

1/// Primitive for vitem
2pub mod vitem;
3
4use std::{
5    any::{Any, TypeId},
6    collections::HashMap,
7    sync::Arc,
8};
9
10use variadics_please::all_tuples;
11
12use crate::utils::WgpuContext;
13
14use super::RenderTextures;
15
16/// The RenderResource encapsules the wgpu resources.
17///
18/// It has a `Data` type that is used to initialize/update the resource.
19pub trait RenderResource {
20    /// The type used for [`RenderResource::init`] and [`RenderResource::update`].
21    type Data;
22    /// Initialize a RenderResource using [`RenderResource::Data`]
23    fn init(ctx: &WgpuContext, data: &Self::Data) -> Self;
24    /// update a RenderResource using [`RenderResource::Data`]
25    fn update(&mut self, ctx: &WgpuContext, data: &Self::Data);
26}
27
28/// The RenderCommand encodes the commands.
29pub trait RenderCommand {
30    /// Encode the compute pass command
31    fn encode_compute_pass_command(&self, cpass: &mut wgpu::ComputePass);
32    /// Encode the render pass command
33    fn encode_render_pass_command(&self, rpass: &mut wgpu::RenderPass);
34    /// Encode the render command
35    fn encode_render_command(
36        &self,
37        ctx: &WgpuContext,
38        pipelines: &mut super::PipelinesStorage,
39        encoder: &mut wgpu::CommandEncoder,
40        uniforms_bind_group: &wgpu::BindGroup,
41        render_textures: &RenderTextures,
42        #[cfg(feature = "profiling")] profiler: &mut wgpu_profiler::GpuProfiler,
43    );
44    /// Debug
45    fn debug(&self, _ctx: &WgpuContext) {}
46}
47
48macro_rules! impl_tuple_render_command {
49    ($($T:ident),*) => {
50        impl<$($T: RenderCommand,)*> RenderCommand for ($($T,)*) {
51            fn encode_compute_pass_command(
52                &self,
53                cpass: &mut wgpu::ComputePass,
54            ) {
55                #[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
56                let ($($T,)*) = self;
57                $($T.encode_compute_pass_command(
58                    cpass,
59                );)*
60            }
61            fn encode_render_pass_command(
62                &self,
63                rpass: &mut wgpu::RenderPass,
64            ) {
65                #[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
66                let ($($T,)*) = self;
67                $($T.encode_render_pass_command(
68                    rpass,
69                );)*
70            }
71            fn encode_render_command(
72                &self,
73                ctx: &WgpuContext,
74                pipelines: &mut super::PipelinesStorage,
75                encoder: &mut wgpu::CommandEncoder,
76                uniforms_bind_group: &wgpu::BindGroup,
77                render_textures: &RenderTextures,
78                #[cfg(feature = "profiling")] profiler: &mut wgpu_profiler::GpuProfiler,
79            ) {
80                #[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
81                let ($($T,)*) = self;
82                $($T.encode_render_command(
83                    ctx,
84                    pipelines,
85                    encoder,
86                    uniforms_bind_group,
87                    render_textures,
88                    #[cfg(feature = "profiling")]
89                    profiler,
90                );)*
91            }
92            fn debug(&self, ctx: &WgpuContext) {
93                #[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
94                let ($($T,)*) = self;
95                $($T.debug(ctx);)*
96            }
97        }
98    };
99}
100
101all_tuples!(impl_tuple_render_command, 1, 16, T);
102
103impl<T: RenderCommand> RenderCommand for Vec<T> {
104    fn encode_compute_pass_command(&self, cpass: &mut wgpu::ComputePass) {
105        self.iter()
106            .for_each(|x| x.encode_compute_pass_command(cpass))
107    }
108    fn encode_render_pass_command(&self, rpass: &mut wgpu::RenderPass) {
109        self.iter()
110            .for_each(|x| x.encode_render_pass_command(rpass))
111    }
112    fn encode_render_command(
113        &self,
114        ctx: &WgpuContext,
115        pipelines: &mut super::PipelinesStorage,
116        encoder: &mut wgpu::CommandEncoder,
117        uniforms_bind_group: &wgpu::BindGroup,
118        render_textures: &RenderTextures,
119        #[cfg(feature = "profiling")] profiler: &mut wgpu_profiler::GpuProfiler,
120    ) {
121        self.iter().for_each(|x| {
122            x.encode_render_command(
123                ctx,
124                pipelines,
125                encoder,
126                uniforms_bind_group,
127                render_textures,
128                #[cfg(feature = "profiling")]
129                profiler,
130            )
131        });
132    }
133    fn debug(&self, _ctx: &WgpuContext) {
134        self.iter().for_each(|x| x.debug(_ctx));
135    }
136}
137
138impl<T: RenderCommand, const N: usize> RenderCommand for [T; N] {
139    fn encode_compute_pass_command(&self, cpass: &mut wgpu::ComputePass) {
140        self.iter()
141            .for_each(|x| x.encode_compute_pass_command(cpass))
142    }
143    fn encode_render_pass_command(&self, rpass: &mut wgpu::RenderPass) {
144        self.iter()
145            .for_each(|x| x.encode_render_pass_command(rpass))
146    }
147    fn encode_render_command(
148        &self,
149        ctx: &WgpuContext,
150        pipelines: &mut super::PipelinesStorage,
151        encoder: &mut wgpu::CommandEncoder,
152        uniforms_bind_group: &wgpu::BindGroup,
153        render_textures: &RenderTextures,
154        #[cfg(feature = "profiling")] profiler: &mut wgpu_profiler::GpuProfiler,
155    ) {
156        self.iter().for_each(|x| {
157            x.encode_render_command(
158                ctx,
159                pipelines,
160                encoder,
161                uniforms_bind_group,
162                render_textures,
163                #[cfg(feature = "profiling")]
164                profiler,
165            )
166        });
167    }
168    fn debug(&self, _ctx: &WgpuContext) {
169        self.iter().for_each(|x| x.debug(_ctx));
170    }
171}
172
173/// The Primitive is the basic renderable object in Ranim.
174///
175/// The Primitive itself is simply the data of the object.
176/// A Primitive has a corresponding [`Primitive::RenderInstance`],
177/// which implements [`RenderResource`] and [`RenderCommand`]:
178/// - [`RenderResource`]: A trait about init or update itself with [`RenderResource::Data`].
179/// - [`RenderCommand`]: A trait about encoding GPU commands.
180pub trait Primitive {
181    /// The RenderInstance
182    type RenderInstance: RenderResource<Data = Self> + RenderCommand;
183}
184
185slotmap::new_key_type! { pub struct RenderInstanceKey; }
186
187// MARK: RenderPool
188#[derive(Default)]
189pub struct RenderPool {
190    #[allow(clippy::type_complexity)]
191    inner: slotmap::SlotMap<
192        RenderInstanceKey,
193        (Arc<RenderInstanceKey>, TypeId, Box<dyn AnyRenderCommand>),
194    >,
195    last_frame_dropped: HashMap<TypeId, Vec<RenderInstanceKey>>,
196}
197
198impl RenderPool {
199    pub fn new() -> Self {
200        Self::default()
201    }
202
203    pub fn get(&self, key: RenderInstanceKey) -> Option<&dyn AnyRenderCommand> {
204        self.inner
205            .get(key)
206            .map(|x| x.2.as_ref() as &dyn AnyRenderCommand)
207    }
208
209    pub fn show(&self) {
210        self.inner
211            .iter()
212            .enumerate()
213            .for_each(|(idx, (_, (k, _, _)))| {
214                print!("{idx}: {}, ", Arc::strong_count(k));
215            });
216        println!();
217    }
218
219    pub fn alloc<T: RenderCommand + RenderResource<Data = D> + Send + Sync + 'static, D>(
220        &mut self,
221        ctx: &WgpuContext,
222        data: &D,
223    ) -> Arc<RenderInstanceKey> {
224        let last_frame_dropped = self
225            .last_frame_dropped
226            .entry(TypeId::of::<T>())
227            .or_default();
228        if let Some(key) = last_frame_dropped.pop() {
229            let entry = self.inner.get_mut(key).unwrap();
230            let key = entry.0.clone();
231            (entry.2.as_mut() as &mut dyn Any)
232                .downcast_mut::<T>()
233                .unwrap()
234                .update(ctx, data);
235            key
236        } else {
237            let handle = self.inner.insert_with_key(|key| {
238                (
239                    Arc::new(key),
240                    TypeId::of::<T>(),
241                    Box::new(T::init(ctx, data)),
242                )
243            });
244            self.inner.get(handle).unwrap().0.clone()
245        }
246    }
247
248    pub fn clean(&mut self) {
249        self.inner.retain(|key, (_, t_id, _)| {
250            self.last_frame_dropped
251                .get(t_id)
252                .map(|x| !x.contains(&key))
253                .unwrap_or(true)
254        });
255        // println!("dropped {}", self.last_frame_dropped.len());
256        self.last_frame_dropped.clear();
257        self.inner
258            .iter()
259            .filter(|(_, (key, _, _))| Arc::strong_count(key) == 1)
260            .for_each(|(key, (_, t_id, _))| {
261                self.last_frame_dropped.entry(*t_id).or_default().push(key);
262            });
263    }
264}
265
266/// Type erased [`RenderCommand`]
267pub trait AnyRenderCommand: Any + RenderCommand + Send + Sync {}
268impl<T: RenderCommand + Any + Send + Sync> AnyRenderCommand for T {}
269
270impl RenderCommand for Vec<&dyn RenderCommand> {
271    fn encode_compute_pass_command(&self, cpass: &mut wgpu::ComputePass) {
272        for render_instance in self {
273            render_instance.encode_compute_pass_command(cpass);
274        }
275    }
276    fn encode_render_pass_command(&self, rpass: &mut wgpu::RenderPass) {
277        for render_instance in self {
278            render_instance.encode_render_pass_command(rpass);
279        }
280    }
281
282    fn encode_render_command(
283        &self,
284        ctx: &WgpuContext,
285        pipelines: &mut super::PipelinesStorage,
286        encoder: &mut wgpu::CommandEncoder,
287        uniforms_bind_group: &wgpu::BindGroup,
288        render_textures: &RenderTextures,
289        #[cfg(feature = "profiling")] profiler: &mut wgpu_profiler::GpuProfiler,
290    ) {
291        for render_instance in self {
292            render_instance.encode_render_command(
293                ctx,
294                pipelines,
295                encoder,
296                uniforms_bind_group,
297                render_textures,
298                #[cfg(feature = "profiling")]
299                profiler,
300            );
301        }
302    }
303    fn debug(&self, ctx: &WgpuContext) {
304        for render_instance in self {
305            render_instance.debug(ctx);
306        }
307    }
308}