rhachis/
graphics.rs

1//! Code specialised in handling graphics(). Most of this is universally applicable.
2
3use std::ops::{Deref, DerefMut};
4
5use downcast_rs::{impl_downcast, DowncastSync};
6use fxhash::FxHashMap;
7use glam::{Mat4, UVec2};
8use wgpu::{
9    util::{BufferInitDescriptor, DeviceExt},
10    Buffer, CommandEncoder, Device, Queue, RenderPass, Sampler, Surface, SurfaceConfiguration,
11    TextureView,
12};
13use winit::{dpi::PhysicalSize, window::Window};
14
15use crate::{game_data, GameInit};
16
17/// A handler over all core graphics components.
18pub struct Graphics {
19    pub device: Device,
20    pub queue: Queue,
21    pub surface: Surface,
22    pub config: SurfaceConfiguration,
23}
24
25impl Graphics {
26    pub(crate) async unsafe fn new(window: &Window, game_init: &GameInit) -> Self {
27        let size = window.inner_size();
28
29        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
30            backends: wgpu::Backends::all(),
31            dx12_shader_compiler: wgpu::Dx12Compiler::default(),
32        });
33        let surface = unsafe {
34            // SAFETY: The Window continues to exist for the duration of the game.
35            instance.create_surface(&window)
36        }
37        .unwrap();
38        let adapter = instance
39            .request_adapter(&wgpu::RequestAdapterOptions {
40                power_preference: wgpu::PowerPreference::default(),
41                force_fallback_adapter: false,
42                compatible_surface: Some(&surface),
43            })
44            .await
45            .unwrap();
46
47        let (device, queue) = adapter
48            .request_device(
49                &wgpu::DeviceDescriptor {
50                    label: None,
51                    features: game_init.features,
52                    limits: wgpu::Limits::default(),
53                },
54                None,
55            )
56            .await
57            .unwrap();
58
59        let config = wgpu::SurfaceConfiguration {
60            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
61            format: surface
62                .get_capabilities(&adapter)
63                .formats
64                .into_iter()
65                .find(|f| f.describe().srgb)
66                .expect("Could not get compatible surface format"),
67            width: size.width,
68            height: size.height,
69            present_mode: game_init.present_mode,
70            alpha_mode: wgpu::CompositeAlphaMode::Auto,
71            view_formats: vec![],
72        };
73        surface.configure(&device, &config);
74
75        Self {
76            device,
77            queue,
78            surface,
79            config,
80        }
81    }
82
83    pub(crate) fn render(&self, renderer: &mut dyn Renderer) -> Result<(), wgpu::SurfaceError> {
84        let output = self.surface.get_current_texture()?;
85        let view = output
86            .texture
87            .create_view(&wgpu::TextureViewDescriptor::default());
88
89        let mut encoder = self
90            .device
91            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
92
93        {
94            renderer.render(&view, &mut encoder);
95        }
96
97        self.queue.submit(std::iter::once(encoder.finish()));
98        output.present();
99
100        Ok(())
101    }
102
103    pub(crate) fn resize(&mut self, size: UVec2) {
104        let size = PhysicalSize::new(size.x, size.y);
105        self.config.width = size.width;
106        self.config.height = size.height;
107        self.surface.configure(&self.device, &self.config);
108    }
109}
110
111#[allow(unused)]
112/// This trait must be implemented on all renderers. It exposes API for rendering a frame.
113pub trait Renderer: DowncastSync {
114    /// This is called every frame once the renderpass has been created.
115    fn render(&mut self, view: &TextureView, encoder: &mut CommandEncoder) {}
116    /// This is called when a render pass has already been created, especially as a result of
117    /// being a child renderer (eg a `UiRenderer` in a `SimpleRenderer`).
118    fn render_child<'a, 'b: 'a>(&'b self, render_pass: &mut RenderPass<'a>) {}
119    /// This is called every frame after the game updates. This is for any state that the renderer
120    /// itself will have to maintain.
121    fn update(&mut self) {}
122    /// This is called when the canvas is resized. This is for things such as updating the size of
123    /// a perspective projection aspect ratio.
124    fn resize(&mut self, size: UVec2) {}
125}
126
127impl_downcast!(sync Renderer);
128
129/// A renderer that does nothing, useful for some tests.
130pub struct EmptyRenderer;
131
132impl Renderer for EmptyRenderer {}
133
134#[derive(Debug, Default)]
135pub struct SamplerManager {
136    pub samplers: FxHashMap<SamplerType, Sampler>,
137}
138
139impl SamplerManager {
140    #[must_use]
141    pub fn new() -> Self {
142        Self {
143            samplers: FxHashMap::default(),
144        }
145    }
146
147    pub fn get(&mut self, ty: SamplerType) -> &Sampler {
148        self.samplers.entry(ty).or_insert_with(|| (&ty).into())
149    }
150}
151
152#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
153pub struct SamplerType {
154    pub address_mode: wgpu::AddressMode,
155    pub filter_mode: wgpu::FilterMode,
156}
157
158impl SamplerType {
159    pub const LINEAR: Self = Self {
160        address_mode: wgpu::AddressMode::Repeat,
161        filter_mode: wgpu::FilterMode::Linear,
162    };
163
164    pub const NEAREST: Self = Self {
165        address_mode: wgpu::AddressMode::Repeat,
166        filter_mode: wgpu::FilterMode::Nearest,
167    };
168}
169
170impl Default for SamplerType {
171    fn default() -> Self {
172        Self {
173            address_mode: wgpu::AddressMode::Repeat,
174            filter_mode: wgpu::FilterMode::Linear,
175        }
176    }
177}
178
179impl From<&SamplerType> for Sampler {
180    fn from(value: &SamplerType) -> Self {
181        game_data()
182            .graphics()
183            .device
184            .create_sampler(&wgpu::SamplerDescriptor {
185                address_mode_u: value.address_mode,
186                address_mode_v: value.address_mode,
187                address_mode_w: value.address_mode,
188                mag_filter: value.filter_mode,
189                min_filter: value.filter_mode,
190                // TODO Make border color customisable
191                border_color: match value.address_mode {
192                    wgpu::AddressMode::ClampToBorder => Some(wgpu::SamplerBorderColor::OpaqueBlack),
193                    _ => None,
194                },
195                ..Default::default()
196            })
197    }
198}
199
200#[must_use]
201pub const fn size_to_uvec2(size: PhysicalSize<u32>) -> UVec2 {
202    UVec2::new(size.width, size.height)
203}
204
205// TODO Add capacity to make enough space in one go
206/// A representation of a collection of data that is linked to a buffer and automatically updates
207/// when modified. Mutably dereferencing the struct allows modifications to `values` and stores
208/// when these changes happen so they may be rewritten to the buffer. `T` is the type of the
209/// values being stored, and `U` is the type that it gets converted to before put onto the buffer
210/// (if any).
211pub struct BufferData<T: BufferCompatible> {
212    /// The values stored on the buffer.
213    pub values: Vec<T>,
214    /// The buffer in case direct access is needed.
215    pub buffer: Buffer,
216    /// Records if the buffer needs to be rewritten at another time. Can be modified to either
217    /// avoid putting changes on the buffer or to force rewrite data to a buffer.
218    pub modified: bool,
219    pub buffer_len: u32,
220}
221
222impl<T: BufferCompatible> BufferData<T> {
223    pub fn new(values: Vec<T>, usage: wgpu::BufferUsages) -> Self {
224        let buffer = game_data()
225            .graphics()
226            .device
227            .create_buffer_init(&BufferInitDescriptor {
228                label: None,
229                contents: bytemuck::cast_slice(&values.iter().map(T::as_pod).collect::<Vec<_>>()),
230                usage,
231            });
232
233        Self {
234            buffer_len: values.len() as u32,
235            values,
236            buffer,
237            modified: false,
238        }
239    }
240
241    pub fn update(&mut self) {
242        // If the buffer is a different size a new buffer will be needed to be made
243        if self.modified {
244            if self.needs_new_buffer() {
245                self.buffer =
246                    game_data()
247                        .graphics()
248                        .device
249                        .create_buffer_init(&BufferInitDescriptor {
250                            label: None,
251                            contents: bytemuck::cast_slice(
252                                &self.values.iter().map(T::as_pod).collect::<Vec<_>>(),
253                            ),
254                            usage: self.buffer.usage(),
255                        });
256                self.buffer_len = self.values.len() as u32;
257            } else {
258                game_data().graphics().queue.write_buffer(
259                    &self.buffer,
260                    0,
261                    bytemuck::cast_slice(&self.values.iter().map(T::as_pod).collect::<Vec<_>>()),
262                );
263            }
264            self.modified = false;
265        }
266    }
267
268    /// Returns whether the buffer needs to be recreated due to it not being able to fit all of the
269    /// current data.
270    pub fn needs_new_buffer(&self) -> bool {
271        self.buffer_len < self.values.len() as u32
272    }
273
274    /// Returns the buffer, updating it if needed.
275    pub fn get(&mut self) -> &Buffer {
276        if self.modified {
277            self.update();
278            self.modified = false;
279        }
280        &self.buffer
281    }
282
283    pub fn push(&mut self, value: T) -> usize {
284        self.modified = true;
285        self.values.push(value);
286        self.values.len() - 1
287    }
288
289    pub fn replace(&mut self, values: Vec<T>) {
290        self.modified = true;
291        self.values = values;
292    }
293
294    pub fn remove(&mut self, index: usize) -> T {
295        self.modified = true;
296        self.values.remove(index)
297    }
298
299    pub fn buffer_slice(&self) -> wgpu::BufferSlice {
300        self.buffer.slice(..)
301    }
302
303    /// Returns an instance of `BufferData` with a single default element.
304    pub fn default(usage: wgpu::BufferUsages) -> Self
305    where
306        T: Default,
307    {
308        Self::new(vec![T::default()], usage)
309    }
310}
311
312impl<T: BufferCompatible> Deref for BufferData<T> {
313    type Target = [T];
314
315    fn deref(&self) -> &Self::Target {
316        &self.values
317    }
318}
319
320impl<T: BufferCompatible> DerefMut for BufferData<T> {
321    fn deref_mut(&mut self) -> &mut Self::Target {
322        self.modified = true;
323        &mut self.values
324    }
325}
326
327// TODO Make it BufferCompatible<T> so they can have multiple PodFormats
328/// Used for items stored on a `BufferData`.
329pub trait BufferCompatible {
330    /// The format the type will be converted to for writing to the buffer.
331    type PodFormat: bytemuck::Pod;
332    /// The method to convert the type into its plain-old-data format.
333    fn as_pod(&self) -> Self::PodFormat;
334}
335
336impl<T> BufferCompatible for T
337where
338    T: bytemuck::Pod,
339{
340    type PodFormat = T;
341    fn as_pod(&self) -> Self::PodFormat {
342        *self
343    }
344}
345
346/// Implemented for things that can be stored onto bind groups.
347pub trait Bindable {
348    fn bind_group_layout() -> wgpu::BindGroupLayout;
349}
350
351impl Bindable for Mat4 {
352    fn bind_group_layout() -> wgpu::BindGroupLayout {
353        game_data()
354            .graphics()
355            .device
356            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
357                label: None,
358                entries: &[wgpu::BindGroupLayoutEntry {
359                    binding: 0,
360                    visibility: wgpu::ShaderStages::VERTEX,
361                    ty: wgpu::BindingType::Buffer {
362                        ty: wgpu::BufferBindingType::Uniform,
363                        has_dynamic_offset: false,
364                        min_binding_size: None,
365                    },
366                    count: None,
367                }],
368            })
369    }
370}