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}