Restore Rust core at repository root
Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
command -v npm >/dev/null
|
|
||||||
|
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
|
||||||
|
for command in npm cargo wasm-pack; do command -v "$command" >/dev/null; done
|
||||||
amp orb services ensure
|
amp orb services ensure
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
|
||||||
|
if ! command -v rustup >/dev/null; then
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
||||||
|
fi
|
||||||
|
toolchain="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' rust-toolchain.toml)"
|
||||||
|
rustup toolchain install "$toolchain" --profile minimal --component rust-src,rustfmt --target wasm32-unknown-unknown
|
||||||
|
if ! command -v wasm-pack >/dev/null; then
|
||||||
|
cargo install wasm-pack --version 0.15.0 --locked
|
||||||
|
fi
|
||||||
npm ci
|
npm ci
|
||||||
amp orb services ensure
|
amp orb services ensure
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[target.wasm32-unknown-unknown]
|
||||||
|
rustflags = [
|
||||||
|
"-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals,+simd128",
|
||||||
|
"-Clink-args=--shared-memory",
|
||||||
|
"-Clink-args=--max-memory=1073741824",
|
||||||
|
"-Clink-args=--import-memory",
|
||||||
|
"-Clink-args=--export=__wasm_init_tls",
|
||||||
|
"-Clink-args=--export=__tls_size",
|
||||||
|
"-Clink-args=--export=__tls_align",
|
||||||
|
"-Clink-args=--export=__tls_base",
|
||||||
|
]
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["std", "panic_abort"]
|
||||||
@@ -10,6 +10,8 @@ lerna-debug.log*
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
target
|
||||||
|
core/pkg
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|||||||
Generated
+186
@@ -0,0 +1,186 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.151"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yawn-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["core"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "yawn-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Shared render-data arena and render-graph compiler"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@yawn/core",
|
"name": "@yawn/core",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"description": "Rust/WASM render data and render graph core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./src/index.js",
|
"exports": "./src/index.js",
|
||||||
"files": ["src"]
|
"files": ["src", "pkg"]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const TYPES = { f32: Float32Array, u32: Uint32Array, i32: Int32Array };
|
const types = { f32: Float32Array, u32: Uint32Array, i32: Int32Array };
|
||||||
|
|
||||||
export class SharedRows {
|
export class SharedRows {
|
||||||
constructor(buffer, descriptor) {
|
constructor(buffer, descriptor) {
|
||||||
@@ -11,7 +11,7 @@ export class SharedRows {
|
|||||||
get stride() { return this.descriptor.stride; }
|
get stride() { return this.descriptor.stride; }
|
||||||
get format() { return this.descriptor.format; }
|
get format() { return this.descriptor.format; }
|
||||||
get view() {
|
get view() {
|
||||||
return new TYPES[this.format](this.buffer, this.descriptor.offset, this.rows * this.stride / 4);
|
return new types[this.format](this.buffer, this.descriptor.offset, this.rows * this.stride / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
row(index) {
|
row(index) {
|
||||||
+424
@@ -0,0 +1,424 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use serde_json::{Map, Number, Value};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
const ALIGNMENT: u32 = 64;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Core {
|
||||||
|
_arena: Box<[u8]>,
|
||||||
|
base: u32,
|
||||||
|
capacity: u32,
|
||||||
|
used: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Core {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(arena_bytes: u32) -> Result<Self, JsError> {
|
||||||
|
if arena_bytes < ALIGNMENT || arena_bytes > u32::MAX - (ALIGNMENT - 1) {
|
||||||
|
return Err(JsError::new("INIT"));
|
||||||
|
}
|
||||||
|
let mut arena = vec![0; (arena_bytes + ALIGNMENT - 1) as usize].into_boxed_slice();
|
||||||
|
let pointer = arena.as_mut_ptr() as usize;
|
||||||
|
let base = ((pointer + ALIGNMENT as usize - 1) & !(ALIGNMENT as usize - 1)) as u32;
|
||||||
|
Ok(Self {
|
||||||
|
_arena: arena,
|
||||||
|
base,
|
||||||
|
capacity: arena_bytes,
|
||||||
|
used: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate(&mut self, rows: u32, stride: u32, format: &str) -> Result<u32, JsError> {
|
||||||
|
if rows == 0 || stride < 16 || stride % 16 != 0 || !matches!(format, "f32" | "u32" | "i32")
|
||||||
|
{
|
||||||
|
return Err(JsError::new("ALLOCATION"));
|
||||||
|
}
|
||||||
|
let offset = align(self.used, ALIGNMENT).ok_or_else(|| JsError::new("ARENA_OOM"))?;
|
||||||
|
let bytes = rows
|
||||||
|
.checked_mul(stride)
|
||||||
|
.ok_or_else(|| JsError::new("ARENA_OOM"))?;
|
||||||
|
self.used = offset
|
||||||
|
.checked_add(bytes)
|
||||||
|
.filter(|end| *end <= self.capacity)
|
||||||
|
.ok_or_else(|| JsError::new("ARENA_OOM"))?;
|
||||||
|
Ok(self.base + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_graph(&self, source: &str) -> Result<String, JsError> {
|
||||||
|
compile_graph(source).map_err(JsError::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align(value: u32, alignment: u32) -> Option<u32> {
|
||||||
|
value
|
||||||
|
.checked_add(alignment - 1)
|
||||||
|
.map(|value| value & !(alignment - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Expression {
|
||||||
|
Atom(Value),
|
||||||
|
List(Vec<Expression>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser<'a> {
|
||||||
|
source: &'a [u8],
|
||||||
|
at: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
fn parse(source: &'a str) -> Result<Expression, &'static str> {
|
||||||
|
let mut parser = Parser {
|
||||||
|
source: source.as_bytes(),
|
||||||
|
at: 0,
|
||||||
|
};
|
||||||
|
let expression = parser.expression()?;
|
||||||
|
parser.whitespace();
|
||||||
|
if parser.at != parser.source.len() {
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
}
|
||||||
|
Ok(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expression(&mut self) -> Result<Expression, &'static str> {
|
||||||
|
self.whitespace();
|
||||||
|
match self.source.get(self.at) {
|
||||||
|
Some(b'(') => self.list(),
|
||||||
|
Some(b'"') => self.string(),
|
||||||
|
Some(b')') | None => Err("GRAPH_WIRE"),
|
||||||
|
Some(_) => self.atom(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&mut self) -> Result<Expression, &'static str> {
|
||||||
|
self.at += 1;
|
||||||
|
let mut values = Vec::new();
|
||||||
|
loop {
|
||||||
|
self.whitespace();
|
||||||
|
match self.source.get(self.at) {
|
||||||
|
Some(b')') => {
|
||||||
|
self.at += 1;
|
||||||
|
return Ok(Expression::List(values));
|
||||||
|
}
|
||||||
|
None => return Err("GRAPH_WIRE"),
|
||||||
|
_ => values.push(self.expression()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string(&mut self) -> Result<Expression, &'static str> {
|
||||||
|
let start = self.at;
|
||||||
|
self.at += 1;
|
||||||
|
let mut escaped = false;
|
||||||
|
while let Some(&byte) = self.source.get(self.at) {
|
||||||
|
self.at += 1;
|
||||||
|
if escaped {
|
||||||
|
escaped = false;
|
||||||
|
} else if byte == b'\\' {
|
||||||
|
escaped = true;
|
||||||
|
} else if byte == b'"' {
|
||||||
|
let value = serde_json::from_slice(&self.source[start..self.at])
|
||||||
|
.map_err(|_| "GRAPH_WIRE")?;
|
||||||
|
return Ok(Expression::Atom(Value::String(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err("GRAPH_WIRE")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn atom(&mut self) -> Result<Expression, &'static str> {
|
||||||
|
let start = self.at;
|
||||||
|
while self
|
||||||
|
.source
|
||||||
|
.get(self.at)
|
||||||
|
.is_some_and(|byte| !byte.is_ascii_whitespace() && !matches!(byte, b'(' | b')'))
|
||||||
|
{
|
||||||
|
self.at += 1;
|
||||||
|
}
|
||||||
|
let token = std::str::from_utf8(&self.source[start..self.at]).map_err(|_| "GRAPH_WIRE")?;
|
||||||
|
let value = match token {
|
||||||
|
"true" => Value::Bool(true),
|
||||||
|
"false" => Value::Bool(false),
|
||||||
|
"null" => Value::Null,
|
||||||
|
_ => serde_json::from_str::<Value>(token)
|
||||||
|
.ok()
|
||||||
|
.filter(Value::is_number)
|
||||||
|
.unwrap_or_else(|| Value::String(token.into())),
|
||||||
|
};
|
||||||
|
Ok(Expression::Atom(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn whitespace(&mut self) {
|
||||||
|
while self
|
||||||
|
.source
|
||||||
|
.get(self.at)
|
||||||
|
.is_some_and(u8::is_ascii_whitespace)
|
||||||
|
{
|
||||||
|
self.at += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_graph(source: &str) -> Result<String, &'static str> {
|
||||||
|
let root = Parser::parse(source)?;
|
||||||
|
let Expression::List(mut root) = root else {
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
};
|
||||||
|
if root.len() != 3
|
||||||
|
|| !matches!(&root[0], Expression::Atom(Value::String(tag)) if tag == "yawn-graph")
|
||||||
|
|| !matches!(&root[1], Expression::Atom(Value::Number(version)) if version.as_u64() == Some(1))
|
||||||
|
{
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
}
|
||||||
|
let mut graph = decode(root.pop().unwrap())?;
|
||||||
|
let object = graph.as_object_mut().ok_or("GRAPH_SHAPE")?;
|
||||||
|
if !object.get("id").is_some_and(Value::is_string) {
|
||||||
|
return Err("GRAPH_SHAPE");
|
||||||
|
}
|
||||||
|
let passes = sort_passes(object.get("passes").ok_or("GRAPH_PASS")?)?;
|
||||||
|
plan_resources(object, &passes)?;
|
||||||
|
object.insert("passes".into(), Value::Array(passes));
|
||||||
|
serde_json::to_string(&graph).map_err(|_| "GRAPH_WIRE")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(expression: Expression) -> Result<Value, &'static str> {
|
||||||
|
let Expression::List(mut values) = expression else {
|
||||||
|
return match expression {
|
||||||
|
Expression::Atom(value) => Ok(value),
|
||||||
|
Expression::List(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if values.is_empty() {
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
}
|
||||||
|
let tag = match values.remove(0) {
|
||||||
|
Expression::Atom(Value::String(tag)) => tag,
|
||||||
|
_ => return Err("GRAPH_WIRE"),
|
||||||
|
};
|
||||||
|
if tag == "array" {
|
||||||
|
return values.into_iter().map(decode).collect();
|
||||||
|
}
|
||||||
|
if tag != "object" {
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
}
|
||||||
|
let mut object = Map::new();
|
||||||
|
for field in values {
|
||||||
|
let Expression::List(mut field) = field else {
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
};
|
||||||
|
if field.len() != 3
|
||||||
|
|| !matches!(&field[0], Expression::Atom(Value::String(tag)) if tag == "field")
|
||||||
|
{
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
}
|
||||||
|
let value = decode(field.pop().unwrap())?;
|
||||||
|
let key = match field.pop().unwrap() {
|
||||||
|
Expression::Atom(Value::String(key)) => key,
|
||||||
|
_ => return Err("GRAPH_WIRE"),
|
||||||
|
};
|
||||||
|
if object.insert(key, value).is_some() {
|
||||||
|
return Err("GRAPH_WIRE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Value::Object(object))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_passes(value: &Value) -> Result<Vec<Value>, &'static str> {
|
||||||
|
let passes = value.as_array().ok_or("GRAPH_PASS")?;
|
||||||
|
let mut ids = HashMap::new();
|
||||||
|
for (index, pass) in passes.iter().enumerate() {
|
||||||
|
let id = pass
|
||||||
|
.as_object()
|
||||||
|
.and_then(|pass| pass.get("id"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or("GRAPH_PASS")?;
|
||||||
|
if ids.insert(id, index).is_some() {
|
||||||
|
return Err("GRAPH_PASS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut dependencies = Vec::with_capacity(passes.len());
|
||||||
|
for pass in passes {
|
||||||
|
let after = pass.get("after").map_or(Ok(&[][..]), |after| {
|
||||||
|
after.as_array().map(Vec::as_slice).ok_or("GRAPH_ARRAY")
|
||||||
|
})?;
|
||||||
|
dependencies.push(
|
||||||
|
after
|
||||||
|
.iter()
|
||||||
|
.map(|dependency| {
|
||||||
|
dependency
|
||||||
|
.as_str()
|
||||||
|
.and_then(|id| ids.get(id).copied())
|
||||||
|
.ok_or("GRAPH_DEPENDENCY")
|
||||||
|
})
|
||||||
|
.collect::<Result<HashSet<_>, _>>()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut emitted = vec![false; passes.len()];
|
||||||
|
let mut result = Vec::with_capacity(passes.len());
|
||||||
|
while result.len() < passes.len() {
|
||||||
|
let ready = (0..passes.len()).find(|&index| {
|
||||||
|
!emitted[index]
|
||||||
|
&& dependencies[index]
|
||||||
|
.iter()
|
||||||
|
.all(|dependency| emitted[*dependency])
|
||||||
|
});
|
||||||
|
let index = ready.ok_or("GRAPH_CYCLE")?;
|
||||||
|
emitted[index] = true;
|
||||||
|
result.push(passes[index].clone());
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plan_resources(graph: &mut Map<String, Value>, passes: &[Value]) -> Result<(), &'static str> {
|
||||||
|
let mut used = HashSet::new();
|
||||||
|
let mut lifetimes = HashMap::new();
|
||||||
|
for (frame, pass) in passes.iter().enumerate() {
|
||||||
|
for id in pass_resources(pass)? {
|
||||||
|
used.insert(id.to_owned());
|
||||||
|
lifetimes
|
||||||
|
.entry(id.to_owned())
|
||||||
|
.and_modify(|lifetime: &mut (usize, usize)| lifetime.1 = frame)
|
||||||
|
.or_insert((frame, frame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(resources) = graph.get_mut("resources") else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let resources = resources.as_object_mut().ok_or("GRAPH_RESOURCE")?;
|
||||||
|
for kind in ["buffers", "samplers"] {
|
||||||
|
if let Some(declarations) = resources.get_mut(kind) {
|
||||||
|
let declarations = declarations.as_array_mut().ok_or("GRAPH_RESOURCE")?;
|
||||||
|
let mut ids = HashSet::new();
|
||||||
|
declarations.retain(|declaration| {
|
||||||
|
declaration
|
||||||
|
.get("id")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.is_some_and(|id| ids.insert(id.to_owned()) && used.contains(id))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(textures) = resources.get_mut("textures") else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let textures = textures.as_array_mut().ok_or("GRAPH_RESOURCE")?;
|
||||||
|
let mut ids = HashSet::new();
|
||||||
|
let mut planned = Vec::new();
|
||||||
|
for mut declaration in textures.drain(..) {
|
||||||
|
let object = declaration.as_object_mut().ok_or("GRAPH_RESOURCE")?;
|
||||||
|
let id = object
|
||||||
|
.get("id")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or("GRAPH_RESOURCE")?;
|
||||||
|
if !ids.insert(id.to_owned()) {
|
||||||
|
return Err("GRAPH_RESOURCE");
|
||||||
|
}
|
||||||
|
let Some(&(first, last)) = lifetimes.get(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
planned.push((declaration, first, last));
|
||||||
|
}
|
||||||
|
planned.sort_by_key(|value| value.1);
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let mut slots: Vec<(String, usize)> = Vec::new();
|
||||||
|
for (mut declaration, first, last) in planned {
|
||||||
|
let object = declaration.as_object_mut().unwrap();
|
||||||
|
let key = texture_key(object)?;
|
||||||
|
let slot = if object.get("transient") == Some(&Value::Bool(false)) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
slots
|
||||||
|
.iter()
|
||||||
|
.position(|value| value.0 == key && value.1 < first)
|
||||||
|
};
|
||||||
|
let slot = match slot {
|
||||||
|
Some(slot) => {
|
||||||
|
slots[slot].1 = last;
|
||||||
|
slot
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
slots.push((key, last));
|
||||||
|
slots.len() - 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
object.insert("slot".into(), Value::Number(Number::from(slot as u64)));
|
||||||
|
output.push(declaration);
|
||||||
|
}
|
||||||
|
*textures = output;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pass_resources(pass: &Value) -> Result<Vec<&str>, &'static str> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for field in ["bindings", "color", "vertexBuffers"] {
|
||||||
|
if let Some(values) = pass.get(field) {
|
||||||
|
for value in values.as_array().ok_or("GRAPH_ARRAY")? {
|
||||||
|
result.push(
|
||||||
|
value
|
||||||
|
.get("resource")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or("GRAPH_RESOURCE")?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for field in ["depth", "indexBuffer"] {
|
||||||
|
if let Some(value) = pass.get(field) {
|
||||||
|
result.push(
|
||||||
|
value
|
||||||
|
.get("resource")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or("GRAPH_RESOURCE")?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn texture_key(texture: &Map<String, Value>) -> Result<String, &'static str> {
|
||||||
|
let mut size = texture.get("size").cloned().unwrap_or_else(|| {
|
||||||
|
Value::Array(vec![
|
||||||
|
Value::String("canvas".into()),
|
||||||
|
Value::String("canvas".into()),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
let size = size.as_array_mut().ok_or("GRAPH_TEXTURE_SIZE")?;
|
||||||
|
if !(2..=3).contains(&size.len()) {
|
||||||
|
return Err("GRAPH_TEXTURE_SIZE");
|
||||||
|
}
|
||||||
|
if size.len() == 2 {
|
||||||
|
size.push(Value::Number(Number::from(1)));
|
||||||
|
}
|
||||||
|
let format = texture
|
||||||
|
.get("format")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or("GRAPH_RESOURCE")?;
|
||||||
|
let mut usage = texture.get("usage").map_or(Ok(Vec::new()), |usage| {
|
||||||
|
usage
|
||||||
|
.as_array()
|
||||||
|
.ok_or("GRAPH_TEXTURE_USAGE")?
|
||||||
|
.iter()
|
||||||
|
.map(|value| value.as_str().ok_or("GRAPH_TEXTURE_USAGE"))
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
})?;
|
||||||
|
usage.sort_unstable();
|
||||||
|
usage.dedup();
|
||||||
|
serde_json::to_string(&(
|
||||||
|
size,
|
||||||
|
format,
|
||||||
|
usage,
|
||||||
|
texture
|
||||||
|
.get("mipLevelCount")
|
||||||
|
.and_then(Value::as_u64)
|
||||||
|
.unwrap_or(1),
|
||||||
|
texture
|
||||||
|
.get("sampleCount")
|
||||||
|
.and_then(Value::as_u64)
|
||||||
|
.unwrap_or(1),
|
||||||
|
texture
|
||||||
|
.get("dimension")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or("2d"),
|
||||||
|
))
|
||||||
|
.map_err(|_| "GRAPH_RESOURCE")
|
||||||
|
}
|
||||||
@@ -1,45 +1,11 @@
|
|||||||
let canvas, context, device, surfaceFormat, memory, used = 0, loadout;
|
import initWasm, { Core as WasmCore } from "../pkg/yawn_core.js";
|
||||||
|
|
||||||
|
let canvas, context, device, surfaceFormat, memory, core, loadout;
|
||||||
const arrays = new Map();
|
const arrays = new Map();
|
||||||
const align = (value, multiple) => Math.ceil(value / multiple) * multiple;
|
const align = (value, multiple) => Math.ceil(value / multiple) * multiple;
|
||||||
const fail = code => { throw new Error(code); };
|
const fail = code => { throw new Error(code); };
|
||||||
const list = value => value === undefined ? [] : Array.isArray(value) ? value : fail("GRAPH_ARRAY");
|
const list = value => value === undefined ? [] : Array.isArray(value) ? value : fail("GRAPH_ARRAY");
|
||||||
|
|
||||||
function parse(source) {
|
|
||||||
if (typeof source !== "string") fail("GRAPH_WIRE");
|
|
||||||
const tokens = source.match(/\s*(\(|\)|"(?:\\.|[^"\\])*"|[^\s()]+)/gu) ?? [];
|
|
||||||
let at = 0;
|
|
||||||
const read = () => {
|
|
||||||
const token = tokens[at++]?.trim();
|
|
||||||
if (token === "(") {
|
|
||||||
const value = [];
|
|
||||||
while (tokens[at]?.trim() !== ")") {
|
|
||||||
if (at >= tokens.length) fail("GRAPH_WIRE");
|
|
||||||
value.push(read());
|
|
||||||
}
|
|
||||||
at++;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (!token || token === ")") fail("GRAPH_WIRE");
|
|
||||||
if (token[0] === '"') return JSON.parse(token);
|
|
||||||
if (token === "true") return true;
|
|
||||||
if (token === "false") return false;
|
|
||||||
if (token === "null") return null;
|
|
||||||
return Number.isFinite(Number(token)) ? Number(token) : token;
|
|
||||||
};
|
|
||||||
const root = read();
|
|
||||||
if (at !== tokens.length || root[0] !== "yawn-graph" || root[1] !== 1) fail("GRAPH_WIRE");
|
|
||||||
const decode = value => {
|
|
||||||
if (!Array.isArray(value)) return value;
|
|
||||||
if (value[0] === "array") return value.slice(1).map(decode);
|
|
||||||
if (value[0] === "object") return Object.fromEntries(value.slice(1).map(field => {
|
|
||||||
if (field[0] !== "field" || field.length !== 3) fail("GRAPH_WIRE");
|
|
||||||
return [field[1], decode(field[2])];
|
|
||||||
}));
|
|
||||||
fail("GRAPH_WIRE");
|
|
||||||
};
|
|
||||||
return decode(root[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function index(items, code) {
|
function index(items, code) {
|
||||||
const result = new Map();
|
const result = new Map();
|
||||||
for (const item of list(items)) {
|
for (const item of list(items)) {
|
||||||
@@ -49,22 +15,6 @@ function index(items, code) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortPasses(passes) {
|
|
||||||
const byId = index(passes, "GRAPH_PASS");
|
|
||||||
const waiting = new Map([...byId].map(([id, pass]) => [id, new Set(list(pass.after))]));
|
|
||||||
for (const dependencies of waiting.values())
|
|
||||||
for (const dependency of dependencies) if (!byId.has(dependency)) fail("GRAPH_DEPENDENCY");
|
|
||||||
const result = [];
|
|
||||||
while (waiting.size) {
|
|
||||||
const ready = [...waiting].find(([, dependencies]) => !dependencies.size);
|
|
||||||
if (!ready) fail("GRAPH_CYCLE");
|
|
||||||
waiting.delete(ready[0]);
|
|
||||||
result.push(byId.get(ready[0]));
|
|
||||||
for (const dependencies of waiting.values()) dependencies.delete(ready[0]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bufferUsage = names => list(names).reduce((usage, name) => usage | ({
|
const bufferUsage = names => list(names).reduce((usage, name) => usage | ({
|
||||||
uniform: GPUBufferUsage.UNIFORM, storage: GPUBufferUsage.STORAGE,
|
uniform: GPUBufferUsage.UNIFORM, storage: GPUBufferUsage.STORAGE,
|
||||||
vertex: GPUBufferUsage.VERTEX, index: GPUBufferUsage.INDEX,
|
vertex: GPUBufferUsage.VERTEX, index: GPUBufferUsage.INDEX,
|
||||||
@@ -78,20 +28,12 @@ const textureUsage = names => list(names).reduce((usage, name) => usage | ({
|
|||||||
|
|
||||||
async function compile(graph) {
|
async function compile(graph) {
|
||||||
if (!graph || typeof graph.id !== "string") fail("GRAPH_SHAPE");
|
if (!graph || typeof graph.id !== "string") fail("GRAPH_SHAPE");
|
||||||
const passes = sortPasses(graph.passes);
|
const passes = list(graph.passes);
|
||||||
const renderDeclarations = index(graph.pipelines?.render, "GRAPH_PIPELINE");
|
const renderDeclarations = index(graph.pipelines?.render, "GRAPH_PIPELINE");
|
||||||
const computeDeclarations = index(graph.pipelines?.compute, "GRAPH_PIPELINE");
|
const computeDeclarations = index(graph.pipelines?.compute, "GRAPH_PIPELINE");
|
||||||
const resources = new Map(), owned = [];
|
const resources = new Map(), owned = [];
|
||||||
try {
|
try {
|
||||||
const usedResources = new Set(passes.flatMap(pass => [
|
|
||||||
...list(pass.bindings).map(x => x.resource),
|
|
||||||
...list(pass.color).map(x => x.resource),
|
|
||||||
...(pass.depth ? [pass.depth.resource] : []),
|
|
||||||
...list(pass.vertexBuffers).map(x => x.resource),
|
|
||||||
...(pass.indexBuffer ? [pass.indexBuffer.resource] : []),
|
|
||||||
]));
|
|
||||||
for (const declaration of list(graph.resources?.buffers)) {
|
for (const declaration of list(graph.resources?.buffers)) {
|
||||||
if (!usedResources.has(declaration.id)) continue;
|
|
||||||
const source = arrays.get(declaration.array);
|
const source = arrays.get(declaration.array);
|
||||||
if (!source) fail("GRAPH_ARRAY_UNKNOWN");
|
if (!source) fail("GRAPH_ARRAY_UNKNOWN");
|
||||||
const gpu = device.createBuffer({ size: align(source.bytes, 4), usage: bufferUsage(declaration.usage) });
|
const gpu = device.createBuffer({ size: align(source.bytes, 4), usage: bufferUsage(declaration.usage) });
|
||||||
@@ -99,18 +41,9 @@ async function compile(graph) {
|
|||||||
owned.push(gpu);
|
owned.push(gpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
const textures = index(graph.resources?.textures, "GRAPH_RESOURCE"), lifetimes = new Map(), slots = [];
|
const textureSlots = new Map();
|
||||||
passes.forEach((pass, frame) => {
|
for (const declaration of list(graph.resources?.textures)) {
|
||||||
for (const id of [...list(pass.bindings).map(x => x.resource), ...list(pass.color).map(x => x.resource), ...(pass.depth ? [pass.depth.resource] : [])]) {
|
if (!Number.isInteger(declaration.slot)) fail("GRAPH_RESOURCE");
|
||||||
if (!textures.has(id)) continue;
|
|
||||||
const lifetime = lifetimes.get(id) ?? [frame, frame];
|
|
||||||
lifetime[1] = frame;
|
|
||||||
lifetimes.set(id, lifetime);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (const declaration of textures.values()) {
|
|
||||||
const lifetime = lifetimes.get(declaration.id);
|
|
||||||
if (!lifetime) continue;
|
|
||||||
const size = declaration.size ?? ["canvas", "canvas"];
|
const size = declaration.size ?? ["canvas", "canvas"];
|
||||||
if (!Array.isArray(size) || size.length < 2 || size.length > 3) fail("GRAPH_TEXTURE_SIZE");
|
if (!Array.isArray(size) || size.length < 2 || size.length > 3) fail("GRAPH_TEXTURE_SIZE");
|
||||||
const descriptor = {
|
const descriptor = {
|
||||||
@@ -121,18 +54,17 @@ async function compile(graph) {
|
|||||||
sampleCount: declaration.sampleCount ?? 1,
|
sampleCount: declaration.sampleCount ?? 1,
|
||||||
dimension: declaration.dimension ?? "2d",
|
dimension: declaration.dimension ?? "2d",
|
||||||
};
|
};
|
||||||
const key = JSON.stringify(descriptor);
|
let slot = textureSlots.get(declaration.slot);
|
||||||
let slot = declaration.transient === false ? undefined : slots.find(value => value.key === key && value.last < lifetime[0]);
|
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
const gpu = device.createTexture(descriptor);
|
const gpu = device.createTexture(descriptor);
|
||||||
slot = { key, last: lifetime[1], gpu, view: gpu.createView() };
|
slot = { gpu, view: gpu.createView() };
|
||||||
slots.push(slot);
|
textureSlots.set(declaration.slot, slot);
|
||||||
owned.push(gpu);
|
owned.push(gpu);
|
||||||
} else slot.last = lifetime[1];
|
}
|
||||||
resources.set(declaration.id, { kind: "texture", gpu: slot.gpu, view: slot.view });
|
resources.set(declaration.id, { kind: "texture", gpu: slot.gpu, view: slot.view });
|
||||||
}
|
}
|
||||||
for (const declaration of list(graph.resources?.samplers))
|
for (const declaration of list(graph.resources?.samplers))
|
||||||
if (usedResources.has(declaration.id)) resources.set(declaration.id, {
|
resources.set(declaration.id, {
|
||||||
kind: "sampler", gpu: device.createSampler(declaration.descriptor),
|
kind: "sampler", gpu: device.createSampler(declaration.descriptor),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,7 +188,10 @@ addEventListener("message", async ({ data: message }) => {
|
|||||||
if (message.type === "init") {
|
if (message.type === "init") {
|
||||||
if (!(message.canvas instanceof OffscreenCanvas) || !Number.isInteger(message.arenaBytes) || message.arenaBytes < 64) fail("INIT");
|
if (!(message.canvas instanceof OffscreenCanvas) || !Number.isInteger(message.arenaBytes) || message.arenaBytes < 64) fail("INIT");
|
||||||
canvas = message.canvas;
|
canvas = message.canvas;
|
||||||
memory = new SharedArrayBuffer(align(message.arenaBytes, 64));
|
const wasm = await initWasm();
|
||||||
|
core = new WasmCore(message.arenaBytes);
|
||||||
|
memory = wasm.memory.buffer;
|
||||||
|
if (!(memory instanceof SharedArrayBuffer)) fail("WASM_MEMORY_NOT_SHARED");
|
||||||
const adapter = await navigator.gpu?.requestAdapter();
|
const adapter = await navigator.gpu?.requestAdapter();
|
||||||
if (!adapter) fail("WEBGPU_UNAVAILABLE");
|
if (!adapter) fail("WEBGPU_UNAVAILABLE");
|
||||||
device = await adapter.requestDevice();
|
device = await adapter.requestDevice();
|
||||||
@@ -268,15 +203,12 @@ addEventListener("message", async ({ data: message }) => {
|
|||||||
tick();
|
tick();
|
||||||
} else if (message.type === "allocate") {
|
} else if (message.type === "allocate") {
|
||||||
const { name, rows, stride, format } = message;
|
const { name, rows, stride, format } = message;
|
||||||
if (typeof name !== "string" || !name || arrays.has(name) || !Number.isInteger(rows) || rows < 1 ||
|
if (typeof name !== "string" || !name || arrays.has(name)) fail("ALLOCATION");
|
||||||
!Number.isInteger(stride) || stride < 16 || stride % 16 || !["f32", "u32", "i32"].includes(format)) fail("ALLOCATION");
|
const offset = core.allocate(rows, stride, format), bytes = rows * stride;
|
||||||
const offset = align(used, 64), bytes = rows * stride;
|
|
||||||
if (!Number.isSafeInteger(bytes) || offset + bytes > memory.byteLength) fail("ARENA_OOM");
|
|
||||||
result = { name, rows, stride, format, offset };
|
result = { name, rows, stride, format, offset };
|
||||||
arrays.set(name, { ...result, bytes });
|
arrays.set(name, { ...result, bytes });
|
||||||
used = offset + bytes;
|
|
||||||
} else if (message.type === "load-graph") {
|
} else if (message.type === "load-graph") {
|
||||||
const next = await compile(parse(message.serialized));
|
const next = await compile(JSON.parse(core.compile_graph(message.serialized)));
|
||||||
const previous = loadout;
|
const previous = loadout;
|
||||||
loadout = next;
|
loadout = next;
|
||||||
previous?.owned.forEach(resource => resource.destroy?.());
|
previous?.owned.forEach(resource => resource.destroy?.());
|
||||||
+4
-4
@@ -3,7 +3,7 @@ layout: home
|
|||||||
hero:
|
hero:
|
||||||
name: Yawn
|
name: Yawn
|
||||||
text: Shared render data and a render graph.
|
text: Shared render data and a render graph.
|
||||||
tagline: Two core files, one fixed arena, no built-in scene model or shader.
|
tagline: One Rust/WASM core, one fixed arena, no built-in scene model or shader.
|
||||||
actions:
|
actions:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Open the playground
|
text: Open the playground
|
||||||
@@ -32,11 +32,11 @@ color.row(0)[0] = 0.8; // direct SharedArrayBuffer write
|
|||||||
await loadGraph(core, graph); // infrequent message
|
await loadGraph(core, graph); // infrequent message
|
||||||
```
|
```
|
||||||
|
|
||||||
`@yawn/core` contains only the public shared-row client and its worker. The worker owns the fixed 64-byte-aligned arena, S-expression graph compiler, WebGPU loadout, and transient texture aliasing. Every scene convention and every byte of WGSL comes from an addon or application.
|
`@yawn/core` contains only the Rust/WASM render-data arena and graph compiler plus the browser worker required to execute WebGPU. Rust owns the fixed 64-byte-aligned shared arena, DAG ordering, resource culling, and transient texture planning; the worker materializes the resulting loadout. Every scene convention and every byte of WGSL comes from an addon or application.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
JSO / FXNode ──▶ AST ──▶ S-expression ──▶ core worker ──▶ WebGPU
|
JSO / FXNode ──▶ AST ──▶ S-expression ──▶ Rust/WASM ──▶ worker ──▶ WebGPU
|
||||||
any JS thread ───────────── direct SAB row writes ────────────┘
|
any JS thread ────────────── direct SAB row writes ────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
The addon packages provide graph serialization, optional WGSL, glTF import directly into shared rows, and conventional camera/material/mesh handles. None of them add semantics to core.
|
The addon packages provide graph serialization, optional WGSL, glTF import directly into shared rows, and conventional camera/material/mesh handles. None of them add semantics to core.
|
||||||
|
|||||||
Generated
+6
-6
@@ -8,7 +8,7 @@
|
|||||||
"name": "yawn",
|
"name": "yawn",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"core",
|
||||||
"addons/*"
|
"addons/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -45,6 +45,10 @@
|
|||||||
"@yawn/render-graph-ast": "0.1.0"
|
"@yawn/render-graph-ast": "0.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"core": {
|
||||||
|
"name": "@yawn/core",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
"node_modules/@algolia/abtesting": {
|
"node_modules/@algolia/abtesting": {
|
||||||
"version": "1.23.0",
|
"version": "1.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.23.0.tgz",
|
||||||
@@ -1238,7 +1242,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@yawn/core": {
|
"node_modules/@yawn/core": {
|
||||||
"resolved": "packages/yawn-core",
|
"resolved": "core",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@yawn/default-pipelines": {
|
"node_modules/@yawn/default-pipelines": {
|
||||||
@@ -2256,10 +2260,6 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"packages/yawn-core": {
|
|
||||||
"name": "@yawn/core",
|
|
||||||
"version": "0.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -3,9 +3,9 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": ["packages/*", "addons/*"],
|
"workspaces": ["core", "addons/*"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vitepress dev docs --host 0.0.0.0 --port ${PORT:-8080}"
|
"start": "wasm-pack build core --target web --out-dir pkg --release && vitepress dev docs --host 0.0.0.0 --port ${PORT:-8080}"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vitepress": "^1.6.4"
|
"vitepress": "^1.6.4"
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2025-10-20"
|
||||||
|
components = ["rust-src", "rustfmt"]
|
||||||
|
targets = ["wasm32-unknown-unknown"]
|
||||||
Reference in New Issue
Block a user