Skip to main content

mew/
pipeline.rs

1use std::{
2    any::TypeId,
3    marker::PhantomData,
4    ops::Deref,
5};
6
7
8/// Define a render pipeline, with internal bind group and vertex types.
9///
10/// `bind_group` needs to be a numbered list of [`crate::bindgroup`] types,
11/// mirrored in the shader.
12///
13/// `vertex_types` must be another numbered list of [`crate::vertexstruct`]s.
14/// These are the allowed types of vertex formats for the shader (I'm not sure
15/// why you would have multiple entries but _wgpu_ allows it so whatever).
16///
17/// `fragment_targets` is in the format
18/// <code>[wgpu::BlendState](https://docs.rs/wgpu/latest/wgpu/struct.BlendState.html) [wgpu::ColorWrites](https://docs.rs/wgpu/latest/wgpu/struct.ColorWrites.html) as `ANY`/[wgpu::TextureFormat](https://docs.rs/wgpu/latest/wgpu/enum.TextureFormat.html)</code>.
19/// The [`wgpu::ColorTargetState`](https://docs.rs/wgpu/latest/wgpu/struct.ColorTargetState.html) will be `Some` if the pipeline is built with
20/// the same format specified here, or if it's `ANY` then always `Some` with
21/// the format it's built with - otherwise it's a `None` entry in the slice.  
22/// (tbh idk what the purpose of having multiple fragment targets is but I assume
23/// it's supposed be used something like this)
24///
25/// `immediate_size` is the length of the
26/// [immediate buffer](https://docs.rs/wgpu/latest/wgpu/struct.Features.html#associatedconstant.IMMEDIATES)
27/// in the [`wgpu::PipelineLayoutDescriptor`](https://docs.rs/wgpu/latest/wgpu/struct.PipelineLayoutDescriptor.html).
28///
29/// `depth` is an
30/// <code>Option<[wgpu::TextureFormat](https://docs.rs/wgpu/latest/wgpu/enum.TextureFormat.html)></code>
31/// to use in the [`wgpu::DepthStencilState`](https://docs.rs/wgpu/latest/wgpu/struct.DepthStencilState.html).
32/// Said `DepthStencilState` comes default with `depth_write_enabled = true`,
33/// <code>depth_compare = [wgpu::CompareFunction::Less](https://docs.rs/wgpu/latest/wgpu/enum.CompareFunction.html#variant.Less)</code>,
34/// and default `stencil` and `bias`.
35///
36/// `cull` is the cull face, an <code>Option<[wgpu::Face](https://docs.rs/wgpu/latest/wgpu/enum.Face.html)></code>.
37///
38/// The pipeline comes with some other defaults set (like 0 mipmaps), but there
39/// are quite a few things to define, so to simplify the macro I've picked out
40/// the most important things. If you really need to change them, copy the
41/// source of this macro into your own struct and implement [`MewPipeline`]
42/// yourself ~~you lazy bastard~~.
43///
44///
45/// ```
46/// pipeline! { Pipeline {
47///     bind_groups: [
48///         0 => BindGroup,
49///         1 => AnotherBindGroup,
50///     ],
51///     vertex_types: [
52///         0 => Vertex,
53///     ],
54///     fragment_targets: [
55///         0 => ALPHA_BLENDING ALL as ANY,
56///     ],
57///     immediate_size: 0,
58///     depth: None,
59///     cull: Some(Front),
60/// } }
61///
62/// let pipeline: Pipeline = render_context.new_pipeline(wgpu::Rgba8Unorm, shader, None, None);
63///
64/// render_pass.set_pipeline(&pipeline);
65/// ```
66#[macro_export]
67macro_rules! pipeline {
68    { @texturefmt $inputfmt:ident , ANY } => {
69        Some($inputfmt)
70    };
71    { @texturefmt $format:ident , $fragmentfmt:ident } => {
72        //Some($crate::wgpu::TextureFormat::$fragmentfmt)
73        if $inputfmt == $fragmentfmt {
74            Some($inputfmt)
75        } else {
76            None
77        }
78    };
79
80    { $struct:ident {
81        bind_groups : [
82            $( $bindgroupnum:tt => $bindgroup:ty ),* $(,)?
83        ],
84        vertex_types: [
85            $( $vertexnum:tt => $vertexty:ty ),* $(,)?
86        ],
87        fragment_targets: [
88            $( $fragmentnum:tt => $blend:ident $mask:ident as $fragmentfmt:ident ),* $(,)?
89        ],
90        immediate_size: $immediate:expr,
91        depth: $depthfmt:expr,
92        cull: $cull:expr,
93    } } => {
94        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
95        pub struct $struct {
96            pipeline: $crate::wgpu::RenderPipeline,
97        }
98
99        impl ::std::ops::Deref for $struct {
100            type Target = $crate::wgpu::RenderPipeline;
101            fn deref(&self) -> &Self::Target {
102                &self.pipeline
103            }
104        }
105
106        /*
107        impl $struct {
108            pub fn get_layouts_from_refs<GETTER>(getter: &GETTER) -> <Self as $crate::pipeline::MewPipeline>::BindGroupLayoutSet<'_>
109            where
110                GETTER: $( AsRef<$crate::bindgroup::MewBindGroupLayout<$bindgroup>> + )*
111            {
112                ( $( {
113                    let layout: &$crate::bindgroup::MewBindGroupLayout<$bindgroup> = getter.as_ref();
114                    layout
115                }, )* )
116            }
117        }
118        */
119
120        impl $crate::pipeline::MewPipeline for $struct {
121            type BindGroupSet = ( $( $bindgroup , )* );
122            //type BindGroupLayoutSet = ( $( MewBindGroupLayout<$bindgroup> , )* );
123            type BindGroupArray<T> = [T; $crate::len! { $( $bindgroupnum )* }];
124            type VertexArray<T> = [T; $crate::len! { $( $vertexnum )* }];
125            type FragmentArray<T> = [T; $crate::len! { $( $fragmentnum )* }];
126
127            fn new(pipeline: $crate::wgpu::RenderPipeline) -> Self {
128                Self {
129                    pipeline,
130                }
131            }
132
133            /*
134            fn bind_group_layout_array(context: &$crate::RenderContext) -> Self::BindGroupArray<$crate::wgpu::BindGroupLayout> {
135                /*
136                [ $(
137                    bind_group_layouts.$bindgroupnum,
138                )* ]
139                */
140                [ $(
141                    context.get_bind_group_layout::<$bindgroup>(),
142                )* ]
143            }
144            */
145
146            fn bind_group_layout_types_array() -> Self::BindGroupArray<(::std::any::TypeId, $crate::wgpu::BindGroupLayoutDescriptor<'static>)> {
147                use $crate::bindgroup::MewBindGroup;
148                [ $( (
149                    ::std::any::TypeId::of::<$bindgroup>(),
150                    <$bindgroup>::layout_desc(),
151                ), )* ]
152            }
153
154            fn map_bind_group_array<'a, A: 'a, B: 'a, F: FnMut(&'a A) -> B>(array: &'a Self::BindGroupArray<A>, f: F) -> Self::BindGroupArray<B> {
155                array.each_ref().map(f)
156            }
157
158            /*
159            fn bind_group_ref_array(layout_array: &Self::BindGroupArray<$crate::wgpu::BindGroupLayout>) -> Self::BindGroupArray<&$crate::wgpu::BindGroupLayout> {
160                layout_array.each_ref()
161            }
162            */
163
164            fn layout_desc<'a, 'b>(bind_group_layouts: &'a Self::BindGroupArray<&'b $crate::wgpu::BindGroupLayout>) -> $crate::wgpu::PipelineLayoutDescriptor<'a> {
165                $crate::wgpu::PipelineLayoutDescriptor {
166                    label: Some(concat!(stringify!($struct), " Layout Descriptor")),
167                    bind_group_layouts,
168                    /*
169                    push_constant_ranges: &[ $(
170                        $crate::wgpu::PushConstantRange {
171                            stages: $crate::wgpu::ShaderStages::$pushshaderstage,
172                            range: $pushfrom..$pushto,
173                        },
174                    )* ],1
175                    */
176                    immediate_size: $immediate,
177                }
178            }
179
180            fn fragment_targets<'a>(surface_fmt: $crate::wgpu::TextureFormat) -> Self::FragmentArray<Option<$crate::wgpu::ColorTargetState>> {
181                [ $( $crate::pipeline! { @texturefmt surface_fmt, $fragmentfmt }.map(|format| $crate::wgpu::ColorTargetState {
182                    format,
183                    blend: Some($crate::wgpu::BlendState::$blend),
184                    write_mask: $crate::wgpu::ColorWrites::$mask,
185                }), )* ]
186            }
187
188            fn pipeline_desc<'a>(
189                layout: &'a $crate::pipeline::MewPipelineLayout<Self>,
190                shader: &'a $crate::wgpu::ShaderModule,
191                vertex_entry: Option<&'a str>,
192                fragment_entry: Option<&'a str>,
193                fragment_targets: &'a Self::FragmentArray<Option<$crate::wgpu::ColorTargetState>>,
194            ) -> $crate::wgpu::RenderPipelineDescriptor<'a> {
195                use ::std::sync::LazyLock;
196                #[allow(unused_imports)]
197                use $crate::wgpu::{
198                    Face::*,
199                    TextureFormat::*,
200                };
201
202                static VERTICES: LazyLock<<$struct as $crate::pipeline::MewPipeline>::VertexArray<$crate::wgpu::VertexBufferLayout<'static>>> = LazyLock::new(|| {
203                    #[allow(unused_imports)]
204                    use $crate::vertexstruct::MewVertexStruct;
205                    [ $( <$vertexty>::vertex_layout(), )* ]
206                });
207
208                $crate::wgpu::RenderPipelineDescriptor {
209                    label: Some(concat!(stringify!($struct), " Descriptor")),
210                    layout: Some(layout),
211                    vertex: $crate::wgpu::VertexState {
212                        module: shader,
213                        entry_point: vertex_entry,
214                        compilation_options: Default::default(),
215                        buffers: &*VERTICES,
216                    },
217                    fragment: Some($crate::wgpu::FragmentState {
218                        module: shader,
219                        entry_point: fragment_entry,
220                        compilation_options: Default::default(),
221                        targets: fragment_targets,
222                    }),
223                    primitive: $crate::wgpu::PrimitiveState {
224                        topology: $crate::wgpu::PrimitiveTopology::TriangleList,
225                        strip_index_format: None,
226                        front_face: $crate::wgpu::FrontFace::Ccw,
227                        cull_mode: $cull,
228                        polygon_mode: $crate::wgpu::PolygonMode::Fill,
229                        unclipped_depth: false,
230                        conservative: false,
231                    },
232                    depth_stencil: $depthfmt.map(|format| $crate::wgpu::DepthStencilState {
233                        format,
234                        depth_write_enabled: true,
235                        depth_compare: $crate::wgpu::CompareFunction::Less,
236                        stencil: Default::default(),
237                        bias: Default::default(),
238                    }),
239                    multisample: $crate::wgpu::MultisampleState {
240                        count: 1,
241                        mask: !0,
242                        alpha_to_coverage_enabled: true,
243                    },
244                    multiview_mask: None,
245                    cache: None,
246                }
247            }
248        }
249    };
250}
251pub use pipeline;
252
253
254/// Trait for pipeline wrapper types, to get layouts and descriptors about the
255/// inner pipeline.
256///
257/// The number of internal bind groups must be defined in the generic parameter,
258/// because associated `const`s are "uNsTAbLe", apparently.
259///
260/// Ideally, avoid using this directly - it's horribly designed, so just look
261/// away and use the [`pipeline`] macro.
262pub trait MewPipeline {
263    /// A tuple of the pipeline's internal bind groups (max of 4).
264    type BindGroupSet;
265    // /// A tuple of layouts of each of the pipeline's internal bind groups.
266    //type BindGroupLayoutSet<'a>;
267    /// A generic array the length of the number of bind groups
268    /// (literally `[T; N]`).
269    type BindGroupArray<T>;
270    /// A generic array the length of the number of vertex types
271    /// (literally `[T; N]`).
272    type VertexArray<T>;
273    /// A generic array the length of the number of fragment targets
274    /// (literally `[T; N]`).
275    type FragmentArray<T>;
276
277    /// Build this pipeline type from a
278    /// [`wgpu::RenderPipeline`](https://docs.rs/wgpu/latest/wgpu/struct.RenderPipeline.html).
279    fn new(pipeline: wgpu::RenderPipeline) -> Self;
280
281    /// Get an array of the internal bind groups' type IDs &
282    /// [`wgpu::BindGroupLayoutDescriptor`](https://docs.rs/wgpu/latest/wgpu/struct.BindGroupLayoutDescriptor.html)s.
283    fn bind_group_layout_types_array() -> Self::BindGroupArray<(TypeId, wgpu::BindGroupLayoutDescriptor<'static>)>;
284    /// Run a closure on each element in an array the lenth of the number of bind groups ([`Self::BindGroupArray`]).
285    ///
286    /// This is only necessary because of a limitation with Rust's generics in traits.
287    /// Take a look at the above [`Self::BindGroupArray`], [`Self::VertexArray`], &
288    /// [`Self::FragmentArray`] - the compiler has no way of knowing those are just
289    /// supposed to be primitive `array`s with a generic `const` length. Ideally I'd
290    /// just specify a `const usize` in the trait, but nooOOoo you're not allowed to
291    /// do that, so I have to make do with this.
292    ///
293    /// Primitive `array`s also aren't distinguishable by any sort of trait to narrow down
294    /// the type (really all I need is a [`std::iter::Map`]), but I don't want to import any
295    /// bloat libraries & writing something is probably overkill for how I'm using it
296    /// (purely internally).
297    fn map_bind_group_array<'a, A: 'a, B: 'a, F: FnMut(&'a A) -> B>(array: &'a Self::BindGroupArray<A>, f: F) -> Self::BindGroupArray<B>;
298
299    //fn bind_group_ref_array(layout_array: &Self::BindGroupArray<wgpu::BindGroupLayout>) -> Self::BindGroupArray<&wgpu::BindGroupLayout>;
300
301    /// Get the [`wgpu::PipelineLayoutDescriptor`](https://docs.rs/wgpu/latest/wgpu/struct.PipelineLayoutDescriptor.html)
302    /// to build the pipeline. Requires an array of references of its internal bind groups'
303    /// <code>&[BindGroupLayout](https://docs.rs/wgpu/latest/wgpu/struct.BindGroupLayout.html)</code>s
304    /// which are mappable from [`Self::bind_group_layout_types_array`].  
305    /// Need to do it separately like this because `const fn`s in traits are "uNsTAbLe",
306    /// apparently, and lifetimes exist.
307    fn layout_desc<'a, 'b>(bind_group_layouts: &'a Self::BindGroupArray<&'b wgpu::BindGroupLayout>) -> wgpu::PipelineLayoutDescriptor<'a>;
308
309    /// Get an array of
310    /// <code>Option<[wgpu::ColorTargetState](https://docs.rs/wgpu/latest/wgpu/struct.ColorTargetState.html)></code>s.
311    ///
312    /// If a specific colour format was specified for this state in the macro, it will
313    /// only be `Some` if the passed surface
314    /// [`wgpu::TextureFormat`](https://docs.rs/wgpu/latest/wgpu/enum.TextureFormat.html)
315    /// matches. If it was set to `ANY`, then for that entry it will always return
316    /// `Some` with the passed-in format.
317    ///
318    /// tbh I'm not sure if this is how it's intended to be used but yolo
319    fn fragment_targets<'a>(surface_fmt: wgpu::TextureFormat) -> Self::FragmentArray<Option<wgpu::ColorTargetState>>;
320
321    /// Get a [`wgpu::RenderPipelineDescriptor`](https://docs.rs/wgpu/latest/wgpu/struct.RenderPipelineDescriptor.html)
322    /// from a layout for this specific type of pipeline, plus a shader and the
323    /// fragment targets array from [`Self::fragment_targets`].
324    fn pipeline_desc<'a>(
325        layout: &'a MewPipelineLayout<Self>,
326        shader: &'a wgpu::ShaderModule,
327        vertex_entry: Option<&'a str>,
328        fragment_entry: Option<&'a str>,
329        fragment_targets: &'a Self::FragmentArray<Option<wgpu::ColorTargetState>>,
330    ) -> wgpu::RenderPipelineDescriptor<'a>;
331}
332
333
334/// Wrapper for the layout of some pipeline, so that the compiler will scream
335/// at you if you mix up pipelines.
336pub struct MewPipelineLayout<PIPE: ?Sized> {
337    layout: wgpu::PipelineLayout,
338    _marker: PhantomData<PIPE>,
339}
340impl<PIPE: ?Sized> Deref for MewPipelineLayout<PIPE> {
341    type Target = wgpu::PipelineLayout;
342    fn deref(&self) -> &Self::Target {
343        &self.layout
344    }
345}
346impl<PIPE: ?Sized> MewPipelineLayout<PIPE> {
347    /// Wrap a [`wgpu::PipelineLayout`](https://docs.rs/wgpu/latest/wgpu/struct.PipelineLayout.html).
348    pub fn new(layout: wgpu::PipelineLayout) -> Self {
349        Self {
350            layout,
351            _marker: PhantomData,
352        }
353    }
354}