mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
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:
committed by
Keavon Chambers
parent
0c93a62d55
commit
45b04f4eb9
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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>,
|
||||
{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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(_))
|
||||
|
||||
89
node-graph/gpu-compiler/Cargo.lock
generated
89
node-graph/gpu-compiler/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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,)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
] }
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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()))),
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
@@ -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",
|
||||
|
||||
@@ -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]),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
43
node-graph/wgpu-executor/src/shader.wgsl
Normal file
43
node-graph/wgpu-executor/src/shader.wgsl
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user