#![doc(html_logo_url = "https://64-tesseract.ftp.sh/tesseract.gif", html_favicon_url = "https://64-tesseract.ftp.sh/tesseract.gif")]
#![doc = include_str!("../README.md")]
#![cfg_attr(doc, feature(doc_cfg))]
#![cfg_attr(doc, doc(auto_cfg(hide(doc))))]
use std::{
any::TypeId,
collections::BTreeMap,
};
#[cfg(not(feature = "sync"))]
use std::cell::RefCell;
#[cfg(feature = "sync")]
use std::sync::Mutex;
use thiserror::Error;
#[cfg(all(feature = "wgpu-29", feature = "wgpu-30", not(doc)))]
compile_error!("wgpu-29 and wgpu-30 conflict");
#[cfg(all(feature = "wgpu-29", not(feature = "wgpu-30")))]
pub use wgpu_29 as wgpu;
#[cfg(feature = "wgpu-30")]
pub use wgpu_30 as wgpu;
pub use pollster;
pub use web_sys;
#[cfg(feature = "winit")]
pub use winit;
#[cfg(doc)]
pub mod doc_example;
mod macromath;
pub mod bindgroup;
pub mod buffer;
pub mod pipeline;
pub mod sampler;
pub mod shaderprimitive;
pub mod shaderstruct;
pub mod texture;
pub mod vertexformat;
pub mod vertexstruct;
#[cfg(any(feature = "winit", doc))]
pub mod winitutils;
pub mod prelude {
pub use crate::{
bindgroup::*,
buffer::*,
pipeline::*,
sampler::*,
shaderprimitive,
shaderstruct::*,
texture::*,
vertexformat,
vertexstruct::*,
*,
};
#[cfg(feature = "winit")]
pub use crate::winitutils::*;
}
#[macro_export]
macro_rules! mew {
{ bind_group $struct:ident [ $($params:tt)* ] ; $($rest:tt)* } => {
$crate::bind_group! { $struct [ $($params)* ] }
$crate::mew! { $($rest)* }
};
{ buffer $struct:ident $(<$inner:ty>)? as $($usage:ident)|+ ; $($rest:tt)* } => {
$crate::buffer! { $struct $(<$inner>)? as $($usage)|+ }
$crate::mew! { $($rest)* }
};
{ pipeline $struct:ident { $($params:tt)* } ; $($rest:tt)* } => {
$crate::pipeline! { $struct { $($params)* } }
$crate::mew! { $($rest)* }
};
{ sampler $struct:ident as $filtering:ident $({$($params:tt)*})? ; $($rest:tt)* } => {
$crate::sampler! { $struct as $filtering $({$($params)*})? }
$crate::mew! { $($rest)* }
};
{ shader_struct $struct:ident [ $($params:tt)* ] ; $($rest:tt)* } => {
$crate::shader_struct! { $struct [ $($params)* ] }
$crate::mew! { $($rest)* }
};
{ texture $struct:ident { $($params:tt)* } ; $($rest:tt)* } => {
$crate::texture! { $struct { $($params)* } }
$crate::mew! { $($rest)* }
};
{ vertex_struct $struct:ident $(step $step:ident)? $(+ $location:literal)? [ $($params:tt)* ] ; $($rest:tt)* } => {
$crate::vertex_struct! { $struct $(step $step)? $(+ $location)? [ $($params)* ] }
$crate::mew! { $($rest)* }
};
{} => {};
}
#[derive(Clone, Debug, Error)]
pub enum BuildContextError {
#[error(transparent)]
Adapter(#[from] wgpu::RequestAdapterError),
#[error(transparent)]
Device(#[from] wgpu::RequestDeviceError),
}
#[derive(Debug)]
pub struct InstanceSettings {
backends: wgpu::Backends,
display_handle: Option<Box<dyn wgpu::wgt::WgpuHasDisplayHandle>>,
}
impl Default for InstanceSettings {
fn default() -> Self {
Self {
backends: wgpu::Backends::all().with_env(),
display_handle: None,
}
}
}
impl Into<wgpu::Instance> for InstanceSettings {
fn into(self) -> wgpu::Instance {
#[cfg(debug_assertions)]
if self.display_handle.is_none() {
eprintln!("Warning: mew may not be able to configure a surface without a display handle (e.g. winit::OwnedDisplayHandle)");
}
wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: self.backends,
flags: wgpu::InstanceFlags::default(),
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
backend_options: wgpu::BackendOptions::default(),
display: self.display_handle,
})
}
}
#[must_use = "RenderContext has not been built"]
#[derive(Debug)]
pub struct RenderContextBuilder<I = InstanceSettings> {
instance: I,
compatible_surface: Option<std::sync::Arc<wgpu::Surface<'static>>>,
features: wgpu::Features,
limits: wgpu::Limits,
}
impl<I: Clone> Clone for RenderContextBuilder<I> {
fn clone(&self) -> Self {
Self {
instance: self.instance.clone(),
compatible_surface: self.compatible_surface.clone(),
features: self.features,
limits: self.limits.clone(),
}
}
}
impl RenderContextBuilder<wgpu::Instance> {
pub fn new_with_instance(instance: wgpu::Instance) -> Self {
#[allow(unused_mut)]
let mut builder = Self {
instance,
compatible_surface: None,
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
};
#[cfg(wasm)]
builder.set_limits(wgpu::Limits::downlevel_webgl2_defaults());
builder
}
fn get_instance(&self) -> wgpu::Instance {
self.instance.clone()
}
#[cfg(any(feature = "winit", doc))]
pub fn deferred_with_instance(self) -> winitutils::DeferredContext {
winitutils::DeferredContext::new_with_instance(self)
}
}
impl<I> RenderContextBuilder<I> {
pub fn with_compatible_surface(mut self, surface: Option<std::sync::Arc<wgpu::Surface<'static>>>) -> Self {
self.set_compatible_surface(surface);
self
}
pub fn set_compatible_surface(&mut self, surface: Option<std::sync::Arc<wgpu::Surface<'static>>>) -> &mut Self {
self.compatible_surface = surface;
self
}
pub fn with_features(mut self, features: wgpu::Features) -> Self {
self.set_features(features);
self
}
pub fn set_features(&mut self, features: wgpu::Features) -> &mut Self {
self.features = features;
self
}
pub fn with_limits(mut self, limits: wgpu::Limits) -> Self {
self.set_limits(limits);
self
}
pub fn set_limits(&mut self, limits: wgpu::Limits) -> &mut Self {
self.limits = limits;
self
}
}
impl RenderContextBuilder {
pub fn new() -> Self {
#[allow(unused_mut)]
let mut builder = Self {
instance: InstanceSettings::default(),
compatible_surface: None,
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
};
#[cfg(wasm)]
builder.set_limits(wgpu::Limits::downlevel_webgl2_defaults());
builder
}
pub fn with_backends(mut self, backends: wgpu::Backends) -> Self {
self.set_backends(backends);
self
}
pub fn set_backends(&mut self, backends: wgpu::Backends) -> &mut Self {
self.instance.backends = backends;
self
}
pub fn with_display_handle(mut self, display_handle: Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) -> Self {
self.set_display_handle(display_handle);
self
}
pub fn set_display_handle(&mut self, display_handle: Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) -> &mut Self {
self.instance.display_handle = Some(display_handle);
self
}
#[cfg(any(feature = "winit", doc))]
pub fn deferred(self) -> winitutils::DeferredContext {
winitutils::DeferredContext::new(self)
}
}
impl Default for RenderContextBuilder {
fn default() -> Self {
Self::new()
}
}
impl<I: Into<wgpu::Instance>> RenderContextBuilder<I> {
fn build_instance(self) -> RenderContextBuilder<wgpu::Instance> {
RenderContextBuilder::<_> {
instance: self.instance.into(),
compatible_surface: self.compatible_surface,
features: self.features,
limits: self.limits,
}
}
pub async fn build(self) -> Result<RenderContext, BuildContextError> {
let this = self.build_instance();
let instance = this.get_instance();
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
compatible_surface: this.compatible_surface.as_deref(),
..Default::default()
},
).await?;
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some("Device"),
required_features: this.features,
required_limits: this.limits,
..Default::default()
}
).await?;
Ok(RenderContext::new(instance, adapter, device, queue))
}
#[cfg(any(not(wasm), doc))]
pub fn build_poll(self) -> Result<RenderContext, BuildContextError> {
pollster::block_on(self.build())
}
}
#[cfg(not(feature = "sync"))]
#[derive(Debug)]
struct LayoutMaps {
bind_groups: RefCell<BTreeMap<TypeId, wgpu::BindGroupLayout>>,
pipelines: RefCell<BTreeMap<TypeId, wgpu::PipelineLayout>>,
}
#[cfg(feature = "sync")]
#[derive(Debug)]
struct LayoutMaps {
bind_groups: Mutex<BTreeMap<TypeId, wgpu::BindGroupLayout>>,
pipelines: Mutex<BTreeMap<TypeId, wgpu::PipelineLayout>>,
}
impl LayoutMaps {
fn new() -> Self {
Self {
bind_groups: BTreeMap::new().into(),
pipelines: BTreeMap::new().into(),
}
}
}
#[derive(Debug)]
pub struct RenderContext<Instance = wgpu::Instance> {
pub instance: Instance,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
layouts: LayoutMaps,
}
impl RenderContext {
pub fn new(instance: wgpu::Instance, adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue) -> Self {
Self {
instance,
adapter,
device,
queue,
layouts: LayoutMaps::new(),
}
}
}
impl RenderContext<()> {
pub fn new_without_instance(adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue) -> Self {
RenderContext {
instance: (),
adapter,
device,
queue,
layouts: LayoutMaps::new(),
}
}
}
impl<I> RenderContext<I> {
pub fn new_buffer<BUFFER: buffer::MewBuffer>(&self, inner_size: u64) -> BUFFER {
let raw_buffer = self.device.create_buffer(&BUFFER::buffer_desc(inner_size));
BUFFER::new(raw_buffer)
}
pub fn new_sampler<SAMPLER: sampler::MewSampler>(&self) -> SAMPLER {
let raw_sampler = self.device.create_sampler(&SAMPLER::buffer_desc());
SAMPLER::new(raw_sampler)
}
pub fn new_texture<TEXTURE: texture::MewTexture>(&self, inner_size: (u32, u32, u32), format: wgpu::TextureFormat) -> TEXTURE {
let raw_texture = self.device.create_texture(&TEXTURE::buffer_desc(inner_size, format));
TEXTURE::new(raw_texture)
}
fn get_bind_group_layout_manual(&self, type_id: TypeId, desc: &wgpu::BindGroupLayoutDescriptor<'static>) -> wgpu::BindGroupLayout {
#[cfg(not(feature = "sync"))]
let mut layouts = self.layouts.bind_groups.borrow_mut();
#[cfg(feature = "sync")]
let mut layouts = self.layouts.bind_groups.lock().expect("Bind group layouts poisoned");
layouts.entry(type_id).or_insert_with(|| self.device.create_bind_group_layout(desc)).clone()
}
fn get_bind_group_layout<BIND: bindgroup::MewBindGroup + 'static>(&self) -> bindgroup::MewBindGroupLayout<BIND> {
let layout = self.get_bind_group_layout_manual(TypeId::of::<BIND>(), &BIND::layout_desc());
bindgroup::MewBindGroupLayout::new(layout)
}
pub fn new_bind_group<BIND: bindgroup::MewBindGroup + 'static>(&self, buffers: BIND::BufferSet) -> BIND {
let raw_bind_group = {
let layout = self.get_bind_group_layout::<BIND>();
let entries = BIND::bind_group_entries(&buffers);
let desc = BIND::bind_group_desc(&layout, &entries);
self.device.create_bind_group(&desc)
};
BIND::new(raw_bind_group, buffers)
}
fn get_pipeline_layout<PIPE: pipeline::MewPipeline + 'static>(&self) -> pipeline::MewPipelineLayout<PIPE> {
let layout = {
#[cfg(not(feature = "sync"))]
let mut layouts = self.layouts.pipelines.borrow_mut();
#[cfg(feature = "sync")]
let mut layouts = self.layouts.pipelines.lock().expect("Pipeline layouts poisoned");
layouts.entry(TypeId::of::<PIPE>()).or_insert_with(|| {
let bind_group_descs = PIPE::bind_group_layout_types_array();
let bind_group_layouts = PIPE::map_bind_group_array(&bind_group_descs, |(type_id, desc)| self.get_bind_group_layout_manual(*type_id, desc));
let bind_group_refs = PIPE::map_bind_group_array(&bind_group_layouts, Some);
let desc = PIPE::layout_desc(&bind_group_refs);
self.device.create_pipeline_layout(&desc)
}).clone()
};
pipeline::MewPipelineLayout::new(layout)
}
pub fn new_pipeline<PIPE: pipeline::MewPipeline + 'static>(
&self,
surface_fmt: wgpu::TextureFormat,
shader: &wgpu::ShaderModule,
vertex_entry: Option<&str>,
fragment_entry: Option<&str>,
) -> PIPE {
let raw_pipeline = {
let layout = self.get_pipeline_layout::<PIPE>();
let targets = PIPE::fragment_targets(surface_fmt);
let desc = &PIPE::pipeline_desc(&layout, shader, vertex_entry, fragment_entry, &targets);
self.device.create_render_pipeline(desc)
};
PIPE::new(raw_pipeline)
}
}