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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
use std::time::SystemTime;
use cgmath::Point2;
use winit::{window::Window, event::WindowEvent};
use crate::render::{RenderEngineApp, primitives::{mesh::Mesh, cameras::Camera, vertices::Vertex}, textures::{textures::Texture, depth_textures::DepthTexture}, pipelines::Pipeline, resources::{ResourceCache, Handle}, files::Files};
/// A struct with all required information to render to a given window.
///
/// DO NOT try to create this object by yourself, this object will be proved to your RenderEngineApp.
/// DO NOT try to modify any values in this struct, this will only cause errors unless you know what you are doing.
#[derive(Debug)]
pub struct RenderEngine {
pub surface: wgpu::Surface,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub config: wgpu::SurfaceConfiguration,
pub size: winit::dpi::PhysicalSize<u32>,
mesh_cache: ResourceCache<Mesh>,
texture_cache: ResourceCache<Texture>,
pub depth_texture: DepthTexture,
pub(crate) start_time: u128,
pub time_since_start: f32,
pub delta_time: f32,
pub window: Window // must be declared after surface due to unsafe code in windows resources
}
impl RenderEngine {
/// Get a reference to the WGPU window used by the render engine.
pub fn window(&self) -> &Window { &self.window }
/// Get a texture from the texture cache using a handle.
///
/// Arguments
/// * handle - The resource handle that will be used to get the texture from the cache.
pub fn texture(&self, handle: &Handle<Texture>) -> &Texture { self.texture_cache.get(handle).unwrap() }
/// Get a mesh from the mesh cache using a handle.
///
/// Arguments
/// * handle - The resource handle that will be used to get the mesh from the cache.
pub fn mesh(&self, handle: &Handle<Mesh>) -> &Mesh { self.mesh_cache.get(handle).unwrap() }
/// Create a new render engine using the given WGPU window.
///
/// Arguments
/// * window - The WGPU window that will be used to create this render engine.
pub async fn new(window: Window) -> Self {
let size = window.inner_size();
// create wgpu instance
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
// create surface
let surface = unsafe { instance.create_surface(&window) }.unwrap();
// create adapter
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false
}
).await.unwrap();
// create device and queue
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::VERTEX_WRITABLE_STORAGE,
limits: wgpu::Limits::default(),
label: None
},
None
).await.unwrap();
// configure surface
let capabilities = surface.get_capabilities(&adapter);
let format = capabilities.formats.iter().copied()
.filter(|f| f.is_srgb()).next()
.unwrap_or(capabilities.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: format, width: size.width, height: size.height,
present_mode: capabilities.present_modes[0],
alpha_mode: capabilities.alpha_modes[0],
view_formats: vec![]
};
surface.configure(&device, &config);
// setup depth texture
let depth_texture = DepthTexture::new(&config, &device, "depth_texture");
// setup timing
let now = SystemTime::now();
let start_time = now.duration_since(std::time::UNIX_EPOCH).unwrap().as_millis();
Self {
window, surface, device,
queue, config, size, depth_texture,
start_time,
time_since_start: 0.0,
delta_time: 0.0,
mesh_cache: ResourceCache::new(),
texture_cache: ResourceCache::new()
}
}
/// Handle inputs to the given window, returning true if the event is handled.
/// This automatically processes and passes appropriate inputs to the given app.
///
/// Arguments
/// * app - The app to which inputs should be passed too.
pub fn input(&mut self, app: &mut Box<impl RenderEngineApp + 'static>, event: &WindowEvent) -> bool {
match event {
WindowEvent::CursorMoved { position, .. } => {
app.input(
self,
RenderEngineInput::MouseMove(
Point2 {
x: position.x as f32,
y: position.y as f32
}
)
);
true
}
WindowEvent::MouseInput { state, button, .. } => {
app.input(
self,
RenderEngineInput::MouseButton(*button, *state)
);
true
}
WindowEvent::MouseWheel { delta, .. } => {
app.input(self, RenderEngineInput::MouseWheel(*delta));
true
},
WindowEvent::KeyboardInput { input, .. } => {
if input.virtual_keycode.is_some() {
app.input(
self,
RenderEngineInput::KeyInput(input.virtual_keycode.unwrap(), input.state)
);
}
true
},
_ => false
}
}
/// Resizes all resources used for rendering to the new size given.
///
/// Arguments
/// * new_size - The new size that this render engine should resize its resources too.
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
// if size is valid, reconfigure surface
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
self.depth_texture = DepthTexture::new(&self.config, &self.device, "depth_texture");
}
}
/// Starts a render cycle. During this cycle, the given apps render function will be called.
///
/// Arguments
/// * app - The app whose render functino should be called
///
/// Returns a result with an error from wgpu if it occurs, this will return nothing if no errors occur.
pub fn render(&mut self, app: &mut Box<impl RenderEngineApp + 'static>) -> Result<(), wgpu::SurfaceError> {
// create output, view, and encoder
let output = self.surface.get_current_texture()?;
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
// call app render
app.render(self, &view, &mut encoder);
// render the queue and wrap up
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
// update time since start and delta time
let now = SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis();
let old_time = self.time_since_start;
let ms_since_start = if now > self.start_time { now - self.start_time } else { 0 };
self.time_since_start = ms_since_start as f32 / 1000.0;
self.delta_time = self.time_since_start - old_time;
Ok(())
}
/// Creates a texture from the given bytes and the given path ID
///
/// Arguments
/// * path - The path ID so that this texture can be identified in the cache
/// * bytes - The bytes of the png/jpg file to be loaded into the texture
///
/// Returns a resource handle for the texture
pub fn create_texture(&mut self, path: impl Into<String>, bytes: &[u8]) -> Handle<Texture> {
self.texture_cache.load(path, || {
Texture::from_bytes(
&self.device,
&self.queue,
bytes,
"diffuse"
).unwrap()
})
}
/// Creates a texture from the given local path
///
/// Arguments
/// * path - The local path to the file to be loaded for the texture
///
/// Returns a resource handle for the texture
pub fn load_texture(&mut self, path: &str) -> Handle<Texture> {
self.texture_cache.load(path, || {
Texture::from_bytes(
&self.device,
&self.queue,
&Files::load_bytes(path).expect("Failed to load file"),
"diffuse"
).unwrap()
})
}
/// Creates a mesh from the given vertices and indices
///
/// Arguments
/// * path - The path ID so that this mesh can be identified in the cache
/// * vertices - An array of vertices for the mesh
/// * indices - An array of indices for the mesh
///
/// Returns a resource handle for the mesh
pub fn create_mesh(&mut self, path: &str, vertices: &[Vertex], indices: &[u16]) -> Handle<Mesh> {
self.mesh_cache.load(path, || {
Mesh::from_raw(&self.device, vertices, indices)
})
}
}
#[derive(Clone, Copy, Debug)]
pub enum RenderEngineInput {
MouseMove(Point2<f32>),
MouseButton(winit::event::MouseButton, winit::event::ElementState),
MouseWheel(winit::event::MouseScrollDelta),
KeyInput(winit::event::VirtualKeyCode, winit::event::ElementState)
}
/// A set of functions for RenderPass to setup cameras and draw mesh.
pub trait DrawMesh<'a, 'b> where 'b: 'a {
/// Prepares to draw mesh by setting up a pipeline and a camera to render with.
///
/// Arguments:
/// * pipeline: &Pipeline - The pipeline that will be used to render.
/// * camera: &Camera - The camera to be used to render.
fn prepare_draw(
&mut self,
pipeline: &'b Pipeline,
camera: &'b Camera,
);
/// Draws a mesh to this render pass.
///
/// Arguments:
/// * engine: &RenderEngine - The render engine that will be used to draw this.
/// * mesh: &Handle<Mesh> - A handle to the mesh to be drawn.
/// * texture: &Handle<Texture> - A handle to the texture the mesh will be drawn with.
/// * instance_buf: &wgpu::Buffer - The instances buffer to draw the mesh with.
/// * instance_count: u32 - The number of instances in the above buffer.
fn draw_mesh(
&mut self,
engine: &'b RenderEngine,
mesh: &'b Handle<Mesh>,
texture: &'b Handle<Texture>,
instance_buf: &'b wgpu::Buffer,
instance_count: u32
);
/// Draws a mesh to this render pass only using its vertices buffer.
///
/// Arguments:
/// * engine: &RenderEngine - The render engine that will be used to draw this.
/// * mesh: &Handle<Mesh> - A handle to the mesh to be drawn. Only vertices will be used, the indices buffer will be disregarded.
/// * texture: &Handle<Texture> - A handle to the texture to draw the mesh with.
/// * instance_buf: &wgpu::Buffer - The instances buffer to draw the mesh with.
/// * instance_count: u32 - The number of instances in the above buffer.
fn draw_list_mesh(
&mut self,
engine: &'b RenderEngine,
mesh: &'b Handle<Mesh>,
texture: &'b Handle<Texture>,
instance_buf: &'b wgpu::Buffer,
instance_count: u32
);
}
/// An implementation of DrawMesh for wgpu::RenderPass. See documentation for more information.
impl<'a, 'b> DrawMesh<'a, 'b> for wgpu::RenderPass<'a> where 'b: 'a {
fn draw_mesh(
&mut self,
engine: &'b RenderEngine,
mesh: &'b Handle<Mesh>,
texture: &'b Handle<Texture>,
instance_buf: &'b wgpu::Buffer,
instance_count: u32
) {
let texture = engine.texture_cache.get(texture).unwrap();
let mesh = engine.mesh_cache.get(mesh).unwrap();
self.set_bind_group(1, &texture.bind_group, &[]);
self.set_vertex_buffer(0, mesh.vertex_buf.slice(..));
self.set_vertex_buffer(1, instance_buf.slice(..));
self.set_index_buffer(mesh.index_buf.slice(..), wgpu::IndexFormat::Uint16);
self.draw_indexed(0..mesh.num_indices, 0, 0..instance_count);
}
fn prepare_draw(
&mut self,
pipeline: &'b Pipeline,
camera: &'b Camera,
) {
self.set_pipeline(&pipeline.render_pipeline);
self.set_bind_group(0, &camera.bind_group, &[]);
}
fn draw_list_mesh(
&mut self,
engine: &'b RenderEngine,
mesh: &'b Handle<Mesh>,
texture: &'b Handle<Texture>,
instance_buf: &'b wgpu::Buffer,
instance_count: u32
) {
let texture = engine.texture_cache.get(texture).unwrap();
let mesh = engine.mesh_cache.get(mesh).unwrap();
self.set_bind_group(1, &texture.bind_group, &[]);
self.set_vertex_buffer(0, mesh.vertex_buf.slice(..));
self.set_vertex_buffer(1, instance_buf.slice(..));
self.set_index_buffer(mesh.index_buf.slice(..), wgpu::IndexFormat::Uint16);
self.draw(0 .. mesh.num_vertices, 0..instance_count);
}
}