peter_engine/
lib.rs

1// Re-export our main dependencies.
2use std::sync::{Arc, Mutex};
3
4pub use eframe;
5use eframe::egui_wgpu::{Callback, CallbackResources, CallbackTrait};
6pub use eframe::{egui, wgpu};
7use graphics::RenderData;
8pub use image;
9pub use nalgebra;
10
11pub mod graphics;
12pub mod mipmapping;
13#[cfg(target_arch = "wasm32")]
14pub mod web;
15
16pub trait PeterEngineApp: Send + 'static {
17  const WINDOW_TITLE: &'static str;
18
19  type RenderResources: Send + Sync + 'static;
20
21  fn get_shader_source() -> String;
22  fn init(
23    &mut self,
24    cc: &eframe::CreationContext,
25    render_data: &mut RenderData,
26  ) -> Self::RenderResources;
27  fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame, dt: f32);
28  fn central_panel_input(
29    &mut self,
30    _egui_ctx: &egui::Context,
31    _response: egui::Response,
32    _allocated_rect: &egui::Rect,
33  ) {
34  }
35  fn prepare(
36    &mut self,
37    render_data: &mut RenderData,
38    resources: &mut Self::RenderResources,
39    device: &wgpu::Device,
40    queue: &wgpu::Queue,
41    encoder: &mut wgpu::CommandEncoder,
42    screen_size: (u32, u32),
43  ) -> Vec<wgpu::CommandBuffer>;
44  fn paint<'rp>(
45    &mut self,
46    render_data: &'rp RenderData,
47    resources: &'rp Self::RenderResources,
48    info: eframe::epaint::PaintCallbackInfo,
49    render_pass: &mut wgpu::RenderPass<'rp>,
50  );
51}
52
53pub struct EframeApp<GameState> {
54  locked_state: Arc<Mutex<GameState>>,
55}
56
57impl<GameState: PeterEngineApp> EframeApp<GameState> {
58  pub fn new(mut game_state: GameState, cc: &eframe::CreationContext) -> Self {
59    let wgpu_render_state = cc.wgpu_render_state.as_ref().unwrap();
60    let mut w = wgpu_render_state.renderer.write();
61    let shader_source = GameState::get_shader_source();
62    let mut rd = RenderData::new(cc, &shader_source);
63    let resources = game_state.init(cc, &mut rd);
64    let locked_state = Arc::new(Mutex::new(game_state));
65    w.callback_resources.insert((rd, resources));
66    Self { locked_state }
67  }
68}
69
70struct PaintCallback<GameState> {
71  pixel_perfect_size: (u32, u32),
72  locked_state:       Arc<Mutex<GameState>>,
73}
74
75impl<GameState: PeterEngineApp> CallbackTrait for PaintCallback<GameState> {
76  fn prepare(
77    &self,
78    device: &wgpu::Device,
79    queue: &wgpu::Queue,
80    encoder: &mut wgpu::CommandEncoder,
81    callback_resources: &mut CallbackResources,
82  ) -> Vec<wgpu::CommandBuffer> {
83    let (render_data, resources) =
84      callback_resources.get_mut::<(RenderData, GameState::RenderResources)>().unwrap();
85    render_data.pixel_perfect_size = self.pixel_perfect_size;
86    self.locked_state.lock().unwrap().prepare(
87      render_data,
88      resources,
89      device,
90      queue,
91      encoder,
92      self.pixel_perfect_size,
93    )
94  }
95
96  fn paint<'rp>(
97    &self,
98    info: eframe::epaint::PaintCallbackInfo,
99    render_pass: &mut wgpu::RenderPass<'rp>,
100    callback_resources: &'rp CallbackResources,
101  ) {
102    let (render_data, resources) =
103      callback_resources.get::<(RenderData, GameState::RenderResources)>().unwrap();
104    self.locked_state.lock().unwrap().paint(render_data, resources, info, render_pass)
105  }
106}
107
108impl<GameState: PeterEngineApp> eframe::App for EframeApp<GameState> {
109  fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) {
110    let dt = egui_ctx.input(|inp| inp.stable_dt.clamp(0.0, 0.15));
111    {
112      let mut guard = self.locked_state.lock().unwrap();
113      guard.update(egui_ctx, frame, dt);
114    }
115
116    egui::CentralPanel::default().frame(egui::Frame::none().fill(egui::Color32::BLACK)).show(
117      egui_ctx,
118      |ui| {
119        let (id, allocated_rect) = ui.allocate_space(ui.available_size());
120        let response = ui.interact(allocated_rect, id, egui::Sense::click_and_drag());
121        self
122          .locked_state
123          .lock()
124          .unwrap()
125          .central_panel_input(&egui_ctx, response, &allocated_rect);
126
127        let painter = ui.painter();
128        // FIXME: Can I somehow get a ScreenDescriptor?
129        let pixel_perfect_rect = egui::Rect::from_two_pos(
130          painter.round_pos_to_pixels(allocated_rect.left_top()),
131          painter.round_pos_to_pixels(allocated_rect.right_bottom()),
132        );
133        let pixels_per_point = egui_ctx.pixels_per_point();
134        // We clamp with 1 to avoid some divide by zeros and wgpu errors.
135        let pixel_perfect_size = (
136          ((pixel_perfect_rect.width() * pixels_per_point).round() as u32).max(1),
137          ((pixel_perfect_rect.height() * pixels_per_point).round() as u32).max(1),
138        );
139
140        painter.add(Callback::new_paint_callback(pixel_perfect_rect, PaintCallback {
141          pixel_perfect_size,
142          locked_state: Arc::clone(&self.locked_state),
143        }));
144      },
145    );
146  }
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150pub fn launch<GameState: PeterEngineApp>(game_state: GameState) -> Result<(), eframe::Error> {
151  let mut native_options = eframe::NativeOptions::default();
152  native_options.depth_buffer = 32;
153  native_options.multisampling = crate::graphics::MSAA_COUNT as u16;
154  eframe::run_native(
155    GameState::WINDOW_TITLE,
156    native_options,
157    Box::new(move |cc| Box::new(EframeApp::new(game_state, cc))),
158  )
159}