Implement experimental WebGPU support (#1238)

* Web gpu execution MVP

Ready infrastructure for wgpu experimentation

Start implementing simple gpu test case

Fix Extract Node not working with nested networks

Convert inputs for extracted node to network inputs

Fix missing cors headers

Feature gate gcore to make it once again no-std compatible

Add skeleton structure gpu shader

Work on gpu node graph output saving

Fix Get and Set nodes

Fix storage nodes

Fix shader construction errors -> spirv errors

Add unsafe version

Add once cell node

Web gpu execution MVP
This commit is contained in:
Dennis Kobert 2023-05-27 19:27:46 +02:00 committed by Keavon Chambers
parent 4bd9fbd073
commit 0586d52f3a
33 changed files with 1080 additions and 239 deletions

View file

@ -107,6 +107,15 @@ impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N
(**self).eval(input)
}
}
#[cfg(feature = "alloc")]
impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for Box<N> {
type Output = O;
fn eval(&'i self, input: I) -> Self::Output {
(**self).eval(input)
}
}
impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'a> Node<'a, I, Output = O> {
type Output = O;

View file

@ -192,6 +192,14 @@ pub trait Sample {
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel>;
}
impl<'i, T: Sample> Sample for &'i T {
type Pixel = T::Pixel;
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
(**self).sample(pos, area)
}
}
// TODO: We might rename this to Bitmap at some point
pub trait Raster {
type Pixel: Pixel;
@ -200,6 +208,38 @@ pub trait Raster {
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel>;
}
impl<'i, T: Raster> Raster for &'i T {
type Pixel = T::Pixel;
fn width(&self) -> u32 {
(**self).width()
}
fn height(&self) -> u32 {
(**self).height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
(**self).get_pixel(x, y)
}
}
impl<'i, T: Raster> Raster for &'i mut T {
type Pixel = T::Pixel;
fn width(&self) -> u32 {
(**self).width()
}
fn height(&self) -> u32 {
(**self).height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
(**self).get_pixel(x, y)
}
}
pub trait RasterMut: Raster {
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel>;
fn set_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) {
@ -215,6 +255,12 @@ pub trait RasterMut: Raster {
}
}
impl<'i, T: RasterMut + Raster> RasterMut for &'i mut T {
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
(*self).get_pixel_mut(x, y)
}
}
#[derive(Debug, Default)]
pub struct MapNode<MapFn> {
map_fn: MapFn,

View file

@ -363,6 +363,19 @@ fn invert_image(color: Color) -> Color {
color.to_linear_srgb()
}
// TODO replace with trait based implementation
impl<'i> Node<'i, &'i Color> for InvertRGBNode {
type Output = Color;
fn eval(&'i self, color: &'i Color) -> Self::Output {
let color = color.to_gamma_srgb();
let color = color.map_rgb(|c| color.a() - c);
color.to_linear_srgb()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ThresholdNode<MinLuminance, MaxLuminance, LuminanceCalc> {
min_luminance: MinLuminance,

View file

@ -1,34 +1,158 @@
use crate::Node;
use core::cell::RefMut;
use core::marker::PhantomData;
use core::ops::{DerefMut, Index, IndexMut};
use core::ops::{Deref, DerefMut, Index, IndexMut};
struct SetNode<S, I, Storage, Index> {
pub struct SetNode<Storage> {
storage: Storage,
index: Index,
_s: PhantomData<S>,
_i: PhantomData<I>,
}
#[node_macro::node_fn(SetNode<_S, _I>)]
fn set_node<T, _S, _I>(value: T, storage: &'input mut _S, index: _I)
/*
#[node_macro::node_fn(SetNode)]
fn set_node<_T, _I, A: 'input>(input: (_T, _I), mut storage: RefMut<'input, A>)
where
_S: IndexMut<_I>,
_S::Output: DerefMut<Target = T> + Sized,
A: DerefMut,
A::Target: IndexMut<_I, Output = _T>,
{
*storage.index_mut(index).deref_mut() = value;
let (value, index) = input;
*storage.deref_mut().index_mut(index).deref_mut() = value;
}*/
impl<'input, T: 'input, I: 'input, A: 'input + 'input, S0: 'input> Node<'input, (T, I)> for SetNode<S0>
where
A: DerefMut,
A::Target: IndexMut<I, Output = T>,
S0: for<'any_input> Node<'input, (), Output = A>,
{
type Output = ();
#[inline]
fn eval(&'input self, input: (T, I)) -> Self::Output {
let mut storage = self.storage.eval(());
let (value, index) = input;
*storage.deref_mut().index_mut(index).deref_mut() = value;
}
}
impl<'input, S0: 'input> SetNode<S0> {
pub const fn new(storage: S0) -> Self {
Self { storage }
}
}
struct GetNode<S, Storage> {
pub struct ExtractXNode {}
#[node_macro::node_fn(ExtractXNode)]
fn extract_x_node(input: glam::UVec3) -> usize {
input.x as usize
}
pub struct SetOwnedNode<Storage> {
storage: core::cell::RefCell<Storage>,
}
impl<Storage> SetOwnedNode<Storage> {
pub fn new(storage: Storage) -> Self {
Self {
storage: core::cell::RefCell::new(storage),
}
}
}
impl<'input, I: 'input, T: 'input, Storage, A: ?Sized> Node<'input, (T, I)> for SetOwnedNode<Storage>
where
Storage: DerefMut<Target = A> + 'input,
A: IndexMut<I, Output = T> + 'input,
{
type Output = ();
fn eval(&'input self, input: (T, I)) -> Self::Output {
let (value, index) = input;
*self.storage.borrow_mut().index_mut(index) = value;
}
}
pub struct GetNode<Storage> {
storage: Storage,
_s: PhantomData<S>,
}
#[node_macro::node_fn(GetNode<_S>)]
fn get_node<_S, I>(index: I, storage: &'input _S) -> &'input _S::Output
where
_S: Index<I>,
_S::Output: Sized,
{
storage.index(index)
impl<Storage> GetNode<Storage> {
pub fn new(storage: Storage) -> Self {
Self { storage }
}
}
impl<'input, I: 'input, T: 'input, Storage, SNode, A: ?Sized> Node<'input, I> for GetNode<SNode>
where
SNode: Node<'input, (), Output = Storage>,
Storage: Deref<Target = A> + 'input,
A: Index<I, Output = T> + 'input,
T: Clone,
{
type Output = T;
fn eval(&'input self, index: I) -> Self::Output {
let storage = self.storage.eval(());
storage.deref().index(index).deref().clone()
}
}
#[cfg(test)]
mod test {
use crate::value::{CopiedNode, OnceCellNode, RefCellMutNode, UnsafeMutValueNode, ValueNode};
use crate::Node;
use super::*;
#[test]
fn get_node_array() {
let storage = [1, 2, 3];
let node = GetNode::new(CopiedNode::new(&storage));
assert_eq!((&node as &dyn Node<'_, usize, Output = i32>).eval(1), 2);
}
#[test]
fn get_node_vec() {
let storage = vec![1, 2, 3];
let node = GetNode::new(CopiedNode::new(&storage));
assert_eq!(node.eval(1), 2);
}
#[test]
fn get_node_slice() {
let storage: &[i32] = &[1, 2, 3];
let node = GetNode::new(CopiedNode::new(storage));
let _ = &node as &dyn Node<'_, usize, Output = i32>;
assert_eq!(node.eval(1), 2);
}
#[test]
fn set_node_slice() {
let mut backing_storage = [1, 2, 3];
let storage: &mut [i32] = &mut backing_storage;
let storage_node = OnceCellNode::new(storage);
let node = SetNode::new(storage_node);
node.eval((4, 1));
assert_eq!(backing_storage, [1, 4, 3]);
}
#[test]
fn set_owned_node_array() {
let mut storage = [1, 2, 3];
let node = SetOwnedNode::new(&mut storage);
node.eval((4, 1));
assert_eq!(storage, [1, 4, 3]);
}
#[test]
fn set_owned_node_vec() {
let mut storage = vec![1, 2, 3];
let node = SetOwnedNode::new(&mut storage);
node.eval((4, 1));
assert_eq!(storage, [1, 4, 3]);
}
#[test]
fn set_owned_node_slice() {
let mut backing_storage = [1, 2, 3];
let storage: &mut [i32] = &mut backing_storage;
let node = SetOwnedNode::new(storage);
let node = &node as &dyn Node<'_, (i32, usize), Output = ()>;
node.eval((4, 1));
assert_eq!(backing_storage, [1, 4, 3]);
}
}

View file

@ -39,6 +39,7 @@ pub struct AsyncComposeNode<First, Second, I> {
phantom: PhantomData<I>,
}
#[cfg(feature = "alloc")]
impl<'i, 'f: 'i, 's: 'i, Input: 'static, First, Second> Node<'i, Input> for AsyncComposeNode<First, Second, Input>
where
First: Node<'i, Input>,
@ -54,6 +55,7 @@ where
}
}
#[cfg(feature = "alloc")]
impl<'i, First, Second, Input: 'i> AsyncComposeNode<First, Second, Input>
where
First: Node<'i, Input>,
@ -77,6 +79,7 @@ pub trait Then<'i, Input: 'i>: Sized {
impl<'i, First: Node<'i, Input>, Input: 'i> Then<'i, Input> for First {}
#[cfg(feature = "alloc")]
pub trait AndThen<'i, Input: 'i>: Sized {
fn and_then<Second>(self, second: Second) -> AsyncComposeNode<Self, Second, Input>
where
@ -88,6 +91,7 @@ pub trait AndThen<'i, Input: 'i>: Sized {
}
}
#[cfg(feature = "alloc")]
impl<'i, First: Node<'i, Input>, Input: 'i> AndThen<'i, Input> for First {}
pub struct ConsNode<I: From<()>, Root>(pub Root, PhantomData<I>);
@ -108,6 +112,38 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
}
}
pub struct ApplyNode<O, N> {
pub node: N,
_o: PhantomData<O>,
}
/*
#[node_macro::node_fn(ApplyNode)]
fn apply<In, N>(input: In, node: &'any_input N) -> ()
where
// TODO: try to allows this to return output other than ()
N: for<'any_input> Node<'any_input, In, Output = ()>,
{
node.eval(input)
}
*/
impl<'input, In: 'input, N: 'input, S0: 'input, O: 'input> Node<'input, In> for ApplyNode<O, S0>
where
N: Node<'input, In, Output = O>,
S0: Node<'input, (), Output = &'input N>,
{
type Output = <N as Node<'input, In>>::Output;
#[inline]
fn eval(&'input self, input: In) -> Self::Output {
let node = self.node.eval(());
node.eval(input)
}
}
impl<'input, S0: 'input, O: 'static> ApplyNode<O, S0> {
pub const fn new(node: S0) -> Self {
Self { node, _o: PhantomData }
}
}
#[cfg(test)]
mod test {
use crate::{ops::IdNode, value::ValueNode};
@ -134,4 +170,15 @@ mod test {
assert_eq!(compose.eval(()), &5);
}
#[test]
fn test_apply() {
let mut array = [1, 2, 3];
let slice = &mut array;
let set_node = crate::storage::SetOwnedNode::new(slice);
let apply = ApplyNode::new(ValueNode::new(set_node));
assert_eq!(apply.eval((1, 2)), ());
}
}

View file

@ -1,6 +1,10 @@
use crate::Node;
use core::marker::PhantomData;
use core::{
borrow::BorrowMut,
cell::{Cell, RefCell, RefMut},
marker::PhantomData,
};
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct IntNode<const N: u32>;
@ -13,7 +17,7 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode<N> {
}
}
#[derive(Default, Debug)]
#[derive(Default, Debug, Clone, Copy)]
pub struct ValueNode<T>(pub T);
impl<'i, T: 'i> Node<'i, ()> for ValueNode<T> {
@ -35,12 +39,62 @@ impl<T> From<T> for ValueNode<T> {
ValueNode::new(value)
}
}
impl<T: Clone> Clone for ValueNode<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
#[derive(Default, Debug, Clone)]
pub struct RefCellMutNode<T>(pub RefCell<T>);
impl<'i, T: 'i> Node<'i, ()> for RefCellMutNode<T> {
type Output = RefMut<'i, T>;
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
#[cfg(not(target_arch = "spirv"))]
let a = self.0.borrow_mut();
#[cfg(target_arch = "spirv")]
let a = unsafe { self.0.try_borrow_mut().unwrap_unchecked() };
a
}
}
impl<T> RefCellMutNode<T> {
pub const fn new(value: T) -> RefCellMutNode<T> {
RefCellMutNode(RefCell::new(value))
}
}
/// #Safety: Never use this as it is unsound.
#[derive(Default, Debug)]
pub struct UnsafeMutValueNode<T>(pub T);
/// #Safety: Never use this as it is unsound.
impl<'i, T: 'i> Node<'i, ()> for UnsafeMutValueNode<T> {
type Output = &'i mut T;
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
unsafe { &mut *(&self.0 as &T as *const T as *mut T) }
}
}
impl<T> UnsafeMutValueNode<T> {
pub const fn new(value: T) -> UnsafeMutValueNode<T> {
UnsafeMutValueNode(value)
}
}
#[derive(Default)]
pub struct OnceCellNode<T>(pub Cell<T>);
impl<'i, T: Default + 'i> Node<'i, ()> for OnceCellNode<T> {
type Output = T;
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
self.0.replace(T::default())
}
}
impl<T> OnceCellNode<T> {
pub const fn new(value: T) -> OnceCellNode<T> {
OnceCellNode(Cell::new(value))
}
}
impl<T: Clone + Copy> Copy for ValueNode<T> {}
#[derive(Clone, Copy)]
pub struct ClonedNode<T: Clone>(pub T);
@ -75,6 +129,7 @@ impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
#[cfg(not(target_arch = "spirv"))]
log::debug!("DebugClonedNode::eval");
self.0.clone()