Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Final changes for 0.5.0 release #41

Merged
merged 3 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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