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
4use std::{
5    any::TypeId,
6    cell::RefCell,
7    collections::BTreeMap,
8};
9use thiserror::Error;
10
11pub use wgpu;
12#[cfg(feature = "poll")]
13pub use pollster;
14#[cfg(feature = "winit")]
15pub use winit;
16
17pub mod doc_example;
18
19mod macromath;
20pub mod bindgroup;
21pub mod buffer;
22pub mod pipeline;
23pub mod sampler;
24pub mod shaderprimitive;
25pub mod shaderstruct;
26pub mod texture;
27//pub mod typemap;
28pub mod vertexformat;
29pub mod vertexstruct;
30#[cfg(feature = "winit")]
31pub mod winitutils;
32
33pub mod prelude {
34    pub use crate::{
35        bindgroup::*,
36        buffer::*,
37        pipeline::*,
38        sampler::*,
39        shaderprimitive,
40        shaderstruct::*,
41        texture::*,
42        vertexformat,
43        vertexstruct::*,
44        *,
45    };
46    #[cfg(feature = "winit")]
47    pub use crate::winitutils::*;
48}
49
50
51/*
52/// The master object that manages everything `wgpu`-related.
53///
54/// On construction it initializes the instance, adapter, device, and queue
55/// using some default settings so you don't need to worry about that repetitive
56/// boilerplate. Not all options are customizable but it should work well
57/// enough.
58///
59/// The render context also keeps track of your buffer, bind group, texture,
60/// and pipeline definition types, so you can construct the from a central
61/// location. Since regular macros don't support changing identifiers, this is
62/// implemented by type parameters.
63///
64/// ```
65/// render_context! { Context {
66///     // List of `wgpu::Features` for the device
67///     features: [MULTIVIEW, VERTEX_WRITABLE_STORAGE],  // idk I usually just leave these blank
68///     // Map of values to override from `wgpu::Limits` defaults for the device
69///     limits: {max_vertex_attributes: 17, max_vertex_buffers: 4},  // same here, random values - definitely don't use
70///     // Numbered (and ordered!) list of your bind group definitions
71///     bind_groups: [
72///         0 => GroupOne,
73///         1 => GroupThree,
74///         2 => GroupNegativeSixtyNine,
75///     ],
76///     // Also numbered (and also ordered!) list of your pipeline definitions
77///     pipelines: [
78///         0 => MainPipeline,
79///         1 => NotMainPipeline,
80///     ],
81/// } }
82/// ```
83#[macro_export]
84macro_rules! render_context {
85    { $struct:ident {
86        features: [
87            $( $feature:ident ),* $(,)?
88        ],
89        limits: {
90            $( $limit:ident : $limitval:expr ),* $(,)?
91        },
92        bind_groups: [
93            $( $bindgroupnum:tt => $bindgroup:ty ),* $(,)?
94        ],
95        pipelines: [
96            $( $pipelinenum:tt => $pipeline:ty ),* $(,)?
97        ] $(,)?
98    } } => {
99        typemap! { BindGroupLayoutTypes [
100            $( $bindgroupnum => $crate::bindgroup::MewBindGroupLayout<$bindgroup> , )*
101        ] }
102
103        typemap! { PipelineLayoutTypes [
104            $( $pipelinenum => $crate::pipeline::MewPipelineLayout<$pipeline> , )*
105        ] }
106
107        pub struct $struct {
108            pub instance: $crate::wgpu::Instance,
109            pub adapter: $crate::wgpu::Adapter,
110            pub device: $crate::wgpu::Device,
111            pub queue: $crate::wgpu::Queue,
112            bind_group_layouts: BindGroupLayoutTypes,
113            pipeline_layouts: PipelineLayoutTypes,
114        }
115
116        impl $struct {
117            pub async fn new(backends: $crate::wgpu::Backends) -> Result<Self, Box<dyn ::std::error::Error>> {
118                let instance = $crate::wgpu::Instance::new(&wgpu::InstanceDescriptor {
119                    backends,
120                    flags: $crate::wgpu::InstanceFlags::empty(),
121                    ..Default::default()
122                });
123
124                Self::new_with_instance(instance).await
125            }
126
127            pub async fn new_with_instance(instance: $crate::wgpu::Instance) -> Result<Self, Box<dyn ::std::error::Error>> {
128                let adapter = instance.request_adapter(
129                    &$crate::wgpu::RequestAdapterOptions {
130                        power_preference: $crate::wgpu::PowerPreference::default(),
131                        compatible_surface: None,
132                        force_fallback_adapter: false,
133                    },
134                ).await.inspect_err(|e| eprintln!("Could not get display adapter: {e}"))?;
135
136                let (device, queue) = adapter.request_device(
137                    &$crate::wgpu::DeviceDescriptor {
138                        label: Some("Device"),
139                        required_features: $crate::wgpu::Features::empty() $( | $crate::wgpu::Features::$feature )*,
140                        required_limits: $crate::wgpu::Limits {
141                            $( $limit: $limitval, )*
142                            ..Default::default()
143                        },
144                        memory_hints: Default::default(),
145                        trace: $crate::wgpu::Trace::Off,
146                    }
147                ).await.inspect_err(|e| eprintln!("Could not get display device: {e}"))?;
148
149                let bind_group_layouts = BindGroupLayoutTypes (
150                    $( {
151                        let desc = <$bindgroup>::layout_desc();
152                        let layout = device.create_bind_group_layout(&desc);
153                        $crate::bindgroup::MewBindGroupLayout::new(layout)
154                    }, )*
155                );
156
157                let pipeline_layouts = PipelineLayoutTypes (
158                    $( {
159                        let bind_groups = <$pipeline>::get_layouts_from_refs(&bind_group_layouts);
160                        let bind_groups_arr = <$pipeline>::bind_group_layouts(&bind_groups);
161                        let desc = <$pipeline>::layout_desc(&bind_groups_arr);
162                        let layout = device.create_pipeline_layout(&desc);
163                        $crate::pipeline::MewPipelineLayout::new(layout)
164                    }, )*
165                );
166
167                Ok(Self {
168                    instance,
169                    adapter,
170                    device,
171                    queue,
172                    bind_group_layouts,
173                    pipeline_layouts,
174                })
175            }
176
177            pub fn new_buffer<BUFFER: $crate::buffer::MewBuffer>(&self, inner_size: u64) -> BUFFER {
178                let raw_buffer = self.device.create_buffer(&BUFFER::buffer_desc(inner_size));
179                BUFFER::new(raw_buffer)
180            }
181
182            pub fn new_sampler<SAMPLER: $crate::sampler::MewSampler>(&self) -> SAMPLER {
183                let raw_sampler = self.device.create_sampler(&SAMPLER::buffer_desc());
184                SAMPLER::new(raw_sampler)
185            }
186
187            pub fn new_texture<TEXTURE: $crate::texture::MewTexture>(&self, inner_size: (u32, u32, u32), format: $crate::wgpu::TextureFormat) -> TEXTURE {
188                let raw_texture = self.device.create_texture(&TEXTURE::buffer_desc(inner_size, format));
189                TEXTURE::new(raw_texture)
190            }
191
192            pub fn new_bind_group<BIND: $crate::bindgroup::MewBindGroup>(&self, buffers: BIND::BufferSet) -> BIND
193            where
194                BindGroupLayoutTypes: AsRef<$crate::bindgroup::MewBindGroupLayout<BIND>>
195            {
196                let raw_bind_group = {
197                    let layout: &$crate::bindgroup::MewBindGroupLayout<BIND> = self.bind_group_layouts.as_ref();
198                    let entries = BIND::bind_group_entries(&buffers);
199                    let desc = BIND::bind_group_desc(layout, &entries);
200                    self.device.create_bind_group(&desc)
201                };
202                BIND::new(raw_bind_group, buffers)
203            }
204
205            pub fn new_pipeline<PIPE: $crate::pipeline::MewPipeline>(
206                &self,
207                surface_fmt: Option<$crate::wgpu::TextureFormat>,
208                shader: &$crate::wgpu::ShaderModule,
209                vertex_entry: Option<&str>,
210                fragment_entry: Option<&str>,
211            ) -> PIPE
212            where
213                PipelineLayoutTypes: AsRef<$crate::pipeline::MewPipelineLayout<PIPE>>
214            {
215                let raw_pipeline = {
216                    let layout: &$crate::pipeline::MewPipelineLayout<PIPE> = self.pipeline_layouts.as_ref();
217                    let targets = PIPE::fragment_targets(surface_fmt);
218                    let desc = &PIPE::pipeline_desc(layout, shader, vertex_entry, fragment_entry, &targets);
219                    self.device.create_render_pipeline(desc)
220                };
221                PIPE::new(raw_pipeline)
222            }
223        }
224
225        impl $crate::MewRenderContext for $struct {
226            fn instance(&self) -> &wgpu::Instance {
227                &self.instance
228            }
229
230            fn adapter(&self) -> &wgpu::Adapter {
231                &self.adapter
232            }
233
234            fn device(&self) -> &wgpu::Device {
235                &self.device
236            }
237
238            fn queue(&self) -> &wgpu::Queue {
239                &self.queue
240            }
241        }
242    };
243}
244*/
245
246
247#[derive(Clone, Debug, Error)]
248pub enum BuildContextError {
249    #[error(transparent)]
250    Adapter(#[from] wgpu::RequestAdapterError),
251    #[error(transparent)]
252    Device(#[from] wgpu::RequestDeviceError),
253}
254
255
256#[must_use]
257#[derive(Clone, Debug)]
258pub struct RenderContextBuilder {
259    instance: Result<wgpu::Instance, wgpu::Backends>,
260    compatible_surface: Option<std::sync::Arc<wgpu::Surface<'static>>>,
261    features: wgpu::Features,
262    limits: wgpu::Limits,
263}
264
265impl RenderContextBuilder {
266    pub fn new_with_instance(instance: wgpu::Instance) -> Self {
267        #[allow(unused_mut)]
268        let mut builder = Self {
269            instance: Ok(instance),
270            compatible_surface: None,
271            features: wgpu::Features::default(),
272            limits: wgpu::Limits::default(),
273        };
274        #[cfg(feature = "webgl")]
275        builder.with_limits(wgpu::Limits::downlevel_webgl2_defaults());
276        builder
277    }
278
279    pub fn new_with_backends(backends: wgpu::Backends) -> Self {
280        #[allow(unused_mut)]
281        let mut builder = Self {
282            instance: Err(backends),
283            compatible_surface: None,
284            features: wgpu::Features::default(),
285            limits: wgpu::Limits::default(),
286        };
287        #[cfg(feature = "webgl")]
288        builder.with_limits(wgpu::Limits::downlevel_webgl2_defaults());
289        builder
290    }
291
292    pub fn with_compatible_surface(&mut self, surface: Option<std::sync::Arc<wgpu::Surface<'static>>>) -> &mut Self {
293        self.compatible_surface = surface;
294        self
295    }
296
297    pub fn with_features(&mut self, features: wgpu::Features) -> &mut Self {
298        self.features = features;
299        self
300    }
301
302    pub fn with_limits(&mut self, limits: wgpu::Limits) -> &mut Self {
303        self.limits = limits;
304        self
305    }
306
307    pub fn get_instance(&mut self) -> wgpu::Instance {
308        self.instance = Ok(self.instance.clone().unwrap_or_else(|backends| wgpu::Instance::new(&wgpu::InstanceDescriptor {
309            backends,
310            ..Default::default()
311        })));
312        self.instance.clone().unwrap()
313    }
314
315    pub async fn build(mut self) -> Result<RenderContext, BuildContextError> {
316        let instance = self.get_instance();
317
318        let adapter = instance.request_adapter(
319            &wgpu::RequestAdapterOptions {
320                power_preference: wgpu::PowerPreference::default(),
321                compatible_surface: self.compatible_surface.as_deref(),
322                force_fallback_adapter: false,
323            },
324        ).await?;
325
326        let (device, queue) = adapter.request_device(
327            &wgpu::DeviceDescriptor {
328                label: Some("Device"),
329                required_features: self.features,
330                required_limits: self.limits,
331                memory_hints: Default::default(),
332                trace: wgpu::Trace::Off,
333            }
334        ).await?;
335
336        Ok(RenderContext::new(instance, adapter, device, queue))
337    }
338
339    #[cfg(feature = "poll")]
340    pub fn build_poll(self) -> Result<RenderContext, BuildContextError> {
341        pollster::block_on(self.build())
342    }
343
344    #[cfg(all(feature = "winit", any(feature = "webgl", feature = "poll")))]
345    pub fn deferred(self) -> winitutils::DeferredContext {
346        winitutils::DeferredContext::new(self)
347    }
348}
349
350
351#[derive(Debug)]
352struct LayoutMaps {
353    bind_groups: RefCell<BTreeMap<TypeId, wgpu::BindGroupLayout>>,
354    pipelines: RefCell<BTreeMap<TypeId, wgpu::PipelineLayout>>,
355}
356
357impl LayoutMaps {
358    fn new() -> Self {
359        Self {
360            bind_groups: RefCell::new(BTreeMap::new()),
361            pipelines: RefCell::new(BTreeMap::new()),
362        }
363    }
364}
365
366
367#[derive(Debug)]
368pub struct RenderContext {
369    pub instance: wgpu::Instance,
370    pub adapter: wgpu::Adapter,
371    pub device: wgpu::Device,
372    pub queue: wgpu::Queue,
373    // TODO: Potentially put in Rc
374    layouts: LayoutMaps,
375}
376
377impl RenderContext {
378    pub fn new(instance: wgpu::Instance, adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue) -> Self {
379        Self {
380            instance,
381            adapter,
382            device,
383            queue,
384            layouts: LayoutMaps::new(),
385        }
386    }
387
388
389    pub fn new_buffer<BUFFER: buffer::MewBuffer>(&self, inner_size: u64) -> BUFFER {
390        let raw_buffer = self.device.create_buffer(&BUFFER::buffer_desc(inner_size));
391        BUFFER::new(raw_buffer)
392    }
393
394    pub fn new_sampler<SAMPLER: sampler::MewSampler>(&self) -> SAMPLER {
395        let raw_sampler = self.device.create_sampler(&SAMPLER::buffer_desc());
396        SAMPLER::new(raw_sampler)
397    }
398
399    pub fn new_texture<TEXTURE: texture::MewTexture>(&self, inner_size: (u32, u32, u32), format: wgpu::TextureFormat) -> TEXTURE {
400        let raw_texture = self.device.create_texture(&TEXTURE::buffer_desc(inner_size, format));
401        TEXTURE::new(raw_texture)
402    }
403
404    fn get_bind_group_layout_manual(&self, type_id: TypeId, desc: wgpu::BindGroupLayoutDescriptor<'static>) -> wgpu::BindGroupLayout {
405        let mut layouts = self.layouts.bind_groups.borrow_mut();
406        layouts.entry(type_id).or_insert_with(|| self.device.create_bind_group_layout(&desc)).clone()
407    }
408
409    fn get_bind_group_layout<BIND: bindgroup::MewBindGroup + 'static>(&self) -> bindgroup::MewBindGroupLayout<BIND> {
410        let layout = self.get_bind_group_layout_manual(TypeId::of::<BIND>(), BIND::layout_desc());
411        bindgroup::MewBindGroupLayout::new(layout)
412    }
413
414    pub fn new_bind_group<BIND: bindgroup::MewBindGroup + 'static>(&self, buffers: BIND::BufferSet) -> BIND {
415        let raw_bind_group = {
416            let layout = self.get_bind_group_layout::<BIND>();
417            let entries = BIND::bind_group_entries(&buffers);
418            let desc = BIND::bind_group_desc(&layout, &entries);
419            self.device.create_bind_group(&desc)
420        };
421        BIND::new(raw_bind_group, buffers)
422    }
423
424
425    fn get_pipeline_layout<PIPE: pipeline::MewPipeline + 'static>(&self) -> pipeline::MewPipelineLayout<PIPE> {
426        let layout = {
427            let mut layouts = self.layouts.pipelines.borrow_mut();
428            layouts.entry(TypeId::of::<PIPE>()).or_insert_with(|| {
429                let bind_group_descs = PIPE::bind_group_layout_types_array();
430                let bind_group_layouts = PIPE::map_bind_group_array(bind_group_descs, |(type_id, desc)| self.get_bind_group_layout_manual(type_id, desc));
431                let bind_group_refs = PIPE::bind_group_ref_array(&bind_group_layouts);
432                let desc = PIPE::layout_desc(&bind_group_refs);
433                self.device.create_pipeline_layout(&desc)
434            }).clone()
435        };
436        pipeline::MewPipelineLayout::new(layout)
437    }
438
439    pub fn new_pipeline<PIPE: pipeline::MewPipeline + 'static>(
440        &self,
441        surface_fmt: Option<wgpu::TextureFormat>,
442        shader: &wgpu::ShaderModule,
443        vertex_entry: Option<&str>,
444        fragment_entry: Option<&str>,
445    ) -> PIPE {
446        let raw_pipeline = {
447            let layout = self.get_pipeline_layout::<PIPE>();
448            let targets = PIPE::fragment_targets(surface_fmt);
449            let desc = &PIPE::pipeline_desc(&layout, shader, vertex_entry, fragment_entry, &targets);
450            self.device.create_render_pipeline(desc)
451        };
452        PIPE::new(raw_pipeline)
453    }
454}