diff options
| author | Micha White <botahamec@outlook.com> | 2024-08-15 20:16:32 -0400 |
|---|---|---|
| committer | Micha White <botahamec@outlook.com> | 2024-08-15 20:16:32 -0400 |
| commit | db9aa9f1bf49e8bede384b9ceb1e1fb82b522799 (patch) | |
| tree | 0d60727acf481f59b42ef0f74ed07c16ec562bcf /render/src | |
| parent | f8a80039c74332e2101a177ef3fde31ef2077224 (diff) | |
Delete stuff
Diffstat (limited to 'render/src')
| -rw-r--r-- | render/src/camera.rs | 187 | ||||
| -rw-r--r-- | render/src/config.rs | 198 | ||||
| -rw-r--r-- | render/src/instance.rs | 150 | ||||
| -rw-r--r-- | render/src/lib.rs | 20 | ||||
| -rw-r--r-- | render/src/renderer.rs | 438 | ||||
| -rw-r--r-- | render/src/texture.rs | 127 | ||||
| -rw-r--r-- | render/src/vertex.rs | 39 |
7 files changed, 0 insertions, 1159 deletions
diff --git a/render/src/camera.rs b/render/src/camera.rs deleted file mode 100644 index ecece90..0000000 --- a/render/src/camera.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::mem::size_of; - -use cgmath::{Matrix4, Vector2}; - -#[derive(Debug)] -pub struct Camera { - position: (f32, f32), - zoom: f32, - rotation: f32, - inverse_aspect_ratio: f32, - buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, -} - -type CameraUniform = [[f32; 4]; 4]; - -#[allow(clippy::cast_precision_loss)] -fn inverse_aspect_ratio(width: u32, height: u32) -> f32 { - (height as f32) / (width as f32) -} - -impl Camera { - /// Create a new camera, with a position of (0, 0), and a zoom of 1.0 - pub(crate) fn new( - device: &wgpu::Device, - width: u32, - height: u32, - ) -> (Self, wgpu::BindGroupLayout) { - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Camera Uniform"), - size: size_of::<CameraUniform>() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Camera Bind Group Layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("Camera Bind Group"), - layout: &bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }); - - ( - Self { - position: (0.0, 0.0), - zoom: 1.0, - rotation: 0.0, - inverse_aspect_ratio: inverse_aspect_ratio(width, height), - buffer, - bind_group, - }, - bind_group_layout, - ) - } - - /// Get the camera's current x position - #[must_use] - pub const fn x(&self) -> f32 { - self.position.0 - } - - /// Get the camera's current y position - #[must_use] - pub const fn y(&self) -> f32 { - self.position.1 - } - - /// Get the camera's current zoom - #[must_use] - pub const fn zoom(&self) -> f32 { - self.zoom - } - - /// Get the camera's current rotation, in radians - #[must_use] - pub const fn rotation(&self) -> f32 { - self.rotation - } - - /// Set the position of the camera - pub fn set_position(&mut self, x: f32, y: f32) { - #[cfg(debug_assertions)] - if !(-1000.0..1000.0).contains(&x) || !(-1000.0..1000.0).contains(&y) { - log::warn!( - "The position of the camera is (x: {}, y: {}). \ - Please keep both the x and y positions above -1000 and below 1000 units. \ - Otherwise, everything will look crazy. \ - For an explanation, see https://www.youtube.com/watch?v=Q2OGwnRik24", - x, - y - ); - } - - self.position = (x, y); - } - - /// Set the zoom of the camera - pub fn set_zoom(&mut self, zoom: f32) { - #[cfg(debug_assertions)] - if !(-1000.0..1000.0).contains(&zoom) { - log::warn!( - "The zoom of the camera is {}. \ - Please keep above -1000, and below 1000, or else smooth zoom may be difficult. \ - For an explanation, see https://www.youtube.com/watch?v=Q2OGwnRik24", - zoom - ); - } - - self.zoom = zoom; - } - - /// Set the camera rotation, in radians - pub fn set_rotation(&mut self, rotation: f32) { - self.rotation = rotation % std::f32::consts::TAU; - } - - /// Set the aspect ratio of the camera - pub(crate) fn set_size(&mut self, width: u32, height: u32) { - self.inverse_aspect_ratio = inverse_aspect_ratio(width, height); - } - - /// Create a matrix that can be multiplied by any vector to transform it - /// according to the current camera - #[allow(clippy::wrong_self_convention)] - fn to_matrix(&self) -> CameraUniform { - let cos = self.rotation.cos(); - let sin = self.rotation.sin(); - - let x_axis = Vector2::new(cos, -sin); - let y_axis = Vector2::new(sin, cos); - - let eye = Vector2::new(self.position.0, self.position.1); - let x_dot = -cgmath::dot(x_axis, eye); - let y_dot = -cgmath::dot(y_axis, eye); - - #[rustfmt::skip] - let view_matrix = Matrix4::new( - x_axis.x, y_axis.x, 0.0, 0.0, - x_axis.y, y_axis.y, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - x_dot, y_dot, 0.0, 1.0 - ); - - #[rustfmt::skip] - // TODO implement more scaling coordinate systems - let projection_matrix = Matrix4::new( - self.inverse_aspect_ratio * self.zoom, 0.0, 0.0, 0.0, - 0.0, self.zoom, 0.0, 0.0, - 0.0, 0.0, 1.0 / 256.0, 0.0, - 0.0, 0.0, 0.0, 1.0 - ); - - let transform = projection_matrix * view_matrix; - transform.into() - } - - /// Get the bind group for the camera - pub(crate) const fn bind_group(&self) -> &wgpu::BindGroup { - &self.bind_group - } - - /// Refresh the camera buffer for the next frame - #[profiling::function] - pub(crate) fn refresh(&self, queue: &wgpu::Queue) { - queue.write_buffer( - &self.buffer, - 0 as wgpu::BufferAddress, - bytemuck::cast_slice(&self.to_matrix()), - ); - } -} diff --git a/render/src/config.rs b/render/src/config.rs deleted file mode 100644 index c3cc6b6..0000000 --- a/render/src/config.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::num::NonZeroU32; - -use winit::dpi::{LogicalPosition, LogicalSize}; -use winit::window::{Fullscreen, WindowBuilder}; - -/// Describes how a window may be resized -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -pub struct Resizable { - /// The minimum width of the window, or None if unconstrained - pub min_width: Option<NonZeroU32>, - /// The minimum height of the window, or None if unconstrained - pub min_height: Option<NonZeroU32>, - /// The maximum width of the window, or None if unconstrained - pub max_width: Option<NonZeroU32>, - /// The maximum height of the window, or None if unconstrained - pub max_height: Option<NonZeroU32>, -} - -/// Information about a window, that is not fullscreened -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct WindowInfo { - pub default_x: i32, - pub default_y: i32, - pub resizable: Option<Resizable>, - pub default_maximized: bool, -} - -impl Default for WindowInfo { - fn default() -> Self { - Self { - default_x: 100, - default_y: 100, - resizable: Some(Resizable::default()), - default_maximized: false, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum WindowMode { - Windowed(WindowInfo), - // TODO support choosing a monitor - BorderlessFullscreen, // TODO exclusive fullscreen -} - -impl Default for WindowMode { - fn default() -> Self { - Self::Windowed(WindowInfo::default()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -// TODO window icon -pub struct RenderWindowConfig<'a> { - /// The width of the window, once initialized - pub default_width: NonZeroU32, - /// The height of the window, once initialized - pub default_height: NonZeroU32, - /// The window may be fullscreen - pub mode: WindowMode, - /// The title for the window - pub title: &'a str, - /// If true, a low-power device will be selected as the GPU, if possible - pub low_power: bool, - /// If true, Fifo mode is used to present frames. If false, then Mailbox or - /// Immediate will be used if available. Otherwise, Fifo will be used. - pub vsync: bool, - /// The initial capacity of the instance buffer. The size will increase if - /// it's not large enough. Increasing this value may improve performance - /// towards the beginning, if a lot of instances are being created. For - /// compatibility with older devices, it's recommended to keep this number - /// below 150 thousand. - pub instance_capacity: usize, -} - -impl<'a> Default for RenderWindowConfig<'a> { - fn default() -> Self { - Self { - default_width: NonZeroU32::new(640).unwrap(), - default_height: NonZeroU32::new(480).unwrap(), - mode: WindowMode::default(), - title: "Alligator Game", - low_power: true, - vsync: true, - instance_capacity: 500, - } - } -} - -impl<'a> RenderWindowConfig<'a> { - /// Based on the vsync settings, choose a presentation mode - pub(crate) fn present_mode( - vsync: bool, - supported_modes: &[wgpu::PresentMode], - ) -> wgpu::PresentMode { - if vsync { - wgpu::PresentMode::Fifo - } else if supported_modes.contains(&wgpu::PresentMode::Mailbox) { - wgpu::PresentMode::Mailbox - } else if supported_modes.contains(&wgpu::PresentMode::Immediate) { - wgpu::PresentMode::Immediate - } else { - wgpu::PresentMode::Fifo - } - } - - /// Pick an alpha mode - fn alpha_mode(supported_modes: &[wgpu::CompositeAlphaMode]) -> wgpu::CompositeAlphaMode { - if supported_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) { - wgpu::CompositeAlphaMode::PostMultiplied - } else { - wgpu::CompositeAlphaMode::Auto - } - } - - /// Create a `WindowBuilder` from the configuration given. This window is - /// initially invisible and must later be made visible. - pub(crate) fn to_window(&self) -> WindowBuilder { - // start building the window - let mut builder = WindowBuilder::new() - .with_title(self.title) - .with_visible(false) - .with_inner_size(LogicalSize::new( - self.default_width.get(), - self.default_height.get(), - )); - - match self.mode { - WindowMode::Windowed(window_info) => { - builder = builder.with_maximized(window_info.default_maximized); - - if let Some(resizing_options) = window_info.resizable { - if resizing_options.max_height.is_some() || resizing_options.max_width.is_some() - { - builder = builder.with_max_inner_size(LogicalSize::new( - resizing_options.max_width.unwrap_or(NonZeroU32::MAX).get(), - resizing_options.max_height.unwrap_or(NonZeroU32::MAX).get(), - )); - } - - if resizing_options.min_height.is_some() || resizing_options.min_width.is_some() - { - builder = builder.with_min_inner_size(LogicalSize::new( - resizing_options.min_width.unwrap_or(NonZeroU32::MAX).get(), - resizing_options.min_height.unwrap_or(NonZeroU32::MAX).get(), - )); - } - } else { - builder = builder.with_resizable(false); - } - - // TODO clamp the position to the monitor's size - builder = builder.with_position(LogicalPosition::new( - window_info.default_x, - window_info.default_y, - )); - } - WindowMode::BorderlessFullscreen => { - builder = builder.with_fullscreen(Some(Fullscreen::Borderless(None))); - } - } - - builder - } - - /// Gets a surface configuration out of the config. - pub(crate) fn to_surface_configuration( - &self, - supported_present_modes: &[wgpu::PresentMode], - supported_alpha_modes: &[wgpu::CompositeAlphaMode], - ) -> wgpu::SurfaceConfiguration { - let present_mode = Self::present_mode(self.vsync, supported_present_modes); - let alpha_mode = Self::alpha_mode(supported_alpha_modes); - - // configuration for the surface - wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8Unorm, - width: self.default_width.get(), - height: self.default_height.get(), - alpha_mode, - present_mode, - view_formats: vec![ - wgpu::TextureFormat::Bgra8Unorm, - wgpu::TextureFormat::Bgra8UnormSrgb, - ], - } - } - - /// Get the power preference - pub(crate) const fn power_preference(&self) -> wgpu::PowerPreference { - if self.low_power { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - } - } -} diff --git a/render/src/instance.rs b/render/src/instance.rs deleted file mode 100644 index 15317c7..0000000 --- a/render/src/instance.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::mem::size_of; - -use bytemuck::{Pod, Zeroable}; - -/// The ID for an instance -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct InstanceId(usize); - -/// A sprite, that can be used by the alligator shader -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)] -pub struct Instance { - /// Position on the screen - pub position: [f32; 2], - /// Relative size - pub size: [f32; 2], - /// The location of the texture in the texture atlas - pub texture_coordinates: [f32; 2], - /// The size of the sprite's texture - pub texture_size: [f32; 2], -} - -impl Default for Instance { - fn default() -> Self { - Self { - position: [0.0; 2], - size: [1.0; 2], - texture_coordinates: [0.0; 2], - texture_size: [1.0; 2], - } - } -} - -impl Instance { - // whenever this is updated, please also update `sprite.wgsl` - const ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![ - 1 => Float32x2, 2 => Float32x2, 3 => Float32x2, 4 => Float32x2, - 5 => Uint32, 6 => Float32, 7 => Float32 - ]; - - pub(crate) fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - // make sure these two don't conflict - debug_assert_eq!( - Self::ATTRIBUTES[0].shader_location as usize, - crate::Vertex::ATTRIBUTES.len() - ); - wgpu::VertexBufferLayout { - array_stride: size_of::<Self>() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &Self::ATTRIBUTES, - } - } -} - -/// A buffer of sprites, for both CPU and GPU memory -pub struct InstanceBuffer { - instances: Vec<Instance>, - instance_buffer: wgpu::Buffer, - instance_buffer_size: usize, -} - -fn create_buffer(device: &wgpu::Device, instances: &Vec<Instance>) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Sprite Instance Buffer"), - size: (instances.capacity() * size_of::<Instance>()) as wgpu::BufferAddress, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }) -} - -impl InstanceBuffer { - /// Create a new buffer with the given capacity - pub(crate) fn new(device: &wgpu::Device, capacity: usize) -> Self { - let instances = Vec::with_capacity(capacity); - let instance_buffer_size = instances.capacity(); - let instance_buffer = create_buffer(device, &instances); - - Self { - instances, - instance_buffer, - instance_buffer_size, - } - } - - /// The number of sprites - pub fn len(&self) -> u32 { - self.instances - .len() - .try_into() - .expect("expected less than 3 billion instances") - } - - /// Returns `true` if there are no sprites - pub fn is_empty(&self) -> bool { - self.instances.is_empty() - } - - /// The capacity of the buffer - pub const fn buffer_size(&self) -> usize { - self.instance_buffer_size - } - - /// Get a slice containing the entire buffer - pub(crate) fn buffer_slice(&self) -> wgpu::BufferSlice { - self.instance_buffer.slice(..) - } - - /// Add a new sprite. The new sprite's `InstanceId` is returned. This ID - /// becomes invalid if the buffer is cleared. - pub fn push_instance(&mut self, instance: Instance) -> InstanceId { - let index = self.instances.len(); - self.instances.push(instance); - InstanceId(index) - } - - /// Get a specific instance - pub fn get_instance(&self, id: InstanceId) -> Option<&Instance> { - self.instances.get(id.0) - } - - /// Get a mutable reference to a specific sprite - pub fn get_instance_mut(&mut self, id: InstanceId) -> Option<&mut Instance> { - self.instances.get_mut(id.0) - } - - /// Clear the instance buffer. This invalidates all `InstanceId`'s - pub fn clear(&mut self) { - self.instances.clear(); - } - - /// Increase the capacity of the buffer - fn expand_buffer(&mut self, device: &wgpu::Device) { - self.instance_buffer_size = self.instances.capacity(); - self.instance_buffer = create_buffer(device, &self.instances); - } - - /// Fill the GPU buffer with the sprites in the CPU buffer. - #[profiling::function] - pub(crate) fn fill_buffer(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) { - if self.instances.len() > self.instance_buffer_size { - self.expand_buffer(device); - } - - queue.write_buffer( - &self.instance_buffer, - 0 as wgpu::BufferAddress, - bytemuck::cast_slice(&self.instances), - ); - } -} diff --git a/render/src/lib.rs b/render/src/lib.rs deleted file mode 100644 index 6f96fc2..0000000 --- a/render/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![feature(type_alias_impl_trait)] -#![warn(clippy::pedantic)] -#![warn(clippy::nursery)] -#![allow(clippy::module_name_repetitions)] - -mod camera; -pub mod config; -pub mod instance; -pub mod renderer; -mod texture; -mod vertex; - -pub(crate) use camera::Camera; -pub use config::*; -pub use instance::Instance; -pub(crate) use instance::InstanceBuffer; -pub use instance::InstanceId; -pub use renderer::Renderer; -pub(crate) use texture::TextureAtlas; -pub(crate) use vertex::Vertex; diff --git a/render/src/renderer.rs b/render/src/renderer.rs deleted file mode 100644 index 4b4f60d..0000000 --- a/render/src/renderer.rs +++ /dev/null @@ -1,438 +0,0 @@ -use std::num::NonZeroU32; -use std::{convert::TryInto, sync::Arc}; - -use crate::{ - vertex::SQUARE, Camera, Instance, InstanceBuffer, RenderWindowConfig, TextureAtlas, Vertex, -}; -use pollster::FutureExt; -use thiserror::Error; -use wgpu::{include_wgsl, util::DeviceExt}; -use winit::{ - dpi::PhysicalSize, - error::OsError, - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::Window, -}; - -/// No device could be found which supports the given surface -#[derive(Clone, Copy, Debug, Error)] -#[error("No GPU could be found on this machine")] -pub struct NoGpuError { - /// Prevents this type from being constructed - _priv: (), -} - -impl NoGpuError { - /// Create a new error - const fn new() -> Self { - Self { _priv: () } - } -} - -/// No device could be found which supports the given surface -#[derive(Clone, Copy, Debug, Error)] -#[error("A WebGPU or WebGL context could not be obtained")] -pub struct NoWebContextError { - /// Prevents this type from being constructed - _priv: (), -} - -impl NoWebContextError { - /// Create a new error - const fn new() -> Self { - Self { _priv: () } - } -} - -#[derive(Debug, Error)] -pub enum NewRendererError { - #[error(transparent)] - NoGpu(#[from] NoGpuError), - #[error(transparent)] - NoWebContext(#[from] NoWebContextError), - #[error(transparent)] - // TODO better error - WindowInitError(#[from] OsError), -} - -// TODO make this Debug -pub struct Renderer { - // TODO move some of this data elsewhere - surface: wgpu::Surface, - surface_config: wgpu::SurfaceConfiguration, - supported_present_modes: Box<[wgpu::PresentMode]>, - device: wgpu::Device, - queue: wgpu::Queue, - render_pipeline: wgpu::RenderPipeline, - square_vertex_buffer: wgpu::Buffer, - square_vertices: u32, - instances: InstanceBuffer, - camera: Camera, - textures: TextureAtlas, - event_loop: Option<EventLoop<()>>, - window: Window, -} - -fn get_adapter( - instance: &wgpu::Instance, - surface: &wgpu::Surface, - power_preference: wgpu::PowerPreference, -) -> Result<wgpu::Adapter, NoGpuError> { - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference, - compatible_surface: Some(surface), - force_fallback_adapter: false, - }) - .block_on(); // TODO this takes too long - - let adapter = adapter.or_else(|| { - instance - .enumerate_adapters(wgpu::Backends::VULKAN) - .find(|adapter| !surface.get_capabilities(adapter).formats.is_empty()) - }); - - adapter.ok_or(NoGpuError::new()) -} - -fn sprite_render_pipeline( - device: &wgpu::Device, - texture_format: wgpu::TextureFormat, - render_pipeline_layout: &wgpu::PipelineLayout, -) -> wgpu::RenderPipeline { - let shader = device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl")); - - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Sprite Render Pipeline"), - layout: Some(render_pipeline_layout), - // information about the vertex shader - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc(), Instance::desc()], - }, - // information about the fragment shader - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: texture_format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - // don't render the back of a sprite - cull_mode: Some(wgpu::Face::Back), - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }) -} - -impl Renderer { - /// Initializes the renderer - /// - /// # Errors - /// - /// Returns a [`NoGpu`] error if no device could be detected that can - /// display to the window - /// - /// # Panics - /// - /// This function **must** be called on the main thread, or else it may - /// panic on some platforms. - // TODO make it possible to use without a window (ie, use a bitmap in memory as a surface) - // TODO this function needs to be smaller - pub fn new(config: &RenderWindowConfig) -> Result<Self, NewRendererError> { - // build the window - let event_loop = EventLoop::new(); - let window = config.to_window().build(&event_loop)?; - let event_loop = Some(event_loop); - - // the instance's main purpose is to create an adapter and a surface - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); - - // the surface is the part of the screen we'll draw to - let surface = - unsafe { instance.create_surface(&window) }.map_err(|_| NoWebContextError::new())?; - - let power_preference = config.power_preference(); - - // the adapter is the handle to the GPU - let adapter = get_adapter(&instance, &surface, power_preference)?; - - // gets a connection to the device, as well as a handle to its command queue - // the options chosen here ensure that this is guaranteed to not panic - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - features: wgpu::Features::empty(), - limits: wgpu::Limits { - max_buffer_size: adapter.limits().max_buffer_size, - max_texture_dimension_2d: adapter.limits().max_texture_dimension_2d, - ..Default::default() - }, - ..Default::default() - }, - None, - ) - .block_on() - .expect("there was no device with the selected features"); - - // configuration for the surface - let capabilities = surface.get_capabilities(&adapter); - let supported_present_modes = capabilities.present_modes.into_boxed_slice(); - let supported_alpha_modes = capabilities.alpha_modes.into_boxed_slice(); - let surface_config = - config.to_surface_configuration(&supported_present_modes, &supported_alpha_modes); - surface.configure(&device, &surface_config); - - // create the camera - let width = window.inner_size().width; - let height = window.inner_size().height; - let (camera, camera_bind_group_layout) = Camera::new(&device, width, height); - - // the vertex buffer used for rendering squares - let square_vertices = SQUARE - .len() - .try_into() - .expect("expected fewer than 3 billion vertices in a square"); - let square_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Square Vertex Buffer"), - contents: bytemuck::cast_slice(&SQUARE), - usage: wgpu::BufferUsages::VERTEX, - }); - - // create the instance buffer - let instances = InstanceBuffer::new(&device, config.instance_capacity); - - // TODO make this configurable - let (textures, texture_layout) = TextureAtlas::new( - &device, - window.inner_size().width, - window.inner_size().height, - ); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Sprite Render Pipeline Layout"), - bind_group_layouts: &[&camera_bind_group_layout, &texture_layout], - push_constant_ranges: &[], - }); - - // set up a pipeline for sprite rendering - let render_pipeline = - sprite_render_pipeline(&device, surface_config.format, &render_pipeline_layout); - - Ok(Self { - surface, - surface_config, - supported_present_modes, - device, - queue, - render_pipeline, - square_vertex_buffer, - square_vertices, - instances, - camera, - textures, - event_loop, - window, - }) - } - - /// Reconfigure the surface - fn reconfigure(&mut self) { - self.surface.configure(&self.device, &self.surface_config); - } - - /// Resize just the renderer. The window will remain unchanged - fn resize_renderer(&mut self, size: PhysicalSize<u32>) { - if size.width == 0 || size.height == 0 { - log::error!("The window was somehow set to a size of zero"); - return; - } - - self.surface_config.height = size.height; - self.surface_config.width = size.width; - self.camera.set_size(size.width, size.height); - self.reconfigure(); - } - - /// Set the physical window and renderer size - pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) { - let size = PhysicalSize::new(width.get(), height.get()); - self.window.set_inner_size(size); - self.resize_renderer(size); - } - - /// Set vsync on or off. See `[RenderWindowConfig::present_mode]` for more details. - pub fn set_vsync(&mut self, vsync: bool) { - self.surface_config.present_mode = - RenderWindowConfig::present_mode(vsync, &self.supported_present_modes); - self.reconfigure(); - } - - /// Set the window's title - pub fn set_title(&mut self, title: &str) { - self.window.set_title(title); - } - - /// The reference buffer - pub const fn instances(&self) -> &InstanceBuffer { - &self.instances - } - - /// The reference buffer - pub fn instances_mut(&mut self) -> &mut InstanceBuffer { - &mut self.instances - } - - /// Get the camera information - pub const fn camera(&self) -> &Camera { - &self.camera - } - - /// Get a mutable reference to the camera - pub fn camera_mut(&mut self) -> &mut Camera { - &mut self.camera - } - - /// Get a reference to the texture atlas - pub const fn texture_atlas(&self) -> &TextureAtlas { - &self.textures - } - - /// Get a mutable reference to the texture atlas - pub fn textures_mut(&mut self) -> &mut TextureAtlas { - &mut self.textures - } - - /// Renders a new frame to the window - /// - /// # Errors - /// - /// A number of problems could occur here. A timeout could occur while - /// trying to acquire the next frame. There may also be no more memory left - /// that can be used for the new frame. - // TODO this needs to be smaller - // TODO don't return wgpu errors - #[profiling::function] - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - // this will allow us to send commands to the gpu - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - let num_instances = self.instances.len(); - self.instances.fill_buffer(&self.device, &self.queue); - self.camera.refresh(&self.queue); - self.textures.fill_textures(&self.queue); - - // the new texture we can render to - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - { - profiling::scope!("encode render pass"); - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: wgpu::StoreOp::Discard, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, self.camera.bind_group(), &[]); - render_pass.set_bind_group(1, self.textures.bind_group(), &[]); - render_pass.set_vertex_buffer(0, self.square_vertex_buffer.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.buffer_slice()); - render_pass.draw(0..self.square_vertices, 0..num_instances); - } - // the encoder can't finish building the command buffer until the - // render pass is dropped - - // submit the command buffer to the GPU - profiling::scope!("submit render"); - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } - - /// Take the event loop out of the Renderer, without moving it - /// - /// # Panics - /// - /// This method must only be called once - // TODO This is a quick fix to get the event loop inside the renderer. - // In the future, we should make a separate struct that contains the - // renderer and the event loop, which we move the event loop out of - // while still being able to move the renderer. - fn event_loop(&mut self) -> EventLoop<()> { - self.event_loop.take().unwrap() - } - - /// Run the renderer indefinitely - // TODO this needs to be smaller - pub fn run<F: FnMut(&mut Self) + 'static>(mut self, mut f: F) -> ! { - self.window.set_visible(true); - let event_loop = self.event_loop(); - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { window_id, event } => { - if window_id == self.window.id() { - match event { - WindowEvent::Resized(size) => self.resize_renderer(size), - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::ExitWithCode(0); - } - _ => (), - } - } - } - Event::MainEventsCleared => { - f(&mut self); - - // a memory leak occurs if we render a zero-size window, - // along with a `SurfaceError::Outdated`. I don't know why that - // happens, but let's make wgpu happy. - // https://github.com/gfx-rs/wgpu/issues/1783#issuecomment-1328463201 - if self.window.inner_size().width != 0 && self.window.inner_size().height != 0 { - match self.render() { - Ok(()) => {} - // reconfigure the surface if it's been lost - Err(wgpu::SurfaceError::Lost) => { - self.reconfigure(); - } - // if we ran out of memory, then we'll die - Err(wgpu::SurfaceError::OutOfMemory) => { - *control_flow = ControlFlow::ExitWithCode(1); - } - // otherwise, we'll just log the error - Err(e) => log::error!("{}", e), - } - } else { - *control_flow = ControlFlow::Wait; - } - profiling::finish_frame!(); - } - _ => {} - }); - } -} diff --git a/render/src/texture.rs b/render/src/texture.rs deleted file mode 100644 index 90d23d1..0000000 --- a/render/src/texture.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::error::Error; - -use image::{EncodableLayout, RgbaImage}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum TextureError { - #[error("Unexpected Error (this is a bug in alligator_render): {}", .0)] - Unexpected(#[source] Box<dyn Error>), -} - -/// Simpler constructor for a wgpu extent3d -const fn extent_3d(width: u32, height: u32) -> wgpu::Extent3d { - wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - } -} - -/// A texture atlas, usable by the renderer -// TODO make these resizable -#[derive(Debug)] -pub struct TextureAtlas { - diffuse_texture: wgpu::Texture, - diffuse_bind_group: wgpu::BindGroup, - image: RgbaImage, -} - -impl TextureAtlas { - /// Creates a new texture atlas, with the given size - // TODO this is still too large - pub fn new(device: &wgpu::Device, width: u32, height: u32) -> (Self, wgpu::BindGroupLayout) { - let atlas_size = extent_3d(width, height); - let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("Diffuse Texture"), - size: atlas_size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], - }); - - // TODO I don't think this refreshes anything - let diffuse_texture_view = - diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Texture Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }); - - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("Diffuse Bind Group"), - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_sampler), - }, - ], - }); - - ( - Self { - diffuse_texture, - diffuse_bind_group, - image: RgbaImage::from_raw( - width, - height, - vec![0; 4 * width as usize * height as usize], - ) - .unwrap(), - }, - texture_bind_group_layout, - ) - } - - /// get the bind group for the texture - pub(crate) const fn bind_group(&self) -> &wgpu::BindGroup { - &self.diffuse_bind_group - } - - pub(crate) fn fill_textures(&self, queue: &wgpu::Queue) { - queue.write_texture( - wgpu::ImageCopyTexture { - texture: &self.diffuse_texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - self.image.as_bytes(), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(self.image.width() * 4), - rows_per_image: Some(self.image.height()), - }, - extent_3d(self.image.width(), self.image.height()), - ); - } -} diff --git a/render/src/vertex.rs b/render/src/vertex.rs deleted file mode 100644 index 570eec4..0000000 --- a/render/src/vertex.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::mem::size_of; - -use bytemuck::{Pod, Zeroable}; - -/// The vertices needed to form a square -pub const SQUARE: [Vertex; 6] = [ - Vertex::new(-0.5, -0.5), - Vertex::new(0.5, -0.5), - Vertex::new(-0.5, 0.5), - Vertex::new(0.5, 0.5), - Vertex::new(-0.5, 0.5), - Vertex::new(0.5, -0.5), -]; - -/// A vertex that is usable by the alligator shader -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)] -pub struct Vertex { - position: [f32; 2], -} - -impl Vertex { - // whenever this is updated, please also update `sprite.wgsl` - pub(crate) const ATTRIBUTES: [wgpu::VertexAttribute; 1] = - wgpu::vertex_attr_array![0 => Float32x2]; - - /// Create a new vertex - const fn new(x: f32, y: f32) -> Self { - Self { position: [x, y] } - } - - pub(crate) const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: size_of::<Self>() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBUTES, - } - } -} |
