/* * 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}; use std::convert::TryInto; use std::iter::once; use std::ops::{Index, IndexMut}; use hal::prelude::*; use hal::{ buffer::Usage, memory::Properties, queue::Submission, MemoryTypeId, }; use crate::error::CreationError; use crate::types::*; /// Create a buffer of the given specifications, allocating more device memory. // TODO: Use a different memory allocator? pub(crate) fn create_buffer( device: &mut Device, adapter: &Adapter, usage: Usage, properties: Properties, size: u64, ) -> Result<(Buffer, Memory), CreationError> { let mut buffer = unsafe { device.create_buffer(size, usage) }.map_err(CreationError::BufferError)?; let requirements = unsafe { device.get_buffer_requirements(&buffer) }; 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(properties) }) .map(|(id, _)| MemoryTypeId(id)) .ok_or(CreationError::BufferNoMemory)?; let memory = unsafe { device.allocate_memory(memory_type_id, requirements.size) } .map_err(|_| CreationError::OutOfMemoryError)?; unsafe { device.bind_buffer_memory(&memory, 0, &mut buffer) } .map_err(|_| CreationError::BufferNoMemory)?; Ok((buffer, memory)) } /// A buffer that can be modified by the CPU pub trait ModifiableBuffer: IndexMut { /// Get a handle to the underlying GPU buffer fn get_buffer(&mut self) -> &Buffer; /// Commit all changes to GPU memory, returning a handle to the GPU buffer fn commit<'a>( &'a mut self, device: &Device, command_queue: &mut CommandQueue, command_pool: &mut CommandPool, ) -> &'a Buffer; } /// A GPU buffer that is written to using a staging buffer pub struct StagedBuffer<'a, T: Sized> { /// CPU-visible buffer staged_buffer: ManuallyDrop, /// CPU-visible memory staged_memory: ManuallyDrop, /// GPU Buffer buffer: ManuallyDrop, /// GPU Memory memory: ManuallyDrop, /// Where staged buffer is mapped in CPU memory staged_mapped_memory: &'a mut [T], /// If staged memory has been changed since last `commit` staged_is_dirty: bool, /// The highest index in the buffer that's been written to. pub highest_used: usize, } impl<'a, T: Sized> StagedBuffer<'a, T> { /// size is the size in T pub fn new( device: &mut Device, adapter: &Adapter, usage: Usage, size: u64, ) -> Result { // Convert size to bytes let size_bytes = size * size_of::() as u64; // Get CPU-visible buffer let (staged_buffer, staged_memory) = create_buffer( device, adapter, Usage::TRANSFER_SRC, Properties::CPU_VISIBLE, size_bytes, )?; // Get GPU Buffer let (buffer, memory) = create_buffer( device, adapter, Usage::TRANSFER_DST | usage, Properties::DEVICE_LOCAL | Properties::COHERENT, size_bytes, )?; // Map it somewhere and get a slice to that memory let staged_mapped_memory = unsafe { let ptr = device.map_memory(&staged_memory, 0..size_bytes).unwrap(); // TODO std::slice::from_raw_parts_mut(ptr as *mut T, size.try_into().unwrap()) }; Ok(StagedBuffer { staged_buffer: ManuallyDrop::new(staged_buffer), staged_memory: ManuallyDrop::new(staged_memory), buffer: ManuallyDrop::new(buffer), memory: ManuallyDrop::new(memory), staged_mapped_memory, staged_is_dirty: false, highest_used: 0, }) } /// Call this before dropping pub(crate) fn deactivate(mut self, device: &mut Device) { unsafe { device.unmap_memory(&self.staged_memory); device.free_memory(ManuallyDrop::take(&mut self.staged_memory)); device.destroy_buffer(ManuallyDrop::take(&mut self.staged_buffer)); device.free_memory(ManuallyDrop::take(&mut self.memory)); device.destroy_buffer(ManuallyDrop::take(&mut self.buffer)); }; } } impl<'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> { fn get_buffer(&mut self) -> &Buffer { &self.buffer } fn commit<'b>( &'b mut self, device: &Device, command_queue: &mut CommandQueue, command_pool: &mut CommandPool, ) -> &'b Buffer { // Only commit if there's changes to commit. if self.staged_is_dirty { // Copy from staged to buffer let buf = unsafe { use hal::command::{BufferCopy, CommandBufferFlags}; // Get a command buffer let mut buf = command_pool.allocate_one(hal::command::Level::Primary); // Put in our copy command buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); buf.copy_buffer( &self.staged_buffer, &self.buffer, &[BufferCopy { src: 0, dst: 0, size: ((self.highest_used + 1) * size_of::()) as u64, }], ); buf.finish(); buf }; // Submit it and wait for completion // TODO: We could use more semaphores or something? // TODO: Better error handling unsafe { let copy_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(©_finished), ); 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; } &self.buffer } } impl<'a, T: Sized> Index for StagedBuffer<'a, T> { type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.staged_mapped_memory[index] } } impl<'a, T: Sized> IndexMut for StagedBuffer<'a, T> { fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.staged_is_dirty = true; if index > self.highest_used { self.highest_used = index; } &mut self.staged_mapped_memory[index] } }