pub mod assets;
pub mod config;
mod error;
pub mod math;
mod renderer;
mod scene;
use {
crate::{
assets::{
DrawLight, DrawModel, DrawShadow, InstanceRaw, Model, ModelDesc, ModelStore,
ModelVertex, Vertex,
},
config::WindowSize,
renderer::{CameraRig, Material, ShadowRig, SkyRig},
scene::{Light, LightRig, WindRig},
},
std::sync::Arc,
wgpu::{
Adapter, BindGroupLayout, CommandEncoder, Device, PipelineLayout, Queue, RenderPass,
RenderPipeline, Surface, SurfaceCapabilities, SurfaceConfiguration, SurfaceTexture,
TextureFormat, TextureView,
},
};
pub use {
assets::{Ingot, Instance, TypeModel},
cgmath::{Deg, Matrix4, Point3, Quaternion, Rotation3, Vector3, ortho},
error::SurfaceError,
winit::{dpi::PhysicalSize, keyboard::KeyCode},
};
pub struct State {
pub window_surface: wgpu::Surface<'static>,
pub device: Arc<wgpu::Device>,
pub queue: Arc<wgpu::Queue>,
pub config: wgpu::SurfaceConfiguration,
pub is_surface_configuration: bool,
pub render_pipeline: wgpu::RenderPipeline,
pub texture_bind_group_layout: Arc<wgpu::BindGroupLayout>,
pub depth_texture: renderer::Texture,
pub last_render_time: web_time::Instant,
pub camera: CameraRig,
pub light: LightRig,
pub wind: WindRig,
pub shadow: ShadowRig,
pub sky: SkyRig,
pub(crate) models: ModelStore,
}
impl State {
pub async fn new(
target: impl raw_window_handle::HasWindowHandle
+ raw_window_handle::HasDisplayHandle
+ wgpu::WasmNotSendSync
+ 'static,
window_size: WindowSize,
) -> anyhow::Result<Self> {
let mut instance_desc: wgpu::InstanceDescriptor =
wgpu::InstanceDescriptor::new_without_display_handle();
#[cfg(target_arch = "wasm32")]
{
instance_desc.backends = wgpu::Backends::GL | wgpu::Backends::BROWSER_WEBGPU;
}
#[cfg(all(not(target_arch = "wasm32"), not(feature = "rpi")))]
{
instance_desc.backends = wgpu::Backends::PRIMARY;
}
#[cfg(all(not(target_arch = "wasm32"), feature = "rpi"))]
{
instance_desc.backends = wgpu::Backends::GL;
}
let backend_instance: wgpu::Instance = wgpu::Instance::new(instance_desc);
let window_surface: Surface = backend_instance.create_surface(target)?;
let adapter: Adapter = backend_instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface: Some(&window_surface),
})
.await?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
required_limits: adapter.limits(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: Default::default(),
trace: wgpu::Trace::Off,
})
.await?;
let device: Arc<Device> = Arc::new(device);
let queue: Arc<Queue> = Arc::new(queue);
let surface_caps: SurfaceCapabilities = window_surface.get_capabilities(&adapter);
let surface_format: TextureFormat = surface_caps
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(surface_caps.formats[0]);
let config: SurfaceConfiguration = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: window_size.width,
height: window_size.height,
present_mode: surface_caps.present_modes[0],
desired_maximum_frame_latency: 2,
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![surface_format.add_srgb_suffix()],
};
let texture_bind_group_layout: Arc<BindGroupLayout> =
Arc::new(Material::bind_group_layout(&device));
let camera: CameraRig = CameraRig::new(&device, config.width as f32 / config.height as f32);
let depth_texture: renderer::Texture =
renderer::Texture::create_depth_texture(&device, &config, "depth_texture");
let sky: SkyRig = SkyRig::new(&device, &queue, &config, &camera.layout).await?;
let light: LightRig = LightRig::new(
&device,
&camera.layout,
&texture_bind_group_layout,
sky.hdr.format(),
);
let shadow: ShadowRig = ShadowRig::new(&device, &light.layout);
let wind: WindRig = WindRig::new(&device);
let pipeline_render_layout: PipelineLayout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[
Some(&texture_bind_group_layout),
Some(&camera.layout),
Some(&light.layout),
Some(&shadow.layout),
Some(&wind.layout),
],
label: Some("render_pipeline_layout"),
..Default::default()
});
let render_pipeline: RenderPipeline = renderer::create_render_pipeline(
&device,
&pipeline_render_layout,
sky.hdr.format(),
Some(renderer::Texture::DEPTH_FORMAT),
&[ModelVertex::desc(), InstanceRaw::desc()],
wgpu::PrimitiveTopology::TriangleList,
wgpu::include_wgsl!("shaders/shaders.wgsl"),
wgpu::CompareFunction::Less,
);
Ok(Self {
window_surface,
device,
queue,
config,
is_surface_configuration: false,
render_pipeline,
texture_bind_group_layout,
depth_texture,
last_render_time: web_time::Instant::now(),
camera,
light,
wind,
shadow,
sky,
models: ModelStore::new(),
})
}
pub fn resize(&mut self, height: u32, width: u32) {
if height > 0 && width > 0 {
self.config.height = height;
self.config.width = width;
self.window_surface.configure(&self.device, &self.config);
self.camera
.set_aspect(self.config.width as f32 / self.config.height as f32);
self.depth_texture = renderer::Texture::create_depth_texture(
&self.device,
&self.config,
"depth_texture",
);
self.sky.hdr.resize(&self.device, width, height);
self.is_surface_configuration = true;
}
}
pub fn render(&mut self) -> Result<(), SurfaceError> {
self.render_with_overlay(&mut |_, _, _, _| {})
}
pub fn render_with_overlay(
&mut self,
overlay: &mut dyn FnMut(
&wgpu::Device,
&wgpu::Queue,
&mut wgpu::CommandEncoder,
&wgpu::TextureView,
),
) -> Result<(), SurfaceError> {
if !self.is_surface_configuration {
return Ok(());
}
let mut encoder: CommandEncoder =
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("encoder"),
});
{
let mut shadow_render_pass: RenderPass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Shadow_render_pass"),
color_attachments: &[],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.shadow.texture.view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
shadow_render_pass.set_pipeline(&self.shadow.pipeline);
shadow_render_pass.set_bind_group(0, &self.light.bind_group, &[]);
for model in self.models.static_loaded() {
shadow_render_pass.draw_shadow_model(model, &self.light.bind_group);
}
}
{
let mut render_pass: RenderPass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: self.sky.hdr.view(),
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture.view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
render_pass.set_pipeline(&self.light.pipeline);
for model in self.models.light_loaded() {
render_pass.draw_light_model(
model,
&self.camera.bind_group,
&self.light.bind_group,
);
}
render_pass.set_pipeline(&self.render_pipeline);
for model in self.models.static_loaded() {
render_pass.draw_model(
model,
&self.camera.bind_group,
&self.light.bind_group,
&self.shadow.bind_group,
&self.wind.bind_group,
);
}
render_pass.set_pipeline(&self.sky.pipeline);
render_pass.set_bind_group(0, &self.camera.bind_group, &[]);
render_pass.set_bind_group(1, &self.sky.bind_group, &[]);
render_pass.draw(0..3, 0..1);
}
let ouput: SurfaceTexture = match self.window_surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(t)
| wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
return Ok(());
}
wgpu::CurrentSurfaceTexture::Outdated => return Err(SurfaceError::Outdated),
wgpu::CurrentSurfaceTexture::Lost => return Err(SurfaceError::Lost),
wgpu::CurrentSurfaceTexture::Validation => return Err(SurfaceError::Validation),
};
let view: TextureView = ouput.texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(self.config.format.add_srgb_suffix()),
..Default::default()
});
self.sky.hdr.process(&mut encoder, &view);
overlay(&self.device, &self.queue, &mut encoder, &view);
self.queue.submit(std::iter::once(encoder.finish()));
ouput.present();
Ok(())
}
pub fn spawn_model(&mut self, model_desc: ModelDesc) -> Ingot<Model> {
self.models.spawn(
&self.device,
&self.queue,
&self.texture_bind_group_layout,
model_desc,
)
}
pub fn light_handle(&mut self) -> Light {
Light
}
pub fn set_wind(&mut self, direction: [f32; 2], intensity: f32) {
self.wind.set(direction, intensity);
}
pub fn evolbe(&mut self) {
self.models.collect_loaded();
let now: web_time::Instant = web_time::Instant::now();
let dt: web_time::Duration = now - self.last_render_time;
self.last_render_time = now;
self.camera.update(&self.queue, dt);
self.light.update(&self.queue);
self.wind.update(&self.queue);
}
}