Lay groundwork for directly rendering to the canvas without a cpu roundrip (#1291)

* Add Texture handle type

* Add Texture View to shader inputs

* Implement basic rendering pipeline

* Render first texture using render pipeline

* Fix output color space

* Precompute the rendering pipeline

* Move gpu context creation to editor api

* Port gpu-executor nodes to node registry

* Fix canvas nodes and make code compile for non wasm targets

* Pin wasm-bindgen version

* Disable miri temoporarily for better ci times

* Fix formatting

* Remove unsafe block

* Bump wasm-pack version

* Bump wasm-bindgen version

* Add gpu feature guard for push node

* Make Into node async
This commit is contained in:
Dennis Kobert
2023-06-07 17:13:21 +02:00
committed by Keavon Chambers
parent 0c93a62d55
commit 45b04f4eb9
33 changed files with 1574 additions and 339 deletions

View File

@@ -32,7 +32,7 @@ async = ["async-trait", "alloc"]
nightly = []
alloc = ["dyn-any", "bezier-rs", "once_cell"]
type_id_logging = []
wasm = ["wasm-bindgen", "web-sys", "js-sys", "std"]
wasm = ["web-sys"]
[dependencies]
dyn-any = { path = "../../libraries/dyn-any", features = [
@@ -77,10 +77,4 @@ js-sys = { version = "0.3.55", optional = true }
[dependencies.web-sys]
version = "0.3.4"
optional = true
features = [
"Window",
"CanvasRenderingContext2d",
"ImageData",
"Document",
"HtmlCanvasElement",
]
features = ["HtmlCanvasElement"]

View File

@@ -45,7 +45,7 @@ impl<S> From<SurfaceHandleFrame<S>> for SurfaceFrame {
}
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct SurfaceHandle<Surface> {
pub surface_id: SurfaceId,
pub surface: Surface,
@@ -55,7 +55,7 @@ unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
type Static = SurfaceHandle<T>;
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct SurfaceHandleFrame<Surface> {
pub surface_handle: Arc<SurfaceHandle<Surface>>,
pub transform: DAffine2,
@@ -87,12 +87,18 @@ impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
pub trait ApplicationIo {
type Surface;
type Executor;
fn create_surface(&self) -> SurfaceHandle<Self::Surface>;
fn destroy_surface(&self, surface_id: SurfaceId);
fn gpu_executor(&self) -> Option<&Self::Executor> {
None
}
}
impl<T: ApplicationIo> ApplicationIo for &T {
type Surface = T::Surface;
type Executor = T::Executor;
fn create_surface(&self) -> SurfaceHandle<T::Surface> {
(**self).create_surface()
}
@@ -100,6 +106,10 @@ impl<T: ApplicationIo> ApplicationIo for &T {
fn destroy_surface(&self, surface_id: SurfaceId) {
(**self).destroy_surface(surface_id)
}
fn gpu_executor(&self) -> Option<&T::Executor> {
(**self).gpu_executor()
}
}
pub struct EditorApi<'a, Io> {
@@ -162,6 +172,3 @@ impl ExtractImageFrame {
Self
}
}
#[cfg(feature = "wasm")]
pub mod wasm_application_io;

View File

@@ -20,6 +20,7 @@ pub mod value;
#[cfg(feature = "gpu")]
pub mod gpu;
#[cfg(feature = "alloc")]
pub mod memo;
pub mod storage;
@@ -34,6 +35,7 @@ pub use graphic_element::*;
#[cfg(feature = "alloc")]
pub mod vector;
#[cfg(feature = "alloc")]
pub mod application_io;
pub mod quantization;
@@ -146,6 +148,9 @@ impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> +
}
}
#[cfg(feature = "alloc")]
pub use crate::application_io::{ExtractImageFrame, SurfaceFrame, SurfaceId};
#[cfg(feature = "wasm")]
pub use application_io::{wasm_application_io, wasm_application_io::WasmEditorApi as EditorApi};
pub type WasmSurfaceHandle = application_io::SurfaceHandle<web_sys::HtmlCanvasElement>;
#[cfg(feature = "wasm")]
pub type WasmSurfaceHandleFrame = application_io::SurfaceHandleFrame<web_sys::HtmlCanvasElement>;

View File

@@ -4,8 +4,8 @@ use core::future::Future;
#[cfg(feature = "alloc")]
use alloc::sync::Arc;
use core::cell::Cell;
use core::marker::PhantomData;
use core::pin::Pin;
use std::marker::PhantomData;
// Caches the output of a given Node and acts as a proxy
#[derive(Default)]
@@ -103,8 +103,6 @@ impl<'i, T: 'i + Clone> Node<'i, Option<T>> for LetNode<T> {
}
}
impl<T> std::marker::Unpin for LetNode<T> {}
impl<T> LetNode<T> {
pub fn new() -> LetNode<T> {
LetNode { cache: Default::default() }

View File

@@ -211,7 +211,7 @@ pub struct IntoNode<I, O> {
_o: PhantomData<O>,
}
#[node_macro::node_fn(IntoNode<_I, _O>)]
fn into<_I, _O>(input: _I) -> _O
async fn into<_I, _O>(input: _I) -> _O
where
_I: Into<_O>,
{

View File

@@ -88,7 +88,7 @@ impl BrushCacheImpl {
impl Hash for BrushCacheImpl {
// Zero hash.
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {}
fn hash<H: core::hash::Hasher>(&self, _state: &mut H) {}
}
#[derive(Clone, Debug, Default)]

View File

@@ -1,7 +1,7 @@
mod font_cache;
mod to_path;
use crate::EditorApi;
use crate::application_io::EditorApi;
pub use font_cache::*;
use node_macro::node_fn;
pub use to_path::*;
@@ -15,7 +15,7 @@ pub struct TextGenerator<Text, FontName, Size> {
}
#[node_fn(TextGenerator)]
fn generate_text<'a: 'input>(editor: EditorApi<'a>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
}

View File

@@ -108,6 +108,10 @@ pub enum Type {
Future(Box<Type>),
}
unsafe impl StaticType for Type {
type Static = Self;
}
impl Type {
pub fn is_generic(&self) -> bool {
matches!(self, Type::Generic(_))

View File

@@ -63,7 +63,7 @@ checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -134,7 +134,7 @@ checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -260,7 +260,7 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -277,7 +277,7 @@ checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -320,7 +320,7 @@ version = "0.3.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -427,7 +427,7 @@ checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -565,6 +565,7 @@ dependencies = [
"num-traits",
"serde",
"spirv",
"web-sys",
]
[[package]]
@@ -596,6 +597,7 @@ dependencies = [
"bytemuck",
"dyn-any",
"glam",
"js-sys",
"kurbo",
"log",
"node-macro",
@@ -608,6 +610,8 @@ dependencies = [
"specta",
"spin",
"spirv-std",
"wasm-bindgen",
"web-sys",
]
[[package]]
@@ -724,9 +728,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.60"
version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
dependencies = [
"wasm-bindgen",
]
@@ -810,7 +814,7 @@ version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -821,7 +825,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -933,7 +937,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -1006,18 +1010,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.49"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
dependencies = [
"proc-macro2",
]
@@ -1141,7 +1145,7 @@ dependencies = [
"smallvec",
"spirt",
"spirv-tools",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -1224,7 +1228,7 @@ checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -1304,7 +1308,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"termcolor",
]
@@ -1383,7 +1387,7 @@ dependencies = [
"proc-macro2",
"quote",
"spirv-std-types",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -1429,6 +1433,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.3.0"
@@ -1491,7 +1506,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@@ -1641,9 +1656,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -1651,24 +1666,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1676,22 +1691,32 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
[[package]]
name = "web-sys"
version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"

View File

@@ -67,7 +67,7 @@ fn constant_attribute(constant: &GPUConstant) -> &'static str {
}
}
pub fn construct_argument(input: &ShaderInput<()>, position: u32, binding_offset: u32) -> String {
pub fn construct_argument<T: gpu_executor::GpuExecutor>(input: &ShaderInput<T>, position: u32, binding_offset: u32) -> String {
let line = match input {
ShaderInput::Constant(constant) => format!("#[spirv({})] i{}: {}", constant_attribute(constant), position, constant.ty()),
ShaderInput::UniformBuffer(_, ty) => {
@@ -76,6 +76,19 @@ pub fn construct_argument(input: &ShaderInput<()>, position: u32, binding_offset
ShaderInput::StorageBuffer(_, ty) | ShaderInput::ReadBackBuffer(_, ty) => {
format!("#[spirv(storage_buffer, descriptor_set = 0, binding = {})] i{}: &[{}]", position + binding_offset, position, ty,)
}
ShaderInput::StorageTextureBuffer(_, ty) => {
format!("#[spirv(storage_buffer, descriptor_set = 0, binding = {})] i{}: &mut [{}]]", position + binding_offset, position, ty,)
}
ShaderInput::TextureView(_, _) => {
format!(
"#[spirv(texture, descriptor_set = 0, binding = {})] i{}: spirv_std::image::Image2d",
position + binding_offset,
position,
)
}
ShaderInput::TextureBuffer(_, _) => {
panic!("Texture Buffers cannot be used as inputs use TextureView instead")
}
ShaderInput::OutputBuffer(_, ty) => {
format!("#[spirv(storage_buffer, descriptor_set = 0, binding = {})] o{}: &mut[{}]", position + binding_offset, position, ty,)
}

View File

@@ -34,3 +34,7 @@ anyhow = "1.0.66"
spirv = "0.2.0"
futures-intrusive = "0.5.0"
futures = "0.3.25"
web-sys = { version = "0.3.4", features = [
"HtmlCanvasElement",
"ImageBitmapRenderingContext",
] }

View File

@@ -5,7 +5,9 @@ use graphene_core::*;
use anyhow::Result;
use dyn_any::{StaticType, StaticTypeSized};
use futures::Future;
use glam::UVec3;
use glam::{DAffine2, UVec3};
use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle};
use graphene_core::raster::{Image, ImageFrame, Pixel, SRGBA8};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::pin::Pin;
@@ -13,6 +15,7 @@ use std::sync::Arc;
type ReadBackFuture = Pin<Box<dyn Future<Output = Result<Vec<u8>>>>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, dyn_any::DynAny)]
pub enum ComputePassDimensions {
X(u32),
XY(u32, u32),
@@ -29,18 +32,33 @@ impl ComputePassDimensions {
}
}
pub trait Texture {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn format(&self) -> TextureBufferType;
fn view<TextureView>(&self) -> TextureView;
}
pub trait GpuExecutor {
type ShaderHandle;
type BufferHandle;
type TextureHandle;
type TextureView;
type Surface;
type Window;
type CommandBuffer;
fn load_shader(&self, shader: Shader) -> Result<Self::ShaderHandle>;
fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<ShaderInput<Self::BufferHandle>>;
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<ShaderInput<Self::BufferHandle>>;
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<ShaderInput<Self::BufferHandle>>;
fn create_compute_pass(&self, layout: &PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self::BufferHandle>>>, instances: ComputePassDimensions) -> Result<Self::CommandBuffer>;
fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<ShaderInput<Self>>;
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<ShaderInput<Self>>;
fn create_texture_buffer<T: ToTextureBuffer>(&self, data: T, options: TextureBufferOptions) -> Result<ShaderInput<Self>>;
fn create_texture_view(&self, texture: ShaderInput<Self>) -> Result<ShaderInput<Self>>;
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<ShaderInput<Self>>;
fn create_compute_pass(&self, layout: &PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self>>>, instances: ComputePassDimensions) -> Result<Self::CommandBuffer>;
fn create_render_pass(&self, texture: Arc<ShaderInput<Self>>, canvas: Arc<SurfaceHandle<Self::Surface>>) -> Result<()>;
fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()>;
fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self::BufferHandle>>) -> ReadBackFuture;
fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self>>) -> ReadBackFuture;
fn create_surface(&self, window: SurfaceHandle<Self::Window>) -> Result<SurfaceHandle<Self::Surface>>;
}
pub trait SpirVCompiler {
@@ -84,37 +102,143 @@ impl GPUConstant {
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DummyExecutor;
impl GpuExecutor for DummyExecutor {
type ShaderHandle = ();
type BufferHandle = ();
type TextureHandle = ();
type TextureView = ();
type Surface = ();
type Window = ();
type CommandBuffer = ();
fn load_shader(&self, _shader: Shader) -> Result<Self::ShaderHandle> {
todo!()
}
fn create_uniform_buffer<T: ToUniformBuffer>(&self, _data: T) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_storage_buffer<T: ToStorageBuffer>(&self, _data: T, _options: StorageBufferOptions) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_texture_buffer<T: ToTextureBuffer>(&self, _data: T, _options: TextureBufferOptions) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_output_buffer(&self, _len: usize, _ty: Type, _cpu_readable: bool) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_compute_pass(&self, _layout: &PipelineLayout<Self>, _read_back: Option<Arc<ShaderInput<Self>>>, _instances: ComputePassDimensions) -> Result<Self::CommandBuffer> {
todo!()
}
fn execute_compute_pipeline(&self, _encoder: Self::CommandBuffer) -> Result<()> {
todo!()
}
fn create_render_pass(&self, _texture: Arc<ShaderInput<Self>>, _canvas: Arc<SurfaceHandle<Self::Surface>>) -> Result<()> {
todo!()
}
fn read_output_buffer(&self, _buffer: Arc<ShaderInput<Self>>) -> ReadBackFuture {
todo!()
}
fn create_texture_view(&self, _texture: ShaderInput<Self>) -> Result<ShaderInput<Self>> {
todo!()
}
fn create_surface(&self, _window: SurfaceHandle<Self::Window>) -> Result<SurfaceHandle<Self::Surface>> {
todo!()
}
}
type AbstractShaderInput = ShaderInput<DummyExecutor>;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
/// All the possible inputs to a shader.
pub enum ShaderInput<BufferHandle> {
UniformBuffer(BufferHandle, Type),
StorageBuffer(BufferHandle, Type),
pub enum ShaderInput<E: GpuExecutor + ?Sized> {
UniformBuffer(E::BufferHandle, Type),
StorageBuffer(E::BufferHandle, Type),
TextureBuffer(E::TextureHandle, Type),
StorageTextureBuffer(E::TextureHandle, Type),
TextureView(E::TextureView, Type),
/// A struct representing a work group memory buffer. This cannot be accessed by the CPU.
WorkGroupMemory(usize, Type),
Constant(GPUConstant),
OutputBuffer(BufferHandle, Type),
ReadBackBuffer(BufferHandle, Type),
OutputBuffer(E::BufferHandle, Type),
ReadBackBuffer(E::BufferHandle, Type),
}
unsafe impl<E: 'static> StaticType for ShaderInput<E>
where
E: GpuExecutor,
{
type Static = Self;
}
pub enum BindingType<'a, E: GpuExecutor> {
UniformBuffer(&'a E::BufferHandle),
StorageBuffer(&'a E::BufferHandle),
TextureView(&'a E::TextureView),
}
/// Extract the buffer handle from a shader input.
impl<BufferHandle> ShaderInput<BufferHandle> {
pub fn buffer(&self) -> Option<&BufferHandle> {
impl<E: GpuExecutor> ShaderInput<E> {
pub fn binding(&self) -> Option<BindingType<E>> {
match self {
ShaderInput::UniformBuffer(buffer, _) => Some(BindingType::UniformBuffer(buffer)),
ShaderInput::StorageBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)),
ShaderInput::WorkGroupMemory(_, _) => None,
ShaderInput::Constant(_) => None,
ShaderInput::TextureBuffer(_, _) => None,
ShaderInput::StorageTextureBuffer(_, _) => None,
ShaderInput::TextureView(tex, _) => Some(BindingType::TextureView(tex)),
ShaderInput::OutputBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)),
ShaderInput::ReadBackBuffer(buffer, _) => Some(BindingType::StorageBuffer(buffer)),
}
}
pub fn buffer(&self) -> Option<&E::BufferHandle> {
match self {
ShaderInput::UniformBuffer(buffer, _) => Some(buffer),
ShaderInput::StorageBuffer(buffer, _) => Some(buffer),
ShaderInput::WorkGroupMemory(_, _) => None,
ShaderInput::Constant(_) => None,
ShaderInput::TextureBuffer(_, _) => None,
ShaderInput::StorageTextureBuffer(_, _) => None,
ShaderInput::TextureView(_tex, _) => None,
ShaderInput::OutputBuffer(buffer, _) => Some(buffer),
ShaderInput::ReadBackBuffer(buffer, _) => Some(buffer),
}
}
pub fn texture(&self) -> Option<&E::TextureHandle> {
match self {
ShaderInput::UniformBuffer(_, _) => None,
ShaderInput::StorageBuffer(_, _) => None,
ShaderInput::WorkGroupMemory(_, _) => None,
ShaderInput::Constant(_) => None,
ShaderInput::TextureBuffer(tex, _) => Some(tex),
ShaderInput::StorageTextureBuffer(tex, _) => Some(tex),
ShaderInput::TextureView(_, _) => None,
ShaderInput::OutputBuffer(_, _) => None,
ShaderInput::ReadBackBuffer(_, _) => None,
}
}
pub fn ty(&self) -> Type {
match self {
ShaderInput::UniformBuffer(_, ty) => ty.clone(),
ShaderInput::StorageBuffer(_, ty) => ty.clone(),
ShaderInput::WorkGroupMemory(_, ty) => ty.clone(),
ShaderInput::Constant(c) => c.ty(),
ShaderInput::TextureBuffer(_, ty) => ty.clone(),
ShaderInput::StorageTextureBuffer(_, ty) => ty.clone(),
ShaderInput::TextureView(_, ty) => ty.clone(),
ShaderInput::OutputBuffer(_, ty) => ty.clone(),
ShaderInput::ReadBackBuffer(_, ty) => ty.clone(),
}
@@ -133,8 +257,8 @@ pub struct Shader<'a> {
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ShaderIO {
pub inputs: Vec<ShaderInput<()>>,
pub output: ShaderInput<()>,
pub inputs: Vec<AbstractShaderInput>,
pub output: AbstractShaderInput,
}
pub struct StorageBufferOptions {
@@ -144,6 +268,12 @@ pub struct StorageBufferOptions {
pub storage: bool,
}
pub enum TextureBufferOptions {
Storage,
Texture,
Surface,
}
pub trait ToUniformBuffer: StaticType {
fn to_bytes(&self) -> Cow<[u8]>;
}
@@ -168,9 +298,61 @@ impl<T: Pod + Zeroable + StaticTypeSized> ToStorageBuffer for Vec<T> {
}
}
pub trait TextureFormat {
fn format() -> TextureBufferType;
}
impl TextureFormat for Color {
fn format() -> TextureBufferType {
TextureBufferType::Rgba32Float
}
}
impl TextureFormat for SRGBA8 {
fn format() -> TextureBufferType {
TextureBufferType::Rgba8Srgb
}
}
pub enum TextureBufferType {
Rgba32Float,
Rgba8Srgb,
}
pub trait ToTextureBuffer: StaticType {
fn to_bytes(&self) -> Cow<[u8]>;
fn ty() -> Type;
fn format() -> TextureBufferType;
fn size(&self) -> (u32, u32);
}
impl<T: Pod + Zeroable + StaticTypeSized + Pixel + TextureFormat> ToTextureBuffer for Image<T>
where
T::Static: Pixel,
{
fn to_bytes(&self) -> Cow<[u8]> {
Cow::Borrowed(bytemuck::cast_slice(self.data.as_slice()))
}
fn ty() -> Type {
concrete!(T)
}
fn format() -> TextureBufferType {
T::format()
}
fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
}
/// Collection of all arguments that are passed to the shader.
pub struct Bindgroup<E: GpuExecutor + ?Sized> {
pub buffers: Vec<Arc<ShaderInput<E::BufferHandle>>>,
pub buffers: Vec<Arc<ShaderInput<E>>>,
}
unsafe impl<E: GpuExecutor + ?Sized + StaticType> StaticType for Bindgroup<E>
where
E::Static: GpuExecutor,
{
type Static = Bindgroup<E::Static>;
}
/// A struct representing a compute pipeline.
@@ -178,7 +360,14 @@ pub struct PipelineLayout<E: GpuExecutor + ?Sized> {
pub shader: E::ShaderHandle,
pub entry_point: String,
pub bind_group: Bindgroup<E>,
pub output_buffer: Arc<ShaderInput<E::BufferHandle>>,
pub output_buffer: Arc<ShaderInput<E>>,
}
unsafe impl<E: GpuExecutor + ?Sized + StaticType> StaticType for PipelineLayout<E>
where
E::Static: GpuExecutor,
{
type Static = PipelineLayout<E::Static>;
}
/// Extracts arguments from the function arguments and wraps them in a node.
@@ -205,7 +394,7 @@ pub struct UniformNode<Executor> {
}
#[node_macro::node_fn(UniformNode)]
fn uniform_node<T: ToUniformBuffer, E: GpuExecutor>(data: T, executor: &'input E) -> ShaderInput<E::BufferHandle> {
async fn uniform_node<'a: 'input, T: ToUniformBuffer, E: GpuExecutor + 'a>(data: T, executor: &'a E) -> ShaderInput<E> {
executor.create_uniform_buffer(data).unwrap()
}
@@ -214,7 +403,7 @@ pub struct StorageNode<Executor> {
}
#[node_macro::node_fn(StorageNode)]
fn storage_node<T: ToStorageBuffer, E: GpuExecutor>(data: T, executor: &'input E) -> ShaderInput<E::BufferHandle> {
async fn storage_node<'a: 'input, T: ToStorageBuffer, E: GpuExecutor + 'a>(data: T, executor: &'a E) -> ShaderInput<E> {
executor
.create_storage_buffer(
data,
@@ -233,7 +422,7 @@ pub struct PushNode<Value> {
}
#[node_macro::node_fn(PushNode)]
fn push_node<T>(mut vec: Vec<T>, value: T) {
async fn push_node<T>(mut vec: Vec<T>, value: T) {
vec.push(value);
}
@@ -243,8 +432,8 @@ pub struct CreateOutputBufferNode<Executor, Ty> {
}
#[node_macro::node_fn(CreateOutputBufferNode)]
fn create_output_buffer_node<E: GpuExecutor>(size: usize, executor: &'input E, ty: Type) -> ShaderInput<E::BufferHandle> {
executor.create_output_buffer(size, ty, true).unwrap()
async fn create_output_buffer_node<'a: 'input, E: GpuExecutor + 'a>(size: usize, executor: &'a E, ty: Type) -> Arc<ShaderInput<E>> {
Arc::new(executor.create_output_buffer(size, ty, true).unwrap())
}
pub struct CreateComputePassNode<Executor, Output, Instances> {
@@ -254,12 +443,7 @@ pub struct CreateComputePassNode<Executor, Output, Instances> {
}
#[node_macro::node_fn(CreateComputePassNode)]
fn create_compute_pass_node<'any_input, E: 'any_input + GpuExecutor>(
layout: PipelineLayout<E>,
executor: &'any_input E,
output: ShaderInput<E::BufferHandle>,
instances: ComputePassDimensions,
) -> E::CommandBuffer {
async fn create_compute_pass_node<'a: 'input, E: 'a + GpuExecutor>(layout: PipelineLayout<E>, executor: &'a E, output: ShaderInput<E>, instances: ComputePassDimensions) -> E::CommandBuffer {
executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap()
}
@@ -271,7 +455,7 @@ pub struct CreatePipelineLayoutNode<_E, EntryPoint, Bindgroup, OutputBuffer> {
}
#[node_macro::node_fn(CreatePipelineLayoutNode<_E>)]
fn create_pipeline_layout_node<_E: GpuExecutor>(shader: _E::ShaderHandle, entry_point: String, bind_group: Bindgroup<_E>, output_buffer: Arc<ShaderInput<_E::BufferHandle>>) -> PipelineLayout<_E> {
async fn create_pipeline_layout_node<_E: GpuExecutor>(shader: _E::ShaderHandle, entry_point: String, bind_group: Bindgroup<_E>, output_buffer: Arc<ShaderInput<_E>>) -> PipelineLayout<_E> {
PipelineLayout {
shader,
entry_point,
@@ -285,15 +469,68 @@ pub struct ExecuteComputePipelineNode<Executor> {
}
#[node_macro::node_fn(ExecuteComputePipelineNode)]
fn execute_compute_pipeline_node<E: GpuExecutor>(encoder: E::CommandBuffer, executor: &'input mut E) {
async fn execute_compute_pipeline_node<'a: 'input, E: 'a + GpuExecutor>(encoder: E::CommandBuffer, executor: &'a E) {
executor.execute_compute_pipeline(encoder).unwrap();
}
// TODO
// pub struct ReadOutputBufferNode<Executor> {
// executor: Executor,
// }
// #[node_macro::node_fn(ReadOutputBufferNode)]
// fn read_output_buffer_node<E: GpuExecutor>(buffer: E::BufferHandle, executor: &'input mut E) -> Vec<u8> {
// executor.read_output_buffer(buffer).await.unwrap()
// }
pub struct ReadOutputBufferNode<Executor, ComputePass> {
executor: Executor,
_compute_pass: ComputePass,
}
#[node_macro::node_fn(ReadOutputBufferNode)]
async fn read_output_buffer_node<'a: 'input, E: 'a + GpuExecutor>(buffer: Arc<ShaderInput<E>>, executor: &'a E, _compute_pass: ()) -> Vec<u8> {
executor.read_output_buffer(buffer).await.unwrap()
}
pub struct CreateGpuSurfaceNode {}
#[node_macro::node_fn(CreateGpuSurfaceNode)]
async fn create_gpu_surface<'a: 'input, E: 'a + GpuExecutor<Window = Io::Surface>, Io: ApplicationIo<Executor = E>>(editor_api: EditorApi<'a, Io>) -> SurfaceHandle<E::Surface> {
let canvas = editor_api.application_io.create_surface();
let executor = editor_api.application_io.gpu_executor().unwrap();
executor.create_surface(canvas).unwrap()
}
pub struct RenderTextureNode<Surface, EditorApi> {
surface: Surface,
executor: EditorApi,
}
#[derive(Clone)]
pub struct ShaderInputFrame<E: GpuExecutor + ?Sized> {
shader_input: Arc<ShaderInput<E>>,
transform: DAffine2,
}
unsafe impl<E: GpuExecutor + ?Sized + StaticType> StaticType for ShaderInputFrame<E>
where
E::Static: GpuExecutor,
{
type Static = ShaderInputFrame<E::Static>;
}
#[node_macro::node_fn(RenderTextureNode)]
async fn render_texture_node<'a: 'input, E: 'a + GpuExecutor>(image: ShaderInputFrame<E>, surface: Arc<SurfaceHandle<E::Surface>>, executor: &'a E) -> SurfaceFrame {
let surface_id = surface.surface_id;
executor.create_render_pass(image.shader_input, surface).unwrap();
SurfaceFrame {
surface_id,
transform: image.transform,
}
}
pub struct UploadTextureNode<E> {
executor: E,
}
#[node_macro::node_fn(UploadTextureNode)]
async fn upload_texture<'a: 'input, E: 'a + GpuExecutor>(input: ImageFrame<Color>, executor: &'a E) -> ShaderInputFrame<E> {
let shader_input = executor.create_texture_buffer(input.image, TextureBufferOptions::Texture).unwrap();
ShaderInputFrame {
shader_input: Arc::new(shader_input),
transform: input.transform,
}
}

View File

@@ -394,9 +394,9 @@ impl NodeNetwork {
(
0,
DocumentNode {
name: "CacheNode".to_string(),
name: "MemoNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(ty)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
),

View File

@@ -299,8 +299,8 @@ impl<'a> TaggedValue {
x if x == TypeId::of::<graphene_core::Artboard>() => Ok(TaggedValue::Artboard(*downcast(input).unwrap())),
x if x == TypeId::of::<glam::IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::wasm_application_io::WasmSurfaceHandleFrame>() => {
let frame = *downcast::<graphene_core::wasm_application_io::WasmSurfaceHandleFrame>(input).unwrap();
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
Ok(TaggedValue::SurfaceFrame(frame.into()))
}
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),

View File

@@ -9,16 +9,12 @@ license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["wgpu"]
gpu = [
"graphene-core/gpu",
"gpu-compiler-bin-wrapper",
"compilation-client",
"gpu-executor",
]
default = ["wasm"]
gpu = ["graphene-core/gpu", "gpu-compiler-bin-wrapper", "compilation-client", "gpu-executor"]
vulkan = ["gpu", "vulkan-executor"]
wgpu = ["gpu", "wgpu-executor"]
quantization = ["autoquant"]
wasm = ["wasm-bindgen", "web-sys", "js-sys"]
[dependencies]
@@ -29,6 +25,7 @@ graphene-core = { path = "../gcore", features = [
"async",
"std",
"serde",
"alloc",
], default-features = false }
dyn-any = { path = "../../libraries/dyn-any", features = ["derive"] }
graph-craft = { path = "../graph-craft" }
@@ -53,8 +50,25 @@ xxhash-rust = { workspace = true }
serde_json = "1.0.96"
reqwest = { version = "0.11.17", features = ["rustls", "rustls-tls"] }
futures = "0.3.28"
wasm-bindgen = { version = "0.2.84", optional = true }
js-sys = { version = "0.3.55", optional = true }
wgpu-types = "0.16.0"
wgpu = "0.16.1"
[dependencies.serde]
version = "1.0"
optional = true
features = ["derive"]
[dependencies.web-sys]
version = "0.3.4"
optional = true
features = [
"Window",
"CanvasRenderingContext2d",
"ImageData",
"Document",
"HtmlCanvasElement",
"ImageBitmapRenderingContext",
]

View File

@@ -6,10 +6,12 @@ use graph_craft::document::*;
use graph_craft::proto::*;
use graphene_core::raster::*;
use graphene_core::*;
use wgpu_executor::NewExecutor;
use wgpu_executor::WgpuExecutor;
use std::sync::Arc;
use crate::wasm_application_io::WasmApplicationIo;
pub struct GpuCompiler<TypingContext, ShaderIO> {
typing_context: TypingContext,
io: ShaderIO,
@@ -37,12 +39,13 @@ async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingConte
compilation_client::compile(proto_networks, input_types, output_types, io).await.unwrap()
}
pub struct MapGpuNode<Node> {
pub struct MapGpuNode<Node, EditorApi> {
node: Node,
editor_api: EditorApi,
}
#[node_macro::node_fn(MapGpuNode)]
async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Color> {
async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, editor_api: graphene_core::application_io::EditorApi<'a, WasmApplicationIo>) -> ImageFrame<Color> {
log::debug!("Executing gpu node");
let compiler = graph_craft::graphene_compiler::Compiler {};
let inner_network = NodeNetwork::value_network(node);
@@ -121,7 +124,24 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
//return ImageFrame::empty();
let len: usize = image.image.data.len();
let executor = NewExecutor::new().await.unwrap();
let executor = &editor_api.application_io.gpu_executor.as_ref().unwrap();
/*
let canvas = editor_api.application_io.create_surface();
let surface = unsafe { executor.create_surface(canvas) }.unwrap();
//log::debug!("id: {:?}", surface);
let surface_id = surface.surface_id;
let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap();
//executor.create_render_pass(texture, surface).unwrap();
let frame = SurfaceFrame {
surface_id,
transform: image.transform,
};
return frame;*/
log::debug!("creating buffer");
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
let storage_buffer = executor
@@ -152,7 +172,6 @@ async fn map_gpu(image: ImageFrame<Color>, node: DocumentNode) -> ImageFrame<Col
io: shader.io,
};
log::debug!("loading shader");
log::debug!("shader: {:?}", shader.source);
let shader = executor.load_shader(shader).unwrap();
log::debug!("loaded shader");
let pipeline = PipelineLayout {
@@ -333,7 +352,7 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
.unwrap();
let len = background.image.data.len();
let executor = NewExecutor::new()
let executor = WgpuExecutor::new()
.await
.expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser.");
log::debug!("creating buffer");

View File

@@ -12,7 +12,7 @@ pub mod http;
pub mod any;
#[cfg(feature = "gpu")]
pub mod executor;
pub mod gpu_nodes;
#[cfg(feature = "quantization")]
pub mod quantization;
@@ -22,3 +22,6 @@ pub use graphene_core::*;
pub mod image_segmentation;
pub mod brush;
#[cfg(feature = "wasm")]
pub mod wasm_application_io;

View File

@@ -1,27 +1,34 @@
use std::{cell::RefCell, collections::HashMap};
use std::cell::RefCell;
use super::{ApplicationIo, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
use crate::{
use dyn_any::StaticType;
use graphene_core::application_io::{ApplicationIo, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
use graphene_core::{
raster::{color::SRGBA8, ImageFrame},
Node,
};
use alloc::sync::Arc;
use dyn_any::StaticType;
use js_sys::{Object, Reflect};
use std::sync::Arc;
use wasm_bindgen::{Clamped, JsCast, JsValue};
use web_sys::{window, CanvasRenderingContext2d, HtmlCanvasElement};
#[cfg(feature = "wgpu")]
use wgpu_executor::WgpuExecutor;
pub struct Canvas(CanvasRenderingContext2d);
#[derive(Debug, Default)]
pub struct WasmApplicationIo {
ids: RefCell<u64>,
canvases: RefCell<HashMap<SurfaceId, CanvasRenderingContext2d>>,
#[cfg(feature = "wgpu")]
pub(crate) gpu_executor: Option<WgpuExecutor>,
}
impl WasmApplicationIo {
pub fn new() -> Self {
Self::default()
pub async fn new() -> Self {
Self {
ids: RefCell::new(0),
#[cfg(feature = "wgpu")]
gpu_executor: WgpuExecutor::new().await,
}
}
}
@@ -29,24 +36,36 @@ unsafe impl StaticType for WasmApplicationIo {
type Static = WasmApplicationIo;
}
pub type WasmEditorApi<'a> = super::EditorApi<'a, WasmApplicationIo>;
impl<'a> From<WasmEditorApi<'a>> for &'a WasmApplicationIo {
fn from(editor_api: WasmEditorApi<'a>) -> Self {
editor_api.application_io
}
}
#[cfg(feature = "wgpu")]
impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {
fn from(app_io: &'a WasmApplicationIo) -> Self {
app_io.gpu_executor.as_ref().unwrap()
}
}
pub type WasmEditorApi<'a> = graphene_core::application_io::EditorApi<'a, WasmApplicationIo>;
impl ApplicationIo for WasmApplicationIo {
type Surface = CanvasRenderingContext2d;
type Surface = HtmlCanvasElement;
#[cfg(feature = "wgpu")]
type Executor = WgpuExecutor;
#[cfg(not(feature = "wgpu"))]
type Executor = ();
fn create_surface(&self) -> SurfaceHandle<Self::Surface> {
let wrapper = || {
let document = window().expect("should have a window in this context").document().expect("window should have a document");
let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::<HtmlCanvasElement>()?;
// TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu)
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
let mut guard = self.ids.borrow_mut();
let id = SurfaceId(*guard);
*guard += 1;
self.canvases.borrow_mut().insert(id, context.clone());
// store the canvas in the global scope so it doesn't get garbage collected
let window = window().expect("should have a window in this context");
let window = Object::from(window);
@@ -60,21 +79,19 @@ impl ApplicationIo for WasmApplicationIo {
// Convert key and value to JsValue
let js_key = JsValue::from_str(format!("canvas{}", id.0).as_str());
let js_value = JsValue::from(context.clone());
let js_value = JsValue::from(canvas.clone());
let canvases = Object::from(canvases.unwrap());
// Use Reflect API to set property
Reflect::set(&canvases, &js_key, &js_value)?;
Ok::<_, JsValue>(SurfaceHandle { surface_id: id, surface: context })
Ok::<_, JsValue>(SurfaceHandle { surface_id: id, surface: canvas })
};
wrapper().expect("should be able to set canvas in global scope")
}
fn destroy_surface(&self, surface_id: SurfaceId) {
self.canvases.borrow_mut().remove(&surface_id);
let window = window().expect("should have a window in this context");
let window = Object::from(window);
@@ -93,15 +110,20 @@ impl ApplicationIo for WasmApplicationIo {
wrapper().expect("should be able to set canvas in global scope")
}
#[cfg(feature = "wgpu")]
fn gpu_executor(&self) -> Option<&Self::Executor> {
self.gpu_executor.as_ref()
}
}
pub type WasmSurfaceHandle = SurfaceHandle<CanvasRenderingContext2d>;
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<CanvasRenderingContext2d>;
pub type WasmSurfaceHandle = SurfaceHandle<HtmlCanvasElement>;
pub type WasmSurfaceHandleFrame = SurfaceHandleFrame<HtmlCanvasElement>;
pub struct CreateSurfaceNode {}
#[node_macro::node_fn(CreateSurfaceNode)]
fn create_surface_node<'a: 'input>(editor: WasmEditorApi<'a>) -> Arc<SurfaceHandle<CanvasRenderingContext2d>> {
async fn create_surface_node<'a: 'input>(editor: WasmEditorApi<'a>) -> Arc<SurfaceHandle<HtmlCanvasElement>> {
editor.application_io.create_surface().into()
}
@@ -110,15 +132,17 @@ pub struct DrawImageFrameNode<Surface> {
}
#[node_macro::node_fn(DrawImageFrameNode)]
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<SurfaceHandle<CanvasRenderingContext2d>>) -> SurfaceHandleFrame<CanvasRenderingContext2d> {
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>) -> SurfaceHandleFrame<HtmlCanvasElement> {
let image_data = image.image.data;
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
if image.image.width > 0 && image.image.height > 0 {
let canvas = surface_handle.surface.canvas().expect("Failed to get canvas");
let canvas = &surface_handle.surface;
canvas.set_width(image.image.width);
canvas.set_height(image.image.height);
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width, image.image.height).expect("Failed to construct ImageData");
surface_handle.surface.put_image_data(&image_data, 0.0, 0.0).unwrap();
// TODO: replace "2d" with "bitmaprenderer" once we switch to ImageBitmap (lives on gpu) from ImageData (lives on cpu)
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(array, image.image.width as u32, image.image.height as u32).expect("Failed to construct ImageData");
context.put_image_data(&image_data, 0.0, 0.0).unwrap();
}
SurfaceHandleFrame {
surface_handle,

View File

@@ -16,6 +16,8 @@ quantization = ["graphene-std/quantization"]
graphene-core = { path = "../gcore", features = ["async", "std"] }
graphene-std = { path = "../gstd" }
graph-craft = { path = "../graph-craft" }
gpu-executor = { path = "../gpu-executor" }
wgpu-executor = { path = "../wgpu-executor" }
dyn-any = { path = "../../libraries/dyn-any", features = [
"log-bad-types",
"glam",

View File

@@ -8,15 +8,19 @@ use graphene_core::structural::Then;
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::VectorData;
use graphene_core::wasm_application_io::WasmSurfaceHandle;
use graphene_core::wasm_application_io::*;
use graphene_core::{application_io::SurfaceHandle, SurfaceFrame, WasmSurfaceHandleFrame};
use graphene_core::{concrete, generic};
use graphene_core::{fn_type, raster::*};
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::wasm_application_io::*;
#[cfg(feature = "gpu")]
use gpu_executor::{GpuExecutor, ShaderInput, ShaderInputFrame};
use graphene_std::raster::*;
use graphene_std::wasm_application_io::WasmEditorApi;
use wgpu_executor::WgpuExecutor;
use dyn_any::StaticType;
use glam::{DAffine2, DVec2};
@@ -79,7 +83,7 @@ macro_rules! async_node {
args.reverse();
let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
any.into_type_erased()
Box::new(any) as TypeErasedBox
})
},
{
@@ -181,7 +185,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::ops::AddNode, input: (u32, u32), params: []),
register_node!(graphene_core::ops::AddNode, input: (u32, &u32), params: []),
register_node!(graphene_core::ops::CloneNode<_>, input: &ImageFrame<Color>, params: []),
register_node!(graphene_core::ops::CloneNode<_>, input: &graphene_core::EditorApi, params: []),
register_node!(graphene_core::ops::CloneNode<_>, input: &WasmEditorApi, params: []),
register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [u32]),
register_node!(graphene_core::ops::AddParameterNode<_>, input: &u32, params: [u32]),
register_node!(graphene_core::ops::AddParameterNode<_>, input: u32, params: [&u32]),
@@ -190,9 +194,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [f64]),
register_node!(graphene_core::ops::AddParameterNode<_>, input: f64, params: [&f64]),
register_node!(graphene_core::ops::AddParameterNode<_>, input: &f64, params: [&f64]),
register_node!(graphene_core::ops::SomeNode, input: graphene_core::EditorApi, params: []),
register_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, params: []),
register_node!(graphene_core::ops::SomeNode, input: WasmEditorApi, params: []),
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<SRGBA8>>, input: ImageFrame<Color>, output: ImageFrame<SRGBA8>, params: []),
async_node!(graphene_core::ops::IntoNode<_, ImageFrame<Color>>, input: ImageFrame<SRGBA8>, output: ImageFrame<Color>, params: []),
#[cfg(feature = "gpu")]
async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: WasmEditorApi, output: &WgpuExecutor, params: []),
register_node!(graphene_std::raster::DownresNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Color>]),
register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame<Color>, params: [ImageFrame<Luma>]),
@@ -244,26 +250,68 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
register_node!(graphene_core::wasm_application_io::CreateSurfaceNode, input: graphene_core::EditorApi, params: []),
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
async_node!(
graphene_core::wasm_application_io::DrawImageFrameNode<_>,
graphene_std::wasm_application_io::DrawImageFrameNode<_>,
input: ImageFrame<SRGBA8>,
output: WasmSurfaceHandleFrame,
params: [Arc<WasmSurfaceHandle>]
),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::UniformNode<_>, input: f32, output: ShaderInput<WgpuExecutor>, params: [&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::StorageNode<_>, input: Vec<u8>, output: ShaderInput<WgpuExecutor>, params: [&WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(
gpu_executor::PushNode<_>,
input: Vec<ShaderInput<WgpuExecutor>>,
output: Vec<ShaderInput<WgpuExecutor>>,
params: [ShaderInput<WgpuExecutor>]
),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::CreateOutputBufferNode<_, _>, input: usize, output: gpu_executor::ShaderInput<WgpuExecutor>, params: [&WgpuExecutor, Type]),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::CreateComputePassNode<_, _, _>, input: gpu_executor::PipelineLayout<WgpuExecutor>, output: <WgpuExecutor as GpuExecutor>::CommandBuffer, params: [&WgpuExecutor, ShaderInput<WgpuExecutor>, gpu_executor::ComputePassDimensions]),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::CreatePipelineLayoutNode<_, _, _, _>, input: <WgpuExecutor as GpuExecutor>::ShaderHandle, output: gpu_executor::PipelineLayout<WgpuExecutor>, params: [String, gpu_executor::Bindgroup<WgpuExecutor>, Arc<ShaderInput<WgpuExecutor>>]),
#[cfg(feature = "gpu")]
async_node!(
gpu_executor::ExecuteComputePipelineNode<_>,
input: <WgpuExecutor as GpuExecutor>::CommandBuffer,
output: (),
params: [&WgpuExecutor]
),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::ReadOutputBufferNode<_, _>, input: Arc<ShaderInput<WgpuExecutor>>, output: Vec<u8>, params: [&WgpuExecutor, ()]),
#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
async_node!(gpu_executor::CreateGpuSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface>>, params: []),
#[cfg(feature = "gpu")]
async_node!(gpu_executor::RenderTextureNode<_, _>, input: ShaderInputFrame<WgpuExecutor>, output: SurfaceFrame, params: [Arc<SurfaceHandle<<WgpuExecutor as GpuExecutor>::Surface>>, &WgpuExecutor]),
#[cfg(feature = "gpu")]
async_node!(
gpu_executor::UploadTextureNode<_>,
input: ImageFrame<Color>,
output: ShaderInputFrame<WgpuExecutor>,
params: [&WgpuExecutor]
),
#[cfg(feature = "gpu")]
vec![(
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<(), WasmEditorApi> = DowncastBothNode::new(args[1].clone());
//let document_node = ClonedNode::new(document_node.eval(()));
let node = graphene_std::executor::MapGpuNode::new(document_node);
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
any.into_type_erased()
})
},
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(graph_craft::document::DocumentNode)]),
NodeIOTypes::new(
concrete!(ImageFrame<Color>),
concrete!(SurfaceFrame),
vec![fn_type!(graph_craft::document::DocumentNode), fn_type!(WasmEditorApi)],
),
)],
#[cfg(feature = "gpu")]
vec![(
@@ -273,7 +321,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2].clone());
let node = graphene_std::executor::BlendGpuImageNode::new(background, blend_mode, opacity);
let node = graphene_std::gpu_nodes::BlendGpuImageNode::new(background, blend_mode, opacity);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
any.into_type_erased()
@@ -374,47 +422,39 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]),
raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]),
register_node!(graphene_core::memo::LetNode<_>, input: Option<ImageFrame<Color>>, params: []),
register_node!(graphene_core::memo::LetNode<_>, input: Option<graphene_core::EditorApi>, params: []),
register_node!(graphene_core::memo::LetNode<_>, input: Option<WasmEditorApi>, params: []),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: ImageFrame<Color>, params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: VectorData, params: [VectorData]),
async_node!(
graphene_core::memo::EndLetNode<_>,
input: graphene_core::EditorApi,
output: ImageFrame<Color>,
params: [ImageFrame<Color>]
),
async_node!(graphene_core::memo::EndLetNode<_>, input: graphene_core::EditorApi, output: VectorData, params: [VectorData]),
async_node!(
graphene_core::memo::EndLetNode<_>,
input: graphene_core::EditorApi,
input: WasmEditorApi,
output: graphene_core::GraphicGroup,
params: [graphene_core::GraphicGroup]
),
async_node!(
graphene_core::memo::EndLetNode<_>,
input: graphene_core::EditorApi,
input: WasmEditorApi,
output: graphene_core::Artboard,
params: [graphene_core::Artboard]
),
async_node!(
graphene_core::memo::EndLetNode<_>,
input: graphene_core::EditorApi,
input: WasmEditorApi,
output: WasmSurfaceHandleFrame,
params: [WasmSurfaceHandleFrame]
),
async_node!(graphene_core::memo::EndLetNode<_>, input: WasmEditorApi, output: SurfaceFrame, params: [SurfaceFrame]),
vec![(
NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"),
|args| {
Box::pin(async move {
let node: DowncastBothNode<Option<graphene_core::EditorApi>, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone());
let node: DowncastBothNode<Option<WasmEditorApi>, WasmEditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone());
let node = <graphene_core::memo::RefNode<_, _>>::new(node);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
any.into_type_erased()
})
},
NodeIOTypes::new(
concrete!(()),
concrete!(graphene_core::EditorApi),
vec![fn_type!(Option<graphene_core::EditorApi>, graphene_core::EditorApi)],
),
NodeIOTypes::new(concrete!(()), concrete!(WasmEditorApi), vec![fn_type!(Option<WasmEditorApi>, WasmEditorApi)]),
)],
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Image<Color>, params: [Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
@@ -443,9 +483,9 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
),
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: graphene_core::EditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: graphene_core::EditorApi, params: []),
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::vector::VectorData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: ImageFrame<Color>, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),

View File

@@ -10,10 +10,19 @@ default = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
graphene-core = { path = "../gcore", features = ["async", "std", "alloc", "gpu"] }
graph-craft = {path = "../graph-craft" }
graphene-core = { path = "../gcore", features = [
"async",
"std",
"alloc",
"gpu",
] }
graph-craft = { path = "../graph-craft" }
gpu-executor = { path = "../gpu-executor" }
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
dyn-any = { path = "../../libraries/dyn-any", features = [
"log-bad-types",
"rc",
"glam",
] }
future-executor = { path = "../future-executor" }
num-traits = "0.2"
log = "0.4"
@@ -21,9 +30,11 @@ serde = { version = "1", features = ["derive", "rc"], optional = true }
glam = { version = "0.22" }
base64 = "0.13"
bytemuck = {version = "1.8" }
bytemuck = { version = "1.8" }
anyhow = "1.0.66"
wgpu = { version = "0.16", features = ["spirv"] }
spirv = "0.2.0"
futures-intrusive = "0.5.0"
futures = "0.3.25"
web-sys = { version = "0.3.4", features = ["HtmlCanvasElement"] }
winit = "0.28.6"

View File

@@ -6,6 +6,7 @@ pub struct Context {
pub device: Arc<Device>,
pub queue: Arc<Queue>,
pub instance: Arc<Instance>,
pub adapter: Arc<wgpu::Adapter>,
}
impl Context {
@@ -16,9 +17,9 @@ impl Context {
// `request_adapter` instantiates the general connection to the GPU
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await?;
let limits = adapter.limits();
//let limits = adapter.limits();
log::trace!("Adapter limits: {:?}", limits);
//log::trace!("Adapter limits: {:?}", limits);
// `request_device` instantiates the feature specific connection to the GPU, defining some parameters,
// `features` being the available features.
@@ -27,7 +28,7 @@ impl Context {
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits,
limits: Default::default(),
},
None,
)
@@ -42,6 +43,7 @@ impl Context {
Some(Self {
device: Arc::new(device),
queue: Arc::new(queue),
adapter: Arc::new(adapter),
instance: Arc::new(instance),
})
}

View File

@@ -2,36 +2,117 @@ mod context;
mod executor;
pub use context::Context;
use dyn_any::{DynAny, StaticType};
pub use executor::GpuExecutor;
use gpu_executor::{ComputePassDimensions, Shader, ShaderInput, StorageBufferOptions, ToStorageBuffer, ToUniformBuffer};
use gpu_executor::{ComputePassDimensions, Shader, ShaderInput, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer};
use graph_craft::Type;
use anyhow::{bail, Result};
use futures::Future;
use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle};
use std::pin::Pin;
use std::sync::Arc;
use wgpu::util::DeviceExt;
use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule};
#[derive(Debug, Clone)]
pub struct NewExecutor {
use wgpu::util::DeviceExt;
use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule, Texture, TextureView};
#[cfg(target_arch = "wasm32")]
use web_sys::HtmlCanvasElement;
#[derive(Debug, dyn_any::DynAny)]
pub struct WgpuExecutor {
context: Context,
render_configuration: RenderConfiguration,
}
impl gpu_executor::GpuExecutor for NewExecutor {
type ShaderHandle = ShaderModule;
impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<EditorApi<'a, T>> for &'a WgpuExecutor {
fn from(editor_api: EditorApi<'a, T>) -> Self {
editor_api.application_io.gpu_executor().unwrap()
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 3],
tex_coords: [f32; 2],
}
impl Vertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
use std::mem;
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
],
}
}
}
const VERTICES: &[Vertex] = &[
Vertex {
position: [-1., 1., 0.0],
tex_coords: [0., 0.],
}, // A
Vertex {
position: [-1., -1., 0.0],
tex_coords: [0., 1.],
}, // B
Vertex {
position: [1., 1., 0.0],
tex_coords: [1., 0.],
}, // C
Vertex {
position: [1., -1., 0.0],
tex_coords: [1., 1.],
}, // D
];
const INDICES: &[u16] = &[0, 1, 2, 2, 1, 3];
type WgpuShaderInput = ShaderInput<WgpuExecutor>;
#[derive(Debug, DynAny)]
#[repr(transparent)]
pub struct CommandBufferWrapper(CommandBuffer);
#[derive(Debug, DynAny)]
#[repr(transparent)]
pub struct ShaderModuleWrapper(ShaderModule);
impl gpu_executor::GpuExecutor for WgpuExecutor {
type ShaderHandle = ShaderModuleWrapper;
type BufferHandle = Buffer;
type CommandBuffer = CommandBuffer;
type TextureHandle = Texture;
type TextureView = TextureView;
type CommandBuffer = CommandBufferWrapper;
type Surface = wgpu::Surface;
#[cfg(target_arch = "wasm32")]
type Window = HtmlCanvasElement;
#[cfg(not(target_arch = "wasm32"))]
type Window = winit::window::Window;
fn load_shader(&self, shader: Shader) -> Result<Self::ShaderHandle> {
let shader_module = self.context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(shader.name),
source: wgpu::ShaderSource::SpirV(shader.source),
});
Ok(shader_module)
Ok(ShaderModuleWrapper(shader_module))
}
fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<ShaderInput<Self::BufferHandle>> {
fn create_uniform_buffer<T: ToUniformBuffer>(&self, data: T) -> Result<WgpuShaderInput> {
let bytes = data.to_bytes();
let buffer = self.context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
@@ -41,7 +122,7 @@ impl gpu_executor::GpuExecutor for NewExecutor {
Ok(ShaderInput::UniformBuffer(buffer, Type::new::<T>()))
}
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<ShaderInput<Self::BufferHandle>> {
fn create_storage_buffer<T: ToStorageBuffer>(&self, data: T, options: StorageBufferOptions) -> Result<WgpuShaderInput> {
let bytes = data.to_bytes();
let mut usage = wgpu::BufferUsages::empty();
@@ -66,8 +147,44 @@ impl gpu_executor::GpuExecutor for NewExecutor {
});
Ok(ShaderInput::StorageBuffer(buffer, data.ty()))
}
fn create_texture_buffer<T: gpu_executor::ToTextureBuffer>(&self, data: T, options: TextureBufferOptions) -> Result<WgpuShaderInput> {
let bytes = data.to_bytes();
let usage = match options {
TextureBufferOptions::Storage => wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
TextureBufferOptions::Texture => wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
TextureBufferOptions::Surface => wgpu::TextureUsages::RENDER_ATTACHMENT,
};
let format = match T::format() {
TextureBufferType::Rgba32Float => wgpu::TextureFormat::Rgba32Float,
TextureBufferType::Rgba8Srgb => wgpu::TextureFormat::Rgba8UnormSrgb,
};
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<ShaderInput<Self::BufferHandle>> {
let buffer = self.context.device.create_texture_with_data(
self.context.queue.as_ref(),
&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: data.size().0,
height: data.size().1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage,
view_formats: &[format],
},
bytes.as_ref(),
);
match options {
TextureBufferOptions::Storage => Ok(ShaderInput::StorageTextureBuffer(buffer, T::ty())),
TextureBufferOptions::Texture => Ok(ShaderInput::TextureBuffer(buffer, T::ty())),
TextureBufferOptions::Surface => Ok(ShaderInput::TextureBuffer(buffer, T::ty())),
}
}
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<WgpuShaderInput> {
log::debug!("Creating output buffer with len: {}", len);
let create_buffer = |usage| {
Ok::<_, anyhow::Error>(self.context.device.create_buffer(&BufferDescriptor {
@@ -83,11 +200,11 @@ impl gpu_executor::GpuExecutor for NewExecutor {
};
Ok(buffer)
}
fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout<Self>, read_back: Option<Arc<ShaderInput<Self::BufferHandle>>>, instances: ComputePassDimensions) -> Result<CommandBuffer> {
fn create_compute_pass(&self, layout: &gpu_executor::PipelineLayout<Self>, read_back: Option<Arc<WgpuShaderInput>>, instances: ComputePassDimensions) -> Result<Self::CommandBuffer> {
let compute_pipeline = self.context.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: None,
layout: None,
module: &layout.shader,
module: &layout.shader.0,
entry_point: layout.entry_point.as_str(),
});
let bind_group_layout = compute_pipeline.get_bind_group_layout(0);
@@ -97,11 +214,15 @@ impl gpu_executor::GpuExecutor for NewExecutor {
.buffers
.iter()
.chain(std::iter::once(&layout.output_buffer))
.flat_map(|input| input.buffer())
.flat_map(|input| input.binding())
.enumerate()
.map(|(i, buffer)| wgpu::BindGroupEntry {
binding: i as u32,
resource: buffer.as_entire_binding(),
resource: match buffer {
gpu_executor::BindingType::UniformBuffer(buf) => buf.as_entire_binding(),
gpu_executor::BindingType::StorageBuffer(buf) => buf.as_entire_binding(),
gpu_executor::BindingType::TextureView(buf) => wgpu::BindingResource::TextureView(buf),
},
})
.collect::<Vec<_>>();
@@ -123,27 +244,77 @@ impl gpu_executor::GpuExecutor for NewExecutor {
// Sets adds copy operation to command encoder.
// Will copy data from storage buffer on GPU to staging buffer on CPU.
if let Some(buffer) = read_back {
let ShaderInput::ReadBackBuffer(output, ty) = buffer.as_ref() else {
let ShaderInput::ReadBackBuffer(output, _ty) = buffer.as_ref() else {
bail!("Tried to read back from a non read back buffer");
};
let size = output.size();
assert_eq!(size, layout.output_buffer.buffer().unwrap().size());
let ShaderInput::OutputBuffer(output_buffer, ty) = layout.output_buffer.as_ref() else {
bail!("Tried to read back from a non output buffer");
};
assert_eq!(size, output_buffer.size());
assert_eq!(ty, &layout.output_buffer.ty());
encoder.copy_buffer_to_buffer(
layout.output_buffer.buffer().ok_or_else(|| anyhow::anyhow!("Tried to use an non buffer as the shader output"))?,
0,
output,
0,
size,
);
encoder.copy_buffer_to_buffer(output_buffer, 0, output, 0, size);
}
// Submits command encoder for processing
Ok(encoder.finish())
Ok(CommandBufferWrapper(encoder.finish()))
}
fn create_render_pass(&self, texture: Arc<ShaderInput<Self>>, canvas: Arc<SurfaceHandle<wgpu::Surface>>) -> Result<()> {
let texture = texture.texture().expect("Expected texture input");
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let output = canvas.as_ref().surface.get_current_texture()?;
let view = output.texture.create_view(&wgpu::TextureViewDescriptor {
format: Some(wgpu::TextureFormat::Rgba8Unorm),
..Default::default()
});
let output_texture_bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.render_configuration.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.render_configuration.sampler),
},
],
label: Some("output_texture_bind_group"),
});
let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
store: true,
},
})],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.render_configuration.render_pipeline);
render_pass.set_bind_group(0, &output_texture_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.render_configuration.vertex_buffer.slice(..));
render_pass.set_index_buffer(self.render_configuration.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..self.render_configuration.num_indices, 0, 0..1);
}
let encoder = encoder.finish();
self.context.queue.submit(Some(encoder));
output.present();
Ok(())
}
fn execute_compute_pipeline(&self, encoder: Self::CommandBuffer) -> Result<()> {
self.context.queue.submit(Some(encoder));
self.context.queue.submit(Some(encoder.0));
// Poll the device in a blocking manner so that our future resolves.
// In an actual application, `device.poll(...)` should
@@ -152,7 +323,7 @@ impl gpu_executor::GpuExecutor for NewExecutor {
Ok(())
}
fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self::BufferHandle>>) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>>>> {
fn read_output_buffer(&self, buffer: Arc<ShaderInput<Self>>) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>>>> {
Box::pin(async move {
if let ShaderInput::ReadBackBuffer(buffer, _) = buffer.as_ref() {
let buffer_slice = buffer.slice(..);
@@ -187,13 +358,168 @@ impl gpu_executor::GpuExecutor for NewExecutor {
} else {
bail!("Tried to read a non readback buffer")
}
}) as _
})
}
fn create_texture_view(&self, texture: ShaderInput<Self>) -> Result<ShaderInput<Self>> {
//Ok(ShaderInput::TextureView(texture.create_view(&wgpu::TextureViewDescriptor::default()), ) )
let ShaderInput::TextureBuffer(texture, ty) = &texture else {
bail!("Tried to create a texture view from a non texture");
};
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Ok(ShaderInput::TextureView(view, ty.clone()))
}
#[cfg(target_arch = "wasm32")]
fn create_surface(&self, canvas: graphene_core::WasmSurfaceHandle) -> Result<SurfaceHandle<wgpu::Surface>> {
let surface = self.context.instance.create_surface_from_canvas(canvas.surface)?;
let surface_caps = surface.get_capabilities(&self.context.adapter);
let surface_format = wgpu::TextureFormat::Rgba8Unorm;
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: 1920,
height: 1080,
present_mode: surface_caps.present_modes[0],
alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
view_formats: vec![wgpu::TextureFormat::Rgba8UnormSrgb],
};
surface.configure(&self.context.device, &config);
Ok(SurfaceHandle {
surface_id: canvas.surface_id,
surface,
})
}
#[cfg(not(target_arch = "wasm32"))]
fn create_surface(&self, window: SurfaceHandle<winit::window::Window>) -> Result<SurfaceHandle<wgpu::Surface>> {
let surface = unsafe { self.context.instance.create_surface(&window.surface) }?;
let surface_id = window.surface_id;
Ok(SurfaceHandle { surface_id, surface })
}
}
impl NewExecutor {
impl WgpuExecutor {
pub async fn new() -> Option<Self> {
let context = Context::new().await?;
Some(Self { context })
let texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: false },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let shader = context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
});
let render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[&texture_bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
// Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE
// or Features::POLYGON_MODE_POINT
polygon_mode: wgpu::PolygonMode::Fill,
// Requires Features::DEPTH_CLIP_CONTROL
unclipped_depth: false,
// Requires Features::CONSERVATIVE_RASTERIZATION
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
// If the pipeline will be used with a multiview render pass, this
// indicates how many array layers the attachments will have.
multiview: None,
});
let vertex_buffer = context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(INDICES),
usage: wgpu::BufferUsages::INDEX,
});
let num_indices = INDICES.len() as u32;
let render_configuration = RenderConfiguration {
vertex_buffer,
index_buffer,
num_indices,
render_pipeline,
texture_bind_group_layout,
sampler,
};
Some(Self { context, render_configuration })
}
}
#[derive(Debug)]
struct RenderConfiguration {
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
num_indices: u32,
render_pipeline: wgpu::RenderPipeline,
texture_bind_group_layout: wgpu::BindGroupLayout,
sampler: wgpu::Sampler,
}

View File

@@ -0,0 +1,43 @@
// Vertex shader
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
}
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coords = model.tex_coords;
out.clip_position = vec4<f32>(model.position, 1.0);
return out;
}
// Fragment shader
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(0)@binding(1)
var s_diffuse: sampler;
fn linearToSRGB(color: vec3<f32>) -> vec3<f32> {
let a = 0.055;
return select(pow(color, vec3<f32>(1.0 / 2.2)) * (1.0 + a) - a,
color / 12.92,
color <= vec3<f32>(0.0031308));
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
var color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
var linearColor = color.rgb;
var srgbColor = linearToSRGB(linearColor);
return vec4<f32>(srgbColor, color.a);
}