[−][src]Struct emu_core::device::DeviceBox
A type for boxing stuff stored on a device
It is generic over a type T
so that we can safely transmute data from the
GPU (DeviceBox<T>
) to and from data from the CPU (T
). There are many ways a DeviceBox<T>
can be constructed.
// this is useful for passing arguments to kernels that are of primitive types let pi = DeviceBox::new(3.1415)?; let data: DeviceBox<[f32]> = DeviceBox::from_ref(&vec![1.0; 2048])?; let data: DeviceBox<[f32]> = DeviceBox::with_size(2048 * std::mem::size_of::<f32>())?; let pi = (3.1415).into_device_boxed()?; let numbers = (0..2048).into_device_boxed()?; let data = vec![0; 2048].into_iter().into_device_boxed()?;
You can also construct a DeviceBox<T>
from existing data.
let one_on_cpu = vec![1.0; 1024]; let zero_on_cpu = vec![0.0; 1024]; let data_on_cpu = vec![0.5; 1024]; let data_on_gpu: DeviceBox<[f32]> = data_on_cpu.as_device_boxed()?; let zero_on_gpu: DeviceBox<[f32]> = zero_on_cpu.into_iter().into_device_boxed()?; // prefer as_device_boxed to avoid the unnecessary copy // that is unless, you really need to construct from an iterator let one_on_gpu: DeviceBox<[f32]> = one_on_cpu.into_iter().take(512).into_device_boxed()?;
And you can also load custom structures onto the GPU.
use {emu_core::prelude::*, emu_glsl::*, zerocopy::*}; #[repr(C)] #[derive(AsBytes, FromBytes, Copy, Clone, Default, Debug)] struct Shape { pos: [f32; 2], num_edges: u32, radius: f32 } fn main() -> Result<(), Box<dyn std::error::Error>> { // `assert_device_pool_initialized` should be called before using any Emu API function // well, except for `pool` which you would only use to manually populate the device pool futures::executor::block_on(assert_device_pool_initialized()); // create data and move to the GPU let shapes = vec![Shape::default(); 512]; let shapes_on_gpu: DeviceBox<[Shape]> = shapes.as_device_boxed()?; Ok(()) }
If you want to make your own collections move-able to the GPU, you can implement either AsDeviceBoxed
or IntoDeviceBoxed
. Lastly, keep in mind that all of the above examples create constant data.
To allow GPU data to be mutated, for most of the above functions, their mutable equivalent has the same name but with a mut
appended.
let one_on_cpu = vec![1.0; 1024]; let zero_on_cpu = vec![0.0; 1024]; let data_on_cpu = vec![0.5; 1024]; let data_on_gpu: DeviceBox<[f32]> = data_on_cpu.as_device_boxed_mut()?; let mut zero_on_gpu: DeviceBox<[f32]> = zero_on_cpu.into_iter().into_device_boxed_mut()?; let one_on_gpu: DeviceBox<[f32]> = one_on_cpu.into_iter().take(512).into_device_boxed_mut()?;
Emu keeps tracks of whether or not data is mutable as well as their type to ensure that data is safely passed back and forth to and from kernels running on a GPU.
Also, DeviceBox
implements From
and Into
to help you switch between DeviceBox
and its WebGPU internals if you want to.
The WebGPU internals are encapsulated in a 4-tuple corresponding simply to the staging buffer, storage buffer, and size in bytes respectively (there is also an optional mutability marker).
You should ignore the staging buffer for now since we are working towards replacing 1 staging buffer per DeviceBox
with a global pool of staging buffers
that is shared by all DeviceBox
s..
Methods
impl<T: ?Sized> DeviceBox<T>
[src]
pub fn new<U: IntoDeviceBoxed<T>>(obj: U) -> Result<Self, NoDeviceError>
[src]
Create a constant DeviceBox<T>
while consuming the given T
pub fn from_ref<U: AsDeviceBoxed<T> + ?Sized>(
obj: &U
) -> Result<Self, NoDeviceError>
[src]
obj: &U
) -> Result<Self, NoDeviceError>
Create a constant DeviceBox<T>
from a reference to T
pub fn with_size(size: usize) -> Result<Self, NoDeviceError>
[src]
Create a constant DeviceBox<T>
where T
has the given number of bytes
pub fn new_mut<U: IntoDeviceBoxed<T>>(obj: U) -> Result<Self, NoDeviceError>
[src]
Create a mutable DeviceBox<T>
while consuming the given T
pub fn from_ref_mut<U: AsDeviceBoxed<T> + ?Sized>(
obj: &U
) -> Result<Self, NoDeviceError>
[src]
obj: &U
) -> Result<Self, NoDeviceError>
Create a mutable DeviceBox<T>
from a reference to T
pub fn with_size_mut(size: usize) -> Result<Self, NoDeviceError>
[src]
Create a mutable DeviceBox<T>
where T
has the given number of bytes
impl<T: AsBytes + ?Sized> DeviceBox<T>
[src]
pub fn set<U: Borrow<T>>(&mut self, obj: U) -> Result<(), NoDeviceError>
[src]
Uploads the given data T
to self (a DeviceBox<T>
)
This function - as are most other functions in the Emu API - doesn't block.
So the data transfer only occurs when the future returned by get
is completed.
set
is pretty easy to use. You just pass in either an owned object or a reference and
the object is uploaded to the GPU.
Here's a quick example.
let mut data: DeviceBox<[f32]> = vec![0.5; 1024].as_device_boxed_mut()?; data.set(vec![1.0; 1024])?;
It is expected that the object you pass in is of the same size (in bytes) as what was
already stored in the DeviceBox
. For example, you should not upload a vector of different
length than that of the slice already stored on the device.
impl<T: FromBytes + Copy> DeviceBox<[T]>
[src]
pub async fn get<'_>(&'_ self) -> Result<Box<[T]>, GetError>
[src]
Downloads from self (a DeviceBox<[T]>
) to a Box<[T]>
This function is asynchronous. So you can either .await
it in an asynchronous context or you
can use an executor to immediately evaluate it.
use {emu_core::prelude::*, emu_glsl::*, zerocopy::*}; fn main() -> Result<(), Box<dyn std::error::Error>> { // first, we ensure that the global pool of devices has been initialized futures::executor::block_on(assert_device_pool_initialized()); // then we create some data, move it to the GPU, and mutate it let mut data: DeviceBox<[f32]> = vec![0.5; 1024].as_device_boxed_mut()?; data.set(vec![1.0; 1024])?; // finally, we download the data from the GPU assert_eq!(futures::executor::block_on(data.get())?, vec![1.0; 1024].into_boxed_slice()); Ok(()) }
For now, we only support getting simple slices but in the future we may support more complex nested slices.
Also, to use this T
must be safe to deserialize. You can ensure this by using #[derive(FromBytes)]
from zerocopy
.
Trait Implementations
impl<T: ?Sized> From<(Buffer, Buffer, u64, Option<Mutability>)> for DeviceBox<T>
[src]
impl<T: AsBytes> FromIterator<T> for DeviceBox<[T]>
[src]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self
[src]
impl<T: ?Sized> Into<(Buffer, Buffer, u64, Option<Mutability>)> for DeviceBox<T>
[src]
Auto Trait Implementations
impl<T> !RefUnwindSafe for DeviceBox<T>
impl<T: ?Sized> Send for DeviceBox<T> where
T: Send,
T: Send,
impl<T: ?Sized> Sync for DeviceBox<T> where
T: Sync,
T: Sync,
impl<T: ?Sized> Unpin for DeviceBox<T> where
T: Unpin,
T: Unpin,
impl<T: ?Sized> UnwindSafe for DeviceBox<T> where
T: UnwindSafe,
T: UnwindSafe,
Blanket Implementations
impl<T> Any for T where
T: 'static + ?Sized,
[src]
T: 'static + ?Sized,
impl<T, U> AsDeviceBoxed<T> for U where
T: AsBytes + ?Sized,
U: Borrow<T>,
[src]
T: AsBytes + ?Sized,
U: Borrow<T>,
fn as_device_boxed(&Self) -> Result<DeviceBox<T>, NoDeviceError>
[src]
fn as_device_boxed_mut(&Self) -> Result<DeviceBox<T>, NoDeviceError>
[src]
impl<T> Borrow<T> for T where
T: ?Sized,
[src]
T: ?Sized,
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]
T: ?Sized,
fn borrow_mut(&mut self) -> &mut T
[src]
impl<T> From<T> for T
[src]
impl<T, U> Into<U> for T where
U: From<T>,
[src]
U: From<T>,
impl<T, U> TryFrom<U> for T where
U: Into<T>,
[src]
U: Into<T>,
type Error = Infallible
The type returned in the event of a conversion error.
fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>
[src]
impl<T, U> TryInto<U> for T where
U: TryFrom<T>,
[src]
U: TryFrom<T>,