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}