diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:20 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:20 +0100 |
commit | c3683cb91a7142be405aa672fcbae4238a3bde72 (patch) | |
tree | 3069cd8aef8212e864181732a27534008f533d25 | |
parent | 7cdb4bb159a7df88390e63c59f0d4c5538d7411b (diff) |
feat(render): texture store
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | examples/render-quad/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/render-quad/data/test1.png | bin | 0 -> 486777 bytes | |||
-rw-r--r-- | examples/render-quad/data/test2.png | bin | 0 -> 375052 bytes | |||
-rw-r--r-- | examples/render-quad/src/main.rs | 37 | ||||
-rw-r--r-- | stockton-render/Cargo.toml | 1 | ||||
-rw-r--r-- | stockton-render/src/draw/buffer.rs | 9 | ||||
-rw-r--r-- | stockton-render/src/draw/context.rs | 85 | ||||
-rw-r--r-- | stockton-render/src/draw/data/stockton.frag | 15 | ||||
-rw-r--r-- | stockton-render/src/draw/data/stockton.vert | 8 | ||||
-rw-r--r-- | stockton-render/src/draw/mod.rs | 1 | ||||
-rw-r--r-- | stockton-render/src/draw/texture.rs | 443 | ||||
-rw-r--r-- | stockton-render/src/lib.rs | 1 | ||||
-rw-r--r-- | stockton-render/src/types.rs | 4 |
14 files changed, 572 insertions, 39 deletions
@@ -7,3 +7,8 @@ A 3D engine. ## License Code & Assets (including from `rust-bsp`) are licensed under the GNU GPL v3.0, all contributions automatically come under this. See LICENSE. + +Exceptions: + + - `examples/render-quad/data/test1.png` - [Photo by Lisa Fotios from Pexels](https://www.pexels.com/photo/white-petaled-flowers-painting-2224220/) + - `examples/render-quad/data/test2.png` - [Photo by Elina Sazonova from Pexels](https://www.pexels.com/photo/brown-tabby-cat-on-pink-textile-3971972/)
\ No newline at end of file diff --git a/examples/render-quad/Cargo.toml b/examples/render-quad/Cargo.toml index 6f75543..7e9bc61 100644 --- a/examples/render-quad/Cargo.toml +++ b/examples/render-quad/Cargo.toml @@ -11,4 +11,4 @@ winit = "^0.21" log = "0.4.0" simple_logger = "1.0" rand = "0.7" - +image = "0.23.2" diff --git a/examples/render-quad/data/test1.png b/examples/render-quad/data/test1.png Binary files differnew file mode 100644 index 0000000..3d37758 --- /dev/null +++ b/examples/render-quad/data/test1.png diff --git a/examples/render-quad/data/test2.png b/examples/render-quad/data/test2.png Binary files differnew file mode 100644 index 0000000..f33cc3e --- /dev/null +++ b/examples/render-quad/data/test2.png diff --git a/examples/render-quad/src/main.rs b/examples/render-quad/src/main.rs index 9cc7310..8d6d439 100644 --- a/examples/render-quad/src/main.rs +++ b/examples/render-quad/src/main.rs @@ -21,9 +21,11 @@ extern crate stockton_render; extern crate winit; extern crate simple_logger; extern crate rand; +extern crate image; use stockton_render::draw::{RenderingContext, UVPoint}; use stockton_types::{Vector2, Vector3}; +use image::load_from_memory; use winit::{ event::{Event, WindowEvent}, @@ -36,19 +38,40 @@ fn main() { simple_logger::init().unwrap(); // Create the renderer. - let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); let mut ctx = RenderingContext::new(&window).unwrap(); - ctx.vert_buffer[0] = UVPoint(Vector2::new(-0.5, -0.5), Vector3::new(1.0, 0.0, 0.0), Vector2::new(0.0, 0.0)); - ctx.vert_buffer[1] = UVPoint(Vector2::new(0.5, -0.5), Vector3::new(0.0, 1.0, 0.0), Vector2::new(1.0, 0.0)); - ctx.vert_buffer[2] = UVPoint(Vector2::new(0.5, 0.5), Vector3::new(0.0, 0.0, 1.0), Vector2::new(1.0, 1.0)); - ctx.vert_buffer[3] = UVPoint(Vector2::new(-0.5, 0.5), Vector3::new(1.0, 0.0, 1.0), Vector2::new(0.0, 1.0)); + // Load 2 test textures + ctx.add_texture( + load_from_memory(include_bytes!("../data/test1.png")) + .expect("Couldn't load test texture 1") + .into_rgba()) + .unwrap(); + ctx.add_texture( + load_from_memory(include_bytes!("../data/test2.png")) + .expect("Couldn't load test texture 2") + .into_rgba()) + .unwrap(); + + // First quad with test1 + ctx.vert_buffer[0] = UVPoint(Vector2::new(-1.0, -1.0), Vector3::new(1.0, 0.0, 0.0), Vector2::new(0.0, 0.0), 0); + ctx.vert_buffer[1] = UVPoint(Vector2::new(0.0, -1.0), Vector3::new(0.0, 1.0, 0.0), Vector2::new(1.0, 0.0), 0); + ctx.vert_buffer[2] = UVPoint(Vector2::new(0.0, 0.0), Vector3::new(0.0, 0.0, 1.0), Vector2::new(1.0, 1.0), 0); + ctx.vert_buffer[3] = UVPoint(Vector2::new(-1.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector2::new(0.0, 1.0), 0); ctx.index_buffer[0] = (0, 1, 2); ctx.index_buffer[1] = (0, 2, 3); + // Second quad with test2 + ctx.vert_buffer[4] = UVPoint(Vector2::new(0.0, -1.0), Vector3::new(1.0, 0.0, 0.0), Vector2::new(0.0, 0.0), 1); + ctx.vert_buffer[5] = UVPoint(Vector2::new(1.0, -1.0), Vector3::new(0.0, 1.0, 0.0), Vector2::new(1.0, 0.0), 1); + ctx.vert_buffer[6] = UVPoint(Vector2::new(1.0, 0.0), Vector3::new(0.0, 0.0, 1.0), Vector2::new(1.0, 1.0), 1); + ctx.vert_buffer[7] = UVPoint(Vector2::new(0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector2::new(0.0, 1.0), 1); + + ctx.index_buffer[2] = (4, 5, 6); + ctx.index_buffer[3] = (4, 7, 6); + event_loop.run(move |event, _, flow| { *flow = ControlFlow::Poll; @@ -72,7 +95,9 @@ fn main() { let mouse_x: f32 = ((position.x / win_size.width as f64) * 2.0 - 1.0) as f32; let mouse_y: f32 = ((position.y / win_size.height as f64) * 2.0 - 1.0) as f32; - ctx.vert_buffer[0] = UVPoint(Vector2::new(mouse_x, mouse_y), Vector3::new(1.0, 0.0, 0.0), Vector2::new(0.0, 0.0)); + // Move a vertex from each quad + ctx.vert_buffer[2] = UVPoint(Vector2::new(mouse_x, mouse_y), Vector3::new(1.0, 0.0, 0.0), Vector2::new(1.0, 1.0), 0); + ctx.vert_buffer[7] = UVPoint(Vector2::new(mouse_x, mouse_y), Vector3::new(1.0, 0.0, 0.0), Vector2::new(0.0, 1.0), 1); } Event::MainEventsCleared => { diff --git a/stockton-render/Cargo.toml b/stockton-render/Cargo.toml index d207159..ba186cd 100644 --- a/stockton-render/Cargo.toml +++ b/stockton-render/Cargo.toml @@ -11,6 +11,7 @@ arrayvec = "0.4.10" nalgebra-glm = "0.4.0" shaderc = "0.6.1" log = "0.4.0" +image = "0.23.2" [features] default = ["vulkan"] diff --git a/stockton-render/src/draw/buffer.rs b/stockton-render/src/draw/buffer.rs index 8ec31e4..a603cd6 100644 --- a/stockton-render/src/draw/buffer.rs +++ b/stockton-render/src/draw/buffer.rs @@ -13,6 +13,7 @@ // 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 std::iter::once; use std::ops::{Index, IndexMut}; use std::convert::TryInto; use core::mem::{ManuallyDrop, size_of}; @@ -28,7 +29,7 @@ use hal::{ use crate::error::CreationError; use crate::types::*; -fn create_buffer(device: &mut Device, +pub(crate) fn create_buffer(device: &mut Device, adapter: &Adapter, usage: Usage, properties: Properties, @@ -55,8 +56,7 @@ fn create_buffer(device: &mut Device, .bind_buffer_memory(&memory, 0, &mut buffer) } .map_err(|_| CreationError::BufferNoMemory)?; - Ok((buffer, memory -)) + Ok((buffer, memory)) } pub trait ModifiableBuffer: IndexMut<usize> { @@ -154,7 +154,10 @@ impl <'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> { 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; diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs index 882db44..f8a057c 100644 --- a/stockton-render/src/draw/context.rs +++ b/stockton-render/src/draw/context.rs @@ -17,9 +17,14 @@ //! In the end, this takes in vertices and renders them to a window. //! You'll need something else to actually generate the vertices though. -use std::mem::{ManuallyDrop, size_of}; +use std::{ + mem::{ManuallyDrop, size_of}, + iter::once, + ops::Deref +}; use winit::window::Window; use arrayvec::ArrayVec; +use image::RgbaImage; use hal::{ prelude::*, @@ -30,6 +35,7 @@ use stockton_types::{Vector2, Vector3}; use crate::types::*; use crate::error; +use super::texture::TextureStore; use super::buffer::{StagedBuffer, ModifiableBuffer}; /// Entry point name for shaders @@ -42,15 +48,24 @@ const COLOR_RANGE: hal::image::SubresourceRange = hal::image::SubresourceRange { layers: 0..1, }; +/// Size of texturestore. This needs to sync up with the array size in the fragment shader +const INITIAL_TEX_SIZE: usize = 2; + +/// Initial size of vertex buffer. TODO: Way of overriding this +const INITIAL_VERT_SIZE: u64 = 32; + +/// Initial size of index buffer. TODO: Way of overriding this +const INITIAL_INDEX_SIZE: u64 = 16; + /// Source for vertex shader. TODO const VERTEX_SOURCE: &str = include_str!("./data/stockton.vert"); /// Source for fragment shader. TODO const FRAGMENT_SOURCE: &str = include_str!("./data/stockton.frag"); -/// Represents a point of a vertices, including RGB and UV information. +/// Represents a point of a vertices, including RGB, UV and texture information. #[derive(Debug, Clone, Copy)] -pub struct UVPoint (pub Vector2, pub Vector3, pub Vector2); +pub struct UVPoint (pub Vector2, pub Vector3, pub Vector2, pub i32); /// Contains all the hal related stuff. /// In the end, this takes some 3D points and puts it on the screen. @@ -59,6 +74,7 @@ pub struct RenderingContext<'a> { // Parents for most of these things instance: ManuallyDrop<back::Instance>, device: ManuallyDrop<Device>, + adapter: Adapter, // Render destination surface: ManuallyDrop<Surface>, @@ -78,7 +94,6 @@ pub struct RenderingContext<'a> { // Pipeline renderpass: ManuallyDrop<RenderPass>, - descriptor_set_layouts: ManuallyDrop<DescriptorSetLayout>, pipeline_layout: ManuallyDrop<PipelineLayout>, pipeline: ManuallyDrop<GraphicsPipeline>, @@ -87,6 +102,9 @@ pub struct RenderingContext<'a> { cmd_buffers: Vec<CommandBuffer>, queue_group: QueueGroup, + // Texture store + texture_store: ManuallyDrop<TextureStore>, + // Vertex and index buffers // These are both staged pub vert_buffer: ManuallyDrop<StagedBuffer<'a, UVPoint>>, @@ -257,8 +275,8 @@ impl<'a> RenderingContext<'a> { let (vert_buffer, index_buffer) = { use hal::buffer::Usage; - let vert = StagedBuffer::new(&mut device, &adapter, Usage::VERTEX, 4)?; - let index = StagedBuffer::new(&mut device, &adapter, Usage::INDEX, 2)?; + 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) }; @@ -311,14 +329,18 @@ impl<'a> RenderingContext<'a> { (cmd_pool, cmd_buffers, get_image, render_complete, present_complete, imageviews, framebuffers) }; + // Texture store + let texture_store = TextureStore::new(&mut device, INITIAL_TEX_SIZE)?; + // Graphics pipeline - let (descriptor_set_layouts, pipeline_layout, pipeline) = Self::create_pipeline(&mut device, extent, &subpass)?; + let (pipeline_layout, pipeline) = Self::create_pipeline(&mut device, extent, &subpass, &texture_store.descriptor_set_layout)?; Ok(RenderingContext { instance: ManuallyDrop::new(instance), surface: ManuallyDrop::new(surface), device: ManuallyDrop::new(device), + adapter, queue_group, swapchain: ManuallyDrop::new(swapchain), viewport, @@ -336,19 +358,28 @@ impl<'a> RenderingContext<'a> { cmd_pool, cmd_buffers, - descriptor_set_layouts: ManuallyDrop::new(descriptor_set_layouts), 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), }) } + /// Load the given image into the texturestore, returning the index or an error. + pub fn add_texture(&mut self, image: RgbaImage) -> Result<usize, &'static str> { + self.texture_store.add_texture(image, + &mut self.device, + &mut self.adapter, + &mut self.queue_group.queues[0], + &mut self.cmd_pool) + } + #[allow(clippy::type_complexity)] - pub fn create_pipeline(device: &mut Device, extent: hal::image::Extent, subpass: &hal::pass::Subpass<back::Backend>) -> Result< + pub fn create_pipeline(device: &mut Device, extent: hal::image::Extent, subpass: &hal::pass::Subpass<back::Backend>, set_layout: &DescriptorSetLayout) -> Result< ( - DescriptorSetLayout, PipelineLayout, GraphicsPipeline, ), error::CreationError> { @@ -404,7 +435,7 @@ impl<'a> RenderingContext<'a> { // Vertex buffers let vertex_buffers: Vec<VertexBufferDesc> = vec![VertexBufferDesc { binding: 0, - stride: (size_of::<f32>() * 7) as u32, + stride: (size_of::<f32>() * 8) as u32, rate: VertexInputRate::Vertex, }]; @@ -429,6 +460,13 @@ impl<'a> RenderingContext<'a> { format: Format::Rg32Sfloat, offset: (size_of::<f32>() * 5) as ElemOffset, } + }, AttributeDesc { // Tex Attribute + location: 3, + binding: 0, + element: Element { + format: Format::R32Sint, + offset: (size_of::<f32>() * 7) as ElemOffset + } }]; // Rasterizer @@ -449,17 +487,9 @@ impl<'a> RenderingContext<'a> { stencil: None, }; - // Descriptor set layout - let set_layout = unsafe { - device.create_descriptor_set_layout( - &[], - &[], - ) - }.map_err(|_| error::CreationError::OutOfMemoryError)?; - // Pipeline layout let layout = unsafe { - device.create_pipeline_layout(std::iter::once(&set_layout), &[]) + device.create_pipeline_layout(once(set_layout), &[]) }.map_err(|_| error::CreationError::OutOfMemoryError)?; // Colour blending @@ -517,7 +547,7 @@ impl<'a> RenderingContext<'a> { device.create_graphics_pipeline(&pipeline_desc, None) }.map_err(|e| error::CreationError::PipelineError (e))?; - Ok((set_layout, layout, pipeline)) + Ok((layout, pipeline)) } /// Draw a frame that's just cleared to the color specified. @@ -663,8 +693,15 @@ impl<'a> RenderingContext<'a> { SubpassContents::Inline ); buffer.bind_graphics_pipeline(&self.pipeline); - buffer.bind_vertex_buffers(0, vbufs); + buffer.bind_graphics_descriptor_sets( + &self.pipeline_layout, + 0, + once(self.texture_store.descriptor_set.deref()), + &[] + ); + + buffer.bind_vertex_buffers(0, vbufs); buffer.bind_index_buffer(IndexBufferView { buffer: ibuf, range: SubRange::WHOLE, @@ -727,6 +764,7 @@ impl<'a> core::ops::Drop for RenderingContext<'a> { 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); self.device.destroy_command_pool( ManuallyDrop::into_inner(read(&self.cmd_pool)), @@ -738,9 +776,6 @@ impl<'a> core::ops::Drop for RenderingContext<'a> { .destroy_swapchain(ManuallyDrop::into_inner(read(&self.swapchain))); self.device - .destroy_descriptor_set_layout(ManuallyDrop::into_inner(read(&self.descriptor_set_layouts))); - - self.device .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); self.instance diff --git a/stockton-render/src/draw/data/stockton.frag b/stockton-render/src/draw/data/stockton.frag index 7cb4a47..09ff8a7 100644 --- a/stockton-render/src/draw/data/stockton.frag +++ b/stockton-render/src/draw/data/stockton.frag @@ -1,10 +1,19 @@ #version 450 -layout(location = 1) in vec3 frag_colour; +layout(set = 0, binding = 0) uniform texture2D tex[2]; +layout(set = 0, binding = 1) uniform sampler samp[2]; -layout(location = 0) out vec4 colour; +layout (location = 1) in vec3 frag_color; +layout (location = 2) in vec2 frag_uv; +layout (location = 3) in flat int frag_tex; + +layout (location = 0) out vec4 color; void main() { - colour = vec4(frag_colour, 1.0); + if(frag_tex == -1) { + color = vec4(frag_color, 1.0); + } else { + color = texture(sampler2D(tex[frag_tex], samp[frag_tex]), frag_uv); + } }
\ No newline at end of file diff --git a/stockton-render/src/draw/data/stockton.vert b/stockton-render/src/draw/data/stockton.vert index f6cafa9..f40d0b8 100644 --- a/stockton-render/src/draw/data/stockton.vert +++ b/stockton-render/src/draw/data/stockton.vert @@ -2,14 +2,20 @@ layout (location = 0) in vec2 position; layout (location = 1) in vec3 colour; +layout (location = 2) in vec2 uv; +layout (location = 3) in int tex; out gl_PerVertex { - vec4 gl_Position; + vec4 gl_Position; }; layout (location = 1) out vec3 frag_colour; +layout (location = 2) out vec2 frag_uv; +layout (location = 3) out flat int frag_tex; void main() { gl_Position = vec4(position, 0.0, 1.0); frag_colour = colour; + frag_uv = uv; + frag_tex = tex; }
\ No newline at end of file diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs index 1b6db2a..a1a5780 100644 --- a/stockton-render/src/draw/mod.rs +++ b/stockton-render/src/draw/mod.rs @@ -17,6 +17,7 @@ mod context; mod buffer; +mod texture; pub use self::context::RenderingContext; pub use self::context::UVPoint; diff --git a/stockton-render/src/draw/texture.rs b/stockton-render/src/draw/texture.rs new file mode 100644 index 0000000..52d899f --- /dev/null +++ b/stockton-render/src/draw/texture.rs @@ -0,0 +1,443 @@ +// 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 +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. + +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. + +// You should have received a copy of the GNU General Public License along +// with this program. If not, see <http://www.gnu.org/licenses/>. + +//! Deals with loading textures into GPU memory + +use core::mem::{ManuallyDrop, size_of}; +use std::{ + mem::replace, + ptr::copy_nonoverlapping, + convert::TryInto, + iter::once, + ops::Deref +}; + +use hal::{ + MemoryTypeId, + buffer::Usage as BufUsage, + format::{Format, Swizzle, Aspects}, + image::{ViewKind, SubresourceRange}, + queue::Submission, + memory::{Properties as MemProperties, Dependencies as MemDependencies, Segment}, + prelude::*, +}; + +use image::RgbaImage; + +use crate::error; +use crate::types::*; +use super::buffer::create_buffer; + +/// The size of each pixel in an image +const PIXEL_SIZE: usize = size_of::<image::Rgba<u8>>(); + +/// Stores all loaded textures in GPU memory. +/// When rendering, the descriptor sets are bound to the buffer +/// The descriptor set layout should have the same count of textures as this does. +/// Note that it's possible not all descriptors are actually initialised images +pub struct TextureStore { + descriptor_pool: ManuallyDrop<DescriptorPool>, + pub descriptor_set: ManuallyDrop<DescriptorSet>, + pub descriptor_set_layout: ManuallyDrop<DescriptorSetLayout>, + loaded_images: Vec<LoadedImage>, + next_index: usize, + size: usize +} + +impl TextureStore { + pub fn new(device: &mut Device, size: usize) -> Result<TextureStore, error::CreationError> { + + // Descriptor set layout + let 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: size, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: size, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false + } + ], + &[], + ) + }.map_err(|_| error::CreationError::OutOfMemoryError)?; + + let (descriptor_pool, descriptor_set) = unsafe { + use hal::pso::{DescriptorRangeDesc, DescriptorType, DescriptorPoolCreateFlags, ImageDescriptorType}; + + let mut pool = device.create_descriptor_pool( + 1, + &[ + DescriptorRangeDesc { + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false + } + }, + count: size + }, + DescriptorRangeDesc { + ty: DescriptorType::Sampler, + count: size + } + ], + DescriptorPoolCreateFlags::empty() + ).map_err(|_| error::CreationError::OutOfMemoryError)?; + + let set = pool.allocate_set(&descriptor_set_layout).map_err(|_| error::CreationError::OutOfMemoryError)?; + + (pool, set) + }; + + Ok(TextureStore { + descriptor_pool: ManuallyDrop::new(descriptor_pool), + descriptor_set: ManuallyDrop::new(descriptor_set), + loaded_images: Vec::with_capacity(size), + descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), + next_index: 0, + size + }) + } + + /// Add the texture to this texturestore + /// Returns the allocated index or the error. + // TODO: Better error + pub fn add_texture(&mut self, image: RgbaImage, + device: &mut Device, + adapter: &mut Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool) -> Result<usize, &'static str> { + + if self.next_index == self.size { + return Err("Texture requested but store is out of space!"); + } + + let idx = self.next_index; + self.put_texture(image, idx, device, adapter, command_queue, command_pool)?; + self.next_index += 1; + + Ok(idx) + } + + 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>{ + + if idx >= self.size || idx > self.loaded_images.len() { + return Err("Texture index out of bounds or non-continuous index!"); + } + + // Load the image + let texture = LoadedImage::load( + image, + device, + adapter, + command_queue, + command_pool, + )?; + + // 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.deref(), + binding: 0, + array_offset: idx, + descriptors: Some(Descriptor::Image( + texture.image_view.deref(), + Layout::ShaderReadOnlyOptimal + )), + }, + DescriptorSetWrite { + set: self.descriptor_set.deref(), + 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.loaded_images.len() { + replace(&mut self.loaded_images[idx], texture).deactivate(device); + } else { + self.loaded_images.push(texture); + } + + Ok(()) + } + + pub fn deactivate(mut self, device: &mut Device) -> () { + unsafe { + use core::ptr::read; + + self.loaded_images.drain(..).map(|img| img.deactivate(device)).collect(); + + self.descriptor_pool.free_sets(once(ManuallyDrop::into_inner(read(&self.descriptor_set)))); + device.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&self.descriptor_pool))); + device + .destroy_descriptor_set_layout(ManuallyDrop::into_inner(read(&self.descriptor_set_layout))); + } + } +} + +/// Holds an image that's loaded into GPU memory and can be sampled from +pub struct LoadedImage { + image: ManuallyDrop<Image>, + pub image_view: ManuallyDrop<ImageView>, + pub sampler: ManuallyDrop<Sampler>, + memory: ManuallyDrop<Memory> +} + +impl LoadedImage { + /// Load the given image into a new buffer + pub fn load(img: RgbaImage, device: &mut Device, adapter: &Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool) -> Result<LoadedImage, &'static str> { + // Round up the size to align properly + 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; + debug_assert!(row_size as usize >= initial_row_size); + + let total_size = (row_size * img.height() as usize) as u64; + + // 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.unmap_memory(&staging_memory); + } + + // Make the image + let mut image_ref = unsafe { + use hal::image::{Kind, Tiling, Usage, ViewCapabilities}; + + device.create_image( + Kind::D2(img.width(), img.height(), 1, 1), + 1, + Format::Rgba8Srgb, + Tiling::Optimal, + Usage::TRANSFER_DST | Usage::SAMPLED, + 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) + }?; + + // 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: &image_ref, + 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, &image_ref, + 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: &image_ref, + 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); + } + + // Create ImageView and sampler + let image_view = unsafe { device.create_image_view( + &image_ref, + ViewKind::D2, + Format::Rgba8Srgb, + Swizzle::NO, + SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + )}.map_err(|_| "Couldn't create the image view!")?; + + 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(LoadedImage { + image: ManuallyDrop::new(image_ref), + image_view: ManuallyDrop::new(image_view), + sampler: ManuallyDrop::new(sampler), + memory: ManuallyDrop::new(memory) + }) + } + + /// 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_sampler(ManuallyDrop::into_inner(read(&self.sampler))); + 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))); + } + } +}
\ No newline at end of file diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index 0222933..7f6dc1c 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -18,6 +18,7 @@ extern crate core; #[cfg(feature = "vulkan")] extern crate gfx_backend_vulkan as back; +extern crate image; extern crate log; extern crate gfx_hal as hal; extern crate stockton_types; diff --git a/stockton-render/src/types.rs b/stockton-render/src/types.rs index 297eb69..7bd597b 100644 --- a/stockton-render/src/types.rs +++ b/stockton-render/src/types.rs @@ -26,8 +26,12 @@ pub type CommandPool = <back::Backend as hal::Backend>::CommandPool; pub type CommandBuffer = <back::Backend as hal::Backend>::CommandBuffer; pub type CommandQueue = <back::Backend as hal::Backend>::CommandQueue; pub type DescriptorSetLayout = <back::Backend as hal::Backend>::DescriptorSetLayout; +pub type DescriptorPool = <back::Backend as hal::Backend>::DescriptorPool; +pub type DescriptorSet = <back::Backend as hal::Backend>::DescriptorSet; pub type PipelineLayout = <back::Backend as hal::Backend>::PipelineLayout; pub type GraphicsPipeline = <back::Backend as hal::Backend>::GraphicsPipeline; +pub type Sampler = <back::Backend as hal::Backend>::Sampler; +pub type Image = <back::Backend as hal::Backend>::Image; pub type ImageView = <back::Backend as hal::Backend>::ImageView; pub type Framebuffer = <back::Backend as hal::Backend>::Framebuffer; pub type RenderPass = <back::Backend as hal::Backend>::RenderPass; |