Skip to main content

mew/
lib.rs

1#![doc(html_logo_url = "https://64-tesseract.ftp.sh/tesseract.gif", html_favicon_url = "https://64-tesseract.ftp.sh/tesseract.gif")]
2#![doc = include_str!("../README.md")]
3
4#![cfg_attr(docs, feature(doc_cfg))]
5#![cfg_attr(docs, doc(auto_cfg(hide(docs))))]
6
7use std::{
8    any::TypeId,
9    cell::RefCell,
10    collections::BTreeMap,
11};
12use thiserror::Error;
13
14pub use wgpu;
15#[cfg(feature = "poll")]
16pub use pollster;
17#[cfg(feature = "winit")]
18pub use winit;
19
20pub mod doc_example;
21
22mod macromath;
23pub mod bindgroup;
24pub mod buffer;
25pub mod pipeline;
26pub mod sampler;
27pub mod shaderprimitive;
28pub mod shaderstruct;
29pub mod texture;
30//pub mod typemap;
31pub mod vertexformat;
32pub mod vertexstruct;
33#[cfg(any(feature = "winit", docs))]
34pub mod winitutils;
35
36pub mod prelude {
37    pub use crate::{
38        bindgroup::*,
39        buffer::*,
40        pipeline::*,
41        sampler::*,
42        shaderprimitive,
43        shaderstruct::*,
44        texture::*,
45        vertexformat,
46        vertexstruct::*,
47        *,
48    };
49    #[cfg(feature = "winit")]
50    pub use crate::winitutils::*;
51}
52
53
54/*
55/// The master object that manages everything `wgpu`-related.
56///
57/// On construction it initializes the instance, adapter, device, and queue
58/// using some default settings so you don't need to worry about that repetitive
59/// boilerplate. Not all options are customizable but it should work well
60/// enough.
61///
62/// The render context also keeps track of your buffer, bind group, texture,
63/// and pipeline definition types, so you can construct the from a central
64/// location. Since regular macros don't support changing identifiers, this is
65/// implemented by type parameters.
66///
67/// ```
68/// render_context! { Context {
69///     // List of `wgpu::Features` for the device
70///     features: [MULTIVIEW, VERTEX_WRITABLE_STORAGE],  // idk I usually just leave these blank
71///     // Map of values to override from `wgpu::Limits` defaults for the device
72///     limits: {max_vertex_attributes: 17, max_vertex_buffers: 4},  // same here, random values - definitely don't use
73///     // Numbered (and ordered!) list of your bind group definitions
74///     bind_groups: [
75///         0 => GroupOne,
76///         1 => GroupThree,
77///         2 => GroupNegativeSixtyNine,
78///     ],
79///     // Also numbered (and also ordered!) list of your pipeline definitions
80///     pipelines: [
81///         0 => MainPipeline,
82///         1 => NotMainPipeline,
83///     ],
84/// } }
85/// ```
86#[macro_export]
87macro_rules! render_context {
88    { $struct:ident {
89        features: [
90            $( $feature:ident ),* $(,)?
91        ],
92        limits: {
93            $( $limit:ident : $limitval:expr ),* $(,)?
94        },
95        bind_groups: [
96            $( $bindgroupnum:tt => $bindgroup:ty ),* $(,)?
97        ],
98        pipelines: [
99            $( $pipelinenum:tt => $pipeline:ty ),* $(,)?
100        ] $(,)?
101    } } => {
102        typemap! { BindGroupLayoutTypes [
103            $( $bindgroupnum => $crate::bindgroup::MewBindGroupLayout<$bindgroup> , )*
104        ] }
105
106        typemap! { PipelineLayoutTypes [
107            $( $pipelinenum => $crate::pipeline::MewPipelineLayout<$pipeline> , )*
108        ] }
109
110        pub struct $struct {
111            pub instance: $crate::wgpu::Instance,
112            pub adapter: $crate::wgpu::Adapter,
113            pub device: $crate::wgpu::Device,
114            pub queue: $crate::wgpu::Queue,
115            bind_group_layouts: BindGroupLayoutTypes,
116            pipeline_layouts: PipelineLayoutTypes,
117        }
118
119        impl $struct {
120            pub async fn new(backends: $crate::wgpu::Backends) -> Result<Self, Box<dyn ::std::error::Error>> {
121                let instance = $crate::wgpu::Instance::new(&wgpu::InstanceDescriptor {
122                    backends,
123                    flags: $crate::wgpu::InstanceFlags::empty(),
124                    ..Default::default()
125                });
126
127                Self::new_with_instance(instance).await
128            }
129
130            pub async fn new_with_instance(instance: $crate::wgpu::Instance) -> Result<Self, Box<dyn ::std::error::Error>> {
131                let adapter = instance.request_adapter(
132                    &$crate::wgpu::RequestAdapterOptions {
133                        power_preference: $crate::wgpu::PowerPreference::default(),
134                        compatible_surface: None,
135                        force_fallback_adapter: false,
136                    },
137                ).await.inspect_err(|e| eprintln!("Could not get display adapter: {e}"))?;
138
139                let (device, queue) = adapter.request_device(
140                    &$crate::wgpu::DeviceDescriptor {
141                        label: Some("Device"),
142                        required_features: $crate::wgpu::Features::empty() $( | $crate::wgpu::Features::$feature )*,
143                        required_limits: $crate::wgpu::Limits {
144                            $( $limit: $limitval, )*
145                            ..Default::default()
146                        },
147                        memory_hints: Default::default(),
148                        trace: $crate::wgpu::Trace::Off,
149                    }
150                ).await.inspect_err(|e| eprintln!("Could not get display device: {e}"))?;
151
152                let bind_group_layouts = BindGroupLayoutTypes (
153                    $( {
154                        let desc = <$bindgroup>::layout_desc();
155                        let layout = device.create_bind_group_layout(&desc);
156                        $crate::bindgroup::MewBindGroupLayout::new(layout)
157                    }, )*
158                );
159
160                let pipeline_layouts = PipelineLayoutTypes (
161                    $( {
162                        let bind_groups = <$pipeline>::get_layouts_from_refs(&bind_group_layouts);
163                        let bind_groups_arr = <$pipeline>::bind_group_layouts(&bind_groups);
164                        let desc = <$pipeline>::layout_desc(&bind_groups_arr);
165                        let layout = device.create_pipeline_layout(&desc);
166                        $crate::pipeline::MewPipelineLayout::new(layout)
167                    }, )*
168                );
169
170                Ok(Self {
171                    instance,
172                    adapter,
173                    device,
174                    queue,
175                    bind_group_layouts,
176                    pipeline_layouts,
177                })
178            }
179
180            pub fn new_buffer<BUFFER: $crate::buffer::MewBuffer>(&self, inner_size: u64) -> BUFFER {
181                let raw_buffer = self.device.create_buffer(&BUFFER::buffer_desc(inner_size));
182                BUFFER::new(raw_buffer)
183            }
184
185            pub fn new_sampler<SAMPLER: $crate::sampler::MewSampler>(&self) -> SAMPLER {
186                let raw_sampler = self.device.create_sampler(&SAMPLER::buffer_desc());
187                SAMPLER::new(raw_sampler)
188            }
189
190            pub fn new_texture<TEXTURE: $crate::texture::MewTexture>(&self, inner_size: (u32, u32, u32), format: $crate::wgpu::TextureFormat) -> TEXTURE {
191                let raw_texture = self.device.create_texture(&TEXTURE::buffer_desc(inner_size, format));
192                TEXTURE::new(raw_texture)
193            }
194
195            pub fn new_bind_group<BIND: $crate::bindgroup::MewBindGroup>(&self, buffers: BIND::BufferSet) -> BIND
196            where
197                BindGroupLayoutTypes: AsRef<$crate::bindgroup::MewBindGroupLayout<BIND>>
198            {
199                let raw_bind_group = {
200                    let layout: &$crate::bindgroup::MewBindGroupLayout<BIND> = self.bind_group_layouts.as_ref();
201                    let entries = BIND::bind_group_entries(&buffers);
202                    let desc = BIND::bind_group_desc(layout, &entries);
203                    self.device.create_bind_group(&desc)
204                };
205                BIND::new(raw_bind_group, buffers)
206            }
207
208            pub fn new_pipeline<PIPE: $crate::pipeline::MewPipeline>(
209                &self,
210                surface_fmt: Option<$crate::wgpu::TextureFormat>,
211                shader: &$crate::wgpu::ShaderModule,
212                vertex_entry: Option<&str>,
213                fragment_entry: Option<&str>,
214            ) -> PIPE
215            where
216                PipelineLayoutTypes: AsRef<$crate::pipeline::MewPipelineLayout<PIPE>>
217            {
218                let raw_pipeline = {
219                    let layout: &$crate::pipeline::MewPipelineLayout<PIPE> = self.pipeline_layouts.as_ref();
220                    let targets = PIPE::fragment_targets(surface_fmt);
221                    let desc = &PIPE::pipeline_desc(layout, shader, vertex_entry, fragment_entry, &targets);
222                    self.device.create_render_pipeline(desc)
223                };
224                PIPE::new(raw_pipeline)
225            }
226        }
227
228        impl $crate::MewRenderContext for $struct {
229            fn instance(&self) -> &wgpu::Instance {
230                &self.instance
231            }
232
233            fn adapter(&self) -> &wgpu::Adapter {
234                &self.adapter
235            }
236
237            fn device(&self) -> &wgpu::Device {
238                &self.device
239            }
240
241            fn queue(&self) -> &wgpu::Queue {
242                &self.queue
243            }
244        }
245    };
246}
247*/
248
249
250/// Error returned when trying to build a context.
251#[derive(Clone, Debug, Error)]
252pub enum BuildContextError {
253    /// Requesting the adapter failed, see
254    /// [`wgpu::Instance::request_adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html#method.request_adapter).
255    #[error(transparent)]
256    Adapter(#[from] wgpu::RequestAdapterError),
257    /// Requesting the device failed, see
258    /// [`wgpu::Adapter::request_device`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html#method.request_device).
259    #[error(transparent)]
260    Device(#[from] wgpu::RequestDeviceError),
261}
262
263
264/// A builder abstraction for making a [`RenderContext`].
265///
266/// It's possible to make one directly with [`RenderContext::new`], but you need
267/// to provide everything manually. This builder takes care of initilizing the
268/// _wgpu_ handles for you with default values.
269#[must_use]
270#[derive(Clone, Debug)]
271pub struct RenderContextBuilder {
272    instance: Result<wgpu::Instance, wgpu::Backends>,
273    compatible_surface: Option<std::sync::Arc<wgpu::Surface<'static>>>,
274    features: wgpu::Features,
275    limits: wgpu::Limits,
276}
277
278impl RenderContextBuilder {
279    /// Prepare a new builder, providing your own
280    /// [`wgpu::Instance`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html).
281    pub fn new_with_instance(instance: wgpu::Instance) -> Self {
282        #[allow(unused_mut)]
283        let mut builder = Self {
284            instance: Ok(instance),
285            compatible_surface: None,
286            features: wgpu::Features::default(),
287            limits: wgpu::Limits::default(),
288        };
289        #[cfg(feature = "webgl")]
290        builder.with_limits(wgpu::Limits::downlevel_webgl2_defaults());
291        builder
292    }
293
294    /// Prepare a new builder, specifying the
295    /// [`wgpu::Backends`](https://docs.rs/wgpu/latest/wgpu/struct.Backends.html)
296    /// you want to support.
297    ///
298    /// The
299    /// [`wgpu::Instance`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html)
300    /// will be built later when needed with these backends.
301    pub fn new_with_backends(backends: wgpu::Backends) -> Self {
302        #[allow(unused_mut)]
303        let mut builder = Self {
304            instance: Err(backends),
305            compatible_surface: None,
306            features: wgpu::Features::default(),
307            limits: wgpu::Limits::default(),
308        };
309        #[cfg(feature = "webgl")]
310        builder.with_limits(wgpu::Limits::downlevel_webgl2_defaults());
311        builder
312    }
313
314    /// Specify a compatible surface for the
315    /// [`wgpu::Adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html).
316    ///
317    /// This is required by WebGL and really annoying to set up, see
318    /// [`winitutils::DeferredContext`].
319    pub fn with_compatible_surface(&mut self, surface: Option<std::sync::Arc<wgpu::Surface<'static>>>) -> &mut Self {
320        self.compatible_surface = surface;
321        self
322    }
323
324    /// Specify the
325    /// [`wgpu::Features`](https://docs.rs/wgpu/latest/wgpu/struct.Features.html)
326    /// the
327    /// [`wgpu::Device`](https://docs.rs/wgpu/latest/wgpu/struct.Device.html)
328    /// will require.
329    pub fn with_features(&mut self, features: wgpu::Features) -> &mut Self {
330        self.features = features;
331        self
332    }
333
334    /// Specify the
335    /// [`wgpu::Limits`](https://docs.rs/wgpu/latest/wgpu/struct.Limits.html)
336    /// the
337    /// [`wgpu::Device`](https://docs.rs/wgpu/latest/wgpu/struct.Device.html)
338    /// will require.
339    ///
340    /// On web, this is automatically set to
341    /// [`wgpu::Limits::downlevel_webgl2_defaults`](https://docs.rs/wgpu/latest/wgpu/struct.Limits.html#method.downlevel_webgl2_defaults),
342    /// but is overridable if you really want to.  
343    /// On native, it's just [`Default::default`].
344    pub fn with_limits(&mut self, limits: wgpu::Limits) -> &mut Self {
345        self.limits = limits;
346        self
347    }
348
349    /// Get a copy of the
350    /// [`wgpu::Instance`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html)
351    /// that'll be used to get the rest of the _wgpu_ handles.
352    ///
353    /// If this wasn't provided when building, it'll be generated now.
354    pub fn get_instance(&mut self) -> wgpu::Instance {
355        self.instance = Ok(self.instance.clone().unwrap_or_else(|backends| wgpu::Instance::new(&wgpu::InstanceDescriptor {
356            backends,
357            ..Default::default()
358        })));
359        self.instance.clone().unwrap()
360    }
361
362    /// Try to build the [`RenderContext`] with the configured settings.
363    ///
364    /// This function is `async` because
365    /// [`wgpu::Instance::request_adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html#method.request_adapter)
366    /// and
367    /// [`wgpu::Adapter::request_device`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html#method.request_device)
368    /// are for some reason.  
369    /// On native, you can use [`Self::build_poll`]. On web, you need to spawn a JS
370    /// thread to await this, or use [`Self::deferred`] which does this internally.
371    pub async fn build(mut self) -> Result<RenderContext, BuildContextError> {
372        let instance = self.get_instance();
373
374        let adapter = instance.request_adapter(
375            &wgpu::RequestAdapterOptions {
376                power_preference: wgpu::PowerPreference::default(),
377                compatible_surface: self.compatible_surface.as_deref(),
378                force_fallback_adapter: false,
379            },
380        ).await?;
381
382        let (device, queue) = adapter.request_device(
383            &wgpu::DeviceDescriptor {
384                label: Some("Device"),
385                required_features: self.features,
386                required_limits: self.limits,
387                ..Default::default()
388            }
389        ).await?;
390
391        Ok(RenderContext::new(instance, adapter, device, queue))
392    }
393
394    /// Try to build the [`RenderContext`] with the configured settings, but block
395    /// on the result with _pollster_.
396    #[cfg(any(feature = "poll", docs))]
397    pub fn build_poll(self) -> Result<RenderContext, BuildContextError> {
398        pollster::block_on(self.build())
399    }
400
401    /// Put this builder into a [`winitutils::DeferredContext`], which on web will
402    /// automate running [`Self::with_compatible_surface`] when a window is created
403    /// and build the [`RenderContext`] as soon as it can.
404    #[cfg(any(all(feature = "winit", any(feature = "webgl", feature = "poll")), docs))]
405    pub fn deferred(self) -> winitutils::DeferredContext {
406        winitutils::DeferredContext::new(self)
407    }
408}
409
410
411#[derive(Debug)]
412struct LayoutMaps {
413    bind_groups: RefCell<BTreeMap<TypeId, wgpu::BindGroupLayout>>,
414    pipelines: RefCell<BTreeMap<TypeId, wgpu::PipelineLayout>>,
415}
416
417impl LayoutMaps {
418    fn new() -> Self {
419        Self {
420            bind_groups: RefCell::new(BTreeMap::new()),
421            pipelines: RefCell::new(BTreeMap::new()),
422        }
423    }
424}
425
426
427/// The heart of _mew_. Bundles all necessary _wgpu_ handles and takes care of
428/// constructing buffers, bind groups, & pipelines with a single function call.
429#[derive(Debug)]
430pub struct RenderContext {
431    pub instance: wgpu::Instance,
432    pub adapter: wgpu::Adapter,
433    pub device: wgpu::Device,
434    pub queue: wgpu::Queue,
435    // TODO: Potentially put in Rc & allow Clone
436    layouts: LayoutMaps,
437}
438
439impl RenderContext {
440    /// Build a context manually by providing the
441    /// [`wgpu::Instance`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html),
442    /// [`wgpu::Adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html),
443    /// [`wgpu::Device`](https://docs.rs/wgpu/latest/wgpu/struct.Device.html),
444    /// and
445    /// [`wgpu::Queue`](https://docs.rs/wgpu/latest/wgpu/struct.Queue.html).
446    ///
447    /// You can build this directly , but a [`RenderContextBuilder`] takes care of
448    /// the annoying stuff for you.
449    pub fn new(instance: wgpu::Instance, adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue) -> Self {
450        Self {
451            instance,
452            adapter,
453            device,
454            queue,
455            layouts: LayoutMaps::new(),
456        }
457    }
458
459    /// Build a new buffer with an internal capacity to fit its internal type.
460    ///
461    /// The type of buffer it makes is determined by its type parameter.
462    ///
463    /// ```
464    /// buffer! { SomeBuffer <InternalType> as STORAGE | COPY_DST }
465    /// let buffer: SomeBuffer = context.new_buffer(10);
466    /// assert_eq!(buffer.size() as usize, size_of::<InternalType>() * 10);
467    /// ```
468    pub fn new_buffer<BUFFER: buffer::MewBuffer>(&self, inner_size: u64) -> BUFFER {
469        let raw_buffer = self.device.create_buffer(&BUFFER::buffer_desc(inner_size));
470        BUFFER::new(raw_buffer)
471    }
472
473    /// Build a new sampler.
474    ///
475    /// The type of sampler it makes is determined by its type parameter.
476    ///
477    /// ```
478    /// sampler! { SomeSampler as Filtering }
479    /// let sampler: SomeSampler = context.new_sampler();
480    /// ```
481    pub fn new_sampler<SAMPLER: sampler::MewSampler>(&self) -> SAMPLER {
482        let raw_sampler = self.device.create_sampler(&SAMPLER::buffer_desc());
483        SAMPLER::new(raw_sampler)
484    }
485
486    /// Build a new sampler with dimensions and a
487    /// [`wgpu::TextureFormat`](https://docs.rs/wgpu/latest/wgpu/enum.TextureFormat.html).
488    ///
489    /// The type of texture it makes is determined by its type parameter.
490    ///
491    /// ```
492    /// texture! { SomeTexture { ... } }
493    /// let texture: SomeTexture = context.new_texture((64, 64, 1), wgpu::TextureFormat::Rgba8Unorm);
494    /// ```
495    pub fn new_texture<TEXTURE: texture::MewTexture>(&self, inner_size: (u32, u32, u32), format: wgpu::TextureFormat) -> TEXTURE {
496        let raw_texture = self.device.create_texture(&TEXTURE::buffer_desc(inner_size, format));
497        TEXTURE::new(raw_texture)
498    }
499
500    fn get_bind_group_layout_manual(&self, type_id: TypeId, desc: &wgpu::BindGroupLayoutDescriptor<'static>) -> wgpu::BindGroupLayout {
501        let mut layouts = self.layouts.bind_groups.borrow_mut();
502        layouts.entry(type_id).or_insert_with(|| self.device.create_bind_group_layout(desc)).clone()
503    }
504
505    fn get_bind_group_layout<BIND: bindgroup::MewBindGroup + 'static>(&self) -> bindgroup::MewBindGroupLayout<BIND> {
506        let layout = self.get_bind_group_layout_manual(TypeId::of::<BIND>(), &BIND::layout_desc());
507        bindgroup::MewBindGroupLayout::new(layout)
508    }
509
510    /// Build a new buffer with a tuple of its bind groups.
511    ///
512    /// The type of bind group it makes is determined by its type parameter.
513    ///
514    /// ```
515    /// bind_group! { StuffBindGroup [
516    ///     0 buffer_one @ VERTEX => SomeBuffer,
517    ///     1 other_buffer @ FRAGMENT => SomeOtherBuffer,
518    /// ] }
519    /// let bind_group: SomeBindGroup = render_context.new_bind_group((buffer, other_buffer));
520    /// ```
521    pub fn new_bind_group<BIND: bindgroup::MewBindGroup + 'static>(&self, buffers: BIND::BufferSet) -> BIND {
522        let raw_bind_group = {
523            let layout = self.get_bind_group_layout::<BIND>();
524            let entries = BIND::bind_group_entries(&buffers);
525            let desc = BIND::bind_group_desc(&layout, &entries);
526            self.device.create_bind_group(&desc)
527        };
528        BIND::new(raw_bind_group, buffers)
529    }
530
531
532    fn get_pipeline_layout<'a, PIPE: pipeline::MewPipeline + 'static>(&self) -> pipeline::MewPipelineLayout<PIPE> {
533        let layout = {
534            let mut layouts = self.layouts.pipelines.borrow_mut();
535            layouts.entry(TypeId::of::<PIPE>()).or_insert_with(|| {
536                let bind_group_descs = PIPE::bind_group_layout_types_array();
537                let bind_group_layouts = PIPE::map_bind_group_array(&bind_group_descs, |(type_id, desc)| self.get_bind_group_layout_manual(*type_id, desc));
538                //let bind_group_refs = PIPE::bind_group_ref_array(&bind_group_layouts);
539                // &[_] -> [&_]
540                let bind_group_refs = PIPE::map_bind_group_array(&bind_group_layouts, |layout| layout);
541                let desc = PIPE::layout_desc(&bind_group_refs);
542                self.device.create_pipeline_layout(&desc)
543            }).clone()
544        };
545        pipeline::MewPipelineLayout::new(layout)
546    }
547
548    /// Build a new pipeline given a
549    /// [`wgpu::TextureFormat`](https://docs.rs/wgpu/latest/wgpu/enum.TextureFormat.html),
550    /// a
551    /// [`wgpu::ShaderModule`](https://docs.rs/wgpu/latest/wgpu/enum.ShaderModule.html)
552    /// (that you need to construct yourself), and the shader's entry points (or
553    /// `None` if you only have one entry point).
554    ///
555    /// The type of pipeline it makes is determined by its type parameter.
556    ///
557    /// ```
558    /// pipeline! { Pipeline { ... } }
559    /// let pipeline: Pipeline = render_context.new_pipeline(wgpu::Rgba8Unorm, shader, Some("vs_main"), None);
560    /// ```
561    pub fn new_pipeline<PIPE: pipeline::MewPipeline + 'static>(
562        &self,
563        surface_fmt: wgpu::TextureFormat,
564        shader: &wgpu::ShaderModule,
565        vertex_entry: Option<&str>,
566        fragment_entry: Option<&str>,
567    ) -> PIPE {
568        let raw_pipeline = {
569            let layout = self.get_pipeline_layout::<PIPE>();
570            let targets = PIPE::fragment_targets(surface_fmt);
571            let desc = &PIPE::pipeline_desc(&layout, shader, vertex_entry, fragment_entry, &targets);
572            self.device.create_render_pipeline(desc)
573        };
574        PIPE::new(raw_pipeline)
575    }
576}