Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/rendering/wgpu/renderer/util.rs
1use std::sync::{
2 atomic::{AtomicBool, Ordering},
3 Arc,
4};
5 
6use wgpu::{
7 util::BufferInitDescriptor, Buffer, BufferAddress, BufferDescriptor, Device,
8 COPY_BUFFER_ALIGNMENT,
9};
10 
11use super::Error;
12 
13/// Calls the provided function, capturing and returning any validation errors
14/// detected by wgpu.
15#[must_use]
16pub 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.
43pub 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.
93fn 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