mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-27 08:24:04 +00:00
Restructure GPU compilation execution pipeline (#903)
* Restructure gpu compilation execution pipeline * Add compilation server/client infrastructure * Add wgpu executor
This commit is contained in:
parent
be32f7949f
commit
79ad3e7908
43 changed files with 2744 additions and 482 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
target/
|
target/
|
||||||
*.spv
|
*.spv
|
||||||
*.exrc
|
*.exrc
|
||||||
rust-toolchain
|
|
||||||
|
|
1020
Cargo.lock
generated
1020
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
16
Cargo.toml
16
Cargo.toml
|
@ -11,16 +11,28 @@ members = [
|
||||||
"node-graph/interpreted-executor",
|
"node-graph/interpreted-executor",
|
||||||
"node-graph/borrow_stack",
|
"node-graph/borrow_stack",
|
||||||
"node-graph/node-macro",
|
"node-graph/node-macro",
|
||||||
|
"node-graph/compilation-server",
|
||||||
|
"node-graph/compilation-client",
|
||||||
|
"node-graph/vulkan-executor",
|
||||||
|
"node-graph/wgpu-executor",
|
||||||
|
"node-graph/future-executor",
|
||||||
|
"node-graph/gpu-compiler/gpu-compiler-bin-wrapper",
|
||||||
"libraries/dyn-any",
|
"libraries/dyn-any",
|
||||||
"libraries/bezier-rs",
|
"libraries/bezier-rs",
|
||||||
"website/other/bezier-rs-demos/wasm",
|
"website/other/bezier-rs-demos/wasm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
exclude = [
|
||||||
|
"node-graph/gpu-compiler",
|
||||||
|
]
|
||||||
|
|
||||||
[profile.release.package.graphite-wasm]
|
[profile.release.package.graphite-wasm]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
[profile.dev.package.graphite-wasm]
|
[profile.dev.package.graphite-wasm]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
[profile.dev]
|
#[profile.dev]
|
||||||
opt-level = 3
|
#opt-level = 3
|
||||||
|
|
12
about.toml
12
about.toml
|
@ -9,6 +9,18 @@ accepted = [
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"ISC",
|
"ISC",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
|
"CC0-1.0",
|
||||||
|
"OpenSSL",
|
||||||
]
|
]
|
||||||
ignore-build-dependencies = true
|
ignore-build-dependencies = true
|
||||||
ignore-dev-dependencies = true
|
ignore-dev-dependencies = true
|
||||||
|
|
||||||
|
workarounds = ["ring"]
|
||||||
|
|
||||||
|
# https://raw.githubusercontent.com/briansmith/webpki/main/LICENSE looks like MIT to me
|
||||||
|
[webpki.clarify]
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[[webpki.clarify.files]]
|
||||||
|
path = 'LICENSE'
|
||||||
|
checksum = "5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a"
|
||||||
|
|
14
deny.toml
14
deny.toml
|
@ -80,6 +80,8 @@ allow = [
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"ISC",
|
"ISC",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
|
"CC0-1.0",
|
||||||
|
"OpenSSL",
|
||||||
#"Apache-2.0 WITH LLVM-exception",
|
#"Apache-2.0 WITH LLVM-exception",
|
||||||
]
|
]
|
||||||
# List of explicitly disallowed licenses
|
# List of explicitly disallowed licenses
|
||||||
|
@ -118,22 +120,22 @@ exceptions = [
|
||||||
# Some crates don't have (easily) machine readable licensing information,
|
# Some crates don't have (easily) machine readable licensing information,
|
||||||
# adding a clarification entry for it allows you to manually specify the
|
# adding a clarification entry for it allows you to manually specify the
|
||||||
# licensing information
|
# licensing information
|
||||||
#[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
# The name of the crate the clarification applies to
|
# The name of the crate the clarification applies to
|
||||||
#name = "ring"
|
name = "ring"
|
||||||
# The optional version constraint for the crate
|
# The optional version constraint for the crate
|
||||||
#version = "*"
|
#version = "*"
|
||||||
# The SPDX expression for the license requirements of the crate
|
# The SPDX expression for the license requirements of the crate
|
||||||
#expression = "MIT AND ISC AND OpenSSL"
|
expression = "MIT AND ISC AND OpenSSL"
|
||||||
# One or more files in the crate's source used as the "source of truth" for
|
# One or more files in the crate's source used as the "source of truth" for
|
||||||
# the license expression. If the contents match, the clarification will be used
|
# the license expression. If the contents match, the clarification will be used
|
||||||
# when running the license check, otherwise the clarification will be ignored
|
# when running the license check, otherwise the clarification will be ignored
|
||||||
# and the crate will be checked normally, which may produce warnings or errors
|
# and the crate will be checked normally, which may produce warnings or errors
|
||||||
# depending on the rest of your configuration
|
# depending on the rest of your configuration
|
||||||
#license-files = [
|
license-files = [
|
||||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||||
#]
|
]
|
||||||
|
|
||||||
[licenses.private]
|
[licenses.private]
|
||||||
# If true, ignores workspace crates that aren't published, or are only
|
# If true, ignores workspace crates that aren't published, or are only
|
||||||
|
|
|
@ -11,7 +11,7 @@ repository = "https://github.com/GraphiteEditor/Graphite"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
gpu = ["graph-craft/gpu", "interpreted-executor/gpu"]
|
gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "graphene-core/gpu"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -95,6 +95,7 @@ static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
|
||||||
outputs: &[FrontendGraphDataType::Raster],
|
outputs: &[FrontendGraphDataType::Raster],
|
||||||
properties: node_properties::no_properties,
|
properties: node_properties::no_properties,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "GpuImage",
|
name: "GpuImage",
|
||||||
category: "Image Adjustments",
|
category: "Image Adjustments",
|
||||||
|
|
|
@ -21,6 +21,7 @@ tauri = { version = "1.2", features = ["api-all", "devtools", "linux-protocol-he
|
||||||
axum = "0.6.1"
|
axum = "0.6.1"
|
||||||
graphite-editor = { version = "0.0.0", path = "../../editor" }
|
graphite-editor = { version = "0.0.0", path = "../../editor" }
|
||||||
chrono = "^0.4.23"
|
chrono = "^0.4.23"
|
||||||
|
tokio = {version ="1", features = ["full"] }
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -32,7 +32,8 @@ async fn respond_to(id: Path<String>) -> impl IntoResponse {
|
||||||
builder.body(StreamBody::new(stream)).unwrap()
|
builder.body(StreamBody::new(stream)).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
println!("Starting server...");
|
println!("Starting server...");
|
||||||
|
|
||||||
let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red);
|
let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red);
|
||||||
|
|
|
@ -12,6 +12,7 @@ license = "Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tauri = ["ron"]
|
tauri = ["ron"]
|
||||||
|
gpu = ["editor/gpu"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
|
@ -161,14 +161,14 @@ impl JsEditorHandle {
|
||||||
#[wasm_bindgen(js_name = tauriResponse)]
|
#[wasm_bindgen(js_name = tauriResponse)]
|
||||||
pub fn tauri_response(&self, _message: JsValue) {
|
pub fn tauri_response(&self, _message: JsValue) {
|
||||||
#[cfg(feature = "tauri")]
|
#[cfg(feature = "tauri")]
|
||||||
match ron::from_str::<Vec<FrontendMessage>>(&message.as_string().unwrap()) {
|
match ron::from_str::<Vec<FrontendMessage>>(&_message.as_string().unwrap()) {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
for message in response {
|
for message in response {
|
||||||
self.send_frontend_message_to_js(message);
|
self.send_frontend_message_to_js(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!("tauri response: {:?}\n{:?}", error, message);
|
log::error!("tauri response: {:?}\n{:?}", error, _message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
node-graph/compilation-client/Cargo.toml
Normal file
17
node-graph/compilation-client/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "compilation-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
#tokio = { version = "1.0", features = ["full"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
graph-craft = { version = "0.1.0", path = "../graph-craft", features = ["serde"] }
|
||||||
|
gpu-compiler-bin-wrapper = { version = "0.1.0", path = "../gpu-compiler/gpu-compiler-bin-wrapper" }
|
||||||
|
tempfile = "3.3.0"
|
||||||
|
anyhow = "1.0.68"
|
||||||
|
reqwest = { version = "0.11", features = ["blocking", "serde_json", "json", "rustls", "rustls-tls"] }
|
||||||
|
future-executor = {path = "../future-executor"}
|
15
node-graph/compilation-client/src/lib.rs
Normal file
15
node-graph/compilation-client/src/lib.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use gpu_compiler_bin_wrapper::CompileRequest;
|
||||||
|
use graph_craft::document::*;
|
||||||
|
|
||||||
|
pub async fn compile<I, O>(network: NodeNetwork) -> Result<Vec<u8>, reqwest::Error> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let compile_request = CompileRequest::new(network, std::any::type_name::<I>().to_owned(), std::any::type_name::<O>().to_owned());
|
||||||
|
let response = client.post("http://localhost:3000/compile/spriv").json(&compile_request).send();
|
||||||
|
let response = response.await?;
|
||||||
|
response.bytes().await.map(|b| b.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_sync<I: 'static, O: 'static>(network: NodeNetwork) -> Result<Vec<u8>, reqwest::Error> {
|
||||||
|
future_executor::block_on(compile::<I, O>(network))
|
||||||
|
}
|
68
node-graph/compilation-client/src/main.rs
Normal file
68
node-graph/compilation-client/src/main.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use gpu_compiler_bin_wrapper::CompileRequest;
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graph_craft::document::*;
|
||||||
|
use graph_craft::proto::*;
|
||||||
|
use graph_craft::{concrete, generic};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
|
let network = NodeNetwork {
|
||||||
|
inputs: vec![0],
|
||||||
|
output: 0,
|
||||||
|
disabled: vec![],
|
||||||
|
previous_output: None,
|
||||||
|
nodes: [(
|
||||||
|
0,
|
||||||
|
DocumentNode {
|
||||||
|
name: "Inc Node".into(),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::Network,
|
||||||
|
NodeInput::Value {
|
||||||
|
tagged_value: TaggedValue::U32(1),
|
||||||
|
exposed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let compile_request = CompileRequest::new(network, "u32".to_owned(), "u32".to_owned());
|
||||||
|
let response = client.post("http://localhost:3000/compile/spriv").json(&compile_request).send().unwrap();
|
||||||
|
println!("response: {:?}", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_network() -> NodeNetwork {
|
||||||
|
NodeNetwork {
|
||||||
|
inputs: vec![0, 0],
|
||||||
|
output: 1,
|
||||||
|
disabled: vec![],
|
||||||
|
previous_output: None,
|
||||||
|
nodes: [
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DocumentNode {
|
||||||
|
name: "Cons".into(),
|
||||||
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), concrete!("u32")])),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
DocumentNode {
|
||||||
|
name: "Add".into(),
|
||||||
|
inputs: vec![NodeInput::Node(0)],
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
18
node-graph/compilation-server/Cargo.toml
Normal file
18
node-graph/compilation-server/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "compilation-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
axum = "0.6.1"
|
||||||
|
serde_json = "1.0"
|
||||||
|
graph-craft = { version = "0.1.0", path = "../graph-craft", features = ["serde"] }
|
||||||
|
gpu-compiler-bin-wrapper = { version = "0.1.0", path = "../gpu-compiler/gpu-compiler-bin-wrapper" }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tempfile = "3.3.0"
|
||||||
|
anyhow = "1.0.68"
|
||||||
|
futures = "0.3"
|
39
node-graph/compilation-server/src/main.rs
Normal file
39
node-graph/compilation-server/src/main.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use gpu_compiler_bin_wrapper::CompileRequest;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Json, State},
|
||||||
|
http::StatusCode,
|
||||||
|
routing::{get, post},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
compile_dir: tempfile::TempDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let shared_state = Arc::new(AppState {
|
||||||
|
compile_dir: tempfile::tempdir().expect("failed to create tempdir"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// build our application with a single route
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(|| async { "Hello from compilation server!" }))
|
||||||
|
.route("/compile", get(|| async { "Supported targets: spirv" }))
|
||||||
|
.route("/compile/spriv", post(post_compile_spriv))
|
||||||
|
.with_state(shared_state);
|
||||||
|
|
||||||
|
// run it with hyper on localhost:3000
|
||||||
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_compile_spriv(State(state): State<Arc<AppState>>, Json(compile_request): Json<CompileRequest>) -> Result<Vec<u8>, StatusCode> {
|
||||||
|
let path = std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/../gpu-compiler/Cargo.toml";
|
||||||
|
compile_request.compile(state.compile_dir.path().to_str().expect("non utf8 tempdir path"), &path).map_err(|e| {
|
||||||
|
eprintln!("compilation failed: {}", e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})
|
||||||
|
}
|
15
node-graph/future-executor/Cargo.toml
Normal file
15
node-graph/future-executor/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "future-executor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.3.25"
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
|
[target.wasm32-unknown-unknown.dependencies]
|
||||||
|
wasm-rs-async-executor = {version = "0.9.0", features = ["cooperative-browser", "debug", "requestIdleCallback"] }
|
26
node-graph/future-executor/src/lib.rs
Normal file
26
node-graph/future-executor/src/lib.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
pub fn block_on<F: Future + 'static>(future: F) -> F::Output {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
use wasm_rs_async_executor::single_threaded as executor;
|
||||||
|
|
||||||
|
let val = std::sync::Arc::new(std::sync::Mutex::new(None));
|
||||||
|
let move_val = val.clone();
|
||||||
|
let result = executor::spawn(async move {
|
||||||
|
let result = executor::yield_async(future).await;
|
||||||
|
*move_val.lock().unwrap() = Some(result);
|
||||||
|
log::info!("Finished");
|
||||||
|
});
|
||||||
|
executor::run(Some(result.task()));
|
||||||
|
loop {
|
||||||
|
if let Some(result) = val.lock().unwrap().take() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
log::info!("Waiting");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
futures::executor::block_on(future)
|
||||||
|
}
|
1349
node-graph/gpu-compiler/Cargo.lock
generated
Normal file
1349
node-graph/gpu-compiler/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
31
node-graph/gpu-compiler/Cargo.toml
Normal file
31
node-graph/gpu-compiler/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "gpu-compiler"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
profiling = ["nvtx"]
|
||||||
|
serde = ["graphene-core/serde", "glam/serde"]
|
||||||
|
|
||||||
|
# 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"] }
|
||||||
|
graph-craft = {path = "../graph-craft", features = ["serde"] }
|
||||||
|
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||||
|
num-traits = "0.2"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
|
log = "0.4"
|
||||||
|
serde = { version = "1", features = ["derive", "rc"]}
|
||||||
|
glam = { version = "0.22" }
|
||||||
|
base64 = "0.13"
|
||||||
|
|
||||||
|
bytemuck = { version = "1.8" }
|
||||||
|
nvtx = { version = "1.1.1", optional = true }
|
||||||
|
tempfile = "3"
|
||||||
|
spirv-builder = { version = "0.4", default-features = false, features=["use-installed-tools"] }
|
||||||
|
tera = { version = "1.17.1" }
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
serde_json = "1.0.91"
|
18
node-graph/gpu-compiler/gpu-compiler-bin-wrapper/Cargo.toml
Normal file
18
node-graph/gpu-compiler/gpu-compiler-bin-wrapper/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "gpu-compiler-bin-wrapper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
profiling = []
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
graph-craft = {path = "../../graph-craft" , features = ["serde"]}
|
||||||
|
log = "0.4"
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
serde_json = "1.0.91"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
53
node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs
Normal file
53
node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
pub fn compile_spirv(network: &graph_craft::document::NodeNetwork, input_type: &str, output_type: &str, compile_dir: Option<&str>, manifest_path: &str) -> anyhow::Result<Vec<u8>> {
|
||||||
|
let serialized_graph = serde_json::to_string(&network)?;
|
||||||
|
let features = "";
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
let features = "profiling";
|
||||||
|
|
||||||
|
println!("calling cargo run!");
|
||||||
|
let non_cargo_env_vars = std::env::vars().filter(|(k, _)| k.starts_with("PATH")).collect::<Vec<_>>();
|
||||||
|
let mut cargo_command = std::process::Command::new("/usr/bin/cargo")
|
||||||
|
.arg("run")
|
||||||
|
.arg("--release")
|
||||||
|
.arg("--manifest-path")
|
||||||
|
.arg(manifest_path)
|
||||||
|
.current_dir(manifest_path.replace("Cargo.toml", ""))
|
||||||
|
.env_clear()
|
||||||
|
.envs(non_cargo_env_vars)
|
||||||
|
.arg("--features")
|
||||||
|
.arg(features)
|
||||||
|
.arg("--")
|
||||||
|
.arg(input_type)
|
||||||
|
.arg(output_type)
|
||||||
|
// TODO: handle None case properly
|
||||||
|
.arg(compile_dir.unwrap())
|
||||||
|
.stdin(std::process::Stdio::piped())
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
cargo_command.stdin.as_mut().unwrap().write_all(serialized_graph.as_bytes())?;
|
||||||
|
let output = cargo_command.wait_with_output()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow::anyhow!("cargo failed: {}", String::from_utf8_lossy(&output.stderr)));
|
||||||
|
}
|
||||||
|
Ok(output.stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub struct CompileRequest {
|
||||||
|
network: graph_craft::document::NodeNetwork,
|
||||||
|
input_type: String,
|
||||||
|
output_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompileRequest {
|
||||||
|
pub fn new(network: graph_craft::document::NodeNetwork, input_type: String, output_type: String) -> Self {
|
||||||
|
Self { network, input_type, output_type }
|
||||||
|
}
|
||||||
|
pub fn compile(&self, compile_dir: &str, manifest_path: &str) -> anyhow::Result<Vec<u8>> {
|
||||||
|
compile_spirv(&self.network, &self.input_type, &self.output_type, Some(compile_dir), manifest_path)
|
||||||
|
}
|
||||||
|
}
|
3
node-graph/gpu-compiler/rust-toolchain
Normal file
3
node-graph/gpu-compiler/rust-toolchain
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2022-10-29"
|
||||||
|
components = ["rust-src", "rustc-dev", "llvm-tools-preview", "clippy", "cargofmt", "rustc"]
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::proto::*;
|
use graph_craft::proto::*;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
|
|
||||||
fn create_cargo_toml(metadata: &Metadata) -> Result<String, tera::Error> {
|
fn create_cargo_toml(metadata: &Metadata) -> Result<String, tera::Error> {
|
||||||
|
@ -52,12 +52,6 @@ pub fn create_files(matadata: &Metadata, network: &ProtoNetwork, compile_dir: &P
|
||||||
|
|
||||||
pub fn serialize_gpu(network: &ProtoNetwork, input_type: &str, output_type: &str) -> anyhow::Result<String> {
|
pub fn serialize_gpu(network: &ProtoNetwork, input_type: &str, output_type: &str) -> anyhow::Result<String> {
|
||||||
assert_eq!(network.inputs.len(), 1);
|
assert_eq!(network.inputs.len(), 1);
|
||||||
/*let input = &network.nodes[network.inputs[0] as usize].1;
|
|
||||||
let output = &network.nodes[network.output as usize].1;
|
|
||||||
let input_type = format!("{}::Input", input.identifier.fully_qualified_name());
|
|
||||||
let output_type = format!("{}::Output", output.identifier.fully_qualified_name());
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn nid(id: &u64) -> String {
|
fn nid(id: &u64) -> String {
|
||||||
format!("n{id}")
|
format!("n{id}")
|
||||||
}
|
}
|
||||||
|
@ -94,6 +88,7 @@ pub fn serialize_gpu(network: &ProtoNetwork, input_type: &str, output_type: &str
|
||||||
|
|
||||||
use spirv_builder::{MetadataPrintout, SpirvBuilder, SpirvMetadata};
|
use spirv_builder::{MetadataPrintout, SpirvBuilder, SpirvMetadata};
|
||||||
pub fn compile(dir: &Path) -> Result<spirv_builder::CompileResult, spirv_builder::SpirvBuilderError> {
|
pub fn compile(dir: &Path) -> Result<spirv_builder::CompileResult, spirv_builder::SpirvBuilderError> {
|
||||||
|
dbg!(&dir);
|
||||||
let result = SpirvBuilder::new(dir, "spirv-unknown-spv1.5")
|
let result = SpirvBuilder::new(dir, "spirv-unknown-spv1.5")
|
||||||
.print_metadata(MetadataPrintout::DependencyOnly)
|
.print_metadata(MetadataPrintout::DependencyOnly)
|
||||||
.multimodule(false)
|
.multimodule(false)
|
26
node-graph/gpu-compiler/src/main.rs
Normal file
26
node-graph/gpu-compiler/src/main.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use graph_craft::document::NodeNetwork;
|
||||||
|
use gpu_compiler as compiler;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
println!("Starting Gpu Compiler!");
|
||||||
|
let mut stdin = std::io::stdin();
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
let input_type = std::env::args().nth(1).expect("input type arg missing");
|
||||||
|
let output_type = std::env::args().nth(2).expect("output type arg missing");
|
||||||
|
let compile_dir = std::env::args().nth(3).map(|x| std::path::PathBuf::from(&x)).unwrap_or(tempfile::tempdir()?.into_path());
|
||||||
|
let network: NodeNetwork = serde_json::from_reader(&mut stdin)?;
|
||||||
|
let compiler = graph_craft::executor::Compiler{};
|
||||||
|
let proto_network = compiler.compile(network, true);
|
||||||
|
dbg!(&compile_dir);
|
||||||
|
|
||||||
|
let metadata = compiler::Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
||||||
|
|
||||||
|
compiler::create_files(&metadata, &proto_network, &compile_dir, &input_type, &output_type)?;
|
||||||
|
let result = compiler::compile(&compile_dir)?;
|
||||||
|
|
||||||
|
let bytes = std::fs::read(result.module.unwrap_single())?;
|
||||||
|
stdout.write_all(&bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
3
node-graph/gpu-compiler/src/templates/rust-toolchain
Normal file
3
node-graph/gpu-compiler/src/templates/rust-toolchain
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2022-10-29"
|
||||||
|
components = ["rust-src", "rustc-dev", "llvm-tools-preview", "clippy", "cargofmt", "rustc"]
|
|
@ -6,8 +6,6 @@ license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
profiling = ["nvtx", "gpu"]
|
|
||||||
gpu = ["serde", "vulkano", "spirv-builder", "tera", "graphene-core/gpu"]
|
|
||||||
serde = ["dep:serde", "graphene-core/serde", "glam/serde"]
|
serde = ["dep:serde", "graphene-core/serde", "glam/serde"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -16,7 +14,6 @@ serde = ["dep:serde", "graphene-core/serde", "glam/serde"]
|
||||||
graphene-core = { path = "../gcore", features = ["async", "std", "alloc"] }
|
graphene-core = { path = "../gcore", features = ["async", "std", "alloc"] }
|
||||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
borrow_stack = { path = "../borrow_stack" }
|
|
||||||
dyn-clone = "1.0"
|
dyn-clone = "1.0"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -24,10 +21,5 @@ serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||||
glam = { version = "0.22" }
|
glam = { version = "0.22" }
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
|
||||||
vulkano = {git = "https://github.com/GraphiteEditor/vulkano", branch = "fix_rust_gpu", optional = true}
|
|
||||||
bytemuck = {version = "1.8" }
|
bytemuck = {version = "1.8" }
|
||||||
nvtx = {version = "1.1.1", optional = true}
|
|
||||||
tempfile = "3"
|
|
||||||
spirv-builder = {git = "https://github.com/EmbarkStudios/rust-gpu" , branch = "main", optional = true, default-features = false, features=["use-installed-tools"]}
|
|
||||||
tera = {version = "1.17.1", optional = true}
|
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.66"
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod compiler;
|
|
||||||
pub mod context;
|
|
||||||
pub mod executor;
|
|
|
@ -6,6 +6,3 @@ pub mod proto;
|
||||||
|
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub mod imaginate_input;
|
pub mod imaginate_input;
|
||||||
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
pub mod gpu;
|
|
||||||
|
|
|
@ -11,7 +11,9 @@ license = "MIT OR Apache-2.0"
|
||||||
[features]
|
[features]
|
||||||
memoization = ["once_cell"]
|
memoization = ["once_cell"]
|
||||||
default = ["memoization"]
|
default = ["memoization"]
|
||||||
gpu = ["graph-craft/gpu", "graphene-core/gpu"]
|
gpu = ["graphene-core/gpu", "gpu-compiler-bin-wrapper", "compilation-client"]
|
||||||
|
vulkan = ["gpu", "vulkan-executor"]
|
||||||
|
wgpu = ["gpu", "wgpu-executor"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -19,6 +21,10 @@ graphene-core = {path = "../gcore", features = ["async", "std" ], default-featur
|
||||||
borrow_stack = {path = "../borrow_stack"}
|
borrow_stack = {path = "../borrow_stack"}
|
||||||
dyn-any = {path = "../../libraries/dyn-any", features = ["derive"]}
|
dyn-any = {path = "../../libraries/dyn-any", features = ["derive"]}
|
||||||
graph-craft = {path = "../graph-craft"}
|
graph-craft = {path = "../graph-craft"}
|
||||||
|
vulkan-executor = {path = "../vulkan-executor", optional = true}
|
||||||
|
wgpu-executor = {path = "../wgpu-executor", optional = true}
|
||||||
|
gpu-compiler-bin-wrapper = {path = "../gpu-compiler/gpu-compiler-bin-wrapper", optional = true}
|
||||||
|
compilation-client = {path = "../compilation-client", optional = true}
|
||||||
bytemuck = {version = "1.8" }
|
bytemuck = {version = "1.8" }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
once_cell = {version= "1.10", optional = true}
|
once_cell = {version= "1.10", optional = true}
|
||||||
|
|
|
@ -14,47 +14,26 @@ impl<'n, I: IntoIterator<Item = S>, NN: Node<(), Output = &'n NodeNetwork> + Cop
|
||||||
fn eval(self, input: I) -> Self::Output {
|
fn eval(self, input: I) -> Self::Output {
|
||||||
let network = self.0.eval(());
|
let network = self.0.eval(());
|
||||||
|
|
||||||
use graph_craft::executor::Compiler;
|
map_gpu_impl(network, input)
|
||||||
use graph_craft::executor::Executor;
|
|
||||||
use graph_craft::gpu::compiler::Metadata;
|
|
||||||
let compiler = Compiler {};
|
|
||||||
let proto_network = compiler.compile(network.clone(), true);
|
|
||||||
|
|
||||||
let m = Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
|
||||||
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
|
|
||||||
|
|
||||||
use graph_craft::gpu::context::Context;
|
|
||||||
use graph_craft::gpu::executor::GpuExecutor;
|
|
||||||
let executor: GpuExecutor<S, O> = GpuExecutor::new(Context::new(), proto_network, m, temp_dir.path()).unwrap();
|
|
||||||
|
|
||||||
let data: Vec<_> = input.into_iter().collect();
|
|
||||||
let result = executor.execute(Box::new(data)).unwrap();
|
|
||||||
let result = dyn_any::downcast::<Vec<O>>(result).unwrap();
|
|
||||||
*result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_gpu_impl<I: IntoIterator<Item = S>, S: StaticTypeSized + Sync + Send + Pod, O: StaticTypeSized + Sync + Send + Pod>(network: &NodeNetwork, input: I) -> Vec<O> {
|
||||||
|
use graph_craft::executor::Executor;
|
||||||
|
let bytes = compilation_client::compile_sync::<S, O>(network.clone()).unwrap();
|
||||||
|
let words = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const u32, bytes.len() / 4) };
|
||||||
|
use wgpu_executor::{Context, GpuExecutor};
|
||||||
|
let executor: GpuExecutor<S, O> = GpuExecutor::new(Context::new_sync().unwrap(), words.into(), "gpu::eval".into()).unwrap();
|
||||||
|
let data: Vec<_> = input.into_iter().collect();
|
||||||
|
let result = executor.execute(Box::new(data)).unwrap();
|
||||||
|
let result = dyn_any::downcast::<Vec<O>>(result).unwrap();
|
||||||
|
*result
|
||||||
|
}
|
||||||
impl<'n, I: IntoIterator<Item = S>, NN: Node<(), Output = &'n NodeNetwork> + Copy, S: StaticTypeSized + Sync + Send + Pod, O: StaticTypeSized + Sync + Send + Pod> Node<I> for MapGpuNode<NN, I, S, O> {
|
impl<'n, I: IntoIterator<Item = S>, NN: Node<(), Output = &'n NodeNetwork> + Copy, S: StaticTypeSized + Sync + Send + Pod, O: StaticTypeSized + Sync + Send + Pod> Node<I> for MapGpuNode<NN, I, S, O> {
|
||||||
type Output = Vec<O>;
|
type Output = Vec<O>;
|
||||||
fn eval(self, input: I) -> Self::Output {
|
fn eval(self, input: I) -> Self::Output {
|
||||||
let network = self.0.eval(());
|
let network = self.0.eval(());
|
||||||
|
map_gpu_impl(network, input)
|
||||||
use graph_craft::executor::Compiler;
|
|
||||||
use graph_craft::executor::Executor;
|
|
||||||
use graph_craft::gpu::compiler::Metadata;
|
|
||||||
let compiler = Compiler {};
|
|
||||||
let proto_network = compiler.compile(network.clone(), true);
|
|
||||||
|
|
||||||
let m = Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
|
||||||
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
|
|
||||||
|
|
||||||
use graph_craft::gpu::context::Context;
|
|
||||||
use graph_craft::gpu::executor::GpuExecutor;
|
|
||||||
let executor: GpuExecutor<S, O> = GpuExecutor::new(Context::new(), proto_network, m, temp_dir.path()).unwrap();
|
|
||||||
|
|
||||||
let data: Vec<_> = input.into_iter().collect();
|
|
||||||
let result = executor.execute(Box::new(data)).unwrap();
|
|
||||||
let result = dyn_any::downcast::<Vec<O>>(result).unwrap();
|
|
||||||
*result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +58,8 @@ impl<NN: Node<(), Output = String> + Copy> Node<Image> for MapGpuSingleImageNode
|
||||||
|
|
||||||
let network = NodeNetwork {
|
let network = NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![0],
|
||||||
|
disabled: vec![],
|
||||||
|
previous_output: None,
|
||||||
output: 0,
|
output: 0,
|
||||||
nodes: [(
|
nodes: [(
|
||||||
0,
|
0,
|
||||||
|
@ -114,6 +95,8 @@ impl<NN: Node<(), Output = String> + Copy> Node<Image> for &MapGpuSingleImageNod
|
||||||
let network = NodeNetwork {
|
let network = NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![0],
|
||||||
output: 0,
|
output: 0,
|
||||||
|
disabled: vec![],
|
||||||
|
previous_output: None,
|
||||||
nodes: [(
|
nodes: [(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
|
|
|
@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
serde = ["dep:serde", "graphene-std/serde", "glam/serde"]
|
serde = ["dep:serde", "graphene-std/serde", "glam/serde"]
|
||||||
gpu = ["graphene-std/gpu"]
|
gpu = ["graphene-std/gpu", "graphene-core/gpu", "graphene-std/wgpu"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -493,7 +493,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn push_node<'a>(proto_node: ProtoNode, stack: &'a FixedSizeStack<TypeErasedNode<'static>>) {
|
pub fn push_node(proto_node: ProtoNode, stack: &FixedSizeStack<TypeErasedNode<'static>>) {
|
||||||
if let Some((_id, f)) = NODE_REGISTRY.iter().find(|(id, _)| *id == proto_node.identifier) {
|
if let Some((_id, f)) = NODE_REGISTRY.iter().find(|(id, _)| *id == proto_node.identifier) {
|
||||||
f(proto_node, stack);
|
f(proto_node, stack);
|
||||||
} else {
|
} else {
|
||||||
|
|
25
node-graph/vulkan-executor/Cargo.toml
Normal file
25
node-graph/vulkan-executor/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "vulkan-executor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
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" }
|
||||||
|
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||||
|
num-traits = "0.2"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
|
log = "0.4"
|
||||||
|
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||||
|
glam = { version = "0.22" }
|
||||||
|
base64 = "0.13"
|
||||||
|
|
||||||
|
vulkano = { git = "https://github.com/GraphiteEditor/vulkano", branch = "fix_rust_gpu"}
|
||||||
|
bytemuck = {version = "1.8" }
|
||||||
|
anyhow = "1.0.66"
|
|
@ -1,9 +1,9 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::{compiler::Metadata, context::Context};
|
use super::context::Context;
|
||||||
use crate::{executor::Any, gpu::compiler};
|
|
||||||
use bytemuck::Pod;
|
use bytemuck::Pod;
|
||||||
use dyn_any::StaticTypeSized;
|
use dyn_any::StaticTypeSized;
|
||||||
|
use graph_craft::executor::{Any, Executor};
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
buffer::{self, BufferUsage, CpuAccessibleBuffer},
|
buffer::{self, BufferUsage, CpuAccessibleBuffer},
|
||||||
command_buffer::{allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage},
|
command_buffer::{allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage},
|
||||||
|
@ -14,7 +14,7 @@ use vulkano::{
|
||||||
sync::GpuFuture,
|
sync::GpuFuture,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::proto::*;
|
use graph_craft::proto::*;
|
||||||
use graphene_core::gpu::PushConstants;
|
use graphene_core::gpu::PushConstants;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -26,13 +26,8 @@ pub struct GpuExecutor<I: StaticTypeSized, O> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: StaticTypeSized, O> GpuExecutor<I, O> {
|
impl<I: StaticTypeSized, O> GpuExecutor<I, O> {
|
||||||
pub fn new(context: Context, network: ProtoNetwork, metadata: Metadata, compile_dir: &Path) -> anyhow::Result<Self> {
|
pub fn new(context: Context, shader: &[u8], entry_point: String) -> anyhow::Result<Self> {
|
||||||
compiler::create_files(&metadata, &network, compile_dir, std::any::type_name::<I>(), std::any::type_name::<O>())?;
|
let shader = unsafe { vulkano::shader::ShaderModule::from_bytes(context.device.clone(), shader)? };
|
||||||
let result = compiler::compile(compile_dir)?;
|
|
||||||
|
|
||||||
let bytes = std::fs::read(result.module.unwrap_single())?;
|
|
||||||
let shader = unsafe { vulkano::shader::ShaderModule::from_bytes(context.device.clone(), &bytes)? };
|
|
||||||
let entry_point = result.entry_points.first().expect("No entry points").clone();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
context,
|
context,
|
||||||
|
@ -43,7 +38,7 @@ impl<I: StaticTypeSized, O> GpuExecutor<I, O> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Sync + Pod> crate::executor::Executor for GpuExecutor<I, O> {
|
impl<I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Sync + Pod> Executor for GpuExecutor<I, O> {
|
||||||
fn execute(&self, input: Any<'static>) -> Result<Any<'static>, Box<dyn std::error::Error>> {
|
fn execute(&self, input: Any<'static>) -> Result<Any<'static>, Box<dyn std::error::Error>> {
|
||||||
let input = dyn_any::downcast::<Vec<I>>(input).expect("Wrong input type");
|
let input = dyn_any::downcast::<Vec<I>>(input).expect("Wrong input type");
|
||||||
let context = &self.context;
|
let context = &self.context;
|
||||||
|
@ -119,9 +114,8 @@ fn create_buffer<T: Pod + Send + Sync>(data: Vec<T>, alloc: &StandardMemoryAlloc
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::concrete;
|
use graph_craft::concrete;
|
||||||
use crate::generic;
|
use graph_craft::generic;
|
||||||
use crate::gpu::compiler;
|
|
||||||
|
|
||||||
fn inc_network() -> ProtoNetwork {
|
fn inc_network() -> ProtoNetwork {
|
||||||
let mut construction_network = ProtoNetwork {
|
let mut construction_network = ProtoNetwork {
|
||||||
|
@ -169,7 +163,8 @@ mod test {
|
||||||
construction_network
|
construction_network
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// TODO: Fix this test
|
||||||
|
/*#[test]
|
||||||
fn add_on_gpu() {
|
fn add_on_gpu() {
|
||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
let m = compiler::Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
let m = compiler::Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
||||||
|
@ -184,5 +179,5 @@ mod test {
|
||||||
for (i, r) in result.iter().enumerate() {
|
for (i, r) in result.iter().enumerate() {
|
||||||
assert_eq!(*r, i as u32 + 3);
|
assert_eq!(*r, i as u32 + 3);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
5
node-graph/vulkan-executor/src/lib.rs
Normal file
5
node-graph/vulkan-executor/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod context;
|
||||||
|
mod executor;
|
||||||
|
|
||||||
|
pub use context::Context;
|
||||||
|
pub use executor::GpuExecutor;
|
29
node-graph/wgpu-executor/Cargo.toml
Normal file
29
node-graph/wgpu-executor/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "wgpu-executor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
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" }
|
||||||
|
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||||
|
future-executor = { path = "../future-executor" }
|
||||||
|
num-traits = "0.2"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
|
log = "0.4"
|
||||||
|
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||||
|
glam = { version = "0.22" }
|
||||||
|
base64 = "0.13"
|
||||||
|
|
||||||
|
bytemuck = {version = "1.8" }
|
||||||
|
anyhow = "1.0.66"
|
||||||
|
wgpu = { version = "0.14.2", features = ["spirv"] }
|
||||||
|
spirv = "0.2.0"
|
||||||
|
futures-intrusive = "0.5.0"
|
||||||
|
futures = "0.3.25"
|
48
node-graph/wgpu-executor/src/context.rs
Normal file
48
node-graph/wgpu-executor/src/context.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use wgpu::{Device, Instance, Queue};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Context {
|
||||||
|
pub device: Arc<Device>,
|
||||||
|
pub queue: Arc<Queue>,
|
||||||
|
pub instance: Arc<Instance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub async fn new() -> Option<Self> {
|
||||||
|
// Instantiates instance of WebGPU
|
||||||
|
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||||
|
|
||||||
|
// `request_adapter` instantiates the general connection to the GPU
|
||||||
|
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await?;
|
||||||
|
|
||||||
|
// `request_device` instantiates the feature specific connection to the GPU, defining some parameters,
|
||||||
|
// `features` being the available features.
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: wgpu::Limits::downlevel_defaults(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let info = adapter.get_info();
|
||||||
|
// skip this on LavaPipe temporarily
|
||||||
|
if info.vendor == 0x10005 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self {
|
||||||
|
device: Arc::new(device),
|
||||||
|
queue: Arc::new(queue),
|
||||||
|
instance: Arc::new(instance),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_sync() -> Option<Self> {
|
||||||
|
future_executor::block_on(Self::new())
|
||||||
|
}
|
||||||
|
}
|
243
node-graph/wgpu-executor/src/executor.rs
Normal file
243
node-graph/wgpu-executor/src/executor.rs
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
use super::context::Context;
|
||||||
|
use bytemuck::Pod;
|
||||||
|
use dyn_any::StaticTypeSized;
|
||||||
|
use graph_craft::executor::{Any, Executor};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GpuExecutor<'a, I: StaticTypeSized, O> {
|
||||||
|
context: Context,
|
||||||
|
entry_point: String,
|
||||||
|
shader: Cow<'a, [u32]>,
|
||||||
|
_phantom: std::marker::PhantomData<(I, O)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: StaticTypeSized, O> GpuExecutor<'a, I, O> {
|
||||||
|
pub fn new(context: Context, shader: Cow<'a, [u32]>, entry_point: String) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
entry_point,
|
||||||
|
shader,
|
||||||
|
_phantom: std::marker::PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Sync + Pod> Executor for GpuExecutor<'a, I, O> {
|
||||||
|
fn execute(&self, input: Any<'static>) -> Result<Any<'static>, Box<dyn std::error::Error>> {
|
||||||
|
let input = dyn_any::downcast::<Vec<I>>(input).expect("Wrong input type");
|
||||||
|
let context = &self.context;
|
||||||
|
let future = execute_shader(context.device.clone(), context.queue.clone(), self.shader.to_vec().into(), *input, self.entry_point.clone());
|
||||||
|
let result = future_executor::block_on(future);
|
||||||
|
|
||||||
|
let result: Vec<O> = result.ok_or_else(|| String::from("Failed to execute shader"))?;
|
||||||
|
Ok(Box::new(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_shader<I: Pod + Send + Sync, O: Pod + Send + Sync>(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, shader: Vec<u32>, data: Vec<I>, entry_point: String) -> Option<Vec<O>> {
|
||||||
|
// Loads the shader from WGSL
|
||||||
|
let cs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::SpirV(shader.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gets the size in bytes of the buffer.
|
||||||
|
let slice_size = data.len() * std::mem::size_of::<O>();
|
||||||
|
let size = slice_size as wgpu::BufferAddress;
|
||||||
|
|
||||||
|
// Instantiates buffer without data.
|
||||||
|
// `usage` of buffer specifies how it can be used:
|
||||||
|
// `BufferUsages::MAP_READ` allows it to be read (outside the shader).
|
||||||
|
// `BufferUsages::COPY_DST` allows it to be the destination of the copy.
|
||||||
|
let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: None,
|
||||||
|
size,
|
||||||
|
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instantiates buffer with data (`numbers`).
|
||||||
|
// Usage allowing the buffer to be:
|
||||||
|
// A storage buffer (can be bound within a bind group and thus available to a shader).
|
||||||
|
// The destination of a copy.
|
||||||
|
// The source of a copy.
|
||||||
|
let storage_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Storage Buffer"),
|
||||||
|
contents: bytemuck::cast_slice(&data),
|
||||||
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instantiates empty buffer for the result.
|
||||||
|
// Usage allowing the buffer to be:
|
||||||
|
// A storage buffer (can be bound within a bind group and thus available to a shader).
|
||||||
|
// The destination of a copy.
|
||||||
|
// The source of a copy.
|
||||||
|
let dest_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Destination Buffer"),
|
||||||
|
size,
|
||||||
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// A bind group defines how buffers are accessed by shaders.
|
||||||
|
// It is to WebGPU what a descriptor set is to Vulkan.
|
||||||
|
// `binding` here refers to the `binding` of a buffer in the shader (`layout(set = 0, binding = 0) buffer`).
|
||||||
|
|
||||||
|
// A pipeline specifies the operation of a shader
|
||||||
|
|
||||||
|
// Instantiates the pipeline.
|
||||||
|
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: None,
|
||||||
|
module: &cs_module,
|
||||||
|
entry_point: entry_point.as_str(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instantiates the bind group, once again specifying the binding of buffers.
|
||||||
|
let bind_group_layout = compute_pipeline.get_bind_group_layout(0);
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: storage_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: dest_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// A command encoder executes one or many pipelines.
|
||||||
|
// It is to WebGPU what a command buffer is to Vulkan.
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None });
|
||||||
|
cpass.set_pipeline(&compute_pipeline);
|
||||||
|
cpass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
cpass.insert_debug_marker("compute node network evaluation");
|
||||||
|
cpass.dispatch_workgroups(data.len() as u32, 1, 1); // Number of cells to run, the (x,y,z) size of item being processed
|
||||||
|
}
|
||||||
|
// Sets adds copy operation to command encoder.
|
||||||
|
// Will copy data from storage buffer on GPU to staging buffer on CPU.
|
||||||
|
encoder.copy_buffer_to_buffer(&dest_buffer, 0, &staging_buffer, 0, size);
|
||||||
|
|
||||||
|
// Submits command encoder for processing
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
|
||||||
|
// Note that we're not calling `.await` here.
|
||||||
|
let buffer_slice = staging_buffer.slice(..);
|
||||||
|
// Sets the buffer up for mapping, sending over the result of the mapping back to us when it is finished.
|
||||||
|
let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
|
||||||
|
buffer_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
|
||||||
|
|
||||||
|
// Poll the device in a blocking manner so that our future resolves.
|
||||||
|
// In an actual application, `device.poll(...)` should
|
||||||
|
// be called in an event loop or on another thread.
|
||||||
|
device.poll(wgpu::Maintain::Wait);
|
||||||
|
|
||||||
|
// Awaits until `buffer_future` can be read from
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
nvtx::range_push!("compute");
|
||||||
|
let result = receiver.receive().await;
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
nvtx::range_pop!();
|
||||||
|
if let Some(Ok(())) = result {
|
||||||
|
// Gets contents of buffer
|
||||||
|
let data = buffer_slice.get_mapped_range();
|
||||||
|
// Since contents are got in bytes, this converts these bytes back to u32
|
||||||
|
let result = bytemuck::cast_slice(&data).to_vec();
|
||||||
|
|
||||||
|
// With the current interface, we have to make sure all mapped views are
|
||||||
|
// dropped before we unmap the buffer.
|
||||||
|
drop(data);
|
||||||
|
staging_buffer.unmap(); // Unmaps buffer from memory
|
||||||
|
// If you are familiar with C++ these 2 lines can be thought of similarly to:
|
||||||
|
// delete myPointer;
|
||||||
|
// myPointer = NULL;
|
||||||
|
// It effectively frees the memory
|
||||||
|
|
||||||
|
// Returns data from buffer
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
panic!("failed to run compute on gpu!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use graph_craft::concrete;
|
||||||
|
use graph_craft::generic;
|
||||||
|
use graph_craft::proto::*;
|
||||||
|
|
||||||
|
fn inc_network() -> ProtoNetwork {
|
||||||
|
let mut construction_network = ProtoNetwork {
|
||||||
|
inputs: vec![10],
|
||||||
|
output: 1,
|
||||||
|
nodes: [
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
ProtoNode {
|
||||||
|
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("u32")]),
|
||||||
|
input: ProtoNodeInput::Node(11),
|
||||||
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
10,
|
||||||
|
ProtoNode {
|
||||||
|
identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("&ValueNode<u32>"), generic!("()")]),
|
||||||
|
input: ProtoNodeInput::Network,
|
||||||
|
construction_args: ConstructionArgs::Nodes(vec![14]),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
11,
|
||||||
|
ProtoNode {
|
||||||
|
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("u32"), generic!("u32")]),
|
||||||
|
input: ProtoNodeInput::Node(10),
|
||||||
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
14,
|
||||||
|
ProtoNode {
|
||||||
|
identifier: NodeIdentifier::new("graphene_core::value::ValueNode", &[concrete!("u32")]),
|
||||||
|
input: ProtoNodeInput::None,
|
||||||
|
construction_args: ConstructionArgs::Value(Box::new(3_u32)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
construction_network.resolve_inputs();
|
||||||
|
construction_network.reorder_ids();
|
||||||
|
construction_network
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix this test
|
||||||
|
/*#[test]
|
||||||
|
fn add_on_gpu() {
|
||||||
|
use crate::executor::Executor;
|
||||||
|
let m = compiler::Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
||||||
|
let network = inc_network();
|
||||||
|
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
|
||||||
|
|
||||||
|
let executor: GpuExecutor<u32, u32> = GpuExecutor::new(Context::new(), network, m, temp_dir.path()).unwrap();
|
||||||
|
|
||||||
|
let data: Vec<_> = (0..1024).map(|x| x as u32).collect();
|
||||||
|
let result = executor.execute(Box::new(data)).unwrap();
|
||||||
|
let result = dyn_any::downcast::<Vec<u32>>(result).unwrap();
|
||||||
|
for (i, r) in result.iter().enumerate() {
|
||||||
|
assert_eq!(*r, i as u32 + 3);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
5
node-graph/wgpu-executor/src/lib.rs
Normal file
5
node-graph/wgpu-executor/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod context;
|
||||||
|
mod executor;
|
||||||
|
|
||||||
|
pub use context::Context;
|
||||||
|
pub use executor::GpuExecutor;
|
Loading…
Add table
Add a link
Reference in a new issue