use std::borrow::Cow;
use std::mem;
use std::num::NonZeroU64;
use bytemuck::{Pod, Zeroable};
use cgmath::Matrix4;
use wgpu::util::DeviceExt;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) const SAMPLE_COUNT: u32 = 4;
#[cfg(target_arch = "wasm32")]
pub(crate) const SAMPLE_COUNT: u32 = 1;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub(crate) struct Vertex {
pub _pos: [f32; 4],
pub _color: [f32; 4],
}
pub struct WgpuState {
pub(crate) device: wgpu::Device,
pub(crate) queue: wgpu::Queue,
pub(crate) _bind_group_layout: wgpu::BindGroupLayout,
pub(crate) render_pipeline_fill: wgpu::RenderPipeline,
pub(crate) render_pipeline_line: wgpu::RenderPipeline,
pub(crate) uniforms_buffer: wgpu::Buffer,
pub(crate) bind_groups: Vec<wgpu::BindGroup>,
pub(crate) multisampled_framebuffer_descriptor: wgpu::TextureDescriptor<'static>,
pub(crate) multisampled_framebuffer: wgpu::Texture,
pub(crate) format: wgpu::TextureFormat,
pub(crate) background_color: wgpu::Color,
}
pub enum CompatibleSurface<'a> {
Surface(&'a wgpu::Surface),
Headless(wgpu::TextureFormat),
}
impl WgpuState {
pub async fn new_for_gif() -> WgpuState {
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
WgpuState::new(
instance,
CompatibleSurface::Headless(wgpu::TextureFormat::Rgba8UnormSrgb),
wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
)
.await
}
pub async fn new(
instance: wgpu::Instance,
compatible_surface: CompatibleSurface<'_>,
background_color: wgpu::Color,
) -> WgpuState {
let surface = match compatible_surface {
CompatibleSurface::Surface(surface) => Some(surface),
CompatibleSurface::Headless(_) => None,
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: surface,
force_fallback_adapter: false,
})
.await
.unwrap();
let format = match compatible_surface {
CompatibleSurface::Surface(surface) => surface.get_preferred_format(&adapter).unwrap(),
CompatibleSurface::Headless(format) => format,
};
#[cfg(not(target_arch = "wasm32"))]
let features = wgpu::Features::empty().union(wgpu::Features::POLYGON_MODE_LINE);
#[cfg(target_arch = "wasm32")]
let features = wgpu::Features::empty();
let device_descriptor = wgpu::DeviceDescriptor {
limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
features,
label: None,
};
let (device, queue) = adapter
.request_device(&device_descriptor, None)
.await
.unwrap();
let shader_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/shader.wgsl"))),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: None,
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline_line = WgpuState::render_pipeline(
&device,
format,
&shader_module,
&pipeline_layout,
#[cfg(not(target_arch = "wasm32"))]
wgpu::PolygonMode::Line,
#[cfg(target_arch = "wasm32")]
wgpu::PolygonMode::Fill,
);
let render_pipeline_fill = WgpuState::render_pipeline(
&device,
format,
&shader_module,
&pipeline_layout,
wgpu::PolygonMode::Fill,
);
const UNIFORM_SIZE: usize = mem::size_of::<Matrix4<f32>>();
const UNIFORM_COUNT: usize = 1000;
const UNIFORM_SIZE_PADDED: usize = 256;
let initial_data = [0; UNIFORM_SIZE_PADDED * UNIFORM_COUNT];
let uniforms_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: &initial_data,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let mut bind_groups = vec![];
for i in 0..UNIFORM_COUNT {
let uniforms_offset = (i * UNIFORM_SIZE_PADDED) as u64;
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniforms_buffer,
offset: uniforms_offset,
size: NonZeroU64::new(UNIFORM_SIZE as u64),
}),
}],
label: None,
});
bind_groups.push(bind_group);
}
let multisampled_framebuffer_descriptor = wgpu::TextureDescriptor {
size: wgpu::Extent3d {
width: 100,
height: 100,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: SAMPLE_COUNT,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
label: None,
};
let multisampled_framebuffer = device.create_texture(&multisampled_framebuffer_descriptor);
WgpuState {
device,
queue,
_bind_group_layout: bind_group_layout,
render_pipeline_fill,
render_pipeline_line,
uniforms_buffer,
bind_groups,
multisampled_framebuffer_descriptor,
multisampled_framebuffer,
format,
background_color,
}
}
fn render_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
shader_module: &wgpu::ShaderModule,
pipeline_layout: &wgpu::PipelineLayout,
polygon_mode: wgpu::PolygonMode,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: shader_module,
entry_point: "vs_main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Vertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
shader_location: 0,
format: wgpu::VertexFormat::Float32x4,
offset: 0,
},
wgpu::VertexAttribute {
shader_location: 1,
format: wgpu::VertexFormat::Float32x4,
offset: 4 * 4,
},
],
}],
},
fragment: Some(wgpu::FragmentState {
module: shader_module,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
}],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: SAMPLE_COUNT,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
})
}
pub fn poll(&self) {
self.device.poll(wgpu::Maintain::Wait);
}
}