Skip to content

Commit

Permalink
Merge pull request #41 from NotAPenguin0/develop
Browse files Browse the repository at this point in the history
Final changes for 0.5.0 release
  • Loading branch information
NotAPenguin0 authored Mar 17, 2023
2 parents 247e2a1 + cc035a7 commit 3bcaa36
Show file tree
Hide file tree
Showing 45 changed files with 652 additions and 131 deletions.
14 changes: 13 additions & 1 deletion examples/basic/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::io::Read;
use std::path::Path;
use std::sync::{Arc, Mutex};

use phobos::prelude as ph;
use phobos::{domain, PipelineStage, prelude as ph};
use phobos::command_buffer::traits::*;
use phobos::RecordGraphToCommandBuffer;
use ph::vk;
Expand Down Expand Up @@ -36,6 +36,18 @@ fn main_loop(frame: &mut ph::FrameManager,
exec: ph::ExecutionManager,
surface: &ph::Surface,
window: &winit::window::Window) -> Result<()> {

// Lets try the new batch submit API
block_on({
let cmd1 = exec.on_domain::<domain::All>(None, None)?.finish()?;
let cmd2 = exec.on_domain::<domain::All>(None, None)?.finish()?;
let mut batch = exec.start_submit_batch()?;
batch.submit(cmd1)?
.then(PipelineStage::COLOR_ATTACHMENT_OUTPUT, cmd2, &mut batch)?;
batch.finish()?
});


// Define a virtual resource pointing to the swapchain
let swap_resource = ph::VirtualResource::image("swapchain".to_string());
let offscreen = ph::VirtualResource::image("offscreen".to_string());
Expand Down
7 changes: 7 additions & 0 deletions src/allocator/default_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,25 @@ use gpu_allocator::vulkan::AllocationScheme;
use crate::{Device, Error, PhysicalDevice, VkInstance};
use crate::allocator::memory_type::MemoryType;

/// The default allocator. This calls into the `gpu_allocator` crate.
/// It's important to note that this allocator is `Clone`, `Send` and `Sync`. All its internal state is safely
/// wrapped inside an `Arc<Mutex<T>>`. This is to facilitate passing it around everywhere.
#[derive(Clone, Derivative)]
#[derivative(Debug)]
pub struct DefaultAllocator {
#[derivative(Debug="ignore")]
alloc: Arc<Mutex<vk_alloc::Allocator>>,
}

/// Allocation returned from the default allocator. This must be freed explicitly by calling [`DefaultAllocator::free()`]
#[derive(Default, Derivative)]
#[derivative(Debug)]
pub struct Allocation {
allocation: vk_alloc::Allocation,
}

impl DefaultAllocator {
/// Create a new default allocator.
pub fn new(instance: &VkInstance, device: &Arc<Device>, physical_device: &PhysicalDevice) -> Result<Self> {
Ok(Self {
alloc: Arc::new(Mutex::new(vk_alloc::Allocator::new(
Expand All @@ -44,6 +49,7 @@ impl DefaultAllocator {
impl traits::Allocator for DefaultAllocator {
type Allocation = Allocation;

/// Allocates raw memory of a specific memory type. The given name is used for internal tracking.
fn allocate(&mut self, name: &'static str, requirements: &MemoryRequirements, ty: MemoryType) -> Result<Self::Allocation> {
let mut alloc = self.alloc.lock().map_err(|_| Error::PoisonError)?;
let allocation = alloc.allocate(&vk_alloc::AllocationCreateDesc {
Expand All @@ -59,6 +65,7 @@ impl traits::Allocator for DefaultAllocator {
})
}

/// Free some memory allocated from this allocator.
fn free(&mut self, allocation: Self::Allocation) -> Result<()> {
let mut alloc = self.alloc.lock().map_err(|_| Error::PoisonError)?;
alloc.free(allocation.allocation)?;
Expand Down
1 change: 1 addition & 0 deletions src/allocator/memory_type.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// The memory type of an allocation indicates where it should live.
#[derive(Debug)]
pub enum MemoryType {
/// Store the allocation in GPU only accessible memory - typically this is the faster GPU resource and this should be
Expand Down
12 changes: 12 additions & 0 deletions src/allocator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
//! The allocator module exposes a couple interesting parts of the API
//! <br>
//! <br>
//! # Allocator traits
//! These are defined in [`traits`], and can be implemented to supply a custom allocator type to all phobos functions.
//! # Default allocator
//! A default allocator based on the `gpu_allocator` crate is implemented here. Most types that take a generic allocator
//! parameter default to this allocator.
//! # Scratch allocator
//! A linear allocator used for making temporary, short lived allocations. For more information check the [`scratch_allocator`]
//! module documentation.
pub mod traits;
pub mod default_allocator;
pub mod memory_type;
Expand Down
22 changes: 16 additions & 6 deletions src/allocator/scratch_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
//! # Example
//!
//! ```
//! use ash::vk;
//! use phobos as ph;
//!
//! use phobos::prelude::*;
//! // Some allocator
//! let alloc = create_allocator();
//! // Create a scratch allocator with at most 1 KiB of available memory for uniform buffers
//! let mut allocator = ph::ScratchAllocator::new(device.clone(), alloc.clone(), (1 * 1024) as vk::DeviceSize, vk::BufferUsageFlags::UNIFORM_BUFFER);
//! let mut allocator = ScratchAllocator::new(device.clone(), alloc.clone(), 1 * 1024u64, vk::BufferUsageFlags::UNIFORM_BUFFER);
//!
//! // Allocate a 64 byte uniform buffer and use it
//! let buffer = allocator.allocate(64 as vk::DeviceSize)?;
//! let buffer = allocator.allocate(64 as u64)?;
//! // For buffer usage, check the buffer module documentation.
//!
//! // Once we're ready for the next batch of allocations, call reset(). This must happen
Expand All @@ -31,6 +31,7 @@ use crate::{Allocator, Buffer, BufferView, DefaultAllocator, Device, Error, Memo
use crate::Error::AllocationError;
use anyhow::Result;

/// Very simple linear allocator. For example usage, see the module level documentation.
#[derive(Debug)]
pub struct ScratchAllocator<A: Allocator = DefaultAllocator> {
buffer: Buffer<A>,
Expand All @@ -39,6 +40,8 @@ pub struct ScratchAllocator<A: Allocator = DefaultAllocator> {
}

impl<A: Allocator> ScratchAllocator<A> {
/// Create a new scratch allocator with a specified max capacity. All possible usages for buffers allocated from this should be
/// given in the usage flags.
pub fn new(device: Arc<Device>, allocator: &mut A, max_size: impl Into<vk::DeviceSize>, usage: vk::BufferUsageFlags) -> Result<Self> {
let buffer = Buffer::new(device.clone(), allocator, max_size, usage, MemoryType::CpuToGpu)?;
let alignment = if usage.intersects(vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::INDEX_BUFFER) {
Expand All @@ -62,6 +65,9 @@ impl<A: Allocator> ScratchAllocator<A> {
}
}

/// Allocates a fixed amount of bytes from the allocator.
/// # Errors
/// - Fails if the allocator has ran out of memory.
pub fn allocate(&mut self, size: impl Into<vk::DeviceSize>) -> Result<BufferView> {
let size = size.into();
// Part of the buffer that is over the min alignment
Expand All @@ -84,7 +90,11 @@ impl<A: Allocator> ScratchAllocator<A> {
}
}

pub fn reset(&mut self) {
/// Resets the linear allocator back to the beginning. Proper external synchronization needs to be
/// added to ensure old buffers are not overwritten.
/// # Safety
/// This function is only safe if the old allocations can be completely discarded by the next time [`Self::allocate()`] is called.
pub unsafe fn reset(&mut self) {
self.offset = 0;
}
}
10 changes: 10 additions & 0 deletions src/allocator/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ use ash::vk;
use anyhow::Result;
use crate::allocator::memory_type::MemoryType;

/// To supply custom allocators to phobos, this trait must be implemented.
/// Note that all allocators must be `Clone`, `Send` and `Sync`. To do this, wrap internal state in
/// `Arc<Mutex<T>>` where applicable.
pub trait Allocator: Clone + Send + Sync {
/// Allocation type for this allocator. Must implement [`Allocation`]
type Allocation: Allocation;

/// Allocates raw memory of a specific memory type. The given name is used for internal tracking.
fn allocate(&mut self, name: &'static str, requirements: &vk::MemoryRequirements, ty: MemoryType) -> Result<Self::Allocation>;
/// Free some memory allocated from this allocator.
fn free(&mut self, allocation: Self::Allocation) -> Result<()>;
}

/// Represents an allocation. This trait exposes methods for accessing the underlying device memory, obtain a mapped pointer, etc.
pub trait Allocation: Default {
/// Access the underlying [`VkDeviceMemory`]. Remember to always `Self::offset()` into this.
unsafe fn memory(&self) -> vk::DeviceMemory;
/// The offset of this allocation in the underlying memory block.
fn offset(&self) -> vk::DeviceSize;
/// Obtain a mapped pointer to the memory, or None if this is not possible.
fn mapped_ptr(&self) -> Option<NonNull<c_void>>;
}
28 changes: 23 additions & 5 deletions src/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Similarly to the [`image`] module, this module exposes two types: [`Buffer`] and [`BufferView`]. The difference here is that a
//! Similarly to the [`image`](crate::image) module, this module exposes two types: [`Buffer`] and [`BufferView`]. The difference here is that a
//! [`BufferView`] does not own a vulkan resource, so it cane be freely copied around as long as the owning [`Buffer`] lives.
//!
//! It also exposes some utilities for writing to memory-mapped buffers. For this you can use [`BufferView::mapped_slice`]. This only succeeds
Expand All @@ -7,9 +7,8 @@
//! # Example
//!
//! ```
//! use ash::vk;
//! use gpu_allocator::MemoryLocation;
//! use phobos as ph;
//! use phobos::prelude::*;
//!
//! // Allocate a new buffer
//! let buf = Buffer::new(device.clone(),
//! alloc.clone(),
Expand All @@ -19,7 +18,7 @@
//! vk::BufferUsageFlags::UNIFORM_BUFFER,
//! // CpuToGpu will always set HOST_VISIBLE and HOST_COHERENT, and try to set DEVICE_LOCAL.
//! // Usually this resides on the PCIe BAR.
//! MemoryLocation::CpuToGpu);
//! MemoryType::CpuToGpu);
//! // Obtain a buffer view to the entire buffer.
//! let mut view = buf.view_full();
//! // Obtain a slice of floats
Expand All @@ -38,6 +37,7 @@ use crate::{Allocation, Allocator, DefaultAllocator, Device, Error, MemoryType};
use anyhow::Result;


/// Wrapper around a [`VkBuffer`](vk::Buffer).
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Buffer<A: Allocator = DefaultAllocator> {
Expand All @@ -52,6 +52,9 @@ pub struct Buffer<A: Allocator = DefaultAllocator> {
pub size: vk::DeviceSize,
}

/// View into a specific offset and range of a [`Buffer`].
/// Care should be taken with the lifetime of this, as there is no checking that the buffer
/// is not dropped while using this.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct BufferView {
pub(crate) handle: vk::Buffer,
Expand All @@ -61,6 +64,8 @@ pub struct BufferView {
}

impl<A: Allocator> Buffer<A> {
/// Allocate a new buffer with a specific size, at a specific memory location.
/// All usage flags must be given.
pub fn new(device: Arc<Device>, allocator: &mut A, size: impl Into<vk::DeviceSize>, usage: vk::BufferUsageFlags, location: MemoryType) -> Result<Self> {
let size = size.into();
let handle = unsafe {
Expand Down Expand Up @@ -91,10 +96,16 @@ impl<A: Allocator> Buffer<A> {
})
}

/// Allocate a new buffer with device local memory (VRAM). This is usually the correct memory location for most buffers.
pub fn new_device_local(device: Arc<Device>, allocator: &mut A, size: impl Into<vk::DeviceSize>, usage: vk::BufferUsageFlags) -> Result<Self> {
Self::new(device, allocator, size, usage, MemoryType::GpuOnly)
}

/// Creates a view into an offset and size of the buffer.
/// # Lifetime
/// This view is valid as long as the buffer is valid.
/// # Errors
/// Fails if `offset + size >= self.size`.
pub fn view(&self, offset: impl Into<vk::DeviceSize>, size: impl Into<vk::DeviceSize>) -> Result<BufferView> {
let offset = offset.into();
let size = size.into();
Expand All @@ -110,6 +121,9 @@ impl<A: Allocator> Buffer<A> {
}
}

/// Creates a view of the entire buffer.
/// # Lifetime
/// This view is valid as long as the buffer is valid.
pub fn view_full(&self) -> BufferView {
BufferView {
handle: self.handle,
Expand All @@ -119,6 +133,7 @@ impl<A: Allocator> Buffer<A> {
}
}

/// True if this buffer has a mapped pointer and thus can directly be written to.
pub fn is_mapped(&self) -> bool {
self.pointer.is_some()
}
Expand All @@ -133,6 +148,9 @@ impl<A: Allocator> Drop for Buffer<A> {
}

impl BufferView {
/// Obtain a slice to the mapped memory of this buffer.
/// # Errors
/// Fails if this buffer is not mappable (not `HOST_VISIBLE`).
pub fn mapped_slice<T>(&mut self) -> Result<&mut [T]> {
if let Some(pointer) = self.pointer {
Ok(unsafe { std::slice::from_raw_parts_mut(pointer.cast::<T>().as_ptr(), self.size as usize / std::mem::size_of::<T>()) })
Expand Down
17 changes: 15 additions & 2 deletions src/command_buffer/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anyhow::Result;
use crate::command_buffer::IncompleteCommandBuffer;

impl<D: GfxSupport + ExecutionDomain> GraphicsCmdBuffer for IncompleteCommandBuffer<'_, D> {

/// Sets the viewport and scissor regions to the entire render area. Can only be called inside a renderpass.
fn full_viewport_scissor(self) -> Self {
let area = self.current_render_area;
self.viewport(vk::Viewport {
Expand All @@ -20,29 +20,37 @@ impl<D: GfxSupport + ExecutionDomain> GraphicsCmdBuffer for IncompleteCommandBuf
.scissor(area)
}


/// Sets the viewport. Directly translates to [`vkCmdSetViewport`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetViewport.html).
fn viewport(self, viewport: vk::Viewport) -> Self {
unsafe { self.device.cmd_set_viewport(self.handle, 0, std::slice::from_ref(&viewport)); }
self
}

/// Sets the scissor region. Directly translates to [`vkCmdSetScissor`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdSetScissor.html).
fn scissor(self, scissor: vk::Rect2D) -> Self {
unsafe { self.device.cmd_set_scissor(self.handle, 0, std::slice::from_ref(&scissor)); }
self
}

/// Issue a drawcall. This will flush the current descriptor set state and actually bind the descriptor sets.
/// Directly translates to [`vkCmdDraw`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdDraw.html).
fn draw(mut self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) -> Result<Self> {
self = self.ensure_descriptor_state()?;
unsafe { self.device.cmd_draw(self.handle, vertex_count, instance_count, first_vertex, first_instance); }
Ok(self)
}

/// Issue an indexed drawcall. This will flush the current descriptor state and actually bind the
/// descriptor sets. Directly translates to [`vkCmdDrawIndexed`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdDrawIndexed.html).
fn draw_indexed(mut self, index_count: u32, instance_count: u32, first_index: u32, vertex_offset: i32, first_instance: u32) -> Result<Self> {
self = self.ensure_descriptor_state()?;
unsafe { self.device.cmd_draw_indexed(self.handle, index_count, instance_count, first_index, vertex_offset, first_instance) }
Ok(self)
}

/// Bind a graphics pipeline by name.
/// # Errors
/// Fails if the pipeline was not previously registered in the pipeline cache.
fn bind_graphics_pipeline(mut self, name: &str) -> Result<Self> {
let Some(cache) = &self.pipeline_cache else { return Err(Error::NoPipelineCache.into()); };
{
Expand All @@ -57,16 +65,21 @@ impl<D: GfxSupport + ExecutionDomain> GraphicsCmdBuffer for IncompleteCommandBuf
Ok(self)
}

/// Binds a vertex buffer to the specified binding point. Note that currently there is no validation as to whether this
/// binding actually exists for the given pipeline. Direct translation of [`vkCmdBindVertexBuffers`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdBindVertexBuffers.html).
fn bind_vertex_buffer(self, binding: u32, buffer: &BufferView) -> Self where Self: Sized {
unsafe { self.device.cmd_bind_vertex_buffers(self.handle, binding, std::slice::from_ref(&buffer.handle), std::slice::from_ref(&buffer.offset)) };
self
}

/// Bind the an index buffer. The index type must match. Direct translation of [`vkCmdBindIndexBuffer`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdBindIndexBuffer.html)
fn bind_index_buffer(self, buffer: &BufferView, ty: vk::IndexType) -> Self where Self: Sized {
unsafe { self.device.cmd_bind_index_buffer(self.handle, buffer.handle, buffer.offset, ty); }
self
}

/// Blit a source image to a destination image, using the specified offsets into the images and a filter. Direct and thin wrapper around
/// [`vkCmdBlitImage`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdBlitImage.html)
fn blit_image(self, src: &ImageView, dst: &ImageView, src_offsets: &[vk::Offset3D; 2], dst_offsets: &[vk::Offset3D; 2], filter: vk::Filter) -> Self where Self: Sized {
let blit = vk::ImageBlit {
src_subresource: vk::ImageSubresourceLayers {
Expand Down
12 changes: 11 additions & 1 deletion src/command_buffer/incomplete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl<D: ExecutionDomain> IncompleteCommandBuffer<'_, D> {
/// - This function can error if allocating the descriptor set fails.
/// # Example
/// ```
/// use phobos::{DescriptorSetBuilder, domain, ExecutionManager};
/// use phobos::prelude::*;
/// let exec = ExecutionManager::new(device.clone(), &physical_device); ///
/// let cmd = exec.on_domain::<domain::All>()?
/// .bind_graphics_pipeline("my_pipeline", pipeline_cache.clone())
Expand All @@ -162,6 +162,9 @@ impl<D: ExecutionDomain> IncompleteCommandBuffer<'_, D> {
self
}

/// Resolve a virtual resource from the given bindings, and bind it as a sampled image to the given slot.
/// # Errors
/// Fails if the virtual resource has no binding associated to it.
pub fn resolve_and_bind_sampled_image(mut self,
set: u32,
binding: u32,
Expand All @@ -176,6 +179,7 @@ impl<D: ExecutionDomain> IncompleteCommandBuffer<'_, D> {
Ok(self)
}

/// Binds a combined image + sampler to the specified slot.
pub fn bind_sampled_image(mut self, set: u32, binding: u32, image: &ImageView, sampler: &Sampler) -> Result<Self> {
self.modify_descriptor_set(set, |builder| {
builder.bind_sampled_image(binding, image, sampler);
Expand All @@ -184,6 +188,7 @@ impl<D: ExecutionDomain> IncompleteCommandBuffer<'_, D> {
Ok(self)
}

/// Binds a uniform buffer to teh specified slot.
pub fn bind_uniform_buffer(mut self, set: u32, binding: u32, buffer: &BufferView) -> Result<Self> {
self.modify_descriptor_set(set, |builder| {
builder.bind_uniform_buffer(binding, buffer);
Expand Down Expand Up @@ -239,7 +244,12 @@ impl<D: ExecutionDomain> IncompleteCommandBuffer<'_, D> {
self
}

/// Upload push constants. These are small packets of data stored inside the command buffer, so their state is tracked while executing.
/// Direct translation of [`vkCmdPushConstants`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushConstants.html).
/// Tends to crash on some drivers if the specified push constant range does not exist (possible due to unused variable optimization in the shader,
/// or incorrect stage flags specified)
pub fn push_constants<T: Copy>(self, stage: vk::ShaderStageFlags, offset: u32, data: &[T]) -> Self {
// TODO: Validate push constant ranges with current pipeline layout to prevent crashes.
unsafe {
let (_, data, _) = data.align_to::<u8>();
self.device.cmd_push_constants(self.handle, self.current_pipeline_layout, stage, offset, data);
Expand Down
Loading

0 comments on commit 3bcaa36

Please sign in to comment.