aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--examples/render-quad/Cargo.toml2
-rw-r--r--examples/render-quad/data/test1.pngbin0 -> 486777 bytes
-rw-r--r--examples/render-quad/data/test2.pngbin0 -> 375052 bytes
-rw-r--r--examples/render-quad/src/main.rs37
-rw-r--r--stockton-render/Cargo.toml1
-rw-r--r--stockton-render/src/draw/buffer.rs9
-rw-r--r--stockton-render/src/draw/context.rs85
-rw-r--r--stockton-render/src/draw/data/stockton.frag15
-rw-r--r--stockton-render/src/draw/data/stockton.vert8
-rw-r--r--stockton-render/src/draw/mod.rs1
-rw-r--r--stockton-render/src/draw/texture.rs443
-rw-r--r--stockton-render/src/lib.rs1
-rw-r--r--stockton-render/src/types.rs4
14 files changed, 572 insertions, 39 deletions
diff --git a/README.md b/README.md
index 47c6725..e390f81 100644
--- a/README.md
+++ b/README.md
@@ -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
new file mode 100644
index 0000000..3d37758
--- /dev/null
+++ b/examples/render-quad/data/test1.png
Binary files differ
diff --git a/examples/render-quad/data/test2.png b/examples/render-quad/data/test2.png
new file mode 100644
index 0000000..f33cc3e
--- /dev/null
+++ b/examples/render-quad/data/test2.png
Binary files differ
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(&copy_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;