[][src]Struct emu_core::device::DeviceBox

pub struct DeviceBox<T> where
    T: ?Sized
{ /* fields omitted */ }

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 DeviceBoxs..

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]

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]

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]

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

impl<T: ?Sized> Sync for DeviceBox<T> where
    T: Sync

impl<T: ?Sized> Unpin for DeviceBox<T> where
    T: Unpin

impl<T: ?Sized> UnwindSafe for DeviceBox<T> where
    T: UnwindSafe

Blanket Implementations

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T, U> AsDeviceBoxed<T> for U where
    T: AsBytes + ?Sized,
    U: Borrow<T>, 
[src]

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

impl<T> BorrowMut<T> for T where
    T: ?Sized
[src]

impl<T> From<T> for T[src]

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

type Error = Infallible

The type returned in the event of a conversion error.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.