1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Re-export our main dependencies.
use std::sync::{Arc, Mutex};

pub use eframe;
use eframe::egui_wgpu::{Callback, CallbackResources, CallbackTrait};
pub use eframe::{egui, wgpu};
pub use enum_map;
pub use anymap;
pub use image;
use graphics::RenderData;
pub use nalgebra;

pub mod graphics;
pub mod mipmapping;
#[cfg(target_arch = "wasm32")]
pub mod web;

pub trait PeterEngineApp: Send + 'static {
  const WINDOW_TITLE: &'static str;
  const SHADER_SOURCE: &'static str;

  fn init(&mut self, cc: &eframe::CreationContext, render_data: &mut RenderData<Self>);
  fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame, dt: f32);
  fn central_panel_input(
    &mut self,
    _egui_ctx: &egui::Context,
    _response: egui::Response,
    _allocated_rect: &egui::Rect,
  ) {
  }
  fn prepare(
    &mut self,
    render_data: &mut RenderData<Self>,
    encoder: &mut wgpu::CommandEncoder,
  ) -> Vec<wgpu::CommandBuffer>;
  fn paint(
    &mut self,
    render_data: &RenderData<Self>,
    info: eframe::epaint::PaintCallbackInfo,
    render_pass: &mut wgpu::RenderPass,
  );
}

struct EframeApp<GameState> {
  locked_state: Arc<Mutex<GameState>>,
}

impl<GameState: PeterEngineApp> EframeApp<GameState> {
  fn new(mut game_state: GameState, cc: &eframe::CreationContext) -> Self {
    let wgpu_render_state = cc.wgpu_render_state.as_ref().unwrap();
    let mut w = wgpu_render_state.renderer.write();
    let mut rd = RenderData::<GameState>::new(cc);
    game_state.init(cc, &mut rd);
    let locked_state = Arc::new(Mutex::new(game_state));
    w.callback_resources.insert(rd);
    Self { locked_state }
  }
}

struct PaintCallback<GameState> {
  pixel_perfect_size: (u32, u32),
  locked_state:       Arc<Mutex<GameState>>,
}

impl<GameState: PeterEngineApp> CallbackTrait for PaintCallback<GameState> {
  fn prepare(
    &self,
    _device: &wgpu::Device,
    _queue: &wgpu::Queue,
    encoder: &mut wgpu::CommandEncoder,
    callback_resources: &mut CallbackResources,
  ) -> Vec<wgpu::CommandBuffer> {
    let render_data = callback_resources.get_mut::<RenderData<GameState>>().unwrap();
    render_data.pixel_perfect_size = self.pixel_perfect_size;
    // FIXME: I should maybe assert that the above device and queue are the same as the ones in render_data.
    self.locked_state.lock().unwrap().prepare(render_data, encoder)
  }

  fn paint<'rp>(
    &'rp self,
    info: eframe::epaint::PaintCallbackInfo,
    render_pass: &mut wgpu::RenderPass<'rp>,
    callback_resources: &'rp CallbackResources,
  ) {
    let render_data = callback_resources.get::<RenderData<GameState>>().unwrap();
    self.locked_state.lock().unwrap().paint(render_data, info, render_pass)
  }
}

impl<GameState: PeterEngineApp> eframe::App for EframeApp<GameState> {
  fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) {
    let dt = egui_ctx.input(|inp| inp.stable_dt.clamp(0.0, 0.15));
    {
      let mut guard = self.locked_state.lock().unwrap();
      guard.update(egui_ctx, frame, dt);
    }

    egui::CentralPanel::default().frame(egui::Frame::none().fill(egui::Color32::BLACK)).show(
      egui_ctx,
      |ui| {
        let (id, allocated_rect) = ui.allocate_space(ui.available_size());
        let response = ui.interact(allocated_rect, id, egui::Sense::click_and_drag());
        self
          .locked_state
          .lock()
          .unwrap()
          .central_panel_input(&egui_ctx, response, &allocated_rect);

        let painter = ui.painter();
        // FIXME: Can I somehow get a ScreenDescriptor?
        let pixel_perfect_rect = egui::Rect::from_two_pos(
          painter.round_pos_to_pixels(allocated_rect.left_top()),
          painter.round_pos_to_pixels(allocated_rect.right_bottom()),
        );
        let pixels_per_point = egui_ctx.pixels_per_point();
        // We clamp with 1 to avoid some divide by zeros and wgpu errors.
        let pixel_perfect_size = (
          ((pixel_perfect_rect.width() * pixels_per_point).round() as u32).max(1),
          ((pixel_perfect_rect.height() * pixels_per_point).round() as u32).max(1),
        );

        painter.add(Callback::new_paint_callback(pixel_perfect_rect, PaintCallback {
          pixel_perfect_size,
          locked_state: Arc::clone(&self.locked_state),
        }));
      },
    );
  }
}

pub fn launch<GameState: PeterEngineApp>(game_state: GameState) -> Result<(), eframe::Error> {
  let mut native_options = eframe::NativeOptions::default();
  native_options.depth_buffer = 32;
  native_options.multisampling = 4;
  eframe::run_native(
    GameState::WINDOW_TITLE,
    native_options,
    Box::new(move |cc| Box::new(EframeApp::new(game_state, cc))),
  )
}