Expand description

Easy GPGPU

A high level, easy to use gpgpu crate based on wgpu. It is made for very large computations on powerful gpus

Main goals :

  • make general purpose computing very simple
  • make it as easy as possible to write wgsl shaders
  • deal with binding buffers automatically

Limitations :

  • only types available for buffers : bool, i32, u32, f32

  • max buffer byte_size : around 134_000_000 (~33 million i32)

    use device.apply_on_vector to be able to go up to one billion bytes (260 million i32s)

  • takes time to initiate the device for the first time (due to wgpu backends)

Example

recreating wgpu's hello-compute (205 sloc when writen with wgpu)

use easy_gpgpu::*;
fn wgpu_hello_compute() {
    let mut device = Device::new();
    let v = vec![1u32, 4, 3, 295];
    let result = device.apply_on_vector(v.clone(), r"
    fn collatz_iterations(n_base: u32) -> u32{
        var n: u32 = n_base;
        var i: u32 = 0u;
        loop {
            if (n <= 1u) {
                break;
            }
            if (n % 2u == 0u) {
                n = n / 2u;
            }
            else {
                // Overflow? (i.e. 3*n + 1 > 0xffffffffu?)
                if (n >= 1431655765u) {   // 0x55555555u
                    return 4294967295u;   // 0xffffffffu
                }
                n = 3u * n + 1u;
            }
            i = i + 1u;
    }
    return i;
    }
    collatz_iterations(element)
    ");
    assert_eq!(result, vec![0, 2, 7, 55]);
}

=> No binding, no annoying global_id, no need to use a low level api.

You just need to write the minimum amount of wgsl shader code.

Usage with .apply_on_vector

use easy_gpgpu::*;
// create a device
let mut device = Device::new();
// create the vector we want to apply a computation on
let v1 = vec![1.0f32, 2.0, 3.0];
// the next line reads : for every element in v1, perform : element = element * 2.0
let v1 = device.apply_on_vector(v1, "element * 2.0");
println!("{v1:?}");

Usage with .execute_shader_code

//First create a device :
use easy_gpgpu::*;
let mut device = Device::new();
// Then create some buffers, specify if you want to get their content after the execution :
let v1 = vec![1i32, 2, 3, 4, 5, 6];
// from a vector
device.create_buffer_from("v1", &v1, BufferUsage::ReadOnly, false);
// creates an empty buffer
device.create_buffer("output", BufferType::I32, v1.len(), BufferUsage::WriteOnly, true);
// Finaly, execute a shader :
let result = device.execute_shader_code(Dispatch::Linear(v1.len()), r"
fn main() {
    output[index] = v1[index] * 2;
}").into_iter().next().unwrap().unwrap_i32();
assert_eq!(result, vec![2i32, 4, 6, 8, 10, 12])

The buffers are available in the shader with the name provided when created with the device.

The index variable is provided thanks to the use of Dispatch::Linear (index is a u32).

We had only specified one buffer with is_output: true so we get only one vector as an output.

We just need to unwrap the data as a vector of i32s with .unwrap_i32()

Modules

Structs

The main struct which provides abstraction over the wgpu library.

Enums

Defines the usage of the buffer in the wgsl shader

Defines a custom global_id manager, if Linear(n) is used, the index variable is automatically going to be available in the shader it will range from 0 to n excluded (max n : 2^32)

An enum to represent the different output vectors possible To get the vector back, call .unwrap_i32() for example.

Traits

Trait to obtain raw data of vectors in order to convert them to buffer as seamlessly as possible and to obtain back a vector from the raw Vec

Functions