1pub mod assets;
2pub mod config;
3mod error;
4pub mod math;
5mod renderer;
6mod scene;
7
8use {
10 crate::{
11 assets::{
12 DrawLight, DrawModel, DrawShadow, InstanceRaw, Model, ModelDesc, ModelStore,
13 ModelVertex, Vertex,
14 },
15 config::{WindowSize, config::FerrumConfig},
16 renderer::{CameraRig, HdrPipeline, Material, ShadowRig, SkyRig},
17 scene::{Light, LightRig, WindRig},
18 },
19 std::sync::Arc,
20 wgpu::{
21 Adapter, BindGroupLayout, CommandEncoder, Device, PipelineLayout, Queue, RenderPass,
22 RenderPipeline, Surface, SurfaceCapabilities, SurfaceConfiguration, SurfaceTexture,
23 TextureFormat, TextureView,
24 },
25};
26
27pub use {
29 assets::{Ingot, Instance, TypeModel},
30 cgmath::{Deg, Matrix4, Point3, Quaternion, Rotation3, Vector3, ortho},
31 error::SurfaceError,
32 renderer::{EnviroimentDesc, SkyFormat},
33 winit::{dpi::PhysicalSize, keyboard::KeyCode},
34};
35
36pub struct State {
37 pub window_surface: wgpu::Surface<'static>,
38 pub device: Arc<wgpu::Device>,
39 pub queue: Arc<wgpu::Queue>,
40 pub ferrum_config: FerrumConfig,
41 pub is_surface_configuration: bool,
42 pub render_pipeline: wgpu::RenderPipeline,
43 pub texture_bind_group_layout: Arc<wgpu::BindGroupLayout>,
44 pub depth_texture: renderer::Texture,
45 pub last_render_time: web_time::Instant,
46 pub camera: CameraRig,
47 pub light: LightRig,
48 pub wind: WindRig,
49 pub shadow: ShadowRig,
50 pub hdr: HdrPipeline,
51 pub sky: Option<SkyRig>,
52 pub sky_desc: Option<EnviroimentDesc>,
53 pub(crate) models: ModelStore,
54}
55
56impl State {
57 pub async fn new(
58 target: impl raw_window_handle::HasWindowHandle
59 + raw_window_handle::HasDisplayHandle
60 + wgpu::WasmNotSendSync
61 + 'static,
62 window_size: WindowSize,
63 asset: crate::assets::Asset,
64 ) -> anyhow::Result<Self> {
65 let mut instance_desc: wgpu::InstanceDescriptor =
66 wgpu::InstanceDescriptor::new_without_display_handle();
67 #[cfg(target_arch = "wasm32")]
68 {
69 instance_desc.backends = wgpu::Backends::GL | wgpu::Backends::BROWSER_WEBGPU;
70 }
71 #[cfg(all(not(target_arch = "wasm32"), not(feature = "rpi")))]
72 {
73 instance_desc.backends = wgpu::Backends::PRIMARY;
74 }
75 #[cfg(all(not(target_arch = "wasm32"), feature = "rpi"))]
76 {
77 instance_desc.backends = wgpu::Backends::GL;
78 }
79 let backend_instance: wgpu::Instance = wgpu::Instance::new(instance_desc);
80
81 let window_surface: Surface = backend_instance.create_surface(target)?;
83
84 let adapter: Adapter = backend_instance
86 .request_adapter(&wgpu::RequestAdapterOptions {
87 power_preference: wgpu::PowerPreference::default(),
88 force_fallback_adapter: false,
89 compatible_surface: Some(&window_surface),
90 })
91 .await?;
92
93 let (device, queue) = adapter
95 .request_device(&wgpu::DeviceDescriptor {
96 label: None,
97 required_features: wgpu::Features::empty(),
100 required_limits: adapter.limits(),
105 experimental_features: wgpu::ExperimentalFeatures::disabled(),
106 memory_hints: Default::default(),
107 trace: wgpu::Trace::Off,
108 })
109 .await?;
110 let device: Arc<Device> = Arc::new(device);
111 let queue: Arc<Queue> = Arc::new(queue);
112
113 let surface_caps: SurfaceCapabilities = window_surface.get_capabilities(&adapter);
115
116 let surface_format: TextureFormat = surface_caps
118 .formats
119 .iter()
120 .find(|f| f.is_srgb())
121 .copied()
122 .unwrap_or(surface_caps.formats[0]); let surface_config: SurfaceConfiguration = wgpu::SurfaceConfiguration {
126 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
127 format: surface_format,
128 width: window_size.width,
129 height: window_size.height,
130 present_mode: surface_caps.present_modes[0],
131 desired_maximum_frame_latency: 2,
132 alpha_mode: surface_caps.alpha_modes[0],
133 view_formats: vec![surface_format.add_srgb_suffix()],
134 };
135 let ferrum_config: FerrumConfig = FerrumConfig {
136 surface_config: Some(surface_config.clone()),
137 asset,
138 ..Default::default()
139 };
140
141 let texture_bind_group_layout: Arc<BindGroupLayout> =
144 Arc::new(Material::bind_group_layout(&device));
145
146 let camera: CameraRig = CameraRig::new(
147 &device,
148 surface_config.width as f32 / surface_config.height as f32,
149 );
150
151 let depth_texture: renderer::Texture =
152 renderer::Texture::create_depth_texture(&device, &surface_config, "depth_texture");
153
154 let hdr: HdrPipeline = HdrPipeline::new(&device, &surface_config);
156
157 let light: LightRig = LightRig::new(
158 &device,
159 &camera.layout,
160 &texture_bind_group_layout,
161 hdr.format(),
162 );
163
164 let shadow: ShadowRig = ShadowRig::new(&device, &light.layout);
165
166 let wind: WindRig = WindRig::new(&device);
167
168 let pipeline_render_layout: PipelineLayout =
170 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
171 bind_group_layouts: &[
172 Some(&texture_bind_group_layout),
173 Some(&camera.layout),
174 Some(&light.layout),
175 Some(&shadow.layout),
176 Some(&wind.layout),
177 ],
178 label: Some("render_pipeline_layout"),
179 ..Default::default()
180 });
181
182 let render_pipeline: RenderPipeline = renderer::create_render_pipeline(
183 &device,
184 &pipeline_render_layout,
185 hdr.format(),
186 Some(renderer::Texture::DEPTH_FORMAT),
187 &[ModelVertex::desc(), InstanceRaw::desc()],
188 wgpu::PrimitiveTopology::TriangleList,
189 wgpu::include_wgsl!("shaders/shaders.wgsl"),
190 wgpu::CompareFunction::Less,
191 );
192
193 Ok(Self {
194 window_surface,
195 device,
196 queue,
197 ferrum_config,
198 is_surface_configuration: false,
199 render_pipeline,
200 texture_bind_group_layout,
201 depth_texture,
202 last_render_time: web_time::Instant::now(),
203 camera,
204 light,
205 wind,
206 shadow,
207 hdr,
208 sky: None,
209 sky_desc: None,
210 models: ModelStore::new(),
211 })
212 }
213
214 pub fn resize(&mut self, height: u32, width: u32) {
215 if height > 0 && width > 0 {
216 self.ferrum_config.size.height = height;
217 self.ferrum_config.size.width = width;
218
219 self.camera.set_aspect(
220 self.ferrum_config.size.width as f32 / self.ferrum_config.size.height as f32,
221 );
222
223 if let Some(sc) = &self.ferrum_config.surface_config {
224 self.window_surface.configure(&self.device, sc);
225
226 self.depth_texture =
227 renderer::Texture::create_depth_texture(&self.device, sc, "depth_texture");
228 };
229
230 self.hdr.resize(&self.device, width, height);
231 self.is_surface_configuration = true;
232 }
233 }
234
235 pub fn render(&mut self) -> Result<(), SurfaceError> {
236 self.render_with_overlay(&mut |_, _, _, _| {})
237 }
238
239 pub fn render_with_overlay(
240 &mut self,
241 overlay: &mut dyn FnMut(
242 &wgpu::Device,
243 &wgpu::Queue,
244 &mut wgpu::CommandEncoder,
245 &wgpu::TextureView,
246 ),
247 ) -> Result<(), SurfaceError> {
248 if !self.is_surface_configuration {
249 return Ok(());
250 }
251
252 let mut encoder: CommandEncoder =
253 self.device
254 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
255 label: Some("encoder"),
256 });
257
258 {
259 let mut shadow_render_pass: RenderPass =
260 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
261 label: Some("Shadow_render_pass"),
262 color_attachments: &[],
263 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
264 view: &self.shadow.texture.view,
265 depth_ops: Some(wgpu::Operations {
266 load: wgpu::LoadOp::Clear(1.0),
267 store: wgpu::StoreOp::Store,
268 }),
269 stencil_ops: None,
270 }),
271 timestamp_writes: None,
272 occlusion_query_set: None,
273 multiview_mask: None,
274 });
275
276 shadow_render_pass.set_pipeline(&self.shadow.pipeline);
277 shadow_render_pass.set_bind_group(0, &self.light.bind_group, &[]);
278 for model in self.models.static_loaded() {
279 shadow_render_pass.draw_shadow_model(model, &self.light.bind_group);
280 }
281 }
282 {
283 let mut render_pass: RenderPass =
284 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
285 label: Some("render_pass"),
286 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
287 view: self.hdr.view(),
288 depth_slice: None,
289 resolve_target: None,
290 ops: wgpu::Operations {
291 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
292 store: wgpu::StoreOp::Store,
293 },
294 })],
295 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
296 view: &self.depth_texture.view,
297 depth_ops: Some(wgpu::Operations {
298 load: wgpu::LoadOp::Clear(1.0),
299 store: wgpu::StoreOp::Store,
300 }),
301 stencil_ops: None,
302 }),
303 timestamp_writes: None,
304 occlusion_query_set: None,
305 multiview_mask: None,
306 });
307
308 render_pass.set_pipeline(&self.light.pipeline);
309 for model in self.models.light_loaded() {
310 render_pass.draw_light_model(
311 model,
312 &self.camera.bind_group,
313 &self.light.bind_group,
314 );
315 }
316
317 render_pass.set_pipeline(&self.render_pipeline);
318 for model in self.models.static_loaded() {
319 render_pass.draw_model(
320 model,
321 &self.camera.bind_group,
322 &self.light.bind_group,
323 &self.shadow.bind_group,
324 &self.wind.bind_group,
325 );
326 }
327
328 if let Some(sky) = &self.sky {
331 render_pass.set_pipeline(&sky.pipeline);
332 render_pass.set_bind_group(0, &self.camera.bind_group, &[]);
333 render_pass.set_bind_group(1, &sky.bind_group, &[]);
334 render_pass.draw(0..3, 0..1);
335 };
336 }
337
338 let ouput: SurfaceTexture = match self.window_surface.get_current_texture() {
339 wgpu::CurrentSurfaceTexture::Success(t)
340 | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
341 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
344 return Ok(());
345 }
346 wgpu::CurrentSurfaceTexture::Outdated => return Err(SurfaceError::Outdated),
347 wgpu::CurrentSurfaceTexture::Lost => return Err(SurfaceError::Lost),
348 wgpu::CurrentSurfaceTexture::Validation => return Err(SurfaceError::Validation),
349 };
350
351 if let Some(sc) = &self.ferrum_config.surface_config {
352 let view: TextureView = ouput.texture.create_view(&wgpu::TextureViewDescriptor {
353 format: Some(sc.format.add_srgb_suffix()),
354 ..Default::default()
355 });
356 self.hdr.process(&mut encoder, &view);
357 overlay(&self.device, &self.queue, &mut encoder, &view);
358 }
359 self.queue.submit(std::iter::once(encoder.finish()));
360
361 ouput.present();
362
363 Ok(())
364 }
365
366 pub fn spawn_model(&mut self, model_desc: ModelDesc) -> Ingot<Model> {
367 self.models.spawn(
368 &self.device,
369 &self.queue,
370 &self.texture_bind_group_layout,
371 model_desc,
372 &self.ferrum_config,
373 )
374 }
375
376 pub fn light_handle(&mut self) -> Light {
377 Light
378 }
379
380 pub fn spawn_enviroiment(&mut self, enviroiment: EnviroimentDesc) {
381 self.sky_desc = Some(enviroiment);
382 }
383
384 pub fn set_wind(&mut self, direction: [f32; 2], intensity: f32) {
387 self.wind.set(direction, intensity);
388 }
389
390 pub fn evolbe(&mut self) {
393 self.models.collect_loaded();
394
395 let now: web_time::Instant = web_time::Instant::now();
396 let dt: web_time::Duration = now - self.last_render_time;
397 self.last_render_time = now;
398
399 if let (Some(desc), Some(sc)) = (self.sky_desc.take(), &self.ferrum_config.surface_config) {
400 self.sky = Some(
402 SkyRig::new(
403 &self.device,
404 &self.queue,
405 sc,
406 &self.camera.layout,
407 self.hdr.format(),
408 desc,
409 )
410 .expect("Error with load enviroiment"),
411 );
412 }
413
414 self.camera.update(&self.queue, dt);
415 self.light.update(&self.queue);
416 self.wind.update(&self.queue);
417 }
418}