StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use std::sync::{ |
| 2 | atomic::{AtomicBool, Ordering}, |
| 3 | Arc, |
| 4 | }; |
| 5 | |
| 6 | use wgpu::{ |
| 7 | util::BufferInitDescriptor, Buffer, BufferAddress, BufferDescriptor, Device, |
| 8 | COPY_BUFFER_ALIGNMENT, |
| 9 | }; |
| 10 | |
| 11 | use super::Error; |
| 12 | |
| 13 | /// Calls the provided function, capturing and returning any validation errors |
| 14 | /// detected by wgpu. |
| 15 | #[must_use] |
| 16 | pub fn with_error_scope<T>( |
| 17 | device: &wgpu::Device, |
| 18 | callback: impl FnOnce() -> T, |
| 19 | ) -> (T, Option<Error>) { |
| 20 | let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 21 | let ret = callback(); |
| 22 | // On native platforms, the future returned by `pop_error_scope` resolves |
| 23 | // immediately. On wasm, it may take longer due to asynchronous browser |
| 24 | // APIs, but it's necessary to wait here to know if it is safe to continue. |
| 25 | let error_future = error_scope.pop(); |
| 26 | cfg_if::cfg_if! { |
| 27 | if #[cfg(target_family = "wasm")] { |
| 28 | let error = crate::r#async::block_on(error_future); |
| 29 | } else { |
| 30 | use futures::FutureExt; |
| 31 | let error = error_future.now_or_never().expect("always resolves immediately"); |
| 32 | } |
| 33 | } |
| 34 | (ret, error.map(Into::into)) |
| 35 | } |
| 36 | |
| 37 | /// Creates a buffer and initializes it with data, synchronously returning an |
| 38 | /// error if the buffer could not be created successfully. |
| 39 | /// |
| 40 | /// This is adapted from [`wgpu::util::DeviceExt::create_buffer_init`], with |
| 41 | /// added logic to check for and return errors from the underlying buffer |
| 42 | /// creation. |
| 43 | pub fn create_buffer_init( |
| 44 | device: &Device, |
| 45 | device_lost: &Arc<AtomicBool>, |
| 46 | descriptor: &BufferInitDescriptor<'_>, |
| 47 | ) -> Result<Buffer, super::Error> { |
| 48 | // Skip mapping if the buffer is zero sized |
| 49 | if descriptor.contents.is_empty() { |
| 50 | let wgt_descriptor = BufferDescriptor { |
| 51 | label: descriptor.label, |
| 52 | size: 0, |
| 53 | usage: descriptor.usage, |
| 54 | mapped_at_creation: false, |
| 55 | }; |
| 56 | |
| 57 | create_buffer(device, &wgt_descriptor) |
| 58 | } else { |
| 59 | let unpadded_size = descriptor.contents.len() as BufferAddress; |
| 60 | // Valid vulkan usage is |
| 61 | // 1. buffer size must be a multiple of COPY_BUFFER_ALIGNMENT. |
| 62 | // 2. buffer size must be greater than 0. |
| 63 | // Therefore we round the value up to the nearest multiple, and ensure it's at least COPY_BUFFER_ALIGNMENT. |
| 64 | let align_mask = COPY_BUFFER_ALIGNMENT - 1; |
| 65 | let padded_size = ((unpadded_size + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT); |
| 66 | |
| 67 | let wgt_descriptor = BufferDescriptor { |
| 68 | label: descriptor.label, |
| 69 | size: padded_size, |
| 70 | usage: descriptor.usage, |
| 71 | mapped_at_creation: true, |
| 72 | }; |
| 73 | |
| 74 | let buffer = create_buffer(device, &wgt_descriptor)?; |
| 75 | |
| 76 | if device_lost.load(Ordering::SeqCst) { |
| 77 | return Err(super::Error::DeviceLost); |
| 78 | } |
| 79 | |
| 80 | buffer |
| 81 | .slice(..) |
| 82 | .get_mapped_range_mut() |
| 83 | .slice(..unpadded_size as usize) |
| 84 | .copy_from_slice(descriptor.contents); |
| 85 | buffer.unmap(); |
| 86 | |
| 87 | Ok(buffer) |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | /// Creates a buffer using the given device and descriptor, synchronously |
| 92 | /// returning an error if the buffer could not be created successfully. |
| 93 | fn create_buffer(device: &Device, desc: &BufferDescriptor<'_>) -> Result<Buffer, Error> { |
| 94 | let (buffer, error) = with_error_scope(device, || device.create_buffer(desc)); |
| 95 | |
| 96 | match error { |
| 97 | Some(error) => { |
| 98 | log::warn!("Failed to create wgpu::Buffer: {error:#}"); |
| 99 | Err(error) |
| 100 | } |
| 101 | None => Ok(buffer), |
| 102 | } |
| 103 | } |
| 104 |