/*
* Copyright (C) Oscar Shrimpton 2020
*
* 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 .
*/
use core::{
mem::{size_of, ManuallyDrop},
ptr::copy_nonoverlapping,
};
use hal::{
buffer::Usage as BufUsage,
format::{Aspects, Format, Swizzle},
image::{SubresourceRange, Usage as ImgUsage, ViewKind},
memory::{Dependencies as MemDependencies, Properties as MemProperties},
prelude::*,
queue::Submission,
MemoryTypeId,
};
use image::RgbaImage;
use rendy_memory::{Allocator, Block};
use std::{convert::TryInto, iter::once};
use crate::draw::buffer::create_buffer;
use crate::types::*;
/// The size of each pixel in an image
const PIXEL_SIZE: usize = size_of::() * 4;
/// An object that can be loaded as an image into GPU memory
pub trait LoadableImage {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn copy_row(&self, y: u32, ptr: *mut u8);
}
impl LoadableImage for RgbaImage {
fn width(&self) -> u32 {
self.width()
}
fn height(&self) -> u32 {
self.height()
}
fn copy_row(&self, y: u32, ptr: *mut u8) {
let row_size_bytes = self.width() as usize * PIXEL_SIZE;
let raw: &Vec = self.as_raw();
let row = &raw[y as usize * row_size_bytes..(y as usize + 1) * row_size_bytes];
unsafe {
copy_nonoverlapping(row.as_ptr(), ptr, row.len());
}
}
}
/// Holds an image that's loaded into GPU memory and can be sampled from
pub struct LoadedImage {
/// The GPU Image handle
image: ManuallyDrop,
/// The full view of the image
pub image_view: ManuallyDrop,
/// The memory backing the image
memory: ManuallyDrop,
}
pub fn create_image_view(
device: &mut Device,
adapter: &Adapter,
allocator: &mut DynamicAllocator,
format: Format,
usage: ImgUsage,
width: usize,
height: usize,
) -> Result<(DynamicBlock, 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 (block, _) = unsafe {
let requirements = device.get_image_requirements(&image_ref);
allocator.alloc(device, requirements.size, requirements.alignment)
}
.map_err(|_| "Out of memory")?;
unsafe {
device
.bind_image_memory(&block.memory(), block.range().start, &mut image_ref)
.map_err(|_| "Couldn't bind memory to image")?;
}
Ok((block, image_ref))
}
impl LoadedImage {
pub fn new(
device: &mut Device,
adapter: &Adapter,
allocator: &mut DynamicAllocator,
format: Format,
usage: ImgUsage,
resources: SubresourceRange,
width: usize,
height: usize,
) -> Result {
let (memory, image_ref) =
create_image_view(device, adapter, allocator, 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: T,
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 | MemProperties::COHERENT,
total_size,
)
.map_err(|_| "Couldn't create staging buffer")?;
// Copy everything into it
unsafe {
let mapped_memory: *mut u8 = std::mem::transmute(
device
.map_memory(&staging_memory, 0..total_size)
.map_err(|_| "Couldn't map buffer memory")?,
);
for y in 0..img.height() as usize {
let dest_base: isize = (y * row_size).try_into().unwrap();
img.copy_row(y as u32, mapped_memory.offset(dest_base));
}
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(())
}
/// Properly frees/destroys all the objects in this struct
/// Dropping without doing this is a bad idea
pub fn deactivate(self, device: &mut Device, allocator: &mut DynamicAllocator) {
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)));
allocator.free(device, ManuallyDrop::into_inner(read(&self.memory)));
}
}
}
pub struct SampledImage {
pub image: ManuallyDrop,
pub sampler: ManuallyDrop,
}
impl SampledImage {
pub fn new(
device: &mut Device,
adapter: &Adapter,
allocator: &mut DynamicAllocator,
format: Format,
usage: ImgUsage,
width: usize,
height: usize,
) -> Result {
let image = LoadedImage::new(
device,
adapter,
allocator,
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: T,
device: &mut Device,
adapter: &Adapter,
allocator: &mut DynamicAllocator,
command_queue: &mut CommandQueue,
command_pool: &mut CommandPool,
format: Format,
usage: ImgUsage,
) -> Result {
let mut sampled_image = SampledImage::new(
device,
adapter,
allocator,
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, allocator: &mut DynamicAllocator) {
unsafe {
use core::ptr::read;
device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler)));
ManuallyDrop::into_inner(read(&self.image)).deactivate(device, allocator);
}
}
}
/// Holds an image that's loaded into GPU memory dedicated only to that image, bypassing the memory allocator.
pub struct DedicatedLoadedImage {
/// The GPU Image handle
image: ManuallyDrop,
/// The full view of the image
pub image_view: ManuallyDrop,
/// The memory backing the image
memory: ManuallyDrop,
}
impl DedicatedLoadedImage {
pub fn new(
device: &mut Device,
adapter: &Adapter,
format: Format,
usage: ImgUsage,
resources: SubresourceRange,
width: usize,
height: usize,
) -> Result {
let (memory, image_ref) = {
// 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
// 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))
}?;
// 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(DedicatedLoadedImage {
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: T,
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 | MemProperties::COHERENT,
total_size,
)
.map_err(|_| "Couldn't create staging buffer")?;
// Copy everything into it
unsafe {
let mapped_memory: *mut u8 = std::mem::transmute(
device
.map_memory(&staging_memory, 0..total_size)
.map_err(|_| "Couldn't map buffer memory")?,
);
for y in 0..img.height() as usize {
let dest_base: isize = (y * row_size).try_into().unwrap();
img.copy_row(y as u32, mapped_memory.offset(dest_base));
}
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: T,
device: &mut Device,
adapter: &Adapter,
command_queue: &mut CommandQueue,
command_pool: &mut CommandPool,
format: Format,
usage: ImgUsage,
) -> Result {
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: &mut 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)));
}
}
}