Skip to main content

renderforge/
lib.rs

1use wgpu::{Device, Queue, RenderPass};
2use crate::render::camera::Camera;
3
4pub mod geometry;
5pub mod render;
6pub mod builtin;
7
8#[cfg(feature = "debug")]
9pub trait SizedThreadSafe: Sized + Sync + Send + std::fmt::Debug {}
10#[cfg(not(feature = "debug"))]
11pub trait SizedThreadSafe: Sized + Sync + Send {}
12
13#[cfg(feature = "debug")]
14impl<T> SizedThreadSafe for T where T: Sized + Sync + Send + std::fmt::Debug {}
15#[cfg(not(feature = "debug"))]
16impl<T> SizedThreadSafe for T where T: Sized + Sync + Send {}
17
18
19pub trait Renderable<Shared> : Send + Sync {
20    fn pre_render(&mut self, device: &Device, queue: &Queue, camera: &Camera, shared: &Shared) {
21        let _ = (device, queue, camera, shared);
22    }
23    fn render(&mut self, device: &Device, pass: &mut RenderPass, camera: &Camera, shared: &Shared) {
24        let _ = (device, pass, camera, shared);
25    }
26}
27
28
29#[cfg(feature = "egui")]
30use egui_winit::winit::{
31    application::ApplicationHandler,
32    error::EventLoopError,
33    event::{DeviceEvent, DeviceId, KeyEvent, WindowEvent},
34    event_loop::{ActiveEventLoop, EventLoop},
35    keyboard::{KeyCode, PhysicalKey},
36    window::{Window, WindowId}
37};
38#[cfg(feature = "egui")]
39use egui_winit::winit::dpi::LogicalSize;
40#[cfg(feature = "egui")]
41use glam::Mat4;
42#[cfg(feature = "egui")]
43use std::collections::HashSet;
44#[cfg(feature = "egui")]
45use std::sync::Arc;
46
47#[cfg(feature = "egui")]
48#[cfg_attr(feature = "debug", derive(Debug))]
49pub struct Viewport {
50    texture: wgpu::Texture,
51    view: wgpu::TextureView,
52    depth_tex: wgpu::Texture,
53    depth_view: wgpu::TextureView,
54    size: (u32, u32),
55    egui_id: egui::TextureId,
56    clear_color: wgpu::Color,
57}
58
59#[cfg(feature = "egui")]
60impl Viewport {
61    pub fn new(
62        device: &Device,
63        egui_renderer: &mut egui_wgpu::Renderer,
64        fmt: wgpu::TextureFormat,
65        clear_color: wgpu::Color,
66    ) -> Self {
67        let texture = device.create_texture(&wgpu::TextureDescriptor {
68            label: Some("viewport"),
69            size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
70            mip_level_count: 1,
71            sample_count: 1,
72            dimension: wgpu::TextureDimension::D2,
73            format: fmt,
74            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
75            view_formats: &[],
76        });
77        let view = texture.create_view(&Default::default());
78        let (depth_tex, depth_view) = create_depth(device, 1, 1);
79
80        let egui_id = egui_renderer.register_native_texture(
81            device,
82            &view,
83            wgpu::FilterMode::Linear,
84        );
85
86        Self { texture, view, depth_tex, depth_view, size: (0, 0), egui_id, clear_color }
87    }
88
89    fn resize_if_needed(
90        &mut self,
91        device:        &Device,
92        egui_renderer: &mut egui_wgpu::Renderer,
93        w: u32, h: u32,
94        fmt: wgpu::TextureFormat,
95    ) {
96        if self.size == (w, h) { return; }
97        self.texture = device.create_texture(&wgpu::TextureDescriptor {
98            label: Some("viewport"),
99            size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
100            mip_level_count: 1,
101            sample_count: 1,
102            dimension: wgpu::TextureDimension::D2,
103            format: fmt,
104            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
105            view_formats: &[],
106        });
107        self.view = self.texture.create_view(&Default::default());
108        let (dt, dv) = create_depth(device, w, h);
109        self.depth_tex  = dt;
110        self.depth_view = dv;
111        self.size = (w, h);
112        egui_renderer.update_egui_texture_from_wgpu_texture(
113            device,
114            &self.view,
115            wgpu::FilterMode::Linear,
116            self.egui_id,
117        );
118    }
119}
120
121#[cfg(feature = "egui")]
122pub struct Core {
123    window: Arc<Window>,
124    surface: Option<wgpu::Surface<'static>>,
125    surface_config: wgpu::SurfaceConfiguration,
126    device: Arc<Device>,
127    queue: Arc<Queue>,
128    depth_texture: wgpu::Texture,
129    depth_view: wgpu::TextureView,
130    egui_ctx: egui::Context,
131    egui_winit: egui_winit::State,
132    egui_renderer: egui_wgpu::Renderer,
133    surface_format: wgpu::TextureFormat,
134    camera: Camera,
135    suppress_keys: HashSet<KeyCode>,
136}
137
138#[cfg(feature = "egui")]
139impl Core {
140    pub fn surface_format(&self) -> wgpu::TextureFormat { self.surface_format }
141    pub fn device(&self) -> &Arc<Device> { &self.device }
142    pub fn queue(&self) -> &Arc<Queue> { &self.queue }
143    pub fn depth_view(&self) -> &wgpu::TextureView { &self.depth_view }
144
145    pub fn suppress_key(&mut self, key: KeyCode) { self.suppress_keys.insert(key); }
146    pub fn unsuppress_key(&mut self, key: KeyCode) { self.suppress_keys.remove(&key); }
147
148    pub fn window(&self) -> &Arc<Window> { &self.window }
149    pub fn surface_size(&self) -> (u32, u32) { (self.surface_config.width, self.surface_config.height) }
150    pub fn camera(&self) -> &Camera { &self.camera }
151    pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera }
152
153    pub fn create_viewport(&mut self, clear_color: wgpu::Color) -> Viewport {
154        Viewport::new(&self.device, &mut self.egui_renderer, self.surface_format, clear_color)
155    }
156
157    pub fn render_to_rect<Shared: Send + Sync>(
158        &mut self,
159        ui: &mut egui::Ui,
160        rect: egui::Rect,
161        viewport: &mut Viewport,
162        scene: &mut dyn Renderable<Shared>,
163        shared: &Shared,
164    ) {
165        // let rect = ui.available_rect_before_wrap();
166        let ppp = ui.ctx().pixels_per_point();
167        let w = (rect.width() * ppp) as u32;
168        let h = (rect.height() * ppp) as u32;
169
170        if w == 0 || h == 0 { return }
171
172        viewport.resize_if_needed(&self.device, &mut self.egui_renderer, w, h, self.surface_format);
173
174        scene.pre_render(&self.device, &self.queue, self.camera(), shared);
175
176        let mut encoder = self.device.create_command_encoder(&Default::default());
177        {
178            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
179                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
180                    view: &viewport.view,
181                    resolve_target: None,
182                    depth_slice: None,
183                    ops: wgpu::Operations {
184                        load: wgpu::LoadOp::Clear(viewport.clear_color),
185                        store: wgpu::StoreOp::Store,
186                    },
187                })],
188                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
189                    view: &viewport.depth_view,
190                    depth_ops: Some(wgpu::Operations {
191                        load: wgpu::LoadOp::Clear(1.0),
192                        store: wgpu::StoreOp::Discard,
193                    }),
194                    stencil_ops: None,
195                }),
196                ..Default::default()
197            });
198            scene.render(&self.device, &mut pass, self.camera(), shared);
199        }
200        self.queue.submit([encoder.finish()]);
201
202        ui.put(rect, egui::Image::new(egui::load::SizedTexture::new(
203            viewport.egui_id,
204            egui::vec2(rect.width(), rect.height()),
205        )));
206    }
207
208    pub fn handle_window_event(&mut self, event: &WindowEvent) -> bool {
209        let pass_to_egui = match event {
210            WindowEvent::KeyboardInput { event: KeyEvent {
211                physical_key: PhysicalKey::Code(code), ..
212            }, .. } => !self.suppress_keys.contains(code),
213            _ => true,
214        };
215        if pass_to_egui {
216            self.egui_winit.on_window_event(&self.window, event).consumed
217        } else {
218            false
219        }
220    }
221
222    pub fn begin_egui(&mut self) -> egui::Context {
223        let raw = self.egui_winit.take_egui_input(&self.window);
224        self.egui_ctx.begin_pass(raw);
225        self.egui_ctx.clone()
226    }
227
228    pub fn finish_egui(&mut self, egui_out: egui::FullOutput, view: &wgpu::TextureView) {
229        self.egui_winit.handle_platform_output(&self.window, egui_out.platform_output);
230        let tris = self.egui_ctx.tessellate(egui_out.shapes, egui_out.pixels_per_point);
231        let sd = egui_wgpu::ScreenDescriptor {
232            size_in_pixels: [self.surface_config.width, self.surface_config.height],
233            pixels_per_point: egui_out.pixels_per_point,
234        };
235        for (id, img) in &egui_out.textures_delta.set {
236            self.egui_renderer.update_texture(&self.device, &self.queue, *id, img);
237        }
238        let mut encoder = self.device.create_command_encoder(&Default::default());
239        let cmds = self.egui_renderer.update_buffers(&self.device, &self.queue, &mut encoder, &tris, &sd);
240        self.queue.submit(std::iter::once(encoder.finish()).chain(cmds));
241
242        let mut encoder = self.device.create_command_encoder(&Default::default());
243        {
244            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
245                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
246                    view,
247                    resolve_target: None,
248                    depth_slice: None,
249                    ops: wgpu::Operations {
250                        load: wgpu::LoadOp::Load,
251                        store: wgpu::StoreOp::Store
252                    },
253                })],
254                depth_stencil_attachment: None,
255                ..Default::default()
256            }).forget_lifetime();
257            self.egui_renderer.render(&mut pass, &tris, &sd);
258        }
259        self.queue.submit([encoder.finish()]);
260        for id in &egui_out.textures_delta.free { self.egui_renderer.free_texture(id); }
261    }
262
263    pub fn new(window: Arc<Window>, backends: wgpu::Backends) -> Self {
264        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
265            backends,
266            ..Default::default()
267        });
268        let surface = instance.create_surface(Arc::clone(&window)).unwrap();
269        let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
270            power_preference: wgpu::PowerPreference::HighPerformance,
271            compatible_surface: Some(&surface),
272            force_fallback_adapter: false,
273        })).expect("no adapter");
274        let (device, queue) = pollster::block_on(adapter.request_device(
275            &wgpu::DeviceDescriptor::default(),
276        )).expect("no device");
277        let device = Arc::new(device);
278        let queue = Arc::new(queue);
279        let size = window.inner_size();
280        let caps = surface.get_capabilities(&adapter);
281        let fmt = caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(caps.formats[0]);
282        let surface_config = wgpu::SurfaceConfiguration {
283            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
284            format: fmt,
285            width: size.width,
286            height: size.height,
287            present_mode: wgpu::PresentMode::Fifo,
288            alpha_mode: caps.alpha_modes[0],
289            view_formats: vec![],
290            desired_maximum_frame_latency: 2,
291        };
292        surface.configure(&device, &surface_config);
293        let (depth_texture, depth_view) = create_depth(&device, size.width, size.height);
294        let egui_ctx = egui::Context::default();
295        let egui_winit = egui_winit::State::new(egui_ctx.clone(), egui_ctx.viewport_id(), &window, None, None, None);
296        let egui_renderer = egui_wgpu::Renderer::new(&device, fmt, egui_wgpu::RendererOptions {
297            msaa_samples: 1,
298            depth_stencil_format: None,
299            dithering: false,
300            predictable_texture_filtering: false,
301        });
302        Self {
303            window, surface: Some(surface), surface_config,
304            device, queue, depth_texture, depth_view,
305            egui_ctx, egui_winit, egui_renderer,
306            surface_format: fmt,
307            camera: Camera { view: Mat4::IDENTITY, proj: Mat4::IDENTITY },
308            suppress_keys: HashSet::new(),
309        }
310    }
311
312    pub fn resize(&mut self, w: u32, h: u32) {
313        if w == 0 || h == 0 { return; }
314        self.surface_config.width = w;
315        self.surface_config.height = h;
316        self.surface.as_ref().unwrap().configure(&self.device, &self.surface_config);
317        let (dt, dv) = create_depth(&self.device, w, h);
318        self.depth_texture = dt;
319        self.depth_view = dv;
320    }
321}
322
323#[cfg(feature = "egui")]
324fn create_depth(device: &Device, w: u32, h: u32) -> (wgpu::Texture, wgpu::TextureView) {
325    let tex = device.create_texture(&wgpu::TextureDescriptor {
326        label: Some("depth"),
327        size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
328        mip_level_count: 1,
329        sample_count: 1,
330        dimension: wgpu::TextureDimension::D2,
331        format: wgpu::TextureFormat::Depth32Float,
332        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
333        view_formats: &[],
334    });
335    let view = tex.create_view(&Default::default());
336    (tex, view)
337}
338
339#[cfg(feature = "egui")]
340pub trait App: Sized + 'static {
341    fn name() -> &'static str { "App" }
342    fn size() -> LogicalSize<u32> { LogicalSize::new(1280, 720) }
343    fn backends() -> wgpu::Backends { wgpu::Backends::default() }
344    fn new(core: &mut Core) -> Self;
345    /// Return true to consume event
346    fn on_event(&mut self, core: &mut Core, event: &WindowEvent) -> bool;
347    fn on_device_event(&mut self, core: &mut Core, event: &DeviceEvent) { let _ = (core, event); }
348    fn update(&mut self, core: &mut Core, dt: f32) { let _ = (core, dt); }
349    fn ui(&mut self, core: &mut Core, ctx: &egui::Context) { let _ = (core, ctx); }
350}
351
352#[cfg(feature = "egui")]
353struct AppRunner<G: App> {
354    core: Option<Core>,
355    game: Option<G>,
356    last: std::time::Instant,
357}
358
359#[cfg(feature = "egui")]
360impl<G: App> ApplicationHandler for AppRunner<G> {
361    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
362        let window = Arc::new(
363            event_loop.create_window(
364                egui_winit::winit::window::WindowAttributes::default()
365                    .with_title(G::name())
366                    .with_inner_size(G::size())
367            ).unwrap()
368        );
369        let mut core = Core::new(window, G::backends());
370        let game = G::new(&mut core);
371        self.core = Some(core);
372        self.game = Some(game);
373    }
374
375    fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
376        let (core, game) = match (self.core.as_mut(), self.game.as_mut()) {
377            (Some(c), Some(g)) => (c, g),
378            _ => return,
379        };
380
381        let consumed = game.on_event(core, &event);
382        if consumed { return; }
383
384        let _ = core.handle_window_event(&event);
385
386        match &event {
387            WindowEvent::CloseRequested => event_loop.exit(),
388            WindowEvent::Resized(size) => core.resize(size.width, size.height),
389            WindowEvent::RedrawRequested => {
390                let now = std::time::Instant::now();
391                let dt = (now - self.last).as_secs_f32().min(0.1);
392                self.last = now;
393
394                let (core, game) = (self.core.as_mut().unwrap(), self.game.as_mut().unwrap());
395
396                game.update(core, dt);
397
398                let output = match core.surface.as_ref().unwrap().get_current_texture() {
399                    Ok(o)  => o,
400                    Err(_) => { core.window.request_redraw(); return; }
401                };
402                let view = output.texture.create_view(&Default::default());
403
404
405                let raw = core.egui_winit.take_egui_input(&core.window);
406                let egui_ctx = core.egui_ctx.clone();
407                let egui_out = egui_ctx.run(raw, |ctx| game.ui(core, ctx));
408
409                core.finish_egui(egui_out, &view);
410
411                output.present();
412                core.window.request_redraw();
413            }
414            _ => {}
415        }
416    }
417
418    fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) {
419        if let (Some(c), Some(g)) = (self.core.as_mut(), self.game.as_mut()) {
420            g.on_device_event(c, &event);
421        }
422    }
423
424    fn exiting(&mut self, _: &ActiveEventLoop) {
425        self.game = None;
426        self.core = None;
427    }
428}
429
430#[cfg(feature = "egui")]
431pub fn run<G: App>() -> Result<(), EventLoopError> {
432    let event_loop = EventLoop::new()?;
433    let mut runner = AppRunner::<G> {
434        core: None, game: None, last: std::time::Instant::now()
435    };
436    event_loop.run_app(&mut runner)
437}
438
439
440