Skip to main content

rogalik_wgpu/
lib.rs

1use std::sync::{
2    atomic::{AtomicBool, Ordering},
3    Arc, Mutex,
4};
5use winit::window::Window;
6
7use rogalik_common::{BuiltInShader, EngineError, GraphicsContext, ResourceId, SpriteParams};
8use rogalik_math::vectors::Vector2f;
9
10mod assets;
11mod renderer2d;
12mod structs;
13mod utils;
14
15const MAX_TIME: f32 = 3600.;
16
17// because of WASM
18// static SURFACE_STATE: Arc<Mutex<Option<SurfaceState>>> =
19// Arc::new(Mutex::new(None)); static mut SURFACE_STATE:
20// Option<Arc<Mutex<SurfaceState>>> = None;
21static SURFACE_REFRESH: AtomicBool = AtomicBool::new(false);
22
23struct SurfaceState {
24    surface: wgpu::Surface<'static>,
25    device: wgpu::Device,
26    queue: wgpu::Queue,
27    config: wgpu::SurfaceConfiguration,
28}
29
30pub struct WgpuContext {
31    assets: assets::WgpuAssets,
32    current_camera_id: ResourceId,
33    clear_color: wgpu::Color,
34    renderer2d: renderer2d::Renderer2d,
35    rendering_resolution: Option<(u32, u32)>,
36    surface_state: Arc<Mutex<Option<SurfaceState>>>, // because of WASM
37    time: f32,
38}
39impl WgpuContext {
40    pub fn new(asset_store: Arc<Mutex<rogalik_assets::AssetStore>>) -> Self {
41        Self {
42            assets: assets::WgpuAssets::new(asset_store),
43            current_camera_id: ResourceId::default(),
44            clear_color: wgpu::Color::BLACK,
45            renderer2d: renderer2d::Renderer2d::new(),
46            rendering_resolution: None,
47            surface_state: Arc::new(Mutex::new(None)),
48            time: 0.,
49        }
50    }
51    /// Returns (vw, vh, rw, rh)
52    fn get_current_resolutions(&self) -> (u32, u32, u32, u32) {
53        let (w, h) = match self.surface_state.lock() {
54            Ok(s) => match s.as_ref() {
55                Some(s) => (s.config.width, s.config.height),
56                _ => (0, 0),
57            },
58            _ => (0, 0),
59        };
60        let (rw, rh) = match self.rendering_resolution {
61            Some((rw, rh)) => (rw, rh),
62            None => (w, h),
63        };
64        (w, h, rw, rh)
65    }
66    fn resize_renderer(&mut self) {
67        if let Ok(state) = self.surface_state.lock() {
68            if let Some(state) = state.as_ref() {
69                self.renderer2d
70                    .resize(state.config.width, state.config.height);
71            }
72        }
73    }
74    fn resize_cameras(&mut self) {
75        let (vw, vh, rw, rh) = self.get_current_resolutions();
76        for camera in self.assets.cameras.iter_mut() {
77            camera.resize_viewport(vw as f32, vh as f32, rw as f32, rh as f32);
78        }
79    }
80    fn post_surface_state(&mut self) {
81        if let Ok(state) = self.surface_state.lock() {
82            if let Some(state) = state.as_ref() {
83                let w = state.config.width;
84                let h = state.config.height;
85                log::debug!("State config dim: {}, {}", w, h);
86
87                let _ = self.assets.create_wgpu_data(
88                    w,
89                    h,
90                    &state.device,
91                    &state.queue,
92                    &state.config.format,
93                );
94                log::debug!("Asset data created");
95                let _ = self.renderer2d.create_wgpu_data(
96                    &self.assets,
97                    w,
98                    h,
99                    &state.device,
100                    &state.queue,
101                    &state.config.format,
102                );
103                log::debug!("Renderer2d data created");
104            }
105            SURFACE_REFRESH.store(false, Ordering::Relaxed);
106        }
107        self.resize_cameras();
108        self.resize_renderer();
109    }
110}
111impl GraphicsContext for WgpuContext {
112    fn has_context(&self) -> bool {
113        match self.surface_state.lock() {
114            Ok(s) => s.is_some(),
115            _ => false,
116        }
117    }
118    fn create_context(&mut self, window: Arc<Window>) {
119        #[cfg(not(target_arch = "wasm32"))]
120        pollster::block_on(create_surface_state(self.surface_state.clone(), window));
121        #[cfg(target_arch = "wasm32")]
122        wasm_bindgen_futures::spawn_local(create_surface_state(self.surface_state.clone(), window));
123    }
124    fn update_time(&mut self, delta: f32) {
125        self.time += delta;
126        self.time = self.time % MAX_TIME;
127    }
128    fn update_assets(&mut self) {
129        if let Ok(state) = self.surface_state.lock() {
130            if let Some(state) = state.as_ref() {
131                let _ =
132                    self.assets
133                        .update_assets(&state.device, &state.queue, &state.config.format);
134            }
135        }
136    }
137    fn set_clear_color(&mut self, color: rogalik_common::Color) {
138        self.clear_color = utils::color_to_wgpu(color);
139        self.renderer2d.set_clear_color(self.clear_color);
140    }
141    fn resize(&mut self, width: u32, height: u32) {
142        if width > 0 && height > 0 {
143            if let Ok(mut state) = self.surface_state.lock() {
144                if let Some(state) = state.as_mut() {
145                    state.config.width = width;
146                    state.config.height = height;
147                    state.surface.configure(&state.device, &state.config);
148                    let _ = self.renderer2d.create_wgpu_data(
149                        &self.assets,
150                        width,
151                        height,
152                        &state.device,
153                        &state.queue,
154                        &state.config.format,
155                    );
156                    let _ = self.assets.update_postprocess_wgpu_data(
157                        width,
158                        height,
159                        &state.device,
160                        &state.queue,
161                        &state.config.format,
162                    );
163                }
164            }
165            self.resize_cameras();
166            self.resize_renderer();
167        }
168    }
169    fn render(&mut self) {
170        if SURFACE_REFRESH.load(Ordering::Relaxed) {
171            self.post_surface_state();
172        }
173        if let Ok(state) = self.surface_state.lock() {
174            if let Some(state) = state.as_ref() {
175                let _ = self.renderer2d.render(
176                    &self.assets,
177                    self.time,
178                    &state.surface,
179                    &state.device,
180                    &state.queue,
181                );
182            }
183        }
184    }
185    fn set_rendering_resolution(&mut self, w: u32, h: u32) {
186        log::debug!("Setting rendering resolution at: {}x{}", w, h);
187        self.rendering_resolution = Some((w, h));
188        if self
189            .renderer2d
190            .set_rendering_resolution(&mut self.assets, w, h)
191            .is_ok()
192        {
193            if let Ok(state) = self.surface_state.lock() {
194                if let Some(state) = state.as_ref() {
195                    let _ = self.renderer2d.create_upscale_pass(
196                        &self.assets,
197                        &state.device,
198                        &state.queue,
199                        &state.config.format,
200                    );
201                }
202            }
203        }
204        self.resize_cameras();
205    }
206    fn load_texture(&mut self, path: &str) -> ResourceId {
207        self.assets.texture_from_path(path)
208    }
209    fn load_material(&mut self, name: &str, params: rogalik_common::MaterialParams) {
210        self.assets.create_material(name, params);
211        // TODO if self.surface_state build bind_group
212    }
213    fn load_shader(&mut self, kind: rogalik_common::ShaderKind, path: &str) -> ResourceId {
214        // TODO if self.surface_state build pipeline
215        self.assets.create_shader(kind, path)
216    }
217    fn load_font(
218        &mut self,
219        name: &str,
220        path: &str,
221        rows: usize,
222        cols: usize,
223        padding: Option<(f32, f32)>,
224        shader: Option<ResourceId>,
225    ) {
226        self.assets
227            .load_font(name, path, rows, cols, padding, shader);
228    }
229    fn add_post_process(&mut self, name: &str, params: rogalik_common::PostProcessParams) {
230        self.assets.create_post_process(name, params);
231    }
232    fn draw_sprite(
233        &mut self,
234        material: &str,
235        position: Vector2f,
236        z_index: i32,
237        size: Vector2f,
238        params: SpriteParams,
239    ) -> Result<(), EngineError> {
240        self.renderer2d.draw_atlas_sprite(
241            &self.assets,
242            0,
243            material,
244            self.current_camera_id,
245            position,
246            z_index,
247            size,
248            params,
249        )
250    }
251    fn draw_atlas_sprite(
252        &mut self,
253        atlas: &str,
254        index: usize,
255        position: rogalik_math::vectors::Vector2f,
256        z_index: i32,
257        size: rogalik_math::vectors::Vector2f,
258        params: SpriteParams,
259    ) -> Result<(), EngineError> {
260        self.renderer2d.draw_atlas_sprite(
261            &self.assets,
262            index,
263            atlas,
264            self.current_camera_id,
265            position,
266            z_index,
267            size,
268            params,
269        )
270    }
271    fn draw_text(
272        &mut self,
273        font: &str,
274        text: &str,
275        position: Vector2f,
276        z_index: i32,
277        size: f32,
278        params: SpriteParams,
279    ) -> Result<(), EngineError> {
280        self.renderer2d.draw_text(
281            &self.assets,
282            font,
283            text,
284            self.current_camera_id,
285            position,
286            z_index,
287            size,
288            params,
289        )
290    }
291    fn draw_mesh(
292        &mut self,
293        material: &str,
294        vertices: &[Vector2f],
295        uvs: &[Vector2f],
296        indices: &[u16],
297        z_index: i32,
298    ) -> Result<(), EngineError> {
299        let vs = vertices
300            .iter()
301            .zip(uvs)
302            .map(|(v, uv)| crate::structs::Vertex {
303                position: [v.x, v.y, 0.],
304                color: [1., 1., 1., 1.],
305                tex_coords: [uv.x, uv.y],
306            })
307            // TODO allocation can be avoided here
308            .collect::<Vec<_>>();
309        self.renderer2d.draw_mesh(
310            &self.assets,
311            material,
312            self.current_camera_id,
313            &vs,
314            indices,
315            z_index,
316        )
317    }
318    fn set_ambient(&mut self, color: rogalik_common::Color) {
319        self.renderer2d.set_ambient(color);
320    }
321    fn set_postprocess_strength(&mut self, name: &str, value: f32) -> Result<(), EngineError> {
322        let id = *self
323            .assets
324            .get_postprocess_id(name)
325            .ok_or(EngineError::ResourceNotFound)?;
326        let pass = self
327            .assets
328            .get_postprocess_mut(id)
329            .ok_or(EngineError::ResourceNotFound)?;
330
331        pass.set_strength(value);
332
333        if let Ok(state) = self.surface_state.lock() {
334            if let Some(state) = state.as_ref() {
335                pass.write_buffer(&state.queue)?;
336            };
337        }
338        Ok(())
339    }
340    fn add_light(
341        &mut self,
342        strength: f32,
343        color: rogalik_common::Color,
344        position: Vector2f,
345    ) -> Result<(), EngineError> {
346        self.renderer2d.add_light(strength, color, position)
347    }
348    fn text_dimensions(&self, font: &str, text: &str, size: f32) -> Vector2f {
349        self.assets
350            .get_text_dimensions(font, text, size)
351            .unwrap_or(Vector2f::ZERO)
352    }
353    fn create_camera(&mut self, scale: f32, target: Vector2f) -> ResourceId {
354        let (vw, vh, rw, rh) = self.get_current_resolutions();
355        self.assets
356            .create_camera(vw as f32, vh as f32, rw as f32, rh as f32, scale, target)
357    }
358    fn set_camera(&mut self, id: ResourceId) {
359        self.current_camera_id = id;
360    }
361    fn get_current_camera(&self) -> &dyn rogalik_common::Camera {
362        self.assets.get_camera(self.current_camera_id).unwrap()
363    }
364    fn get_current_camera_mut(&mut self) -> &mut dyn rogalik_common::Camera {
365        self.assets.get_camera_mut(self.current_camera_id).unwrap()
366    }
367    fn get_camera(&self, id: ResourceId) -> Option<&dyn rogalik_common::Camera> {
368        Some(self.assets.get_camera(id)?)
369    }
370    fn get_camera_mut(&mut self, id: ResourceId) -> Option<&mut dyn rogalik_common::Camera> {
371        Some(self.assets.get_camera_mut(id)?)
372    }
373    fn get_builtin_shader(&self, shader: BuiltInShader) -> Option<ResourceId> {
374        self.assets.builtin_shaders.get(&shader).copied()
375    }
376}
377
378async fn create_surface_state(
379    surface_state: Arc<Mutex<Option<SurfaceState>>>,
380    window: Arc<Window>,
381) {
382    log::debug!("Creating WGPU instance");
383    let size = (window.inner_size().width, window.inner_size().height);
384
385    if size.0 == 0 || size.1 == 0 {
386        return;
387    }
388
389    let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
390        // backends: wgpu::Backends::all(),
391        backends: get_backends(),
392        ..Default::default()
393    });
394    log::debug!("Creating WGPU surface");
395    let surface = instance.create_surface(window).unwrap();
396    log::debug!("Creating WGPU adapter");
397
398    let adapter = instance
399        .request_adapter(&wgpu::RequestAdapterOptions {
400            power_preference: wgpu::PowerPreference::default(),
401            compatible_surface: Some(&surface),
402            force_fallback_adapter: false,
403        })
404        .await
405        .expect("Request for adapter failed!");
406
407    log::debug!("Creating WGPU device");
408
409    let (device, queue) = adapter
410        .request_device(
411            &wgpu::DeviceDescriptor {
412                required_features: wgpu::Features::empty(),
413                required_limits: get_limits(),
414                label: None,
415                memory_hints: Default::default(),
416            },
417            None,
418        )
419        .await
420        .expect("Could not create the device!");
421
422    log::debug!("Config WGPU surface");
423    let surface_caps = surface.get_capabilities(&adapter);
424    log::debug!("WGPU surface capabilities: {:?}", surface_caps);
425    let surface_format = surface_caps
426        .formats
427        .iter()
428        .copied()
429        .find(|f| f.is_srgb())
430        .unwrap_or(surface_caps.formats[0]);
431    log::debug!("WGPU surface format: {:?}", surface_format);
432
433    let present_mode = if surface_caps
434        .present_modes
435        .contains(&wgpu::PresentMode::Fifo)
436        || surface_caps
437            .present_modes
438            .contains(&wgpu::PresentMode::FifoRelaxed)
439    {
440        wgpu::PresentMode::AutoVsync
441    } else {
442        surface_caps.present_modes[0]
443    };
444    log::debug!("WGPU present mode: {:?}", present_mode);
445
446    let config = wgpu::SurfaceConfiguration {
447        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
448        format: surface_format,
449        width: size.0,
450        height: size.1,
451        present_mode,
452        alpha_mode: surface_caps.alpha_modes[0],
453        view_formats: vec![],
454        desired_maximum_frame_latency: 2,
455    };
456    log::debug!("WGPU surface config: {:?}", config);
457    surface.configure(&device, &config);
458    log::debug!("WGPU surface configured");
459
460    if let Ok(mut state) = surface_state.lock() {
461        *state = Some(SurfaceState {
462            surface,
463            device,
464            queue,
465            config,
466        });
467    };
468    SURFACE_REFRESH.store(true, Ordering::Relaxed);
469}
470
471#[cfg(not(target_arch = "wasm32"))]
472fn get_backends() -> wgpu::Backends {
473    wgpu::Backends::all()
474}
475#[cfg(target_arch = "wasm32")]
476fn get_backends() -> wgpu::Backends {
477    wgpu::Backends::GL
478}
479
480#[cfg(not(target_arch = "wasm32"))]
481fn get_limits() -> wgpu::Limits {
482    wgpu::Limits::default()
483}
484#[cfg(target_arch = "wasm32")]
485fn get_limits() -> wgpu::Limits {
486    let mut limits = wgpu::Limits::downlevel_webgl2_defaults();
487    limits.max_color_attachments = 4;
488    limits
489}