diff options
Diffstat (limited to 'stockton-render')
-rw-r--r-- | stockton-render/src/culling.rs | 90 | ||||
-rw-r--r-- | stockton-render/src/draw/buffer.rs | 373 | ||||
-rw-r--r-- | stockton-render/src/draw/camera.rs | 339 | ||||
-rw-r--r-- | stockton-render/src/draw/context.rs | 1273 | ||||
-rw-r--r-- | stockton-render/src/draw/macros.rs | 10 | ||||
-rw-r--r-- | stockton-render/src/draw/mod.rs | 4 | ||||
-rw-r--r-- | stockton-render/src/draw/target.rs | 788 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/chunk.rs | 265 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/image.rs | 655 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/loader.rs | 286 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/mod.rs | 6 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/resolver.rs | 47 | ||||
-rw-r--r-- | stockton-render/src/error.rs | 57 | ||||
-rw-r--r-- | stockton-render/src/lib.rs | 67 | ||||
-rw-r--r-- | stockton-render/src/types.rs | 2 |
15 files changed, 2280 insertions, 1982 deletions
diff --git a/stockton-render/src/culling.rs b/stockton-render/src/culling.rs index 8240701..dbe5a1e 100644 --- a/stockton-render/src/culling.rs +++ b/stockton-render/src/culling.rs @@ -20,59 +20,61 @@ use stockton_levels::prelude::*; use stockton_levels::traits::tree::{BSPNode, BSPNodeValue}; use stockton_types::Vector3; - /// Get the visible faces according to visdata and frustum culling // TODO: Write this. For now, just render all faces pub fn get_visible_faces<X: CoordSystem, T: MinBSPFeatures<X>>(pos: Vector3, file: &T) -> Vec<u32> { - let vis_cluster = get_cluster_id(pos, file); - - if (vis_cluster & 0x80000000) != 0 { // Negative = Invalid camera position - return vec![]; - } + let vis_cluster = get_cluster_id(pos, file); + + if (vis_cluster & 0x80000000) != 0 { + // Negative = Invalid camera position + return vec![]; + } - let mut visible = Vec::with_capacity(file.faces_len() as usize); - walk_bsp_tree(file.get_bsp_root(), vis_cluster, &mut visible, file); + let mut visible = Vec::with_capacity(file.faces_len() as usize); + walk_bsp_tree(file.get_bsp_root(), vis_cluster, &mut visible, file); - return visible; + visible } -pub fn walk_bsp_tree<X: CoordSystem, T: MinBSPFeatures<X>>(node: &BSPNode, vis_cluster: u32, visible_faces: &mut Vec<u32>, file: &T) -> () { - if let BSPNodeValue::Children(front, back) = &node.value { - walk_bsp_tree(back, vis_cluster, visible_faces, file); - walk_bsp_tree(front, vis_cluster, visible_faces, file); - } else if let BSPNodeValue::Leaf(leaf) = &node.value { - if (leaf.cluster_id & 0x80000000) != 0 { // Negative means invalid leaf - return; - } else if file.cluster_visible_from(vis_cluster, leaf.cluster_id) { - for face_idx in leaf.faces_idx.iter() { - // TODO: Culling or something - visible_faces.push(*face_idx); - } - } - } +pub fn walk_bsp_tree<X: CoordSystem, T: MinBSPFeatures<X>>( + node: &BSPNode, + vis_cluster: u32, + visible_faces: &mut Vec<u32>, + file: &T, +) { + if let BSPNodeValue::Children(front, back) = &node.value { + walk_bsp_tree(back, vis_cluster, visible_faces, file); + walk_bsp_tree(front, vis_cluster, visible_faces, file); + } else if let BSPNodeValue::Leaf(leaf) = &node.value { + if (leaf.cluster_id & 0x80000000) != 0 { + // Negative means invalid leaf + return; + } else if file.cluster_visible_from(vis_cluster, leaf.cluster_id) { + for face_idx in leaf.faces_idx.iter() { + // TODO: Culling or something + visible_faces.push(*face_idx); + } + } + } } -/// Get the viscluster pos lies in +/// Get the viscluster pos lies in fn get_cluster_id<X: CoordSystem, T: MinBSPFeatures<X>>(pos: Vector3, file: &T) -> u32 { - let mut node = file.get_bsp_root(); - loop { - if let BSPNodeValue::Children(front, back) = &node.value { - let plane = file.get_plane(node.plane_idx); - let dist = plane.normal.dot(&pos) - plane.dist; + let mut node = file.get_bsp_root(); + while let BSPNodeValue::Children(front, back) = &node.value { + let plane = file.get_plane(node.plane_idx); + let dist = plane.normal.dot(&pos) - plane.dist; - if dist >= 0.0 { - node = front; - } else { - node = back; - } - } else { - break; - } - } + if dist >= 0.0 { + node = front; + } else { + node = back; + } + } - if let BSPNodeValue::Leaf(leaf) = &node.value { - leaf.cluster_id - } else { - panic!("should have had a leaf but didn't"); - } -}
\ No newline at end of file + if let BSPNodeValue::Leaf(leaf) = &node.value { + leaf.cluster_id + } else { + panic!("should have had a leaf but didn't"); + } +} diff --git a/stockton-render/src/draw/buffer.rs b/stockton-render/src/draw/buffer.rs index cbe56f4..9b3e726 100644 --- a/stockton-render/src/draw/buffer.rs +++ b/stockton-render/src/draw/buffer.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2019 Oscar Shrimpton +// Copyright (C) 2019 Oscar Shrimpton // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free @@ -13,17 +13,17 @@ // You should have received a copy of the GNU General Public License along // with this program. If not, see <http://www.gnu.org/licenses/>. +use core::mem::{size_of, ManuallyDrop}; +use std::convert::TryInto; use std::iter::once; use std::ops::{Index, IndexMut}; -use std::convert::TryInto; -use core::mem::{ManuallyDrop, size_of}; use hal::prelude::*; use hal::{ - MemoryTypeId, - buffer::Usage, - memory::{Properties, Segment}, - queue::Submission + buffer::Usage, + memory::{Properties, Segment}, + queue::Submission, + MemoryTypeId, }; use crate::error::CreationError; @@ -31,193 +31,224 @@ use crate::types::*; /// Create a buffer of the given specifications, allocating more device memory. // TODO: Use a different memory allocator? -pub(crate) fn create_buffer(device: &mut Device, - adapter: &Adapter, - usage: Usage, - properties: Properties, - size: u64) -> Result<(Buffer, Memory), CreationError> { - let mut buffer = unsafe { device - .create_buffer(size, usage) } - .map_err(|e| CreationError::BufferError (e))?; - - let requirements = unsafe { device.get_buffer_requirements(&buffer) }; - let memory_type_id = adapter.physical_device - .memory_properties().memory_types - .iter().enumerate() - .find(|&(id, memory_type)| { - requirements.type_mask & (1 << id) != 0 && memory_type.properties.contains(properties) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or(CreationError::BufferNoMemory)?; - - let memory = unsafe {device - .allocate_memory(memory_type_id, requirements.size) } - .map_err(|_| CreationError::OutOfMemoryError)?; - - unsafe { device - .bind_buffer_memory(&memory, 0, &mut buffer) } - .map_err(|_| CreationError::BufferNoMemory)?; - - Ok((buffer, memory)) +pub(crate) fn create_buffer( + device: &mut Device, + adapter: &Adapter, + usage: Usage, + properties: Properties, + size: u64, +) -> Result<(Buffer, Memory), CreationError> { + let mut buffer = + unsafe { device.create_buffer(size, usage) }.map_err(CreationError::BufferError)?; + + let requirements = unsafe { device.get_buffer_requirements(&buffer) }; + let memory_type_id = adapter + .physical_device + .memory_properties() + .memory_types + .iter() + .enumerate() + .find(|&(id, memory_type)| { + requirements.type_mask & (1 << id) != 0 && memory_type.properties.contains(properties) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or(CreationError::BufferNoMemory)?; + + let memory = unsafe { device.allocate_memory(memory_type_id, requirements.size) } + .map_err(|_| CreationError::OutOfMemoryError)?; + + unsafe { device.bind_buffer_memory(&memory, 0, &mut buffer) } + .map_err(|_| CreationError::BufferNoMemory)?; + + Ok((buffer, memory)) } /// A buffer that can be modified by the CPU pub trait ModifiableBuffer: IndexMut<usize> { - /// Get a handle to the underlying GPU buffer - fn get_buffer<'a>(&'a mut self) -> &'a Buffer; - - /// Commit all changes to GPU memory, returning a handle to the GPU buffer - fn commit<'a>(&'a mut self, device: &Device, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool) -> &'a Buffer; + /// Get a handle to the underlying GPU buffer + fn get_buffer(&mut self) -> &Buffer; + + /// Commit all changes to GPU memory, returning a handle to the GPU buffer + fn commit<'a>( + &'a mut self, + device: &Device, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + ) -> &'a Buffer; } /// A GPU buffer that is written to using a staging buffer pub struct StagedBuffer<'a, T: Sized> { - /// CPU-visible buffer - staged_buffer: ManuallyDrop<Buffer>, + /// CPU-visible buffer + staged_buffer: ManuallyDrop<Buffer>, - /// CPU-visible memory - staged_memory: ManuallyDrop<Memory>, + /// CPU-visible memory + staged_memory: ManuallyDrop<Memory>, - /// GPU Buffer - buffer: ManuallyDrop<Buffer>, + /// GPU Buffer + buffer: ManuallyDrop<Buffer>, - /// GPU Memory - memory: ManuallyDrop<Memory>, + /// GPU Memory + memory: ManuallyDrop<Memory>, - /// Where staged buffer is mapped in CPU memory - staged_mapped_memory: &'a mut [T], + /// Where staged buffer is mapped in CPU memory + staged_mapped_memory: &'a mut [T], - /// If staged memory has been changed since last `commit` - staged_is_dirty: bool, + /// If staged memory has been changed since last `commit` + staged_is_dirty: bool, - /// The highest index in the buffer that's been written to. - pub highest_used: usize + /// The highest index in the buffer that's been written to. + pub highest_used: usize, } - impl<'a, T: Sized> StagedBuffer<'a, T> { - /// size is the size in T - pub fn new(device: &mut Device, adapter: &Adapter, usage: Usage, size: u64) -> Result<Self, CreationError> { - // Convert size to bytes - let size_bytes = size * size_of::<T>() as u64; - - // Get CPU-visible buffer - let (staged_buffer, staged_memory) = create_buffer(device, adapter, Usage::TRANSFER_SRC, Properties::CPU_VISIBLE, size_bytes)?; - - // Get GPU Buffer - let (buffer, memory) = create_buffer(device, adapter, Usage::TRANSFER_DST | usage, Properties::DEVICE_LOCAL, size_bytes)?; - - // Map it somewhere and get a slice to that memory - let staged_mapped_memory = unsafe { - let ptr = device.map_memory(&staged_memory, Segment::ALL).unwrap(); // TODO - - std::slice::from_raw_parts_mut(ptr as *mut T, size.try_into().unwrap()) - }; - - Ok(StagedBuffer { - staged_buffer: ManuallyDrop::new(staged_buffer), - staged_memory: ManuallyDrop::new(staged_memory), - buffer: ManuallyDrop::new(buffer), - memory: ManuallyDrop::new(memory), - staged_mapped_memory, - staged_is_dirty: false, - highest_used: 0 - }) - } - - /// Call this before dropping - pub(crate) fn deactivate(mut self, device: &mut Device) { - unsafe { - device.unmap_memory(&self.staged_memory); - - device.free_memory(ManuallyDrop::take(&mut self.staged_memory)); - device.destroy_buffer(ManuallyDrop::take(&mut self.staged_buffer)); - - device.free_memory(ManuallyDrop::take(&mut self.memory)); - device.destroy_buffer(ManuallyDrop::take(&mut self.buffer)); - }; - } + /// size is the size in T + pub fn new( + device: &mut Device, + adapter: &Adapter, + usage: Usage, + size: u64, + ) -> Result<Self, CreationError> { + // Convert size to bytes + let size_bytes = size * size_of::<T>() as u64; + + // Get CPU-visible buffer + let (staged_buffer, staged_memory) = create_buffer( + device, + adapter, + Usage::TRANSFER_SRC, + Properties::CPU_VISIBLE, + size_bytes, + )?; + + // Get GPU Buffer + let (buffer, memory) = create_buffer( + device, + adapter, + Usage::TRANSFER_DST | usage, + Properties::DEVICE_LOCAL, + size_bytes, + )?; + + // Map it somewhere and get a slice to that memory + let staged_mapped_memory = unsafe { + let ptr = device.map_memory(&staged_memory, Segment::ALL).unwrap(); // TODO + + std::slice::from_raw_parts_mut(ptr as *mut T, size.try_into().unwrap()) + }; + + Ok(StagedBuffer { + staged_buffer: ManuallyDrop::new(staged_buffer), + staged_memory: ManuallyDrop::new(staged_memory), + buffer: ManuallyDrop::new(buffer), + memory: ManuallyDrop::new(memory), + staged_mapped_memory, + staged_is_dirty: false, + highest_used: 0, + }) + } + + /// Call this before dropping + pub(crate) fn deactivate(mut self, device: &mut Device) { + unsafe { + device.unmap_memory(&self.staged_memory); + + device.free_memory(ManuallyDrop::take(&mut self.staged_memory)); + device.destroy_buffer(ManuallyDrop::take(&mut self.staged_buffer)); + + device.free_memory(ManuallyDrop::take(&mut self.memory)); + device.destroy_buffer(ManuallyDrop::take(&mut self.buffer)); + }; + } } -impl <'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> { - fn get_buffer<'b>(&'b mut self) -> &'b Buffer { - &self.buffer - } - - fn commit<'b>(&'b mut self, device: &Device, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool) -> &'b Buffer { - // Only commit if there's changes to commit. - if self.staged_is_dirty { - - // Flush mapped memory to ensure the staged buffer is filled - unsafe { - use std::ops::Deref; - device.flush_mapped_memory_ranges(once((self.staged_memory.deref(), Segment::ALL))).unwrap(); - } - - // Copy from staged to buffer - let buf = unsafe { - use hal::command::{CommandBufferFlags, BufferCopy}; - // Get a command buffer - let mut buf = command_pool.allocate_one(hal::command::Level::Primary); - - // Put in our copy command - buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - buf.copy_buffer(&self.staged_buffer, &self.buffer, &[ - BufferCopy { - src: 0, - dst: 0, - size: ((self.highest_used + 1) * size_of::<T>()) as u64 - } - ]); - buf.finish(); - - buf - }; - - // Submit it and wait for completion - // TODO: We could use more semaphores or something? - // TODO: Better error handling - unsafe { - let copy_finished = device.create_fence(false).unwrap(); - command_queue.submit::<_, _, Semaphore, _, _>(Submission { - command_buffers: &[&buf], - wait_semaphores: std::iter::empty::<_>(), - signal_semaphores: std::iter::empty::<_>() - }, Some(©_finished)); - - device - .wait_for_fence(©_finished, core::u64::MAX).unwrap(); - - // Destroy temporary resources - device.destroy_fence(copy_finished); - command_pool.free(once(buf)); - } - - self.staged_is_dirty = false; - } - - &self.buffer - } +impl<'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> { + fn get_buffer(&mut self) -> &Buffer { + &self.buffer + } + + fn commit<'b>( + &'b mut self, + device: &Device, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + ) -> &'b Buffer { + // Only commit if there's changes to commit. + if self.staged_is_dirty { + // Flush mapped memory to ensure the staged buffer is filled + unsafe { + use std::ops::Deref; + device + .flush_mapped_memory_ranges(once((self.staged_memory.deref(), Segment::ALL))) + .unwrap(); + } + + // Copy from staged to buffer + let buf = unsafe { + use hal::command::{BufferCopy, CommandBufferFlags}; + // Get a command buffer + let mut buf = command_pool.allocate_one(hal::command::Level::Primary); + + // Put in our copy command + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + buf.copy_buffer( + &self.staged_buffer, + &self.buffer, + &[BufferCopy { + src: 0, + dst: 0, + size: ((self.highest_used + 1) * size_of::<T>()) as u64, + }], + ); + buf.finish(); + + buf + }; + + // Submit it and wait for completion + // TODO: We could use more semaphores or something? + // TODO: Better error handling + unsafe { + let copy_finished = device.create_fence(false).unwrap(); + command_queue.submit::<_, _, Semaphore, _, _>( + Submission { + command_buffers: &[&buf], + wait_semaphores: std::iter::empty::<_>(), + signal_semaphores: std::iter::empty::<_>(), + }, + Some(©_finished), + ); + + device + .wait_for_fence(©_finished, core::u64::MAX) + .unwrap(); + + // Destroy temporary resources + device.destroy_fence(copy_finished); + command_pool.free(once(buf)); + } + + self.staged_is_dirty = false; + } + + &self.buffer + } } impl<'a, T: Sized> Index<usize> for StagedBuffer<'a, T> { - type Output = T; + type Output = T; - fn index(&self, index: usize) -> &Self::Output { - &self.staged_mapped_memory[index] - } + fn index(&self, index: usize) -> &Self::Output { + &self.staged_mapped_memory[index] + } } impl<'a, T: Sized> IndexMut<usize> for StagedBuffer<'a, T> { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.staged_is_dirty = true; - if index > self.highest_used { - self.highest_used = index; - } - &mut self.staged_mapped_memory[index] - } -}
\ No newline at end of file + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.staged_is_dirty = true; + if index > self.highest_used { + self.highest_used = index; + } + &mut self.staged_mapped_memory[index] + } +} diff --git a/stockton-render/src/draw/camera.rs b/stockton-render/src/draw/camera.rs index 5737928..cb465fd 100644 --- a/stockton-render/src/draw/camera.rs +++ b/stockton-render/src/draw/camera.rs @@ -15,10 +15,10 @@ //! Things related to converting 3D world space to 2D screen space -use stockton_types::{Vector3, Matrix4}; +use stockton_types::{Matrix4, Vector3}; -use std::f32::consts::PI; use na::{look_at_lh, perspective_lh_zo, Mat4, Vec4}; +use std::f32::consts::PI; /// 90 degrees in radians const R89: f32 = (PI / 180.0) * 89.0; @@ -30,201 +30,204 @@ const R90: f32 = PI / 2.0; const R180: f32 = PI; fn euler_to_direction(euler: &Vector3) -> Vector3 { - let pitch = euler.x; - let yaw = euler.y; - let _roll = euler.z; // TODO: Support camera roll - - Vector3::new( - yaw.sin() * pitch.cos(), - pitch.sin(), - yaw.cos() * pitch.cos() - ) + let pitch = euler.x; + let yaw = euler.y; + let _roll = euler.z; // TODO: Support camera roll + + Vector3::new( + yaw.sin() * pitch.cos(), + pitch.sin(), + yaw.cos() * pitch.cos(), + ) } pub struct CameraSettings { - /// Position of the camera (world units) - pub position: Vector3, + /// Position of the camera (world units) + pub position: Vector3, - /// Rotation of the camera (euler angles in radians) - pub rotation: Vector3, + /// Rotation of the camera (euler angles in radians) + pub rotation: Vector3, - /// The up direction (normalized) - pub up: Vector3, + /// The up direction (normalized) + pub up: Vector3, - /// FOV (radians) - pub fov: f32, + /// FOV (radians) + pub fov: f32, - /// Near clipping plane (world units) - pub near: f32, + /// Near clipping plane (world units) + pub near: f32, - /// Far clipping plane (world units) - pub far: f32, + /// Far clipping plane (world units) + pub far: f32, } /// Holds settings related to the projection of world space to screen space /// Also holds maths for generating important matrices pub struct WorkingCamera { - /// Settings for the camera - settings: CameraSettings, + /// Settings for the camera + settings: CameraSettings, - /// Aspect ratio as a fraction - aspect_ratio: f32, + /// Aspect ratio as a fraction + aspect_ratio: f32, - /// Cached view projection matrix - vp_matrix: Mat4, + /// Cached view projection matrix + vp_matrix: Mat4, - /// If true, cached value needs updated - is_dirty: bool + /// If true, cached value needs updated + is_dirty: bool, } impl WorkingCamera { - /// Return a camera with default settings - pub fn defaults(aspect_ratio: f32) -> WorkingCamera { - WorkingCamera::with_settings(CameraSettings { - position: Vector3::new(0.0, 0.0, 0.0), - rotation: Vector3::new(0.0, R90, 0.0), - up: Vector3::new(0.0, 1.0, 0.0), - fov: f32::to_radians(90.0), - near: 0.1, - far: 1024.0, - }, aspect_ratio) - } - - /// Return a camera with the given settings - pub fn with_settings(settings: CameraSettings, aspect_ratio: f32) -> WorkingCamera { - WorkingCamera { - aspect_ratio, - settings, - vp_matrix: Mat4::identity(), - is_dirty: true - } - } - - /// Get the VP matrix, updating cache if needed - pub fn get_matrix<'a>(&'a mut self) -> &'a Mat4 { - // Update matrix if needed - if self.is_dirty { - self.vp_matrix = self.calc_vp_matrix(); - self.is_dirty = false; - } - - // Return the matrix - &self.vp_matrix - } - - /// Returns a matrix that transforms from world space to screen space - fn calc_vp_matrix(&self) -> Matrix4 { - // Get look direction from euler angles - let direction = euler_to_direction(&self.settings.rotation); - - // Converts world space to camera space - let view_matrix = look_at_lh( - &self.settings.position, - &(direction + &self.settings.position), - &self.settings.up - ); - - // Converts camera space to screen space - let projection_matrix = { - let mut temp = perspective_lh_zo( - self.aspect_ratio, - self.settings.fov, - self.settings.near, - self.settings.far - ); - - // Vulkan's co-ord system is different from OpenGLs - temp[(1, 1)] *= -1.0; - - temp - }; - - // Chain them together into a single matrix - projection_matrix * view_matrix - } - - /// Update the aspect ratio - pub fn update_aspect_ratio(&mut self, new: f32) { - self.aspect_ratio = new; - self.is_dirty = true; - } - - /// Apply rotation of the camera - /// `euler` should be euler angles in degrees - pub fn rotate(&mut self, euler: Vector3) { - // TODO - self.settings.rotation += euler; - - // Clamp -pi/2 < pitch < pi/2 - if self.settings.rotation.x > R89 { - self.settings.rotation.x = R89; - } else if self.settings.rotation.x <= -R89 { - self.settings.rotation.x = -R89; - } - - // -pi < yaw <= pi - if self.settings.rotation.y <= -R180 { - self.settings.rotation.y = R180 - self.settings.rotation.y % -R180; - } else if self.settings.rotation.y > 180.0 { - self.settings.rotation.y = -R180 + self.settings.rotation.y % R180; - } - - self.is_dirty = true; - } - - /// Move the camera by `delta`, relative to the camera's rotation - pub fn move_camera_relative(&mut self, delta: Vector3) { - let rot_matrix = Mat4::from_euler_angles( - -self.settings.rotation.x, - self.settings.rotation.y, - self.settings.rotation.z - ); - - let new = rot_matrix * Vec4::new(delta.x, delta.y, delta.z, 1.0); - self.settings.position.x += new.x; - self.settings.position.y += new.y; - self.settings.position.z += new.z; - - self.is_dirty = true; - } - - pub fn camera_pos(&self) -> Vector3 { - self.settings.position - } + /// Return a camera with default settings + pub fn defaults(aspect_ratio: f32) -> WorkingCamera { + WorkingCamera::with_settings( + CameraSettings { + position: Vector3::new(0.0, 0.0, 0.0), + rotation: Vector3::new(0.0, R90, 0.0), + up: Vector3::new(0.0, 1.0, 0.0), + fov: f32::to_radians(90.0), + near: 0.1, + far: 1024.0, + }, + aspect_ratio, + ) + } + + /// Return a camera with the given settings + pub fn with_settings(settings: CameraSettings, aspect_ratio: f32) -> WorkingCamera { + WorkingCamera { + aspect_ratio, + settings, + vp_matrix: Mat4::identity(), + is_dirty: true, + } + } + + /// Get the VP matrix, updating cache if needed + pub fn get_matrix(&mut self) -> &Mat4 { + // Update matrix if needed + if self.is_dirty { + self.vp_matrix = self.calc_vp_matrix(); + self.is_dirty = false; + } + + // Return the matrix + &self.vp_matrix + } + + /// Returns a matrix that transforms from world space to screen space + fn calc_vp_matrix(&self) -> Matrix4 { + // Get look direction from euler angles + let direction = euler_to_direction(&self.settings.rotation); + + // Converts world space to camera space + let view_matrix = look_at_lh( + &self.settings.position, + &(self.settings.position + direction), + &self.settings.up, + ); + + // Converts camera space to screen space + let projection_matrix = { + let mut temp = perspective_lh_zo( + self.aspect_ratio, + self.settings.fov, + self.settings.near, + self.settings.far, + ); + + // Vulkan's co-ord system is different from OpenGLs + temp[(1, 1)] *= -1.0; + + temp + }; + + // Chain them together into a single matrix + projection_matrix * view_matrix + } + + /// Update the aspect ratio + pub fn update_aspect_ratio(&mut self, new: f32) { + self.aspect_ratio = new; + self.is_dirty = true; + } + + /// Apply rotation of the camera + /// `euler` should be euler angles in degrees + pub fn rotate(&mut self, euler: Vector3) { + // TODO + self.settings.rotation += euler; + + // Clamp -pi/2 < pitch < pi/2 + if self.settings.rotation.x > R89 { + self.settings.rotation.x = R89; + } else if self.settings.rotation.x <= -R89 { + self.settings.rotation.x = -R89; + } + + // -pi < yaw <= pi + if self.settings.rotation.y <= -R180 { + self.settings.rotation.y = R180 - self.settings.rotation.y % -R180; + } else if self.settings.rotation.y > 180.0 { + self.settings.rotation.y = -R180 + self.settings.rotation.y % R180; + } + + self.is_dirty = true; + } + + /// Move the camera by `delta`, relative to the camera's rotation + pub fn move_camera_relative(&mut self, delta: Vector3) { + let rot_matrix = Mat4::from_euler_angles( + -self.settings.rotation.x, + self.settings.rotation.y, + self.settings.rotation.z, + ); + + let new = rot_matrix * Vec4::new(delta.x, delta.y, delta.z, 1.0); + self.settings.position.x += new.x; + self.settings.position.y += new.y; + self.settings.position.z += new.z; + + self.is_dirty = true; + } + + pub fn camera_pos(&self) -> Vector3 { + self.settings.position + } } #[cfg(test)] mod tests { - use stockton_types::Matrix4; -use stockton_types::Vector3; - use draw::camera::WorkingCamera; + use draw::camera::WorkingCamera; + use stockton_types::Matrix4; + use stockton_types::Vector3; - fn contains_nan(mat: &Matrix4) -> bool{ - for x in mat.iter() { - if *x == std::f32::NAN { - return true; - } - } - return false; - } + fn contains_nan(mat: &Matrix4) -> bool { + for x in mat.iter() { + if (*x).is_nan() { + return true; + } + } + false + } - #[test] - fn camera_vp() { - let mut camera = WorkingCamera::defaults(16.0 / 9.0); + #[test] + fn camera_vp() { + let mut camera = WorkingCamera::defaults(16.0 / 9.0); - let old = camera.calc_vp_matrix(); - println!("initial vp matrix: {:?}", old); + let old = camera.calc_vp_matrix(); + println!("initial vp matrix: {:?}", old); - assert!(!contains_nan(&old), "No NaNs for initial matrix"); + assert!(!contains_nan(&old), "No NaNs for initial matrix"); - // Do a 180 - camera.rotate(Vector3::new(0.0, 180.0, 0.0)); + // Do a 180 + camera.rotate(Vector3::new(0.0, 180.0, 0.0)); - let new = camera.calc_vp_matrix(); - assert!(!contains_nan(&new), "No NaNs after rotating"); + let new = camera.calc_vp_matrix(); + assert!(!contains_nan(&new), "No NaNs after rotating"); - println!("new vp matrix: {:?}", new); + println!("new vp matrix: {:?}", new); - assert!(old != new, "VP Matrix changes when camera rotates"); - } + assert!(old != new, "VP Matrix changes when camera rotates"); + } } diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs index ed5b5ec..3fff042 100644 --- a/stockton-render/src/draw/context.rs +++ b/stockton-render/src/draw/context.rs @@ -17,36 +17,29 @@ //! In the end, this takes in a depth-sorted list of faces and a map file and renders them. //! You'll need something else to actually find/sort the faces though. - use std::{ - mem::{ManuallyDrop, size_of}, - ops::Deref, - borrow::Borrow, - convert::TryInto + borrow::Borrow, + convert::TryInto, + mem::{size_of, ManuallyDrop}, + ops::Deref, }; use arrayvec::ArrayVec; -use hal::{ - prelude::*, - pool::CommandPoolCreateFlags -}; +use hal::{pool::CommandPoolCreateFlags, prelude::*}; use log::debug; use winit::window::Window; -use stockton_types::{Vector2, Vector3}; use stockton_levels::prelude::*; use stockton_levels::traits::faces::FaceType; +use stockton_types::{Vector2, Vector3}; -use crate::{ - types::*, - error -}; use super::{ - target::{TargetChain, SwapchainProperties}, - camera::WorkingCamera, - texture::TextureStore, - buffer::{StagedBuffer, ModifiableBuffer} + buffer::{ModifiableBuffer, StagedBuffer}, + camera::WorkingCamera, + target::{SwapchainProperties, TargetChain}, + texture::TextureStore, }; +use crate::{error, types::*}; /// Entry point name for shaders const ENTRY_NAME: &str = "main"; @@ -65,605 +58,697 @@ const FRAGMENT_SOURCE: &str = include_str!("./data/stockton.frag"); /// Represents a point of a triangle, including UV and texture information. #[derive(Debug, Clone, Copy)] -pub struct UVPoint (pub Vector3, pub i32, pub Vector2); +pub struct UVPoint(pub Vector3, pub i32, pub Vector2); /// Contains all the hal related stuff. /// In the end, this takes in a depth-sorted list of faces and a map file and renders them. // TODO: Settings for clear colour, buffer sizes, etc pub struct RenderingContext<'a> { - // Parents for most of these things + // Parents for most of these things + /// Vulkan Instance + instance: ManuallyDrop<back::Instance>, - /// Vulkan Instance - instance: ManuallyDrop<back::Instance>, + /// Device we're using + device: ManuallyDrop<Device>, - /// Device we're using - device: ManuallyDrop<Device>, + /// Adapter we're using + adapter: Adapter, - /// Adapter we're using - adapter: Adapter, + // Render destination + /// Surface to draw to + surface: ManuallyDrop<Surface>, - // Render destination + /// Swapchain and stuff + target_chain: ManuallyDrop<TargetChain>, - /// Surface to draw to - surface: ManuallyDrop<Surface>, + // Pipeline + /// Our main render pass + renderpass: ManuallyDrop<RenderPass>, - /// Swapchain and stuff - target_chain: ManuallyDrop<TargetChain>, + /// The layout of our main graphics pipeline + pipeline_layout: ManuallyDrop<PipelineLayout>, - // Pipeline + /// Our main graphics pipeline + pipeline: ManuallyDrop<GraphicsPipeline>, - /// Our main render pass - renderpass: ManuallyDrop<RenderPass>, + // Command pool and buffers + /// The command pool used for our buffers + cmd_pool: ManuallyDrop<CommandPool>, - /// The layout of our main graphics pipeline - pipeline_layout: ManuallyDrop<PipelineLayout>, + /// The queue group our buffers belong to + queue_group: QueueGroup, - /// Our main graphics pipeline - pipeline: ManuallyDrop<GraphicsPipeline>, + /// Texture store + texture_store: ManuallyDrop<TextureStore>, - // Command pool and buffers + /// (Staged) Vertex Buffer + pub vert_buffer: ManuallyDrop<StagedBuffer<'a, UVPoint>>, - /// The command pool used for our buffers - cmd_pool: ManuallyDrop<CommandPool>, + /// (Staged) Index Buffer + pub index_buffer: ManuallyDrop<StagedBuffer<'a, (u16, u16, u16)>>, - /// The queue group our buffers belong to - queue_group: QueueGroup, + /// Our camera settings + camera: WorkingCamera, - /// Texture store - texture_store: ManuallyDrop<TextureStore>, + /// The vertex shader module + vs_module: ManuallyDrop<ShaderModule>, - /// (Staged) Vertex Buffer - pub vert_buffer: ManuallyDrop<StagedBuffer<'a, UVPoint>>, - - /// (Staged) Index Buffer - pub index_buffer: ManuallyDrop<StagedBuffer<'a, (u16, u16, u16)>>, - - /// Our camera settings - camera: WorkingCamera, - - /// The vertex shader module - vs_module: ManuallyDrop<ShaderModule>, - - /// The fragment shader module - fs_module: ManuallyDrop<ShaderModule> + /// The fragment shader module + fs_module: ManuallyDrop<ShaderModule>, } impl<'a> RenderingContext<'a> { - /// Create a new RenderingContext for the given window. - pub fn new<T: HasTextures>(window: &Window, file: &T) -> Result<Self, error::CreationError> { - // Create surface - let (instance, mut surface, mut adapters) = unsafe { - use hal::Instance; - - let instance = back::Instance::create("stockton", 1).map_err(|_| error::CreationError::WindowError)?; - let surface = instance.create_surface(window).map_err(|_| error::CreationError::WindowError)?; - let adapters = instance.enumerate_adapters(); - - (instance, surface, adapters) - }; - - // TODO: Properly figure out which adapter to use - let mut adapter = adapters.remove(0); - - // Device & Queue group - let (mut device, mut queue_group) = { - let family = adapter - .queue_families - .iter() - .find(|family| { - surface.supports_queue_family(family) && family.queue_type().supports_graphics() - }) - .unwrap(); - - let mut gpu = unsafe { - adapter - .physical_device - .open(&[(family, &[1.0])], hal::Features::empty()) - .unwrap() - }; - - (gpu.device, gpu.queue_groups.pop().unwrap()) - }; - - // Figure out what our swapchain will look like - let swapchain_properties = SwapchainProperties::find_best(&adapter, &surface).map_err(|_| error::CreationError::BadSurface)?; - - debug!("Detected following swapchain properties: {:?}", swapchain_properties); - - // Command pool - let mut cmd_pool = unsafe { - device.create_command_pool(queue_group.family, CommandPoolCreateFlags::RESET_INDIVIDUAL) - }.map_err(|_| error::CreationError::OutOfMemoryError)?; - - // Renderpass - let renderpass = { - use hal::{ - pass::*, - pso::PipelineStage, - image::{Access, Layout}, - memory::Dependencies - }; - - let img_attachment = Attachment { - format: Some(swapchain_properties.format), - samples: 1, - ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), - stencil_ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), - layouts: Layout::Undefined..Layout::Present - }; - - let depth_attachment = Attachment { - format: Some(swapchain_properties.depth_format), - samples: 1, - ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), - stencil_ops: AttachmentOps::new(AttachmentLoadOp::DontCare, AttachmentStoreOp::DontCare), - layouts: Layout::Undefined..Layout::DepthStencilAttachmentOptimal - }; - - let subpass = SubpassDesc { - colors: &[(0, Layout::ColorAttachmentOptimal)], - depth_stencil: Some(&(1, Layout::DepthStencilAttachmentOptimal)), - inputs: &[], - resolves: &[], - preserves: &[] - }; - - let in_dependency = SubpassDependency { - flags: Dependencies::empty(), - passes: None..Some(0), - stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT.. - (PipelineStage::COLOR_ATTACHMENT_OUTPUT | PipelineStage::EARLY_FRAGMENT_TESTS), - accesses: Access::empty() - ..(Access::COLOR_ATTACHMENT_READ - | Access::COLOR_ATTACHMENT_WRITE - | Access::DEPTH_STENCIL_ATTACHMENT_READ - | Access::DEPTH_STENCIL_ATTACHMENT_WRITE) - }; - - let out_dependency = SubpassDependency { - flags: Dependencies::empty(), - passes: Some(0)..None, - stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT | PipelineStage::EARLY_FRAGMENT_TESTS.. - PipelineStage::COLOR_ATTACHMENT_OUTPUT, - accesses: (Access::COLOR_ATTACHMENT_READ - | Access::COLOR_ATTACHMENT_WRITE - | Access::DEPTH_STENCIL_ATTACHMENT_READ - | Access::DEPTH_STENCIL_ATTACHMENT_WRITE).. - Access::empty() - }; - - unsafe { device.create_render_pass(&[img_attachment, depth_attachment], &[subpass], &[in_dependency, out_dependency]) } - .map_err(|_| error::CreationError::OutOfMemoryError)? - }; - - // Subpass - let subpass = hal::pass::Subpass { - index: 0, - main_pass: &renderpass - }; - - // Camera - // TODO: Settings - let ratio = swapchain_properties.extent.width as f32 / swapchain_properties.extent.height as f32; - let camera = WorkingCamera::defaults(ratio); - - - // Vertex and index buffers - let (vert_buffer, index_buffer) = { - use hal::buffer::Usage; - - let vert = StagedBuffer::new(&mut device, &adapter, Usage::VERTEX, INITIAL_VERT_SIZE)?; - let index = StagedBuffer::new(&mut device, &adapter, Usage::INDEX, INITIAL_INDEX_SIZE)?; - - (vert, index) - }; - - // Texture store - let texture_store = TextureStore::new(&mut device, - &mut adapter, - &mut queue_group.queues[0], - &mut cmd_pool, file)?; - - let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); - descriptor_set_layouts.push(texture_store.descriptor_set_layout.deref()); - - // Graphics pipeline - let (pipeline_layout, pipeline, vs_module, fs_module) = Self::create_pipeline(&mut device, swapchain_properties.extent, &subpass, descriptor_set_layouts)?; - - // Swapchain and associated resources - let target_chain = TargetChain::new(&mut device, &adapter, &mut surface, &renderpass, &mut cmd_pool, swapchain_properties, None).map_err(|e| error::CreationError::TargetChainCreationError (e) )?; - - Ok(RenderingContext { - instance: ManuallyDrop::new(instance), - surface: ManuallyDrop::new(surface), - - device: ManuallyDrop::new(device), - adapter, - queue_group, - - renderpass: ManuallyDrop::new(renderpass), - target_chain: ManuallyDrop::new(target_chain), - cmd_pool: ManuallyDrop::new(cmd_pool), - - pipeline_layout: ManuallyDrop::new(pipeline_layout), - pipeline: ManuallyDrop::new(pipeline), - - texture_store: ManuallyDrop::new(texture_store), - - vert_buffer: ManuallyDrop::new(vert_buffer), - index_buffer: ManuallyDrop::new(index_buffer), - - vs_module: ManuallyDrop::new(vs_module), - fs_module: ManuallyDrop::new(fs_module), - - camera - }) - } - - /// If this function fails the whole context is probably dead - /// The context must not be used while this is being called - pub unsafe fn handle_surface_change(&mut self) -> Result<(), error::CreationError> { - self.device.wait_idle().unwrap(); - - let properties = SwapchainProperties::find_best(&self.adapter, &self.surface).map_err(|_| error::CreationError::BadSurface)?; - - // Camera settings (aspect ratio) - self.camera.update_aspect_ratio( - properties.extent.width as f32 / - properties.extent.height as f32); - - use core::ptr::read; - - // Graphics pipeline - self.device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); - - self.device - .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); - - self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); - self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.fs_module))); - - let (pipeline_layout, pipeline, vs_module, fs_module) = { - let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); - descriptor_set_layouts.push(self.texture_store.descriptor_set_layout.deref()); - - let subpass = hal::pass::Subpass { - index: 0, - main_pass: &(*self.renderpass) - }; - - Self::create_pipeline(&mut self.device, properties.extent, &subpass, descriptor_set_layouts)? - }; - - self.pipeline_layout = ManuallyDrop::new(pipeline_layout); - self.pipeline = ManuallyDrop::new(pipeline); - - self.vs_module = ManuallyDrop::new(vs_module); - self.fs_module = ManuallyDrop::new(fs_module); - - let old_swapchain = ManuallyDrop::into_inner(read(&self.target_chain)).deactivate_with_recyling(&mut self.device, &mut self.cmd_pool); - self.target_chain = ManuallyDrop::new(TargetChain::new(&mut self.device, &self.adapter, &mut self.surface, &self.renderpass, &mut self.cmd_pool, properties, Some(old_swapchain)) - .map_err(|e| error::CreationError::TargetChainCreationError (e) )?); - - Ok(()) - } - - #[allow(clippy::type_complexity)] - fn create_pipeline<T>(device: &mut Device, extent: hal::image::Extent, subpass: &hal::pass::Subpass<back::Backend>, set_layouts: T) -> Result< - ( - PipelineLayout, - GraphicsPipeline, - ShaderModule, - ShaderModule - ), error::CreationError> where T: IntoIterator, T::Item: Borrow<DescriptorSetLayout> { - use hal::pso::*; - use hal::format::Format; - - // Shader modules - let (vs_module, fs_module) = { - let mut compiler = shaderc::Compiler::new().ok_or(error::CreationError::NoShaderC)?; - - let vertex_compile_artifact = compiler - .compile_into_spirv(VERTEX_SOURCE, shaderc::ShaderKind::Vertex, "vertex.vert", ENTRY_NAME, None) - .map_err(|e| error::CreationError::ShaderCError (e))?; - - let fragment_compile_artifact = compiler - .compile_into_spirv(FRAGMENT_SOURCE, shaderc::ShaderKind::Fragment, "fragment.frag", ENTRY_NAME, None) - .map_err(|e| error::CreationError::ShaderCError (e))?; - - // Make into shader module - unsafe { - (device - .create_shader_module(vertex_compile_artifact.as_binary()) - .map_err(|e| error::CreationError::ShaderModuleFailed (e))?, - device - .create_shader_module(fragment_compile_artifact.as_binary()) - .map_err(|e| error::CreationError::ShaderModuleFailed (e))?) - } - }; - - // Shader entry points (ShaderStage) - let (vs_entry, fs_entry) = ( - EntryPoint::<back::Backend> { - entry: ENTRY_NAME, - module: &vs_module, - specialization: Specialization::default() - }, - EntryPoint::<back::Backend> { - entry: ENTRY_NAME, - module: &fs_module, - specialization: Specialization::default() - } - ); - - // Shader set - let shaders = GraphicsShaderSet { - vertex: vs_entry, - fragment: Some(fs_entry), - hull: None, - domain: None, - geometry: None - }; - - // Vertex buffers - let vertex_buffers: Vec<VertexBufferDesc> = vec![VertexBufferDesc { - binding: 0, - stride: (size_of::<f32>() * 6) as u32, - rate: VertexInputRate::Vertex, - }]; - - let attributes: Vec<AttributeDesc> = pipeline_vb_attributes!(0, - size_of::<f32>() * 3; Rgb32Sfloat, - size_of::<u32>(); R32Sint, - size_of::<f32>() * 2; Rg32Sfloat - ); - - // Rasterizer - let rasterizer = Rasterizer { - polygon_mode: PolygonMode::Fill, - cull_face: Face::BACK, - front_face: FrontFace::CounterClockwise, - depth_clamping: false, - depth_bias: None, - conservative: true, - line_width: hal::pso::State::Static(1.0) - }; - - // Depth stencil - let depth_stencil = DepthStencilDesc { - depth: Some(DepthTest { - fun: Comparison::Less, - write: true - }), - depth_bounds: false, - stencil: None, - }; - - // Pipeline layout - let layout = unsafe { - device.create_pipeline_layout( - set_layouts, - // vp matrix, 4x4 f32 - &[(ShaderStageFlags::VERTEX, 0..64)] - ) - }.map_err(|_| error::CreationError::OutOfMemoryError)?; - - // Colour blending - let blender = { - let blend_state = BlendState { - color: BlendOp::Add { - src: Factor::One, - dst: Factor::Zero, - }, - alpha: BlendOp::Add { - src: Factor::One, - dst: Factor::Zero, - }, - }; - - BlendDesc { - logic_op: Some(LogicOp::Copy), - targets: vec![ColorBlendDesc { mask: ColorMask::ALL, blend: Some(blend_state) }], - } - }; - - // Baked states - let baked_states = BakedStates { - viewport: Some(Viewport { - rect: extent.rect(), - depth: (0.0..1.0) - }), - scissor: Some(extent.rect()), - blend_color: None, - depth_bounds: None, - }; - - // Input assembler - let input_assembler = InputAssemblerDesc::new(Primitive::TriangleList); - - // Pipeline description - let pipeline_desc = GraphicsPipelineDesc { - shaders, - rasterizer, - vertex_buffers, - blender, - depth_stencil, - multisampling: None, - baked_states, - layout: &layout, - subpass: *subpass, - flags: PipelineCreationFlags::empty(), - parent: BasePipeline::None, - input_assembler, - attributes - }; - - // Pipeline - let pipeline = unsafe { - device.create_graphics_pipeline(&pipeline_desc, None) - }.map_err(|e| error::CreationError::PipelineError (e))?; - - Ok((layout, pipeline, vs_module, fs_module)) - } - - /// Draw all vertices in the buffer - pub fn draw_vertices<M: MinBSPFeatures<VulkanSystem>>(&mut self, file: &M,faces: &Vec<u32>) -> Result<(), &'static str> { - // Prepare command buffer - let cmd_buffer = self.target_chain.prep_next_target( - &mut self.device, - self.vert_buffer.get_buffer(), - self.index_buffer.get_buffer(), - &self.renderpass, - &self.pipeline, - &self.pipeline_layout, - &mut self.camera - )?; - - // Iterate over faces, copying them in and drawing groups that use the same texture chunk all at once. - let mut current_chunk = file.get_face(0).texture_idx as usize / 8; - let mut chunk_start = 0; - - let mut curr_vert_idx: usize = 0; - let mut curr_idx_idx: usize = 0; - - for face in faces.into_iter().map(|idx| file.get_face(*idx)) { - if current_chunk != face.texture_idx as usize / 8 { - // Last index was last of group, so draw it all. - let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); - descriptor_sets.push(self.texture_store.get_chunk_descriptor_set(current_chunk)); - unsafe { - - cmd_buffer.bind_graphics_descriptor_sets( - &self.pipeline_layout, - 0, - descriptor_sets, - &[] - ); - cmd_buffer.draw_indexed(chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, 0, 0..1); - } - - // Next group of same-chunked faces starts here. - chunk_start = curr_idx_idx; - current_chunk = face.texture_idx as usize / 8; - } - - if face.face_type == FaceType::Polygon || face.face_type == FaceType::Mesh { - // 2 layers of indirection - let base = face.vertices_idx.start; - - for idx in face.meshverts_idx.clone().step_by(3) { - let start_idx: u16 = curr_vert_idx.try_into().unwrap(); - - for idx2 in idx..idx+3 { - let vert = &file.resolve_meshvert(idx2 as u32, base); - let uv = Vector2::new(vert.tex.u[0], vert.tex.v[0]); - - let uvp = UVPoint (vert.position, face.texture_idx.try_into().unwrap(), uv); - self.vert_buffer[curr_vert_idx] = uvp; - - curr_vert_idx += 1; - } - - - self.index_buffer[curr_idx_idx] = (start_idx, start_idx + 1, start_idx + 2); - - curr_idx_idx += 1; - - if curr_vert_idx >= INITIAL_VERT_SIZE.try_into().unwrap() || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into().unwrap() { - println!("out of vertex buffer space!"); - break; - } - } - } else { - // TODO: Other types of faces - } - - if curr_vert_idx >= INITIAL_VERT_SIZE.try_into().unwrap() || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into().unwrap() { - println!("out of vertex buffer space!"); - break; - } - } - - // Draw the final group of chunks - let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); - descriptor_sets.push(self.texture_store.get_chunk_descriptor_set(current_chunk)); - unsafe { - cmd_buffer.bind_graphics_descriptor_sets( - &self.pipeline_layout, - 0, - descriptor_sets, - &[] - ); - cmd_buffer.draw_indexed(chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, 0, 0..1); - } - - // Update our buffers before we actually start drawing - self.vert_buffer.commit( - &self.device, - &mut self.queue_group.queues[0], - &mut self.cmd_pool - ); - - self.index_buffer.commit( - &self.device, - &mut self.queue_group.queues[0], - &mut self.cmd_pool - ); - - // Send commands off to GPU - self.target_chain.finish_and_submit_target(&mut self.queue_group.queues[0])?; - - Ok(()) - } - - /// Get current position of camera - pub fn camera_pos(&self) -> Vector3 { - self.camera.camera_pos() - } - - /// Move the camera by `delta` relative to its rotation - pub fn move_camera_relative(&mut self, delta: Vector3) { - self.camera.move_camera_relative(delta) - } - - /// Rotate the camera - /// `euler` should be euler angles in radians - pub fn rotate(&mut self, euler: Vector3) { - self.camera.rotate(euler) - } + /// Create a new RenderingContext for the given window. + pub fn new<T: HasTextures>(window: &Window, file: &T) -> Result<Self, error::CreationError> { + // Create surface + let (instance, mut surface, mut adapters) = unsafe { + use hal::Instance; + + let instance = back::Instance::create("stockton", 1) + .map_err(|_| error::CreationError::WindowError)?; + let surface = instance + .create_surface(window) + .map_err(|_| error::CreationError::WindowError)?; + let adapters = instance.enumerate_adapters(); + + (instance, surface, adapters) + }; + + // TODO: Properly figure out which adapter to use + let mut adapter = adapters.remove(0); + + // Device & Queue group + let (mut device, mut queue_group) = { + let family = adapter + .queue_families + .iter() + .find(|family| { + surface.supports_queue_family(family) && family.queue_type().supports_graphics() + }) + .unwrap(); + + let mut gpu = unsafe { + adapter + .physical_device + .open(&[(family, &[1.0])], hal::Features::empty()) + .unwrap() + }; + + (gpu.device, gpu.queue_groups.pop().unwrap()) + }; + + // Figure out what our swapchain will look like + let swapchain_properties = SwapchainProperties::find_best(&adapter, &surface) + .map_err(|_| error::CreationError::BadSurface)?; + + debug!( + "Detected following swapchain properties: {:?}", + swapchain_properties + ); + + // Command pool + let mut cmd_pool = unsafe { + device.create_command_pool(queue_group.family, CommandPoolCreateFlags::RESET_INDIVIDUAL) + } + .map_err(|_| error::CreationError::OutOfMemoryError)?; + + // Renderpass + let renderpass = { + use hal::{ + image::{Access, Layout}, + memory::Dependencies, + pass::*, + pso::PipelineStage, + }; + + let img_attachment = Attachment { + format: Some(swapchain_properties.format), + samples: 1, + ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), + stencil_ops: AttachmentOps::new( + AttachmentLoadOp::Clear, + AttachmentStoreOp::DontCare, + ), + layouts: Layout::Undefined..Layout::Present, + }; + + let depth_attachment = Attachment { + format: Some(swapchain_properties.depth_format), + samples: 1, + ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), + stencil_ops: AttachmentOps::new( + AttachmentLoadOp::DontCare, + AttachmentStoreOp::DontCare, + ), + layouts: Layout::Undefined..Layout::DepthStencilAttachmentOptimal, + }; + + let subpass = SubpassDesc { + colors: &[(0, Layout::ColorAttachmentOptimal)], + depth_stencil: Some(&(1, Layout::DepthStencilAttachmentOptimal)), + inputs: &[], + resolves: &[], + preserves: &[], + }; + + let in_dependency = SubpassDependency { + flags: Dependencies::empty(), + passes: None..Some(0), + stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT + ..(PipelineStage::COLOR_ATTACHMENT_OUTPUT + | PipelineStage::EARLY_FRAGMENT_TESTS), + accesses: Access::empty() + ..(Access::COLOR_ATTACHMENT_READ + | Access::COLOR_ATTACHMENT_WRITE + | Access::DEPTH_STENCIL_ATTACHMENT_READ + | Access::DEPTH_STENCIL_ATTACHMENT_WRITE), + }; + + let out_dependency = SubpassDependency { + flags: Dependencies::empty(), + passes: Some(0)..None, + stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT | PipelineStage::EARLY_FRAGMENT_TESTS + ..PipelineStage::COLOR_ATTACHMENT_OUTPUT, + accesses: (Access::COLOR_ATTACHMENT_READ + | Access::COLOR_ATTACHMENT_WRITE + | Access::DEPTH_STENCIL_ATTACHMENT_READ + | Access::DEPTH_STENCIL_ATTACHMENT_WRITE) + ..Access::empty(), + }; + + unsafe { + device.create_render_pass( + &[img_attachment, depth_attachment], + &[subpass], + &[in_dependency, out_dependency], + ) + } + .map_err(|_| error::CreationError::OutOfMemoryError)? + }; + + // Subpass + let subpass = hal::pass::Subpass { + index: 0, + main_pass: &renderpass, + }; + + // Camera + // TODO: Settings + let ratio = + swapchain_properties.extent.width as f32 / swapchain_properties.extent.height as f32; + let camera = WorkingCamera::defaults(ratio); + + // Vertex and index buffers + let (vert_buffer, index_buffer) = { + use hal::buffer::Usage; + + let vert = StagedBuffer::new(&mut device, &adapter, Usage::VERTEX, INITIAL_VERT_SIZE)?; + let index = StagedBuffer::new(&mut device, &adapter, Usage::INDEX, INITIAL_INDEX_SIZE)?; + + (vert, index) + }; + + // Texture store + let texture_store = TextureStore::new( + &mut device, + &mut adapter, + &mut queue_group.queues[0], + &mut cmd_pool, + file, + )?; + + let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); + descriptor_set_layouts.push(texture_store.descriptor_set_layout.deref()); + + // Graphics pipeline + let (pipeline_layout, pipeline, vs_module, fs_module) = Self::create_pipeline( + &mut device, + swapchain_properties.extent, + &subpass, + descriptor_set_layouts, + )?; + + // Swapchain and associated resources + let target_chain = TargetChain::new( + &mut device, + &adapter, + &mut surface, + &renderpass, + &mut cmd_pool, + swapchain_properties, + None, + ) + .map_err(error::CreationError::TargetChainCreationError)?; + + Ok(RenderingContext { + instance: ManuallyDrop::new(instance), + surface: ManuallyDrop::new(surface), + + device: ManuallyDrop::new(device), + adapter, + queue_group, + + renderpass: ManuallyDrop::new(renderpass), + target_chain: ManuallyDrop::new(target_chain), + cmd_pool: ManuallyDrop::new(cmd_pool), + + pipeline_layout: ManuallyDrop::new(pipeline_layout), + pipeline: ManuallyDrop::new(pipeline), + + texture_store: ManuallyDrop::new(texture_store), + + vert_buffer: ManuallyDrop::new(vert_buffer), + index_buffer: ManuallyDrop::new(index_buffer), + + vs_module: ManuallyDrop::new(vs_module), + fs_module: ManuallyDrop::new(fs_module), + + camera, + }) + } + + /// If this function fails the whole context is probably dead + /// # Safety + /// The context must not be used while this is being called + pub unsafe fn handle_surface_change(&mut self) -> Result<(), error::CreationError> { + self.device.wait_idle().unwrap(); + + let properties = SwapchainProperties::find_best(&self.adapter, &self.surface) + .map_err(|_| error::CreationError::BadSurface)?; + + // Camera settings (aspect ratio) + self.camera + .update_aspect_ratio(properties.extent.width as f32 / properties.extent.height as f32); + + use core::ptr::read; + + // Graphics pipeline + self.device + .destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); + + self.device + .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); + + self.device + .destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); + self.device + .destroy_shader_module(ManuallyDrop::into_inner(read(&self.fs_module))); + + let (pipeline_layout, pipeline, vs_module, fs_module) = { + let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); + descriptor_set_layouts.push(self.texture_store.descriptor_set_layout.deref()); + + let subpass = hal::pass::Subpass { + index: 0, + main_pass: &(*self.renderpass), + }; + + Self::create_pipeline( + &mut self.device, + properties.extent, + &subpass, + descriptor_set_layouts, + )? + }; + + self.pipeline_layout = ManuallyDrop::new(pipeline_layout); + self.pipeline = ManuallyDrop::new(pipeline); + + self.vs_module = ManuallyDrop::new(vs_module); + self.fs_module = ManuallyDrop::new(fs_module); + + let old_swapchain = ManuallyDrop::into_inner(read(&self.target_chain)) + .deactivate_with_recyling(&mut self.device, &mut self.cmd_pool); + self.target_chain = ManuallyDrop::new( + TargetChain::new( + &mut self.device, + &self.adapter, + &mut self.surface, + &self.renderpass, + &mut self.cmd_pool, + properties, + Some(old_swapchain), + ) + .map_err(error::CreationError::TargetChainCreationError)?, + ); + + Ok(()) + } + + #[allow(clippy::type_complexity)] + fn create_pipeline<T>( + device: &mut Device, + extent: hal::image::Extent, + subpass: &hal::pass::Subpass<back::Backend>, + set_layouts: T, + ) -> Result<(PipelineLayout, GraphicsPipeline, ShaderModule, ShaderModule), error::CreationError> + where + T: IntoIterator, + T::Item: Borrow<DescriptorSetLayout>, + { + use hal::format::Format; + use hal::pso::*; + + // Shader modules + let (vs_module, fs_module) = { + let mut compiler = shaderc::Compiler::new().ok_or(error::CreationError::NoShaderC)?; + + let vertex_compile_artifact = compiler + .compile_into_spirv( + VERTEX_SOURCE, + shaderc::ShaderKind::Vertex, + "vertex.vert", + ENTRY_NAME, + None, + ) + .map_err(error::CreationError::ShaderCError)?; + + let fragment_compile_artifact = compiler + .compile_into_spirv( + FRAGMENT_SOURCE, + shaderc::ShaderKind::Fragment, + "fragment.frag", + ENTRY_NAME, + None, + ) + .map_err(error::CreationError::ShaderCError)?; + + // Make into shader module + unsafe { + ( + device + .create_shader_module(vertex_compile_artifact.as_binary()) + .map_err(error::CreationError::ShaderModuleFailed)?, + device + .create_shader_module(fragment_compile_artifact.as_binary()) + .map_err(error::CreationError::ShaderModuleFailed)?, + ) + } + }; + + // Shader entry points (ShaderStage) + let (vs_entry, fs_entry) = ( + EntryPoint::<back::Backend> { + entry: ENTRY_NAME, + module: &vs_module, + specialization: Specialization::default(), + }, + EntryPoint::<back::Backend> { + entry: ENTRY_NAME, + module: &fs_module, + specialization: Specialization::default(), + }, + ); + + // Shader set + let shaders = GraphicsShaderSet { + vertex: vs_entry, + fragment: Some(fs_entry), + hull: None, + domain: None, + geometry: None, + }; + + // Vertex buffers + let vertex_buffers: Vec<VertexBufferDesc> = vec![VertexBufferDesc { + binding: 0, + stride: (size_of::<f32>() * 6) as u32, + rate: VertexInputRate::Vertex, + }]; + + let attributes: Vec<AttributeDesc> = pipeline_vb_attributes!(0, + size_of::<f32>() * 3; Rgb32Sfloat, + size_of::<u32>(); R32Sint, + size_of::<f32>() * 2; Rg32Sfloat + ); + + // Rasterizer + let rasterizer = Rasterizer { + polygon_mode: PolygonMode::Fill, + cull_face: Face::BACK, + front_face: FrontFace::CounterClockwise, + depth_clamping: false, + depth_bias: None, + conservative: true, + line_width: hal::pso::State::Static(1.0), + }; + + // Depth stencil + let depth_stencil = DepthStencilDesc { + depth: Some(DepthTest { + fun: Comparison::Less, + write: true, + }), + depth_bounds: false, + stencil: None, + }; + + // Pipeline layout + let layout = unsafe { + device.create_pipeline_layout( + set_layouts, + // vp matrix, 4x4 f32 + &[(ShaderStageFlags::VERTEX, 0..64)], + ) + } + .map_err(|_| error::CreationError::OutOfMemoryError)?; + + // Colour blending + let blender = { + let blend_state = BlendState { + color: BlendOp::Add { + src: Factor::One, + dst: Factor::Zero, + }, + alpha: BlendOp::Add { + src: Factor::One, + dst: Factor::Zero, + }, + }; + + BlendDesc { + logic_op: Some(LogicOp::Copy), + targets: vec![ColorBlendDesc { + mask: ColorMask::ALL, + blend: Some(blend_state), + }], + } + }; + + // Baked states + let baked_states = BakedStates { + viewport: Some(Viewport { + rect: extent.rect(), + depth: (0.0..1.0), + }), + scissor: Some(extent.rect()), + blend_color: None, + depth_bounds: None, + }; + + // Input assembler + let input_assembler = InputAssemblerDesc::new(Primitive::TriangleList); + + // Pipeline description + let pipeline_desc = GraphicsPipelineDesc { + shaders, + rasterizer, + vertex_buffers, + blender, + depth_stencil, + multisampling: None, + baked_states, + layout: &layout, + subpass: *subpass, + flags: PipelineCreationFlags::empty(), + parent: BasePipeline::None, + input_assembler, + attributes, + }; + + // Pipeline + let pipeline = unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } + .map_err(error::CreationError::PipelineError)?; + + Ok((layout, pipeline, vs_module, fs_module)) + } + + /// Draw all vertices in the buffer + pub fn draw_vertices<M: MinBSPFeatures<VulkanSystem>>( + &mut self, + file: &M, + faces: &[u32], + ) -> Result<(), &'static str> { + // Prepare command buffer + let cmd_buffer = self.target_chain.prep_next_target( + &mut self.device, + self.vert_buffer.get_buffer(), + self.index_buffer.get_buffer(), + &self.renderpass, + &self.pipeline, + &self.pipeline_layout, + &mut self.camera, + )?; + + // Iterate over faces, copying them in and drawing groups that use the same texture chunk all at once. + let mut current_chunk = file.get_face(0).texture_idx as usize / 8; + let mut chunk_start = 0; + + let mut curr_vert_idx: usize = 0; + let mut curr_idx_idx: usize = 0; + + for face in faces.iter().map(|idx| file.get_face(*idx)) { + if current_chunk != face.texture_idx as usize / 8 { + // Last index was last of group, so draw it all. + let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); + descriptor_sets.push(self.texture_store.get_chunk_descriptor_set(current_chunk)); + unsafe { + cmd_buffer.bind_graphics_descriptor_sets( + &self.pipeline_layout, + 0, + descriptor_sets, + &[], + ); + cmd_buffer.draw_indexed( + chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, + 0, + 0..1, + ); + } + + // Next group of same-chunked faces starts here. + chunk_start = curr_idx_idx; + current_chunk = face.texture_idx as usize / 8; + } + + if face.face_type == FaceType::Polygon || face.face_type == FaceType::Mesh { + // 2 layers of indirection + let base = face.vertices_idx.start; + + for idx in face.meshverts_idx.clone().step_by(3) { + let start_idx: u16 = curr_vert_idx.try_into().unwrap(); + + for idx2 in idx..idx + 3 { + let vert = &file.resolve_meshvert(idx2 as u32, base); + let uv = Vector2::new(vert.tex.u[0], vert.tex.v[0]); + + let uvp = UVPoint(vert.position, face.texture_idx.try_into().unwrap(), uv); + self.vert_buffer[curr_vert_idx] = uvp; + + curr_vert_idx += 1; + } + + self.index_buffer[curr_idx_idx] = (start_idx, start_idx + 1, start_idx + 2); + + curr_idx_idx += 1; + + if curr_vert_idx >= INITIAL_VERT_SIZE.try_into().unwrap() + || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into().unwrap() + { + println!("out of vertex buffer space!"); + break; + } + } + } else { + // TODO: Other types of faces + } + + if curr_vert_idx >= INITIAL_VERT_SIZE.try_into().unwrap() + || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into().unwrap() + { + println!("out of vertex buffer space!"); + break; + } + } + + // Draw the final group of chunks + let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); + descriptor_sets.push(self.texture_store.get_chunk_descriptor_set(current_chunk)); + unsafe { + cmd_buffer.bind_graphics_descriptor_sets( + &self.pipeline_layout, + 0, + descriptor_sets, + &[], + ); + cmd_buffer.draw_indexed( + chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, + 0, + 0..1, + ); + } + + // Update our buffers before we actually start drawing + self.vert_buffer.commit( + &self.device, + &mut self.queue_group.queues[0], + &mut self.cmd_pool, + ); + + self.index_buffer.commit( + &self.device, + &mut self.queue_group.queues[0], + &mut self.cmd_pool, + ); + + // Send commands off to GPU + self.target_chain + .finish_and_submit_target(&mut self.queue_group.queues[0])?; + + Ok(()) + } + + /// Get current position of camera + pub fn camera_pos(&self) -> Vector3 { + self.camera.camera_pos() + } + + /// Move the camera by `delta` relative to its rotation + pub fn move_camera_relative(&mut self, delta: Vector3) { + self.camera.move_camera_relative(delta) + } + + /// Rotate the camera + /// `euler` should be euler angles in radians + pub fn rotate(&mut self, euler: Vector3) { + self.camera.rotate(euler) + } } impl<'a> core::ops::Drop for RenderingContext<'a> { - fn drop(&mut self) { - self.device.wait_idle().unwrap(); - - unsafe { - use core::ptr::read; - - ManuallyDrop::into_inner(read(&self.vert_buffer)).deactivate(&mut self.device); - ManuallyDrop::into_inner(read(&self.index_buffer)).deactivate(&mut self.device); - ManuallyDrop::into_inner(read(&self.texture_store)).deactivate(&mut self.device); - - ManuallyDrop::into_inner(read(&self.target_chain)).deactivate(&mut self.device, &mut self.cmd_pool); - - self.device.destroy_command_pool( - ManuallyDrop::into_inner(read(&self.cmd_pool)), - ); - self.device - .destroy_render_pass(ManuallyDrop::into_inner(read(&self.renderpass))); - - self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); - self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.fs_module))); - - self.device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); - - self.device - .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); - - self.instance - .destroy_surface(ManuallyDrop::into_inner(read(&self.surface))); - - ManuallyDrop::drop(&mut self.device); - } - } -}
\ No newline at end of file + fn drop(&mut self) { + self.device.wait_idle().unwrap(); + + unsafe { + use core::ptr::read; + + ManuallyDrop::into_inner(read(&self.vert_buffer)).deactivate(&mut self.device); + ManuallyDrop::into_inner(read(&self.index_buffer)).deactivate(&mut self.device); + ManuallyDrop::into_inner(read(&self.texture_store)).deactivate(&mut self.device); + + ManuallyDrop::into_inner(read(&self.target_chain)) + .deactivate(&mut self.device, &mut self.cmd_pool); + + self.device + .destroy_command_pool(ManuallyDrop::into_inner(read(&self.cmd_pool))); + self.device + .destroy_render_pass(ManuallyDrop::into_inner(read(&self.renderpass))); + + self.device + .destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); + self.device + .destroy_shader_module(ManuallyDrop::into_inner(read(&self.fs_module))); + + self.device + .destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); + + self.device + .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); + + self.instance + .destroy_surface(ManuallyDrop::into_inner(read(&self.surface))); + + ManuallyDrop::drop(&mut self.device); + } + } +} diff --git a/stockton-render/src/draw/macros.rs b/stockton-render/src/draw/macros.rs index d191d5b..65566a2 100644 --- a/stockton-render/src/draw/macros.rs +++ b/stockton-render/src/draw/macros.rs @@ -19,10 +19,10 @@ /// ``` /// // 0 is the binding value /// let attributes: Vec<AttributeDesc> = pipeline_vb_attributes!(0, -/// size_of::<f32>() * 3; Rgb32Sfloat -/// size_of::<f32>() * 2; Rg32Sfloat, -/// size_of::<u32>(); R32Sint -/// ); +/// size_of::<f32>() * 3; Rgb32Sfloat +/// size_of::<f32>() * 2; Rg32Sfloat, +/// size_of::<u32>(); R32Sint +/// ); /// ``` /// See the hal::pso::Format enum for possible types macro_rules! pipeline_vb_attributes { @@ -98,4 +98,4 @@ macro_rules! pipeline_vb_attributes { } }); }); -}
\ No newline at end of file +} diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs index af86b46..1151192 100644 --- a/stockton-render/src/draw/mod.rs +++ b/stockton-render/src/draw/mod.rs @@ -19,10 +19,10 @@ pub mod target; #[macro_use] mod macros; -mod context; mod buffer; -mod texture; mod camera; +mod context; +mod texture; pub use self::context::RenderingContext; pub use self::context::UVPoint; diff --git a/stockton-render/src/draw/target.rs b/stockton-render/src/draw/target.rs index 3d0ecdf..5e0ade0 100644 --- a/stockton-render/src/draw/target.rs +++ b/stockton-render/src/draw/target.rs @@ -14,394 +14,498 @@ // with this program. If not, see <http://www.gnu.org/licenses/>. //! Resources needed for drawing on the screen, including sync objects +use super::{camera::WorkingCamera, texture::image::LoadedImage}; use crate::types::*; -use super::{ - camera::WorkingCamera, - texture::image::LoadedImage -}; -use core::{ - iter::once, - mem::ManuallyDrop -}; +use core::{iter::once, mem::ManuallyDrop}; use arrayvec::ArrayVec; use hal::{ - prelude::*, - queue::Submission, - pso::Viewport, - format::{ChannelType, Format, Swizzle}, - image::{Extent, ViewKind, Usage as ImgUsage}, - window::{CompositeAlphaMode, PresentMode, SwapchainConfig, Extent2D} + format::{ChannelType, Format, Swizzle}, + image::{Extent, Usage as ImgUsage, ViewKind}, + prelude::*, + pso::Viewport, + queue::Submission, + window::{CompositeAlphaMode, Extent2D, PresentMode, SwapchainConfig}, }; /// Defines the colour range we use. const COLOR_RANGE: hal::image::SubresourceRange = hal::image::SubresourceRange { - aspects: hal::format::Aspects::COLOR, - levels: 0..1, - layers: 0..1, + aspects: hal::format::Aspects::COLOR, + levels: 0..1, + layers: 0..1, }; #[derive(Debug, Clone)] pub struct SwapchainProperties { - pub format: Format, - pub depth_format: Format, - pub present_mode: PresentMode, - pub composite_alpha_mode: CompositeAlphaMode, - pub viewport: Viewport, - pub extent: Extent + pub format: Format, + pub depth_format: Format, + pub present_mode: PresentMode, + pub composite_alpha_mode: CompositeAlphaMode, + pub viewport: Viewport, + pub extent: Extent, } impl SwapchainProperties { - pub fn find_best(adapter: &Adapter, surface: &Surface) -> Result<SwapchainProperties, ()> { - let caps = surface.capabilities(&adapter.physical_device); - let formats = surface.supported_formats(&adapter.physical_device); - - // Find which settings we'll actually use based on preset preferences - let format = formats.clone().map_or(Format::Rgba8Srgb, |formats| { - formats.iter() - .find(|format| format.base_format().1 == ChannelType::Srgb) - .map(|format| *format) - .unwrap_or(formats[0]) - }); - - let depth_format = *[Format::D32SfloatS8Uint, Format::D24UnormS8Uint, Format::D32Sfloat].iter().find(|format| { - use hal::format::ImageFeature; - - format.is_depth() && adapter.physical_device.format_properties(Some(**format)).optimal_tiling.contains(ImageFeature::DEPTH_STENCIL_ATTACHMENT) - }).ok_or_else(|| ())?; - - let present_mode = { - [PresentMode::MAILBOX, PresentMode::FIFO, PresentMode::RELAXED, PresentMode::IMMEDIATE] - .iter() - .cloned() - .find(|pm| caps.present_modes.contains(*pm)) - .ok_or(())? - }; - let composite_alpha_mode = { - [CompositeAlphaMode::OPAQUE, CompositeAlphaMode::INHERIT, CompositeAlphaMode::PREMULTIPLIED, CompositeAlphaMode::POSTMULTIPLIED] - .iter() - .cloned() - .find(|ca| caps.composite_alpha_modes.contains(*ca)) - .ok_or(())? - }; - - let extent = caps.extents.end().to_extent(); // Size - let viewport = Viewport { - rect: extent.rect(), - depth: 0.0..1.0 - }; - - Ok(SwapchainProperties {format, depth_format, present_mode, composite_alpha_mode, extent, viewport}) - } + pub fn find_best(adapter: &Adapter, surface: &Surface) -> Result<SwapchainProperties, ()> { + let caps = surface.capabilities(&adapter.physical_device); + let formats = surface.supported_formats(&adapter.physical_device); + + // Find which settings we'll actually use based on preset preferences + let format = formats.map_or(Format::Rgba8Srgb, |formats| { + formats + .iter() + .find(|format| format.base_format().1 == ChannelType::Srgb) + .copied() + .unwrap_or(formats[0]) + }); + + let depth_format = *[ + Format::D32SfloatS8Uint, + Format::D24UnormS8Uint, + Format::D32Sfloat, + ] + .iter() + .find(|format| { + use hal::format::ImageFeature; + + format.is_depth() + && adapter + .physical_device + .format_properties(Some(**format)) + .optimal_tiling + .contains(ImageFeature::DEPTH_STENCIL_ATTACHMENT) + }) + .ok_or_else(|| ())?; + + let present_mode = { + [ + PresentMode::MAILBOX, + PresentMode::FIFO, + PresentMode::RELAXED, + PresentMode::IMMEDIATE, + ] + .iter() + .cloned() + .find(|pm| caps.present_modes.contains(*pm)) + .ok_or(())? + }; + let composite_alpha_mode = { + [ + CompositeAlphaMode::OPAQUE, + CompositeAlphaMode::INHERIT, + CompositeAlphaMode::PREMULTIPLIED, + CompositeAlphaMode::POSTMULTIPLIED, + ] + .iter() + .cloned() + .find(|ca| caps.composite_alpha_modes.contains(*ca)) + .ok_or(())? + }; + + let extent = caps.extents.end().to_extent(); // Size + let viewport = Viewport { + rect: extent.rect(), + depth: 0.0..1.0, + }; + + Ok(SwapchainProperties { + format, + depth_format, + present_mode, + composite_alpha_mode, + extent, + viewport, + }) + } } pub struct TargetChain { - /// Swapchain we're targeting - pub swapchain: ManuallyDrop<Swapchain>, + /// Swapchain we're targeting + pub swapchain: ManuallyDrop<Swapchain>, - pub properties: SwapchainProperties, + pub properties: SwapchainProperties, - /// The depth buffer/image used for drawing - pub depth_buffer: ManuallyDrop<LoadedImage>, + /// The depth buffer/image used for drawing + pub depth_buffer: ManuallyDrop<LoadedImage>, - /// Resources tied to each target frame in the swapchain - pub targets: Box<[TargetResources]>, + /// Resources tied to each target frame in the swapchain + pub targets: Box<[TargetResources]>, - /// The last target drawn to - last_drawn: usize, + /// The last target drawn to + last_drawn: usize, - /// Last image index of the swapchain drawn to - last_image_index: u32, + /// Last image index of the swapchain drawn to + last_image_index: u32, } impl TargetChain { - pub fn new(device: &mut Device, adapter: &Adapter, surface: &mut Surface, renderpass: &RenderPass, cmd_pool: &mut CommandPool, properties: SwapchainProperties, old_swapchain: Option<Swapchain>) -> Result<TargetChain, TargetChainCreationError> { - let caps = surface.capabilities(&adapter.physical_device); - - // Number of frames to pre-render - let image_count = if properties.present_mode == PresentMode::MAILBOX { - ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) - } else { - ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) - }; - - // Swap config - let swap_config = SwapchainConfig { - present_mode: properties.present_mode, - composite_alpha_mode: properties.composite_alpha_mode, - format: properties.format, - extent: Extent2D { width: properties.extent.width, height: properties.extent.height }, - image_count, - image_layers: 1, - image_usage: ImgUsage::COLOR_ATTACHMENT, - }; - - // Swapchain - let (swapchain, mut backbuffer) = unsafe { - device.create_swapchain(surface, swap_config, old_swapchain) - .map_err(|_| TargetChainCreationError::Todo)? - }; - - let depth_buffer: LoadedImage = { - use hal::image::SubresourceRange; - use hal::format::Aspects; - - LoadedImage::new(device, adapter, properties.depth_format, ImgUsage::DEPTH_STENCIL_ATTACHMENT, SubresourceRange { - aspects: Aspects::DEPTH, - levels: 0..1, - layers: 0..1, - },properties.extent.width as usize, properties.extent.height as usize).map_err(|_| TargetChainCreationError::Todo) - }?; - - let mut targets: Vec<TargetResources> = Vec::with_capacity(backbuffer.len()); - for image in backbuffer.drain(..) { - targets.push(TargetResources::new(device, cmd_pool, renderpass, image, &(*depth_buffer.image_view), properties.extent, properties.format) - .map_err(|_| TargetChainCreationError::Todo)?); - } - - Ok(TargetChain { - swapchain: ManuallyDrop::new(swapchain), - targets: targets.into_boxed_slice(), - depth_buffer: ManuallyDrop::new(depth_buffer), - properties, - last_drawn: (image_count - 1) as usize, // This means the next one to be used is index 0 - last_image_index: 0 - }) - } - - pub fn deactivate(self, device: &mut Device, cmd_pool: &mut CommandPool) -> () { - use core::ptr::read; - unsafe { - ManuallyDrop::into_inner(read(&self.depth_buffer)).deactivate(device); - - for i in 0..self.targets.len() { - read(&self.targets[i]).deactivate(device, cmd_pool); - } - - device.destroy_swapchain(ManuallyDrop::into_inner(read(&self.swapchain))); - } - } - - pub fn deactivate_with_recyling(self, device: &mut Device, cmd_pool: &mut CommandPool) -> Swapchain { - use core::ptr::read; - unsafe { - ManuallyDrop::into_inner(read(&self.depth_buffer)).deactivate(device); - - for i in 0..self.targets.len() { - read(&self.targets[i]).deactivate(device, cmd_pool); - } - } - - return unsafe { ManuallyDrop::into_inner(read(&self.swapchain)) }; - } - - pub fn prep_next_target<'a>(&'a mut self, device: &mut Device, vert_buffer: &Buffer, index_buffer: &Buffer, renderpass: &RenderPass, pipeline: &GraphicsPipeline, pipeline_layout: &PipelineLayout, camera: &mut WorkingCamera) -> Result<&'a mut crate::types::CommandBuffer, &'static str> { - self.last_drawn = (self.last_drawn + 1) % self.targets.len(); - - let target = &mut self.targets[self.last_drawn]; - - // Get the image - let (image_index, _) = unsafe { - self.swapchain - .acquire_image(core::u64::MAX, Some(&target.get_image), None) - .map_err(|_| "FrameError::AcquireError")? - }; - - self.last_image_index = image_index; - - // Make sure whatever was last using this has finished - unsafe { - device - .wait_for_fence(&target.present_complete, core::u64::MAX) - .map_err(|_| "FrameError::SyncObjectError")?; - device - .reset_fence(&target.present_complete) - .map_err(|_| "FrameError::SyncObjectError")?; - }; - - // Record commands - unsafe { - use hal::buffer::{IndexBufferView, SubRange}; - use hal::command::{SubpassContents, CommandBufferFlags, ClearValue, ClearColor, ClearDepthStencil}; - use hal::pso::ShaderStageFlags; - - // Colour to clear window to - let clear_values = [ClearValue { - color: ClearColor { - float32: [0.0, 0.0, 0.0, 1.0] - } - }, ClearValue { - depth_stencil: ClearDepthStencil { - depth: 1.0, - stencil: 0 - } - }]; - - // Get references to our buffers - let (vbufs, ibuf) = { - let vbufref: &<back::Backend as hal::Backend>::Buffer = vert_buffer; - - let vbufs: ArrayVec<[_; 1]> = [(vbufref, SubRange::WHOLE)].into(); - let ibuf = index_buffer; - - (vbufs, ibuf) - }; - - target.cmd_buffer.begin_primary(CommandBufferFlags::EMPTY); - // Main render pass / pipeline - target.cmd_buffer.begin_render_pass( - renderpass, - &target.framebuffer, - self.properties.viewport.rect, - clear_values.iter(), - SubpassContents::Inline - ); - target.cmd_buffer.bind_graphics_pipeline(&pipeline); - - // VP Matrix - let vp = camera.get_matrix().as_slice(); - let vp = std::mem::transmute::<&[f32], &[u32]>(vp); - - target.cmd_buffer.push_graphics_constants( - &pipeline_layout, - ShaderStageFlags::VERTEX, - 0, - vp); - - // Bind buffers - target.cmd_buffer.bind_vertex_buffers(0, vbufs); - target.cmd_buffer.bind_index_buffer(IndexBufferView { - buffer: ibuf, - range: SubRange::WHOLE, - index_type: hal::IndexType::U16 - }); - }; - - Ok(&mut target.cmd_buffer) - } - - pub fn finish_and_submit_target(&mut self, command_queue: &mut CommandQueue) -> Result<(), &'static str> { - let target = &mut self.targets[self.last_drawn]; - - unsafe { - target.cmd_buffer.end_render_pass(); - target.cmd_buffer.finish(); - } - - // Make submission object - let command_buffers: std::iter::Once<&CommandBuffer> = once(&target.cmd_buffer); - let wait_semaphores: std::iter::Once<(&Semaphore, hal::pso::PipelineStage)> = once((&target.get_image, hal::pso::PipelineStage::COLOR_ATTACHMENT_OUTPUT)); - let signal_semaphores: std::iter::Once<&Semaphore> = once(&target.render_complete); - - let present_wait_semaphores: std::iter::Once<&Semaphore> = once(&target.render_complete); - - let submission = Submission { - command_buffers, - wait_semaphores, - signal_semaphores, - }; - - // Submit it - unsafe { - command_queue.submit(submission, Some(&target.present_complete)); - self.swapchain - .present(command_queue, self.last_image_index as u32, present_wait_semaphores) - .map_err(|_| "FrameError::PresentError")?; - }; - - // TODO - Ok(()) - } + pub fn new( + device: &mut Device, + adapter: &Adapter, + surface: &mut Surface, + renderpass: &RenderPass, + cmd_pool: &mut CommandPool, + properties: SwapchainProperties, + old_swapchain: Option<Swapchain>, + ) -> Result<TargetChain, TargetChainCreationError> { + let caps = surface.capabilities(&adapter.physical_device); + + // Number of frames to pre-render + let image_count = if properties.present_mode == PresentMode::MAILBOX { + ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) + } else { + ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) + }; + + // Swap config + let swap_config = SwapchainConfig { + present_mode: properties.present_mode, + composite_alpha_mode: properties.composite_alpha_mode, + format: properties.format, + extent: Extent2D { + width: properties.extent.width, + height: properties.extent.height, + }, + image_count, + image_layers: 1, + image_usage: ImgUsage::COLOR_ATTACHMENT, + }; + + // Swapchain + let (swapchain, mut backbuffer) = unsafe { + device + .create_swapchain(surface, swap_config, old_swapchain) + .map_err(|_| TargetChainCreationError::Todo)? + }; + + let depth_buffer: LoadedImage = { + use hal::format::Aspects; + use hal::image::SubresourceRange; + + LoadedImage::new( + device, + adapter, + properties.depth_format, + ImgUsage::DEPTH_STENCIL_ATTACHMENT, + SubresourceRange { + aspects: Aspects::DEPTH, + levels: 0..1, + layers: 0..1, + }, + properties.extent.width as usize, + properties.extent.height as usize, + ) + .map_err(|_| TargetChainCreationError::Todo) + }?; + + let mut targets: Vec<TargetResources> = Vec::with_capacity(backbuffer.len()); + for image in backbuffer.drain(..) { + targets.push( + TargetResources::new( + device, + cmd_pool, + renderpass, + image, + &(*depth_buffer.image_view), + properties.extent, + properties.format, + ) + .map_err(|_| TargetChainCreationError::Todo)?, + ); + } + + Ok(TargetChain { + swapchain: ManuallyDrop::new(swapchain), + targets: targets.into_boxed_slice(), + depth_buffer: ManuallyDrop::new(depth_buffer), + properties, + last_drawn: (image_count - 1) as usize, // This means the next one to be used is index 0 + last_image_index: 0, + }) + } + + pub fn deactivate(self, device: &mut Device, cmd_pool: &mut CommandPool) { + use core::ptr::read; + unsafe { + ManuallyDrop::into_inner(read(&self.depth_buffer)).deactivate(device); + + for i in 0..self.targets.len() { + read(&self.targets[i]).deactivate(device, cmd_pool); + } + + device.destroy_swapchain(ManuallyDrop::into_inner(read(&self.swapchain))); + } + } + + pub fn deactivate_with_recyling( + self, + device: &mut Device, + cmd_pool: &mut CommandPool, + ) -> Swapchain { + use core::ptr::read; + unsafe { + ManuallyDrop::into_inner(read(&self.depth_buffer)).deactivate(device); + + for i in 0..self.targets.len() { + read(&self.targets[i]).deactivate(device, cmd_pool); + } + } + + unsafe { ManuallyDrop::into_inner(read(&self.swapchain)) } + } + + pub fn prep_next_target<'a>( + &'a mut self, + device: &mut Device, + vert_buffer: &Buffer, + index_buffer: &Buffer, + renderpass: &RenderPass, + pipeline: &GraphicsPipeline, + pipeline_layout: &PipelineLayout, + camera: &mut WorkingCamera, + ) -> Result<&'a mut crate::types::CommandBuffer, &'static str> { + self.last_drawn = (self.last_drawn + 1) % self.targets.len(); + + let target = &mut self.targets[self.last_drawn]; + + // Get the image + let (image_index, _) = unsafe { + self.swapchain + .acquire_image(core::u64::MAX, Some(&target.get_image), None) + .map_err(|_| "FrameError::AcquireError")? + }; + + self.last_image_index = image_index; + + // Make sure whatever was last using this has finished + unsafe { + device + .wait_for_fence(&target.present_complete, core::u64::MAX) + .map_err(|_| "FrameError::SyncObjectError")?; + device + .reset_fence(&target.present_complete) + .map_err(|_| "FrameError::SyncObjectError")?; + }; + + // Record commands + unsafe { + use hal::buffer::{IndexBufferView, SubRange}; + use hal::command::{ + ClearColor, ClearDepthStencil, ClearValue, CommandBufferFlags, SubpassContents, + }; + use hal::pso::ShaderStageFlags; + + // Colour to clear window to + let clear_values = [ + ClearValue { + color: ClearColor { + float32: [0.0, 0.0, 0.0, 1.0], + }, + }, + ClearValue { + depth_stencil: ClearDepthStencil { + depth: 1.0, + stencil: 0, + }, + }, + ]; + + // Get references to our buffers + let (vbufs, ibuf) = { + let vbufref: &<back::Backend as hal::Backend>::Buffer = vert_buffer; + + let vbufs: ArrayVec<[_; 1]> = [(vbufref, SubRange::WHOLE)].into(); + let ibuf = index_buffer; + + (vbufs, ibuf) + }; + + target.cmd_buffer.begin_primary(CommandBufferFlags::EMPTY); + // Main render pass / pipeline + target.cmd_buffer.begin_render_pass( + renderpass, + &target.framebuffer, + self.properties.viewport.rect, + clear_values.iter(), + SubpassContents::Inline, + ); + target.cmd_buffer.bind_graphics_pipeline(&pipeline); + + // VP Matrix + let vp = camera.get_matrix().as_slice(); + let vp = &*(vp as *const [f32] as *const [u32]); + + target.cmd_buffer.push_graphics_constants( + &pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + vp, + ); + + // Bind buffers + target.cmd_buffer.bind_vertex_buffers(0, vbufs); + target.cmd_buffer.bind_index_buffer(IndexBufferView { + buffer: ibuf, + range: SubRange::WHOLE, + index_type: hal::IndexType::U16, + }); + }; + + Ok(&mut target.cmd_buffer) + } + + pub fn finish_and_submit_target( + &mut self, + command_queue: &mut CommandQueue, + ) -> Result<(), &'static str> { + let target = &mut self.targets[self.last_drawn]; + + unsafe { + target.cmd_buffer.end_render_pass(); + target.cmd_buffer.finish(); + } + + // Make submission object + let command_buffers: std::iter::Once<&CommandBuffer> = once(&target.cmd_buffer); + let wait_semaphores: std::iter::Once<(&Semaphore, hal::pso::PipelineStage)> = once(( + &target.get_image, + hal::pso::PipelineStage::COLOR_ATTACHMENT_OUTPUT, + )); + let signal_semaphores: std::iter::Once<&Semaphore> = once(&target.render_complete); + + let present_wait_semaphores: std::iter::Once<&Semaphore> = once(&target.render_complete); + + let submission = Submission { + command_buffers, + wait_semaphores, + signal_semaphores, + }; + + // Submit it + unsafe { + command_queue.submit(submission, Some(&target.present_complete)); + self.swapchain + .present( + command_queue, + self.last_image_index as u32, + present_wait_semaphores, + ) + .map_err(|_| "FrameError::PresentError")?; + }; + + // TODO + Ok(()) + } } /// Resources for a single target frame, including sync objects pub struct TargetResources { - /// Command buffer to use when drawing - pub cmd_buffer: ManuallyDrop<CommandBuffer>, + /// Command buffer to use when drawing + pub cmd_buffer: ManuallyDrop<CommandBuffer>, - /// The image for this frame - pub image: ManuallyDrop<Image>, + /// The image for this frame + pub image: ManuallyDrop<Image>, - /// Imageviews for this frame - pub imageview: ManuallyDrop<ImageView>, + /// Imageviews for this frame + pub imageview: ManuallyDrop<ImageView>, - /// Framebuffer for this frame - pub framebuffer: ManuallyDrop<Framebuffer>, + /// Framebuffer for this frame + pub framebuffer: ManuallyDrop<Framebuffer>, - // Sync objects + // Sync objects + /// Triggered when the image is ready to draw to + pub get_image: ManuallyDrop<Semaphore>, - /// Triggered when the image is ready to draw to - pub get_image: ManuallyDrop<Semaphore>, + /// Triggered when rendering is done + pub render_complete: ManuallyDrop<Semaphore>, - /// Triggered when rendering is done - pub render_complete: ManuallyDrop<Semaphore>, - - /// Triggered when the image is on screen - pub present_complete: ManuallyDrop<Fence> + /// Triggered when the image is on screen + pub present_complete: ManuallyDrop<Fence>, } - impl TargetResources { - pub fn new(device: &mut Device, cmd_pool: &mut CommandPool, renderpass: &RenderPass, image: Image, depth_pass: &ImageView, extent: Extent, format: Format) -> Result<TargetResources, TargetResourcesCreationError> { - // Command Buffer - let cmd_buffer = unsafe { cmd_pool.allocate_one(hal::command::Level::Primary) }; - - // ImageView - let imageview = unsafe { device.create_image_view( - &image, - ViewKind::D2, - format, - Swizzle::NO, - COLOR_RANGE.clone(), - ).map_err(|e| TargetResourcesCreationError::ImageViewError (e))? }; - - // Framebuffer - let framebuffer = unsafe { device.create_framebuffer( - &renderpass, - once(&imageview).chain(once(depth_pass)), - extent - ).map_err(|_| TargetResourcesCreationError::FrameBufferNoMemory)? }; - - // Sync objects - let get_image = device.create_semaphore().map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?; - let render_complete = device.create_semaphore().map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?; - let present_complete = device.create_fence(true).map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?; - - Ok(TargetResources { - cmd_buffer: ManuallyDrop::new(cmd_buffer), - image: ManuallyDrop::new(image), - imageview: ManuallyDrop::new(imageview), - framebuffer: ManuallyDrop::new(framebuffer), - get_image: ManuallyDrop::new(get_image), - render_complete: ManuallyDrop::new(render_complete), - present_complete: ManuallyDrop::new(present_complete) - }) - } - - pub fn deactivate(self, device: &mut Device, cmd_pool: &mut CommandPool) -> () { - use core::ptr::read; - unsafe { - cmd_pool.free(once(ManuallyDrop::into_inner(read(&self.cmd_buffer)))); - - device.destroy_framebuffer(ManuallyDrop::into_inner(read(&self.framebuffer))); - device.destroy_image_view(ManuallyDrop::into_inner(read(&self.imageview))); - - device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.get_image))); - device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.render_complete))); - device.destroy_fence(ManuallyDrop::into_inner(read(&self.present_complete))); - } - } + pub fn new( + device: &mut Device, + cmd_pool: &mut CommandPool, + renderpass: &RenderPass, + image: Image, + depth_pass: &ImageView, + extent: Extent, + format: Format, + ) -> Result<TargetResources, TargetResourcesCreationError> { + // Command Buffer + let cmd_buffer = unsafe { cmd_pool.allocate_one(hal::command::Level::Primary) }; + + // ImageView + let imageview = unsafe { + device + .create_image_view( + &image, + ViewKind::D2, + format, + Swizzle::NO, + COLOR_RANGE.clone(), + ) + .map_err(TargetResourcesCreationError::ImageViewError)? + }; + + // Framebuffer + let framebuffer = unsafe { + device + .create_framebuffer( + &renderpass, + once(&imageview).chain(once(depth_pass)), + extent, + ) + .map_err(|_| TargetResourcesCreationError::FrameBufferNoMemory)? + }; + + // Sync objects + let get_image = device + .create_semaphore() + .map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?; + let render_complete = device + .create_semaphore() + .map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?; + let present_complete = device + .create_fence(true) + .map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?; + + Ok(TargetResources { + cmd_buffer: ManuallyDrop::new(cmd_buffer), + image: ManuallyDrop::new(image), + imageview: ManuallyDrop::new(imageview), + framebuffer: ManuallyDrop::new(framebuffer), + get_image: ManuallyDrop::new(get_image), + render_complete: ManuallyDrop::new(render_complete), + present_complete: ManuallyDrop::new(present_complete), + }) + } + + pub fn deactivate(self, device: &mut Device, cmd_pool: &mut CommandPool) { + use core::ptr::read; + unsafe { + cmd_pool.free(once(ManuallyDrop::into_inner(read(&self.cmd_buffer)))); + + device.destroy_framebuffer(ManuallyDrop::into_inner(read(&self.framebuffer))); + device.destroy_image_view(ManuallyDrop::into_inner(read(&self.imageview))); + + device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.get_image))); + device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.render_complete))); + device.destroy_fence(ManuallyDrop::into_inner(read(&self.present_complete))); + } + } } #[derive(Debug)] pub enum TargetChainCreationError { - Todo + Todo, } #[derive(Debug)] pub enum TargetResourcesCreationError { - ImageViewError (hal::image::ViewCreationError), - FrameBufferNoMemory, - SyncObjectsNoMemory + ImageViewError(hal::image::ViewCreationError), + FrameBufferNoMemory, + SyncObjectsNoMemory, } diff --git a/stockton-render/src/draw/texture/chunk.rs b/stockton-render/src/draw/texture/chunk.rs index 40eb21e..02f2dc5 100644 --- a/stockton-render/src/draw/texture/chunk.rs +++ b/stockton-render/src/draw/texture/chunk.rs @@ -16,22 +16,17 @@ //! A chunk of textures is an array of textures, the size of which is known at compile time. //! This reduces the number of times we need to re-bind our descriptor sets -use image::{Rgba, RgbaImage}; use hal::prelude::*; +use image::{Rgba, RgbaImage}; -use core::{ - mem::{replace} -}; -use std::ops::{Range, Deref}; +use core::mem::replace; +use std::ops::{Deref, Range}; -use crate::{ - types::*, - error -}; +use crate::{error, types::*}; -use log::debug; -use super::resolver::TextureResolver; use super::image::SampledImage; +use super::resolver::TextureResolver; +use log::debug; use stockton_levels::prelude::*; /// The size of a chunk. Needs to match up with the fragment shader @@ -39,125 +34,135 @@ pub const CHUNK_SIZE: usize = 8; /// An array of textures pub struct TextureChunk { - pub(crate) descriptor_set: DescriptorSet, - sampled_images: Vec<SampledImage>, + pub(crate) descriptor_set: DescriptorSet, + sampled_images: Vec<SampledImage>, } impl TextureChunk { - /// Create a new texture chunk and load in the textures specified by `range` from `file` using `resolver` - /// Can error if the descriptor pool is too small or if a texture isn't found - pub fn new<T: HasTextures, R: TextureResolver>(device: &mut Device, - adapter: &mut Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - pool: &mut DescriptorPool, - layout: &DescriptorSetLayout, - file: &T, range: Range<u32>, - resolver: &mut R) -> Result<TextureChunk, error::CreationError> { - - // - let descriptor_set = unsafe { - pool.allocate_set(&layout).map_err(|e| { - println!("{:?}", e); - error::CreationError::OutOfMemoryError - })? - }; - - let mut store = TextureChunk { - descriptor_set: descriptor_set, - sampled_images: Vec::with_capacity(CHUNK_SIZE), - }; - - let mut local_idx = 0; - - debug!("Created descriptor set"); - for tex_idx in range { - debug!("Loading tex {}", local_idx + 1); - let tex = file.get_texture(tex_idx); - if let Some(img) = resolver.resolve(tex) { - store.put_texture(img, local_idx, - device, adapter, - command_queue, command_pool).unwrap(); - } else { - // Texture not found. For now, tear everything down. - store.deactivate(device); - - return Err(error::CreationError::BadDataError); - } - - local_idx += 1; - } - - // Pad out the end if needed - while local_idx < CHUNK_SIZE { - debug!("Putting a placeholder in slot {}", local_idx); - store.put_texture(RgbaImage::from_pixel(1, 1, Rgba ([0, 0, 0, 1])), local_idx, - device, adapter, - command_queue, command_pool).unwrap(); - - local_idx += 1; - } - - Ok(store) - } - - - pub fn put_texture(&mut self, image: RgbaImage, - idx: usize, - device: &mut Device, - adapter: &mut Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool) -> Result<(), &'static str>{ - - // Load the image - let texture = SampledImage::load_into_new( - image, - device, - adapter, - command_queue, - command_pool, - hal::format::Format::Rgba8Srgb, // TODO - hal::image::Usage::empty() - )?; - - // Write it to the descriptor set - unsafe { - use hal::pso::{DescriptorSetWrite, Descriptor}; - use hal::image::Layout; - - device.write_descriptor_sets(vec![ - DescriptorSetWrite { - set: &self.descriptor_set, - binding: 0, - array_offset: idx, - descriptors: Some(Descriptor::Image( - texture.image.image_view.deref(), - Layout::ShaderReadOnlyOptimal - )), - }, - DescriptorSetWrite { - set: &self.descriptor_set, - binding: 1, - array_offset: idx, - descriptors: Some(Descriptor::Sampler(texture.sampler.deref())), - }, - ]); - }; - - // Store it so we can safely deactivate it when we need to - // Deactivate the old image if we need to - if idx < self.sampled_images.len() { - replace(&mut self.sampled_images[idx], texture).deactivate(device); - } else { - self.sampled_images.push(texture); - } - - Ok(()) - } - - pub fn deactivate(mut self, device: &mut Device) -> () { - for img in self.sampled_images.drain(..) { - img.deactivate(device); - } - } -}
\ No newline at end of file + /// Create a new texture chunk and load in the textures specified by `range` from `file` using `resolver` + /// Can error if the descriptor pool is too small or if a texture isn't found + pub fn new<T: HasTextures, R: TextureResolver>( + device: &mut Device, + adapter: &mut Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + pool: &mut DescriptorPool, + layout: &DescriptorSetLayout, + file: &T, + range: Range<u32>, + resolver: &mut R, + ) -> Result<TextureChunk, error::CreationError> { + // + let descriptor_set = unsafe { + pool.allocate_set(&layout).map_err(|e| { + println!("{:?}", e); + error::CreationError::OutOfMemoryError + })? + }; + + let mut store = TextureChunk { + descriptor_set, + sampled_images: Vec::with_capacity(CHUNK_SIZE), + }; + + let mut local_idx = 0; + + debug!("Created descriptor set"); + for tex_idx in range { + debug!("Loading tex {}", local_idx + 1); + let tex = file.get_texture(tex_idx); + if let Some(img) = resolver.resolve(tex) { + store + .put_texture(img, local_idx, device, adapter, command_queue, command_pool) + .unwrap(); + } else { + // Texture not found. For now, tear everything down. + store.deactivate(device); + + return Err(error::CreationError::BadDataError); + } + + local_idx += 1; + } + + // Pad out the end if needed + while local_idx < CHUNK_SIZE { + debug!("Putting a placeholder in slot {}", local_idx); + store + .put_texture( + RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])), + local_idx, + device, + adapter, + command_queue, + command_pool, + ) + .unwrap(); + + local_idx += 1; + } + + Ok(store) + } + + pub fn put_texture( + &mut self, + image: RgbaImage, + idx: usize, + device: &mut Device, + adapter: &mut Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + ) -> Result<(), &'static str> { + // Load the image + let texture = SampledImage::load_into_new( + image, + device, + adapter, + command_queue, + command_pool, + hal::format::Format::Rgba8Srgb, // TODO + hal::image::Usage::empty(), + )?; + + // Write it to the descriptor set + unsafe { + use hal::image::Layout; + use hal::pso::{Descriptor, DescriptorSetWrite}; + + device.write_descriptor_sets(vec![ + DescriptorSetWrite { + set: &self.descriptor_set, + binding: 0, + array_offset: idx, + descriptors: Some(Descriptor::Image( + texture.image.image_view.deref(), + Layout::ShaderReadOnlyOptimal, + )), + }, + DescriptorSetWrite { + set: &self.descriptor_set, + binding: 1, + array_offset: idx, + descriptors: Some(Descriptor::Sampler(texture.sampler.deref())), + }, + ]); + }; + + // Store it so we can safely deactivate it when we need to + // Deactivate the old image if we need to + if idx < self.sampled_images.len() { + replace(&mut self.sampled_images[idx], texture).deactivate(device); + } else { + self.sampled_images.push(texture); + } + + Ok(()) + } + + pub fn deactivate(mut self, device: &mut Device) { + for img in self.sampled_images.drain(..) { + img.deactivate(device); + } + } +} diff --git a/stockton-render/src/draw/texture/image.rs b/stockton-render/src/draw/texture/image.rs index 530628a..0b8c64e 100644 --- a/stockton-render/src/draw/texture/image.rs +++ b/stockton-render/src/draw/texture/image.rs @@ -14,23 +14,20 @@ // with this program. If not, see <http://www.gnu.org/licenses/>. use core::{ - ptr::copy_nonoverlapping, - mem::{size_of, ManuallyDrop} + mem::{size_of, ManuallyDrop}, + ptr::copy_nonoverlapping, }; -use std::{ - iter::once, - convert::TryInto -}; -use image::RgbaImage; use hal::{ - MemoryTypeId, - buffer::Usage as BufUsage, - format::{Format, Swizzle, Aspects}, - image::{ViewKind, SubresourceRange, Usage as ImgUsage}, - queue::Submission, - memory::{Properties as MemProperties, Dependencies as MemDependencies, Segment}, - prelude::*, + buffer::Usage as BufUsage, + format::{Aspects, Format, Swizzle}, + image::{SubresourceRange, Usage as ImgUsage, ViewKind}, + memory::{Dependencies as MemDependencies, Properties as MemProperties, Segment}, + prelude::*, + queue::Submission, + MemoryTypeId, }; +use image::RgbaImage; +use std::{convert::TryInto, iter::once}; use crate::types::*; use draw::buffer::create_buffer; @@ -40,297 +37,367 @@ const PIXEL_SIZE: usize = size_of::<image::Rgba<u8>>(); /// Holds an image that's loaded into GPU memory and can be sampled from pub struct LoadedImage { - /// The GPU Image handle - image: ManuallyDrop<Image>, + /// The GPU Image handle + image: ManuallyDrop<Image>, - /// The full view of the image - pub image_view: ManuallyDrop<ImageView>, + /// The full view of the image + pub image_view: ManuallyDrop<ImageView>, - /// The memory backing the image - memory: ManuallyDrop<Memory> + /// The memory backing the image + memory: ManuallyDrop<Memory>, } -pub fn create_image_view(device: &mut Device, adapter: &Adapter, format: Format, usage: ImgUsage, width: usize, height: usize) -> Result<(Memory, Image), &'static str> { - // Round up the size to align properly - let initial_row_size = PIXEL_SIZE * width; - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - - let row_size = ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - debug_assert!(row_size as usize >= initial_row_size); - - // Make the image - let mut image_ref = unsafe { - use hal::image::{Kind, Tiling, ViewCapabilities}; - - device.create_image( - Kind::D2(width as u32, height as u32, 1, 1), - 1, - format, - Tiling::Optimal, - usage, - ViewCapabilities::empty() - ) - }.map_err(|_| "Couldn't create image")?; - - // Allocate memory - let memory = unsafe { - let requirements = device.get_image_requirements(&image_ref); - - let memory_type_id = adapter.physical_device - .memory_properties().memory_types - .iter().enumerate() - .find(|&(id, memory_type)| { - requirements.type_mask & (1 << id) != 0 && memory_type.properties.contains(MemProperties::DEVICE_LOCAL) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or("Couldn't find a memory type for image memory")?; - - let memory = device - .allocate_memory(memory_type_id, requirements.size) - .map_err(|_| "Couldn't allocate image memory")?; - - device.bind_image_memory(&memory, 0, &mut image_ref) - .map_err(|_| "Couldn't bind memory to image")?; - - Ok(memory) - }?; - - Ok((memory, image_ref)) - +pub fn create_image_view( + device: &mut Device, + adapter: &Adapter, + format: Format, + usage: ImgUsage, + width: usize, + height: usize, +) -> Result<(Memory, Image), &'static str> { + // Round up the size to align properly + let initial_row_size = PIXEL_SIZE * width; + let limits = adapter.physical_device.limits(); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + + let row_size = ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + debug_assert!(row_size as usize >= initial_row_size); + + // Make the image + let mut image_ref = unsafe { + use hal::image::{Kind, Tiling, ViewCapabilities}; + + device.create_image( + Kind::D2(width as u32, height as u32, 1, 1), + 1, + format, + Tiling::Optimal, + usage, + ViewCapabilities::empty(), + ) + } + .map_err(|_| "Couldn't create image")?; + + // Allocate memory + let memory = unsafe { + let requirements = device.get_image_requirements(&image_ref); + + let memory_type_id = adapter + .physical_device + .memory_properties() + .memory_types + .iter() + .enumerate() + .find(|&(id, memory_type)| { + requirements.type_mask & (1 << id) != 0 + && memory_type.properties.contains(MemProperties::DEVICE_LOCAL) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or("Couldn't find a memory type for image memory")?; + + let memory = device + .allocate_memory(memory_type_id, requirements.size) + .map_err(|_| "Couldn't allocate image memory")?; + + device + .bind_image_memory(&memory, 0, &mut image_ref) + .map_err(|_| "Couldn't bind memory to image")?; + + Ok(memory) + }?; + + Ok((memory, image_ref)) } impl LoadedImage { - pub fn new(device: &mut Device, adapter: &Adapter, format: Format, usage: ImgUsage, resources: SubresourceRange, width: usize, height: usize) -> Result<LoadedImage, &'static str> { - let (memory, image_ref) = create_image_view(device, adapter, format, usage, width, height)?; - - // Create ImageView and sampler - let image_view = unsafe { device.create_image_view( - &image_ref, - ViewKind::D2, - format, - Swizzle::NO, - resources, - )}.map_err(|_| "Couldn't create the image view!")?; - - Ok(LoadedImage { - image: ManuallyDrop::new(image_ref), - image_view: ManuallyDrop::new(image_view), - memory: ManuallyDrop::new(memory) - }) - } - - /// Load the given image - pub fn load(&mut self, img: RgbaImage, device: &mut Device, adapter: &Adapter, command_queue: &mut CommandQueue, - command_pool: &mut CommandPool) -> Result<(), &'static str> { - let initial_row_size = PIXEL_SIZE * img.width() as usize; - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - - let row_size = ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - let total_size = (row_size * (img.height() as usize)) as u64; - debug_assert!(row_size as usize >= initial_row_size); - - // Make a staging buffer - let (staging_buffer, staging_memory) = create_buffer(device, adapter, BufUsage::TRANSFER_SRC, MemProperties::CPU_VISIBLE, total_size) - .map_err(|_| "Couldn't create staging buffer")?; - - // Copy everything into it - unsafe { - let mapped_memory: *mut u8 = device.map_memory(&staging_memory, Segment::ALL).map_err(|_| "Couldn't map buffer memory")?; - - for y in 0..img.height() as usize { - let row = &(*img)[y * initial_row_size..(y + 1) * initial_row_size]; - let dest_base: isize = (y * row_size).try_into().unwrap(); - - copy_nonoverlapping(row.as_ptr(), mapped_memory.offset(dest_base), row.len()); - } - device.flush_mapped_memory_ranges(once((&staging_memory, Segment::ALL))).map_err(|_| "Couldn't write buffer memory")?; - device.unmap_memory(&staging_memory); - } - - // Copy from staging to image memory - let buf = unsafe { - use hal::command::{CommandBufferFlags, BufferImageCopy}; - use hal::pso::PipelineStage; - use hal::memory::Barrier; - use hal::image::{Access, Layout, SubresourceLayers, Offset, Extent}; - - // Get a command buffer - let mut buf = command_pool.allocate_one(hal::command::Level::Primary); - buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - // Setup the layout of our image for copying - let image_barrier = Barrier::Image { - states: (Access::empty(), Layout::Undefined) - ..( - Access::TRANSFER_WRITE, - Layout::TransferDstOptimal, - ), - target: &(*self.image), - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - MemDependencies::empty(), - &[image_barrier], - ); - - // Copy from buffer to image - buf.copy_buffer_to_image(&staging_buffer, &(*self.image), - Layout::TransferDstOptimal, &[ - BufferImageCopy { - buffer_offset: 0, - buffer_width: (row_size / PIXEL_SIZE) as u32, - buffer_height: img.height(), - image_layers: SubresourceLayers { - aspects: Aspects::COLOR, - level: 0, - layers: 0..1 - }, - image_offset: Offset { - x: 0, y: 0, z: 0 - }, - image_extent: Extent { - width: img.width(), - height: img.height(), - depth: 1 - } - } - ]); - - // Setup the layout of our image for shaders - let image_barrier = Barrier::Image { - states: ( - Access::TRANSFER_WRITE, - Layout::TransferDstOptimal, - )..( - Access::SHADER_READ, - Layout::ShaderReadOnlyOptimal, - ), - target: &(*self.image), - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, - MemDependencies::empty(), - &[image_barrier], - ); - - buf.finish(); - - buf - }; - - // Submit our commands and wait for them to finish - unsafe { - let setup_finished = device.create_fence(false).unwrap(); - command_queue.submit::<_, _, Semaphore, _, _>(Submission { - command_buffers: &[&buf], - wait_semaphores: std::iter::empty::<_>(), - signal_semaphores: std::iter::empty::<_>() - }, Some(&setup_finished)); - - device - .wait_for_fence(&setup_finished, core::u64::MAX).unwrap(); - device.destroy_fence(setup_finished); - }; - - // Clean up temp resources - unsafe { - command_pool.free(once(buf)); - - device.free_memory(staging_memory); - device.destroy_buffer(staging_buffer); - } - - Ok(()) - } - - /// Load the given image into a new buffer - pub fn load_into_new(img: RgbaImage, device: &mut Device, adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, format: Format, usage: ImgUsage) -> Result<LoadedImage, &'static str> { - let mut loaded_image = Self::new(device, adapter, format, usage | ImgUsage::TRANSFER_DST, SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, img.width() as usize, img.height() as usize)?; - loaded_image.load(img, device, adapter, command_queue, command_pool)?; - - Ok(loaded_image) - } - - /// Properly frees/destroys all the objects in this struct - /// Dropping without doing this is a bad idea - pub fn deactivate(self, device: &Device) -> () { - unsafe { - use core::ptr::read; - - device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); - device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); - device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); - } - } + pub fn new( + device: &mut Device, + adapter: &Adapter, + format: Format, + usage: ImgUsage, + resources: SubresourceRange, + width: usize, + height: usize, + ) -> Result<LoadedImage, &'static str> { + let (memory, image_ref) = create_image_view(device, adapter, format, usage, width, height)?; + + // Create ImageView and sampler + let image_view = unsafe { + device.create_image_view(&image_ref, ViewKind::D2, format, Swizzle::NO, resources) + } + .map_err(|_| "Couldn't create the image view!")?; + + Ok(LoadedImage { + image: ManuallyDrop::new(image_ref), + image_view: ManuallyDrop::new(image_view), + memory: ManuallyDrop::new(memory), + }) + } + + /// Load the given image + pub fn load( + &mut self, + img: RgbaImage, + device: &mut Device, + adapter: &Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + ) -> Result<(), &'static str> { + let initial_row_size = PIXEL_SIZE * img.width() as usize; + let limits = adapter.physical_device.limits(); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + + let row_size = + ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + let total_size = (row_size * (img.height() as usize)) as u64; + debug_assert!(row_size as usize >= initial_row_size); + + // Make a staging buffer + let (staging_buffer, staging_memory) = create_buffer( + device, + adapter, + BufUsage::TRANSFER_SRC, + MemProperties::CPU_VISIBLE, + total_size, + ) + .map_err(|_| "Couldn't create staging buffer")?; + + // Copy everything into it + unsafe { + let mapped_memory: *mut u8 = device + .map_memory(&staging_memory, Segment::ALL) + .map_err(|_| "Couldn't map buffer memory")?; + + for y in 0..img.height() as usize { + let row = &(*img)[y * initial_row_size..(y + 1) * initial_row_size]; + let dest_base: isize = (y * row_size).try_into().unwrap(); + + copy_nonoverlapping(row.as_ptr(), mapped_memory.offset(dest_base), row.len()); + } + device + .flush_mapped_memory_ranges(once((&staging_memory, Segment::ALL))) + .map_err(|_| "Couldn't write buffer memory")?; + device.unmap_memory(&staging_memory); + } + + // Copy from staging to image memory + let buf = unsafe { + use hal::command::{BufferImageCopy, CommandBufferFlags}; + use hal::image::{Access, Extent, Layout, Offset, SubresourceLayers}; + use hal::memory::Barrier; + use hal::pso::PipelineStage; + + // Get a command buffer + let mut buf = command_pool.allocate_one(hal::command::Level::Primary); + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + // Setup the layout of our image for copying + let image_barrier = Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &(*self.image), + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }; + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + MemDependencies::empty(), + &[image_barrier], + ); + + // Copy from buffer to image + buf.copy_buffer_to_image( + &staging_buffer, + &(*self.image), + Layout::TransferDstOptimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_size / PIXEL_SIZE) as u32, + buffer_height: img.height(), + image_layers: SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: Extent { + width: img.width(), + height: img.height(), + depth: 1, + }, + }], + ); + + // Setup the layout of our image for shaders + let image_barrier = Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal), + target: &(*self.image), + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }; + + buf.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, + MemDependencies::empty(), + &[image_barrier], + ); + + buf.finish(); + + buf + }; + + // Submit our commands and wait for them to finish + unsafe { + let setup_finished = device.create_fence(false).unwrap(); + command_queue.submit::<_, _, Semaphore, _, _>( + Submission { + command_buffers: &[&buf], + wait_semaphores: std::iter::empty::<_>(), + signal_semaphores: std::iter::empty::<_>(), + }, + Some(&setup_finished), + ); + + device + .wait_for_fence(&setup_finished, core::u64::MAX) + .unwrap(); + device.destroy_fence(setup_finished); + }; + + // Clean up temp resources + unsafe { + command_pool.free(once(buf)); + + device.free_memory(staging_memory); + device.destroy_buffer(staging_buffer); + } + + Ok(()) + } + + /// Load the given image into a new buffer + pub fn load_into_new( + img: RgbaImage, + device: &mut Device, + adapter: &Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + format: Format, + usage: ImgUsage, + ) -> Result<LoadedImage, &'static str> { + let mut loaded_image = Self::new( + device, + adapter, + format, + usage | ImgUsage::TRANSFER_DST, + SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + img.width() as usize, + img.height() as usize, + )?; + loaded_image.load(img, device, adapter, command_queue, command_pool)?; + + Ok(loaded_image) + } + + /// Properly frees/destroys all the objects in this struct + /// Dropping without doing this is a bad idea + pub fn deactivate(self, device: &Device) { + unsafe { + use core::ptr::read; + + device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); + device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); + device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); + } + } } - pub struct SampledImage { - pub image: ManuallyDrop<LoadedImage>, - pub sampler: ManuallyDrop<Sampler> + pub image: ManuallyDrop<LoadedImage>, + pub sampler: ManuallyDrop<Sampler>, } impl SampledImage { - pub fn new(device: &mut Device, adapter: &Adapter, format: Format, usage: ImgUsage, width: usize, height: usize) -> Result<SampledImage, &'static str> { - let image = LoadedImage::new(device, adapter, format, usage | ImgUsage::SAMPLED, SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, width, height)?; - - let sampler = unsafe { - use hal::image::{SamplerDesc, Filter, WrapMode}; - - device.create_sampler(&SamplerDesc::new( - Filter::Nearest, - WrapMode::Tile, - )) - }.map_err(|_| "Couldn't create the sampler!")?; - - Ok(SampledImage { - image: ManuallyDrop::new(image), - sampler: ManuallyDrop::new(sampler) - }) - } - - pub fn load_into_new(img: RgbaImage, device: &mut Device, adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, format: Format, usage: ImgUsage) -> Result<SampledImage, &'static str> { - let mut sampled_image = SampledImage::new(device, adapter, format, usage | ImgUsage::TRANSFER_DST, img.width() as usize, img.height() as usize)?; - sampled_image.image.load(img, device, adapter, command_queue, command_pool)?; - - Ok(sampled_image) - - } - - pub fn deactivate(self, device: &mut Device) -> () { - unsafe { - use core::ptr::read; - - device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler))); - - ManuallyDrop::into_inner(read(&self.image)).deactivate(device); - } - } + pub fn new( + device: &mut Device, + adapter: &Adapter, + format: Format, + usage: ImgUsage, + width: usize, + height: usize, + ) -> Result<SampledImage, &'static str> { + let image = LoadedImage::new( + device, + adapter, + format, + usage | ImgUsage::SAMPLED, + SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + width, + height, + )?; + + let sampler = unsafe { + use hal::image::{Filter, SamplerDesc, WrapMode}; + + device.create_sampler(&SamplerDesc::new(Filter::Nearest, WrapMode::Tile)) + } + .map_err(|_| "Couldn't create the sampler!")?; + + Ok(SampledImage { + image: ManuallyDrop::new(image), + sampler: ManuallyDrop::new(sampler), + }) + } + + pub fn load_into_new( + img: RgbaImage, + device: &mut Device, + adapter: &Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + format: Format, + usage: ImgUsage, + ) -> Result<SampledImage, &'static str> { + let mut sampled_image = SampledImage::new( + device, + adapter, + format, + usage | ImgUsage::TRANSFER_DST, + img.width() as usize, + img.height() as usize, + )?; + sampled_image + .image + .load(img, device, adapter, command_queue, command_pool)?; + + Ok(sampled_image) + } + + pub fn deactivate(self, device: &mut Device) { + unsafe { + use core::ptr::read; + + device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler))); + + ManuallyDrop::into_inner(read(&self.image)).deactivate(device); + } + } } diff --git a/stockton-render/src/draw/texture/loader.rs b/stockton-render/src/draw/texture/loader.rs index 1ac0961..863e1a8 100644 --- a/stockton-render/src/draw/texture/loader.rs +++ b/stockton-render/src/draw/texture/loader.rs @@ -14,17 +14,15 @@ // with this program. If not, see <http://www.gnu.org/licenses/>. //! Deals with loading textures into GPU memory -use std::path::Path; -use draw::texture::resolver::BasicFSResolver; -use draw::texture::chunk::CHUNK_SIZE; -use core::mem::{ManuallyDrop}; use super::chunk::TextureChunk; +use core::mem::ManuallyDrop; +use draw::texture::chunk::CHUNK_SIZE; +use draw::texture::resolver::BasicFSResolver; +use std::path::Path; use log::debug; -use hal::{ - prelude::*, -}; +use hal::prelude::*; use stockton_levels::prelude::*; @@ -36,137 +34,151 @@ use crate::types::*; /// The descriptor set layout should have the same count of textures as this does. /// All descriptors will be properly initialised images. pub struct TextureStore { - descriptor_pool: ManuallyDrop<DescriptorPool>, - pub(crate) descriptor_set_layout: ManuallyDrop<DescriptorSetLayout>, - chunks: Box<[TextureChunk]> + descriptor_pool: ManuallyDrop<DescriptorPool>, + pub(crate) descriptor_set_layout: ManuallyDrop<DescriptorSetLayout>, + chunks: Box<[TextureChunk]>, } impl TextureStore { - /// Create a new texture store for the given file, loading all textures from it. - pub fn new<T: HasTextures>(device: &mut Device, - adapter: &mut Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, file: &T) -> Result<TextureStore, error::CreationError> { - // Figure out how many textures in this file / how many chunks needed - let size = file.textures_iter().count(); - let num_chunks = { - let mut x = size / CHUNK_SIZE; - if size % CHUNK_SIZE != 0 { - x += 1; - } - x - }; - let rounded_size = num_chunks * CHUNK_SIZE; - - // Descriptor pool, where we get our sets from - let mut descriptor_pool = unsafe { - use hal::pso::{DescriptorRangeDesc, DescriptorType, DescriptorPoolCreateFlags, ImageDescriptorType}; - - device.create_descriptor_pool( - num_chunks, - &[ - DescriptorRangeDesc { - ty: DescriptorType::Image { - ty: ImageDescriptorType::Sampled { - with_sampler: false - } - }, - count: rounded_size - }, - DescriptorRangeDesc { - ty: DescriptorType::Sampler, - count: rounded_size - } - ], - DescriptorPoolCreateFlags::empty() - ).map_err(|e| { - println!("{:?}", e); - error::CreationError::OutOfMemoryError - })? - }; - - // Layout of our descriptor sets - let mut descriptor_set_layout = unsafe { - use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags, ImageDescriptorType}; - - device.create_descriptor_set_layout( - &[ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::Image { - ty: ImageDescriptorType::Sampled { - with_sampler: false - } - }, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false - } - ], - &[], - ) - }.map_err(|_| error::CreationError::OutOfMemoryError)?; - - // TODO: Proper way to set up resolver - let mut resolver = BasicFSResolver::new(Path::new(".")); - - // Create texture chunks - debug!("Starting to load textures..."); - let mut chunks = Vec::with_capacity(num_chunks); - for i in 0..num_chunks { - let range = { - let mut r = (i * CHUNK_SIZE) as u32..((i + 1) * CHUNK_SIZE) as u32; - if r.end > size as u32 { - r.end = size as u32; - } - r - }; - debug!("Chunk {} / {} covering {:?}", i + 1, num_chunks, range); - - chunks.push( - TextureChunk::new( - device, adapter, command_queue, - command_pool, &mut descriptor_pool, - &mut descriptor_set_layout, file, - range, &mut resolver - )? - ); - } - - debug!("All textures loaded."); - - Ok(TextureStore { - descriptor_pool: ManuallyDrop::new(descriptor_pool), - descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), - chunks: chunks.into_boxed_slice() - }) - } - - /// Call this before dropping - pub fn deactivate(mut self, device: &mut Device) -> () { - unsafe { - use core::ptr::read; - - for chunk in self.chunks.into_vec().drain(..) { - chunk.deactivate(device) - } - - self.descriptor_pool.reset(); - device - .destroy_descriptor_set_layout(ManuallyDrop::into_inner(read(&self.descriptor_set_layout))); - device.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&self.descriptor_pool))); - } - } - - /// Get the descriptor set for a given chunk - pub fn get_chunk_descriptor_set<'a>(&'a self, idx: usize) -> &'a DescriptorSet { - &self.chunks[idx].descriptor_set - } + /// Create a new texture store for the given file, loading all textures from it. + pub fn new<T: HasTextures>( + device: &mut Device, + adapter: &mut Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + file: &T, + ) -> Result<TextureStore, error::CreationError> { + // Figure out how many textures in this file / how many chunks needed + let size = file.textures_iter().count(); + let num_chunks = { + let mut x = size / CHUNK_SIZE; + if size % CHUNK_SIZE != 0 { + x += 1; + } + x + }; + let rounded_size = num_chunks * CHUNK_SIZE; + + // Descriptor pool, where we get our sets from + let mut descriptor_pool = unsafe { + use hal::pso::{ + DescriptorPoolCreateFlags, DescriptorRangeDesc, DescriptorType, ImageDescriptorType, + }; + + device + .create_descriptor_pool( + num_chunks, + &[ + DescriptorRangeDesc { + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: rounded_size, + }, + DescriptorRangeDesc { + ty: DescriptorType::Sampler, + count: rounded_size, + }, + ], + DescriptorPoolCreateFlags::empty(), + ) + .map_err(|e| { + println!("{:?}", e); + error::CreationError::OutOfMemoryError + })? + }; + + // Layout of our descriptor sets + let descriptor_set_layout = unsafe { + use hal::pso::{ + DescriptorSetLayoutBinding, DescriptorType, ImageDescriptorType, ShaderStageFlags, + }; + + device.create_descriptor_set_layout( + &[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: CHUNK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: CHUNK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ], + &[], + ) + } + .map_err(|_| error::CreationError::OutOfMemoryError)?; + + // TODO: Proper way to set up resolver + let mut resolver = BasicFSResolver::new(Path::new(".")); + + // Create texture chunks + debug!("Starting to load textures..."); + let mut chunks = Vec::with_capacity(num_chunks); + for i in 0..num_chunks { + let range = { + let mut r = (i * CHUNK_SIZE) as u32..((i + 1) * CHUNK_SIZE) as u32; + if r.end > size as u32 { + r.end = size as u32; + } + r + }; + debug!("Chunk {} / {} covering {:?}", i + 1, num_chunks, range); + + chunks.push(TextureChunk::new( + device, + adapter, + command_queue, + command_pool, + &mut descriptor_pool, + &descriptor_set_layout, + file, + range, + &mut resolver, + )?); + } + + debug!("All textures loaded."); + + Ok(TextureStore { + descriptor_pool: ManuallyDrop::new(descriptor_pool), + descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), + chunks: chunks.into_boxed_slice(), + }) + } + + /// Call this before dropping + pub fn deactivate(mut self, device: &mut Device) { + unsafe { + use core::ptr::read; + + for chunk in self.chunks.into_vec().drain(..) { + chunk.deactivate(device) + } + + self.descriptor_pool.reset(); + device.destroy_descriptor_set_layout(ManuallyDrop::into_inner(read( + &self.descriptor_set_layout, + ))); + device.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&self.descriptor_pool))); + } + } + + /// Get the descriptor set for a given chunk + pub fn get_chunk_descriptor_set(&self, idx: usize) -> &DescriptorSet { + &self.chunks[idx].descriptor_set + } } diff --git a/stockton-render/src/draw/texture/mod.rs b/stockton-render/src/draw/texture/mod.rs index 05dfe38..756fe79 100644 --- a/stockton-render/src/draw/texture/mod.rs +++ b/stockton-render/src/draw/texture/mod.rs @@ -15,10 +15,10 @@ //! Everything related to loading textures into GPU memory -mod resolver; -pub mod image; mod chunk; +pub mod image; pub mod loader; +mod resolver; +pub use self::image::{LoadedImage, SampledImage}; pub use self::loader::TextureStore; -pub use self::image::{LoadedImage, SampledImage};
\ No newline at end of file diff --git a/stockton-render/src/draw/texture/resolver.rs b/stockton-render/src/draw/texture/resolver.rs index 5867171..5fab16a 100644 --- a/stockton-render/src/draw/texture/resolver.rs +++ b/stockton-render/src/draw/texture/resolver.rs @@ -17,44 +17,39 @@ use stockton_levels::traits::textures::Texture; -use image::{ - RgbaImage, - io::Reader -}; +use image::{io::Reader, RgbaImage}; use std::path::Path; /// An object that can be used to resolve a texture from a BSP File pub trait TextureResolver { - /// Get the given texture, or None if it's corrupt/not there. - fn resolve(&mut self, texture: &Texture) -> Option<RgbaImage>; + /// Get the given texture, or None if it's corrupt/not there. + fn resolve(&mut self, texture: &Texture) -> Option<RgbaImage>; } /// A basic filesystem resolver which expects no file extension and guesses the image format pub struct BasicFSResolver<'a> { - path: &'a Path + path: &'a Path, } impl<'a> BasicFSResolver<'a> { - pub fn new(path: &'a Path) -> BasicFSResolver<'a> { - BasicFSResolver { - path - } - } + pub fn new(path: &'a Path) -> BasicFSResolver<'a> { + BasicFSResolver { path } + } } impl<'a> TextureResolver for BasicFSResolver<'a> { - fn resolve(&mut self, tex: &Texture) -> Option<RgbaImage> { - let path = self.path.join(&tex.name); - - if let Ok(file) = Reader::open(path) { - if let Ok(guessed) = file.with_guessed_format() { - if let Ok(decoded) = guessed.decode() { - return Some(decoded.into_rgba()); - } - } - } - - None - } -}
\ No newline at end of file + fn resolve(&mut self, tex: &Texture) -> Option<RgbaImage> { + let path = self.path.join(&tex.name); + + if let Ok(file) = Reader::open(path) { + if let Ok(guessed) = file.with_guessed_format() { + if let Ok(decoded) = guessed.decode() { + return Some(decoded.into_rgba()); + } + } + } + + None + } +} diff --git a/stockton-render/src/error.rs b/stockton-render/src/error.rs index 8922423..28d3282 100644 --- a/stockton-render/src/error.rs +++ b/stockton-render/src/error.rs @@ -18,35 +18,30 @@ use super::draw::target::TargetChainCreationError; /// An error encountered creating a rendering context. -/// Falls into 3 main types: -/// - Hardware - No suitable card usually -/// - Sanity - Things that probably aren't true, likely indicating a deeper issue. -/// These aren't guaranteed sanity issues, but they are weird issues. -/// - Runtime - Things caused by runtime conditions, usually resource constraints. Could also be caused by corrupt files #[derive(Debug)] pub enum CreationError { - TargetChainCreationError (TargetChainCreationError), - WindowError, - BadSurface, - - DeviceError (hal::device::CreationError), - - OutOfMemoryError, - - SyncObjectError, - - NoShaderC, - ShaderCError (shaderc::Error), - ShaderModuleFailed (hal::device::ShaderError), - RenderPassError, - PipelineError (hal::pso::CreationError), - BufferError (hal::buffer::CreationError), - BufferNoMemory, - - SwapchainError (hal::window::CreationError), - ImageViewError (hal::image::ViewCreationError), - - BadDataError + TargetChainCreationError(TargetChainCreationError), + WindowError, + BadSurface, + + DeviceError(hal::device::CreationError), + + OutOfMemoryError, + + SyncObjectError, + + NoShaderC, + ShaderCError(shaderc::Error), + ShaderModuleFailed(hal::device::ShaderError), + RenderPassError, + PipelineError(hal::pso::CreationError), + BufferError(hal::buffer::CreationError), + BufferNoMemory, + + SwapchainError(hal::window::CreationError), + ImageViewError(hal::image::ViewCreationError), + + BadDataError, } /// An error encountered when rendering. @@ -54,7 +49,7 @@ pub enum CreationError { /// You'll likely need to exit or create a new context. #[derive(Debug, Clone)] pub enum FrameError { - AcquireError (hal::window::AcquireError), - SyncObjectError, - PresentError -}
\ No newline at end of file + AcquireError(hal::window::AcquireError), + SyncObjectError, + PresentError, +} diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index 0db4b76..7e7ea5b 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -21,58 +21,57 @@ extern crate gfx_hal as hal; extern crate shaderc; extern crate winit; -extern crate nalgebra_glm as na; extern crate image; extern crate log; +extern crate nalgebra_glm as na; -extern crate stockton_types; extern crate stockton_levels; +extern crate stockton_types; extern crate arrayvec; +mod culling; pub mod draw; mod error; mod types; -mod culling; -use stockton_types::World; use stockton_levels::prelude::*; +use stockton_types::World; -use error::{CreationError, FrameError}; -use draw::RenderingContext; use culling::get_visible_faces; +use draw::RenderingContext; +use error::{CreationError, FrameError}; /// Renders a world to a window when you tell it to. pub struct Renderer<'a, T: MinBSPFeatures<VulkanSystem>> { - world: World<T>, - pub context: RenderingContext<'a> + world: World<T>, + pub context: RenderingContext<'a>, } - impl<'a, T: MinBSPFeatures<VulkanSystem>> Renderer<'a, T> { - /// Create a new Renderer. - /// This initialises all the vulkan context, etc needed. - pub fn new(world: World<T>, window: &winit::window::Window) -> Result<Self, CreationError> { - let context = RenderingContext::new(window, &world.map)?; - - Ok(Renderer { - world, context - }) - } - - /// Render a single frame of the world - pub fn render_frame(&mut self) -> Result<(), FrameError>{ - // Get visible faces - let faces = get_visible_faces(self.context.camera_pos(), &self.world.map); - - // Then draw them - if let Err(_) = self.context.draw_vertices(&self.world.map, &faces) { - unsafe {self.context.handle_surface_change().unwrap()}; - - // If it fails twice, then error - self.context.draw_vertices(&self.world.map, &faces).map_err(|_| FrameError::PresentError)?; - } - - Ok(()) - } + /// Create a new Renderer. + /// This initialises all the vulkan context, etc needed. + pub fn new(world: World<T>, window: &winit::window::Window) -> Result<Self, CreationError> { + let context = RenderingContext::new(window, &world.map)?; + + Ok(Renderer { world, context }) + } + + /// Render a single frame of the world + pub fn render_frame(&mut self) -> Result<(), FrameError> { + // Get visible faces + let faces = get_visible_faces(self.context.camera_pos(), &self.world.map); + + // Then draw them + if self.context.draw_vertices(&self.world.map, &faces).is_err() { + unsafe { self.context.handle_surface_change().unwrap() }; + + // If it fails twice, then error + self.context + .draw_vertices(&self.world.map, &faces) + .map_err(|_| FrameError::PresentError)?; + } + + Ok(()) + } } diff --git a/stockton-render/src/types.rs b/stockton-render/src/types.rs index a0f242c..a5aefd8 100644 --- a/stockton-render/src/types.rs +++ b/stockton-render/src/types.rs @@ -38,4 +38,4 @@ pub type Framebuffer = <back::Backend as hal::Backend>::Framebuffer; pub type RenderPass = <back::Backend as hal::Backend>::RenderPass; pub type Adapter = hal::adapter::Adapter<back::Backend>; -pub type QueueGroup = hal::queue::QueueGroup<back::Backend>;
\ No newline at end of file +pub type QueueGroup = hal::queue::QueueGroup<back::Backend>; |