tufa 0.1.0

A wgpu abstraction layer.
Documentation
use std::sync::Arc;

use anyhow::Result;
use egui_wgpu::ScreenDescriptor;
use wgpu::{
    Color, CompositeAlphaMode, LoadOp, Operations, PresentMode, RenderPassColorAttachment,
    RenderPassDepthStencilAttachment, RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration,
    Texture, TextureDescriptor, TextureDimension, TextureUsages, TextureViewDescriptor,
};
use winit::{
    application::ApplicationHandler,
    event::{DeviceEvent, DeviceId, WindowEvent},
    event_loop::{ActiveEventLoop, ControlFlow, EventLoopBuilder},
    window::{WindowAttributes, WindowId},
};

use crate::{gpu::Gpu, DEPTH_TEXTURE_FORMAT, TEXTURE_FORMAT};

use super::{egui::Egui, GraphicsCtx, Interactive};

pub struct Window<'a, T> {
    app: Application<'a, T>,
}

struct Application<'a, T> {
    gpu: Gpu,
    attributes: WindowAttributes,
    state: Option<InnerApplication<'a>>,

    interactive: T,
}

struct InnerApplication<'a> {
    window: Arc<winit::window::Window>,
    surface: Surface<'a>,
    depth_texture: Texture,
    egui: Egui,
}

impl<T: Interactive> Window<'_, T> {
    pub fn run(mut self) -> Result<()> {
        let event_loop_builder = EventLoopBuilder::default().build()?;
        event_loop_builder.set_control_flow(ControlFlow::Wait);
        event_loop_builder.run_app(&mut self.app)?;
        Ok(())
    }
}

impl<T: Interactive> ApplicationHandler for Application<'_, T> {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let window = Arc::new(event_loop.create_window(self.attributes.clone()).unwrap());
        let surface = self.gpu.instance.create_surface(window.clone()).unwrap();
        let egui = Egui::new(&self.gpu.device, TEXTURE_FORMAT, None, 1, &window);

        let window_size = window.inner_size();
        let depth_texture = self.gpu.device.create_texture(&TextureDescriptor {
            label: None,
            size: wgpu::Extent3d {
                width: window_size.width,
                height: window_size.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: TextureDimension::D2,
            format: DEPTH_TEXTURE_FORMAT,
            usage: TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

        let gcx = GraphicsCtx {
            gpu: &self.gpu,
            window: &window,
        };
        self.interactive.init(gcx);

        self.state = Some(InnerApplication {
            window,
            surface,
            depth_texture,
            egui,
        });
    }

    fn device_event(
        &mut self,
        _event_loop: &ActiveEventLoop,
        device_id: DeviceId,
        event: DeviceEvent,
    ) {
        let Some(state) = &mut self.state else { return };
        self.interactive.device_event(
            GraphicsCtx {
                gpu: &self.gpu,
                window: &state.window,
            },
            device_id,
            &event,
        );
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        let Some(state) = &mut self.state else { return };
        if window_id != state.window.id() {
            return;
        }

        let gcx = GraphicsCtx {
            gpu: &self.gpu,
            window: &state.window,
        };
        self.interactive.window_event(gcx.clone(), &event);
        state.egui.handle_input(&state.window, &event);
        match event {
            WindowEvent::CloseRequested => event_loop.exit(),
            WindowEvent::Resized(_size) => self.resize_surface(),
            WindowEvent::RedrawRequested => {
                let output = state.surface.get_current_texture().unwrap();

                self.gpu.immediate_dispatch(|encoder| {
                    let view = output
                        .texture
                        .create_view(&TextureViewDescriptor::default());
                    let depth_view = state
                        .depth_texture
                        .create_view(&TextureViewDescriptor::default());

                    {
                        let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
                            label: None,
                            color_attachments: &[Some(RenderPassColorAttachment {
                                view: &view,
                                resolve_target: None,
                                ops: Operations {
                                    load: LoadOp::Clear(Color::BLACK),
                                    store: StoreOp::Store,
                                },
                            })],
                            depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
                                view: &depth_view,
                                depth_ops: Some(Operations {
                                    load: LoadOp::Clear(1.0),
                                    store: StoreOp::Store,
                                }),
                                stencil_ops: None,
                            }),
                            timestamp_writes: None,
                            occlusion_query_set: None,
                        });
                        self.interactive.render(gcx.clone(), &mut render_pass);
                    }

                    {
                        state.egui.begin_frame(&state.window);
                        self.interactive.ui(gcx, state.egui.context());

                        let size = state.window.inner_size();
                        state.egui.end_frame_and_draw(
                            &self.gpu.device,
                            &self.gpu.queue,
                            encoder,
                            &state.window,
                            &view,
                            ScreenDescriptor {
                                size_in_pixels: [size.width, size.height],
                                pixels_per_point: state.window.scale_factor() as f32,
                            },
                        );
                    }
                });

                output.present();
                state.window.request_redraw();
            }
            _ => {}
        }
    }
}

impl<T> Application<'_, T> {
    fn resize_surface(&mut self) {
        let state = self.state.as_mut().unwrap();
        let size = state.window.inner_size();
        state.surface.configure(
            &self.gpu.device,
            &SurfaceConfiguration {
                usage: TextureUsages::RENDER_ATTACHMENT,
                format: TEXTURE_FORMAT,
                width: size.width,
                height: size.height,
                present_mode: PresentMode::AutoVsync,
                desired_maximum_frame_latency: 1,
                alpha_mode: CompositeAlphaMode::Opaque,
                view_formats: vec![],
            },
        );
        state.depth_texture = self.gpu.device.create_texture(&TextureDescriptor {
            label: None,
            size: wgpu::Extent3d {
                width: size.width,
                height: size.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: TextureDimension::D2,
            format: DEPTH_TEXTURE_FORMAT,
            usage: TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });
    }
}

impl Gpu {
    pub fn create_window<T: Interactive>(
        &self,
        attributes: WindowAttributes,
        interactive: T,
    ) -> Window<T> {
        Window {
            app: Application {
                gpu: self.clone(),
                attributes,
                state: None,

                interactive,
            },
        }
    }
}