summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-04-16 15:29:50 +0000
committers-ol <s+removethis@s-ol.nu>2026-05-14 14:46:13 +0000
commit5925137f3b41cb3a87f098604ef482bced91341e (patch)
tree613ee305358e319bab5d88a85cc923b4870d73f1
parentrefactor (diff)
downloadwgsl-view-5925137f3b41cb3a87f098604ef482bced91341e.tar.gz
wgsl-view-5925137f3b41cb3a87f098604ef482bced91341e.zip
tsv integration, split binaries
-rw-r--r--Cargo.lock870
-rw-r--r--Cargo.toml22
-rw-r--r--rust-toolchain.toml2
-rw-r--r--src/bin/tsv_video_stream.rs185
-rw-r--r--src/bin/tsv_view.rs (renamed from src/main.rs)166
-rw-r--r--src/bin/wgsl_render.rs281
-rw-r--r--src/gpu.rs141
-rw-r--r--src/lib.rs5
-rw-r--r--src/osc.rs60
-rw-r--r--src/renderer.rs122
-rw-r--r--src/uniform.rs456
-rwxr-xr-xtest.sh52
12 files changed, 2042 insertions, 320 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b2a2c30..4762e5b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,8 +24,8 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
- "cfg-if",
- "getrandom",
+ "cfg-if 1.0.0",
+ "getrandom 0.2.15",
"once_cell",
"version_check",
"zerocopy 0.7.35",
@@ -81,6 +81,18 @@ dependencies = [
]
[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -114,6 +126,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -187,7 +210,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -211,7 +234,7 @@ dependencies = [
"bitflags 2.11.1",
"log",
"polling",
- "rustix",
+ "rustix 0.38.44",
"slab",
"thiserror 1.0.69",
]
@@ -223,12 +246,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [
"calloop",
- "rustix",
+ "rustix 0.38.44",
"wayland-backend",
"wayland-client",
]
[[package]]
+name = "cbindgen"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
+dependencies = [
+ "clap 3.2.25",
+ "heck 0.4.1",
+ "indexmap 1.9.3",
+ "log",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn 1.0.109",
+ "tempfile",
+ "toml",
+]
+
+[[package]]
name = "cc"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -241,6 +283,12 @@ dependencies = [
[[package]]
name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
@@ -252,6 +300,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
+name = "chacha20"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
+dependencies = [
+ "atty",
+ "bitflags 1.3.2",
+ "clap_lex 0.2.4",
+ "indexmap 1.9.3",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstyle",
+ "clap_lex 1.1.0",
+ "strsim 0.11.1",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "codespan-reporting"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -322,6 +440,15 @@ dependencies = [
]
[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -340,6 +467,68 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
[[package]]
+name = "cxx"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e"
+dependencies = [
+ "cc",
+ "cxx-build",
+ "cxxbridge-cmd",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "foldhash 0.2.0",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "indexmap 2.14.0",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "cxxbridge-cmd"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328"
+dependencies = [
+ "clap 4.6.1",
+ "codespan-reporting",
+ "indexmap 2.14.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.194"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf"
+dependencies = [
+ "indexmap 2.14.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -424,6 +613,12 @@ dependencies = [
]
[[package]]
+name = "fastrand"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+
+[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -453,7 +648,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -502,12 +697,26 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "r-efi",
+ "rand_core 0.10.1",
+ "wasip2",
+ "wasip3",
+]
+
+[[package]]
name = "gl_generator"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -579,7 +788,7 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"crunchy",
"num-traits",
"zerocopy 0.8.48",
@@ -587,6 +796,12 @@ dependencies = [
[[package]]
name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
@@ -612,6 +827,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -636,6 +872,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -643,6 +895,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
+ "serde",
+ "serde_core",
]
[[package]]
@@ -668,7 +922,7 @@ version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"combine",
"jni-macros",
"jni-sys 0.4.1",
@@ -689,7 +943,7 @@ dependencies = [
"quote",
"rustc_version",
"simd_cesu8",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -714,7 +968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -732,7 +986,7 @@ version = "0.3.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"futures-util",
"once_cell",
"wasm-bindgen",
@@ -756,6 +1010,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
name = "libc"
version = "0.2.185"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -767,7 +1027,7 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"windows-targets 0.52.6",
]
@@ -789,12 +1049,27 @@ dependencies = [
]
[[package]]
+name = "link-cplusplus"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -832,6 +1107,24 @@ dependencies = [
]
[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -846,13 +1139,13 @@ dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.11.1",
- "cfg-if",
+ "cfg-if 1.0.0",
"cfg_aliases",
"codespan-reporting",
"half",
"hashbrown 0.16.1",
"hexf-parse",
- "indexmap",
+ "indexmap 2.14.0",
"libm",
"log",
"num-traits",
@@ -894,6 +1187,31 @@ dependencies = [
]
[[package]]
+name = "nix"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
+dependencies = [
+ "bitflags 1.3.2",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nix"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
+dependencies = [
+ "bitflags 2.11.1",
+ "cfg-if 1.0.0",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -937,7 +1255,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -1224,6 +1542,12 @@ dependencies = [
]
[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+
+[[package]]
name = "owned_ttf_parser"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1248,7 +1572,7 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"libc",
"redox_syscall 0.5.10",
"smallvec",
@@ -1278,7 +1602,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -1299,11 +1623,11 @@ version = "3.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"concurrent-queue",
"hermit-abi 0.4.0",
"pin-project-lite",
- "rustix",
+ "rustix 0.38.44",
"tracing",
"windows-sys 0.59.0",
]
@@ -1336,12 +1660,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy 0.8.48",
+]
+
+[[package]]
name = "presser"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
[[package]]
+name = "prettyplease"
+version = "0.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.100",
+]
+
+[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1384,6 +1727,59 @@ dependencies = [
]
[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.2",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.15",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
name = "range-alloc"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1408,6 +1804,19 @@ dependencies = [
]
[[package]]
+name = "raw_sync"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a34bde3561f980a51c70495164200569a11662644fe5af017f0b5d7015688cc"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "nix 0.31.2",
+ "rand 0.10.1",
+ "winapi",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1495,11 +1904,24 @@ dependencies = [
"bitflags 2.11.1",
"errno",
"libc",
- "linux-raw-sys",
+ "linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags 2.11.1",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.12.1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1527,6 +1949,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "scratch"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
+
+[[package]]
name = "sctk-adwaita"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1572,7 +2000,33 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.149"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "shared_memory"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba8593196da75d9dc4f69349682bd4c2099f8cde114257d1ef7ef1b33d1aba54"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "nix 0.23.2",
+ "rand 0.8.5",
+ "win-sys",
]
[[package]]
@@ -1634,7 +2088,7 @@ dependencies = [
"libc",
"log",
"memmap2",
- "rustix",
+ "rustix 0.38.44",
"thiserror 1.0.69",
"wayland-backend",
"wayland-client",
@@ -1677,6 +2131,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1688,6 +2165,19 @@ dependencies = [
]
[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.2",
+ "once_cell",
+ "rustix 1.1.4",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1697,6 +2187,51 @@ dependencies = [
]
[[package]]
+name = "texture-share-ipc"
+version = "0.1.0"
+source = "git+https://github.com/s-ol/texture-share-vk.git?branch=ash-0.38#c0134ad59b3809d800eea8c0f39f7ac12f4dc120"
+dependencies = [
+ "cbindgen",
+ "libc",
+ "memoffset 0.9.1",
+ "raw_sync",
+ "shared_memory",
+ "uuid",
+]
+
+[[package]]
+name = "texture-share-vk-base"
+version = "0.1.0"
+source = "git+https://github.com/s-ol/texture-share-vk.git?branch=ash-0.38#c0134ad59b3809d800eea8c0f39f7ac12f4dc120"
+dependencies = [
+ "ash",
+ "cbindgen",
+ "cc",
+ "cmake",
+ "cxx",
+ "cxx-build",
+ "libc",
+ "texture-share-ipc",
+]
+
+[[package]]
+name = "texture-share-vk-client"
+version = "0.1.0"
+source = "git+https://github.com/s-ol/texture-share-vk.git?branch=ash-0.38#c0134ad59b3809d800eea8c0f39f7ac12f4dc120"
+dependencies = [
+ "cbindgen",
+ "cxx",
+ "cxx-build",
+ "texture-share-vk-base",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+
+[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1722,7 +2257,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -1733,7 +2268,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -1776,7 +2311,7 @@ dependencies = [
"arrayref",
"arrayvec",
"bytemuck",
- "cfg-if",
+ "cfg-if 1.0.0",
"log",
"tiny-skia-path",
]
@@ -1793,6 +2328,15 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1804,7 +2348,7 @@ version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
- "indexmap",
+ "indexmap 2.14.0",
"toml_datetime",
"winnow",
]
@@ -1850,6 +2394,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "uuid"
+version = "1.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1872,12 +2432,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
name = "wasm-bindgen"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
@@ -1913,7 +2491,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
"wasm-bindgen-shared",
]
@@ -1927,6 +2505,40 @@ dependencies = [
]
[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap 2.14.0",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.11.1",
+ "hashbrown 0.15.2",
+ "indexmap 2.14.0",
+ "semver",
+]
+
+[[package]]
name = "wayland-backend"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1934,7 +2546,7 @@ checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121"
dependencies = [
"cc",
"downcast-rs",
- "rustix",
+ "rustix 0.38.44",
"scoped-tls",
"smallvec",
"wayland-sys",
@@ -1947,7 +2559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61"
dependencies = [
"bitflags 2.11.1",
- "rustix",
+ "rustix 0.38.44",
"wayland-backend",
"wayland-scanner",
]
@@ -1969,7 +2581,7 @@ version = "0.31.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d"
dependencies = [
- "rustix",
+ "rustix 0.38.44",
"wayland-client",
"xcursor",
]
@@ -2064,7 +2676,7 @@ dependencies = [
"arrayvec",
"bitflags 2.11.1",
"bytemuck",
- "cfg-if",
+ "cfg-if 1.0.0",
"cfg_aliases",
"document-features",
"hashbrown 0.16.1",
@@ -2099,7 +2711,7 @@ dependencies = [
"cfg_aliases",
"document-features",
"hashbrown 0.16.1",
- "indexmap",
+ "indexmap 2.14.0",
"log",
"naga",
"once_cell",
@@ -2158,7 +2770,7 @@ dependencies = [
"bitflags 2.11.1",
"block2 0.6.2",
"bytemuck",
- "cfg-if",
+ "cfg-if 1.0.0",
"cfg_aliases",
"glow",
"glutin_wgl_sys",
@@ -2226,17 +2838,46 @@ dependencies = [
name = "wgsl-view"
version = "0.1.0"
dependencies = [
+ "ash",
"bytemuck",
"env_logger",
"log",
"naga",
"pollster",
"rosc",
+ "texture-share-vk-base",
+ "texture-share-vk-client",
"wgpu",
+ "wgpu-hal",
"winit",
]
[[package]]
+name = "win-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b7b128a98c1cfa201b09eb49ba285887deb3cbe7466a98850eb1adabb452be5"
+dependencies = [
+ "windows 0.34.0",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2246,6 +2887,25 @@ dependencies = [
]
[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f"
+dependencies = [
+ "windows_aarch64_msvc 0.34.0",
+ "windows_i686_gnu 0.34.0",
+ "windows_i686_msvc 0.34.0",
+ "windows_x86_64_gnu 0.34.0",
+ "windows_x86_64_msvc 0.34.0",
+]
+
+[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2321,7 +2981,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -2332,7 +2992,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -2343,7 +3003,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -2354,7 +3014,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -2482,6 +3142,12 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
+
+[[package]]
+name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
@@ -2494,6 +3160,12 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
+
+[[package]]
+name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
@@ -2512,6 +3184,12 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
+
+[[package]]
+name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
@@ -2524,6 +3202,12 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
+
+[[package]]
+name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
@@ -2548,6 +3232,12 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"
+
+[[package]]
+name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
@@ -2590,7 +3280,7 @@ dependencies = [
"pin-project",
"raw-window-handle",
"redox_syscall 0.4.1",
- "rustix",
+ "rustix 0.38.44",
"sctk-adwaita",
"smithay-client-toolkit",
"smol_str",
@@ -2620,6 +3310,94 @@ dependencies = [
]
[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "indexmap 2.14.0",
+ "prettyplease",
+ "syn 2.0.100",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.11.1",
+ "indexmap 2.14.0",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.14.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
+[[package]]
name = "x11-dl"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2641,7 +3419,7 @@ dependencies = [
"libc",
"libloading",
"once_cell",
- "rustix",
+ "rustix 0.38.44",
"x11rb-protocol",
]
@@ -2708,7 +3486,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
[[package]]
@@ -2719,5 +3497,11 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.100",
]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/Cargo.toml b/Cargo.toml
index c365be1..e919911 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,13 +3,33 @@ name = "wgsl-view"
version = "0.1.0"
edition = "2021"
+[lib]
+name = "wgsl_view"
+path = "src/lib.rs"
+
+[[bin]]
+name = "wgsl-render"
+path = "src/bin/wgsl_render.rs"
+
+[[bin]]
+name = "tsv-view"
+path = "src/bin/tsv_view.rs"
+
+[[bin]]
+name = "tsv-video-stream"
+path = "src/bin/tsv_video_stream.rs"
+
[dependencies]
winit = "0.30"
env_logger = "0.10"
log = "0.4"
-wgpu = "29"
+wgpu = { version = "29", features = ["vulkan"] }
+wgpu-hal = "29"
+ash = "0.38"
naga = { version = "29", features = ["wgsl-in"] }
pollster = "0.4"
rosc = "0.11"
bytemuck = { version = "1", features = ["derive"] }
+texture-share-vk-client = { git = "https://github.com/s-ol/texture-share-vk.git", branch = "ash-0.38" }
+texture-share-vk-base = { git = "https://github.com/s-ol/texture-share-vk.git", branch = "ash-0.38" }
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..5d56faf
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/src/bin/tsv_video_stream.rs b/src/bin/tsv_video_stream.rs
new file mode 100644
index 0000000..d68f4b4
--- /dev/null
+++ b/src/bin/tsv_video_stream.rs
@@ -0,0 +1,185 @@
+use std::io::Read;
+use std::process::{Command, Stdio};
+use std::thread;
+use std::time::{Duration, Instant};
+
+use ash::vk;
+use wgsl_view::gpu;
+
+/// Options that ffmpeg supports but ffprobe does not.
+const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop"];
+
+fn filter_probe_args(ff_args: &[String]) -> Vec<String> {
+ let mut out = Vec::new();
+ let mut skip_next = false;
+ for arg in ff_args {
+ if skip_next {
+ skip_next = false;
+ continue;
+ }
+ if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) {
+ skip_next = true;
+ continue;
+ }
+ out.push(arg.clone());
+ }
+ out
+}
+
+fn probe_video(ff_args: &[String]) -> (u32, u32, f64) {
+ let probe_args = filter_probe_args(ff_args);
+ let output = Command::new("ffprobe")
+ .args(["-v", "error", "-select_streams", "v:0"])
+ .args(["-show_entries", "stream=width,height,r_frame_rate"])
+ .args(["-of", "csv=p=0"])
+ .args(&probe_args)
+ .output()
+ .expect("failed to run ffprobe");
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ panic!("ffprobe failed: {stderr}");
+ }
+
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ let line = stdout.trim();
+ let parts: Vec<&str> = line.split(',').collect();
+ if parts.len() < 3 {
+ panic!("unexpected ffprobe output: {line}");
+ }
+
+ let width: u32 = parts[0].parse().expect("parse width");
+ let height: u32 = parts[1].parse().expect("parse height");
+
+ let fps: f64 = if let Some((num, den)) = parts[2].split_once('/') {
+ let n: f64 = num.parse().expect("parse fps numerator");
+ let d: f64 = den.parse().expect("parse fps denominator");
+ n / d
+ } else {
+ parts[2].parse().expect("parse fps")
+ };
+
+ (width, height, fps)
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = std::env::args().skip(1).collect();
+ let sep = args.iter().position(|a| a == "--");
+
+ let (own_args, ff_args) = match sep {
+ Some(i) => (&args[..i], &args[i + 1..]),
+ None => {
+ eprintln!("usage: tsv-video-stream [--name NAME] -- <ffmpeg input args>");
+ std::process::exit(1);
+ }
+ };
+
+ let mut name = "tsv-video-stream".to_string();
+
+ let mut i = 0;
+ while i < own_args.len() {
+ match own_args[i].as_str() {
+ "--name" => {
+ name = own_args[i + 1].clone();
+ i += 2;
+ }
+ other => panic!("unknown argument: {other}"),
+ }
+ }
+
+ let (width, height, fps) = probe_video(ff_args);
+ log::info!("{width}x{height} @ {fps:.2}fps, tsv image: {name}");
+
+ let instance = gpu::create_instance();
+ let adapter = gpu::create_adapter(&instance, None);
+ let (device, queue) = gpu::create_device(&adapter);
+
+ let mut client = gpu::create_tsv_client(&device);
+ client
+ .init_image(&name, width, height, gpu::TSV_FORMAT, true)
+ .expect("init tsv image");
+
+ let fence = gpu::create_fence(&client);
+
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("video_frame"),
+ size: wgpu::Extent3d {
+ width,
+ height,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
+ view_formats: &[],
+ });
+
+ let frame_size = (width * height * 4) as usize;
+ let frame_time = Duration::from_secs_f64(1.0 / fps);
+
+ let mut ffmpeg = Command::new("ffmpeg")
+ .args(["-v", "quiet"])
+ .args(ff_args)
+ .args(["-f", "rawvideo", "-pix_fmt", "rgba", "pipe:1"])
+ .stdout(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .spawn()
+ .expect("failed to start ffmpeg");
+
+ let stdout = ffmpeg.stdout.take().unwrap();
+ let mut reader = std::io::BufReader::new(stdout);
+ let mut frame_buf = vec![0u8; frame_size];
+
+ loop {
+ let frame_start = Instant::now();
+
+ if reader.read_exact(&mut frame_buf).is_err() {
+ break;
+ }
+
+ queue.write_texture(
+ wgpu::TexelCopyTextureInfo {
+ texture: &texture,
+ mip_level: 0,
+ origin: wgpu::Origin3d::ZERO,
+ aspect: wgpu::TextureAspect::All,
+ },
+ &frame_buf,
+ wgpu::TexelCopyBufferLayout {
+ offset: 0,
+ bytes_per_row: Some(width * 4),
+ rows_per_image: None,
+ },
+ wgpu::Extent3d {
+ width,
+ height,
+ depth_or_array_layers: 1,
+ },
+ );
+ // flush the pending write_texture command and wait for GPU completion
+ queue.submit([]);
+ device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
+
+ let raw = unsafe { gpu::raw_image(&texture) };
+ if let Err(e) = client.send_image(
+ &name,
+ raw,
+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,
+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,
+ fence,
+ ) {
+ log::warn!("send_image error: {e}");
+ }
+
+ let elapsed = frame_start.elapsed();
+ if elapsed < frame_time {
+ thread::sleep(frame_time - elapsed);
+ }
+ }
+
+ ffmpeg.wait().ok();
+}
diff --git a/src/main.rs b/src/bin/tsv_view.rs
index 3ce4ffc..885a8c4 100644
--- a/src/main.rs
+++ b/src/bin/tsv_view.rs
@@ -1,13 +1,8 @@
-mod osc;
-mod renderer;
-mod uniform;
-mod window;
-
use std::sync::Arc;
-use osc::{OscCommand, OscServer};
-use renderer::Renderer;
-use window::{PreviewWindow, ScaleMode};
+use ash::vk;
+use wgsl_view::gpu;
+use wgsl_view::window::{PreviewWindow, ScaleMode};
use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, WindowEvent};
@@ -17,74 +12,61 @@ use winit::keyboard::{Key, NamedKey};
struct AppState {
device: wgpu::Device,
queue: wgpu::Queue,
- renderer: Renderer,
preview: PreviewWindow,
+ client: texture_share_vk_client::VkClient,
+ fence: vk::Fence,
+ canvas: wgpu::Texture,
+ name: String,
}
struct App {
- width: u32,
- height: u32,
scale_mode: ScaleMode,
- osc: OscServer,
+ name: String,
state: Option<AppState>,
}
-impl App {
- fn handle_osc(&mut self) {
- let state = self.state.as_mut().expect("gpu not initialized");
- self.osc.poll(|cmd| match cmd {
- OscCommand::Shader(source) => {
- if let Err(e) = state.renderer.load_shader(&state.device, &source) {
- log::error!("shader error: {}", e);
- }
+impl ApplicationHandler for App {
+ fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
+ let instance = gpu::create_instance();
+
+ // Create a temporary headless device to connect to TSV and read image dimensions
+ let temp_adapter = gpu::create_adapter(&instance, None);
+ let (temp_device, _temp_queue) = gpu::create_device(&temp_adapter);
+ let mut client = gpu::create_tsv_client(&temp_device);
+
+ let (width, height) = match client.find_image_data(&self.name, true) {
+ Ok(Some((_lock, data))) => (data.width, data.height),
+ Ok(None) => {
+ log::error!("tsv image '{}' not found", self.name);
+ event_loop.exit();
+ return;
}
- OscCommand::Uniform { path, args } => {
- if let Err(e) = state.renderer.set_uniform(&path, &args) {
- log::warn!("uniform error: {}", e);
- }
+ Err(e) => {
+ log::error!("failed to find tsv image '{}': {e}", self.name);
+ event_loop.exit();
+ return;
}
- });
- }
-}
+ };
+ log::info!("found tsv image '{}': {width}x{height}", self.name);
+
+ drop(client);
+ drop(temp_device);
-impl ApplicationHandler for App {
- fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let window = Arc::new(
event_loop
.create_window(
winit::window::Window::default_attributes()
- .with_title("wgsl-view")
- .with_inner_size(winit::dpi::LogicalSize::new(self.width, self.height)),
+ .with_title(format!("tsv-view: {}", self.name))
+ .with_inner_size(winit::dpi::LogicalSize::new(width, height)),
)
.expect("create window"),
);
- let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
- backends: wgpu::Backends::VULKAN,
- flags: wgpu::InstanceFlags::default(),
- backend_options: wgpu::BackendOptions::default(),
- memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
- display: None,
- });
-
let surface = instance
.create_surface(window.clone())
.expect("create surface");
-
- let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::PowerPreference::HighPerformance,
- compatible_surface: Some(&surface),
- force_fallback_adapter: false,
- }))
- .expect("find adapter");
-
- let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
- label: Some("device"),
- required_features: wgpu::Features::empty(),
- required_limits: wgpu::Limits::default(),
- ..Default::default()
- }))
- .expect("create device");
+ let adapter = gpu::create_adapter(&instance, Some(&surface));
+ let (device, queue) = gpu::create_device(&adapter);
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
@@ -94,23 +76,46 @@ impl ApplicationHandler for App {
.copied()
.unwrap_or(surface_caps.formats[0]);
- let renderer = Renderer::new(&device, &queue, self.width, self.height);
+ let mut client = gpu::create_tsv_client(&device);
+ client.find_image(&self.name, true).expect("find tsv image");
+
+ let fence = gpu::create_fence(&client);
+
+ let canvas = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("tsv_canvas"),
+ size: wgpu::Extent3d {
+ width,
+ height,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
+ view_formats: &[],
+ });
+
+ let canvas_view = canvas.create_view(&Default::default());
let preview = PreviewWindow::new(
&device,
window,
surface,
surface_format,
- renderer.canvas_view(),
- self.width,
- self.height,
+ &canvas_view,
+ width,
+ height,
self.scale_mode,
);
self.state = Some(AppState {
device,
queue,
- renderer,
preview,
+ client,
+ fence,
+ canvas,
+ name: self.name.clone(),
});
}
@@ -144,9 +149,22 @@ impl ApplicationHandler for App {
state.preview.resize(&state.device, size.width, size.height);
}
WindowEvent::RedrawRequested => {
- self.handle_osc();
- let state = self.state.as_mut().expect("gpu not initialized");
- state.renderer.render(&state.device, &state.queue);
+ // Receive the latest frame from texture-share-vk.
+ // Use UNDEFINED as orig_layout: the image content is fully overwritten by recv,
+ // and wgpu may not have transitioned it yet.
+ let canvas_image = unsafe { gpu::raw_image(&state.canvas) };
+ match state.client.recv_image(
+ &state.name,
+ canvas_image,
+ vk::ImageLayout::UNDEFINED,
+ vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
+ state.fence,
+ ) {
+ Err(e) => log::warn!("recv_image error: {e}"),
+ Ok(Some(())) => log::debug!("got frame"),
+ Ok(None) => {}
+ }
+
state.preview.draw(&state.device, &state.queue);
}
_ => {}
@@ -160,43 +178,41 @@ impl ApplicationHandler for App {
}
}
+impl Drop for AppState {
+ fn drop(&mut self) {
+ gpu::destroy_fence(&self.client, self.fence);
+ }
+}
+
fn main() {
env_logger::init();
let mut args = std::env::args().skip(1);
- let mut width = 1920u32;
- let mut height = 1080u32;
let mut scale_mode = ScaleMode::Contain;
- let mut port = 9000u16;
+ let mut name = "wgsl-view".to_string();
while let Some(arg) = args.next() {
match arg.as_str() {
- "--width" => width = args.next().expect("--width VALUE").parse().expect("u32"),
- "--height" => height = args.next().expect("--height VALUE").parse().expect("u32"),
- "--port" => port = args.next().expect("--port VALUE").parse().expect("u16"),
+ "--name" => name = args.next().expect("--name VALUE"),
"--scale" => {
scale_mode = match args.next().expect("--scale VALUE").as_str() {
"contain" => ScaleMode::Contain,
"cover" => ScaleMode::Cover,
"center" => ScaleMode::Center,
"natural" => ScaleMode::Natural,
- s => panic!("unknown scale mode: {}", s),
+ s => panic!("unknown scale mode: {s}"),
}
}
- other => panic!("unknown argument: {}", other),
+ other => panic!("unknown argument: {other}"),
}
}
- let osc = OscServer::new(&format!("0.0.0.0:{}", port)).expect("bind OSC socket");
-
let event_loop = EventLoop::new().expect("create event loop");
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
let mut app = App {
- width,
- height,
scale_mode,
- osc,
+ name,
state: None,
};
diff --git a/src/bin/wgsl_render.rs b/src/bin/wgsl_render.rs
new file mode 100644
index 0000000..1860b7c
--- /dev/null
+++ b/src/bin/wgsl_render.rs
@@ -0,0 +1,281 @@
+use std::thread;
+use std::time::{Duration, Instant};
+
+use ash::vk;
+use rosc::OscType;
+use wgsl_view::gpu;
+use wgsl_view::osc::OscServer;
+use wgsl_view::renderer::Renderer;
+use wgsl_view::uniform::UniformError;
+
+fn main() {
+ env_logger::init();
+
+ let mut args = std::env::args().skip(1);
+ let mut width = 1920u32;
+ let mut height = 1080u32;
+ let mut port = 9000u16;
+ let mut name = "wgsl-view".to_string();
+ let mut fps = 60u32;
+ let mut continuous = false;
+
+ while let Some(arg) = args.next() {
+ match arg.as_str() {
+ "--width" => width = args.next().expect("--width VALUE").parse().expect("u32"),
+ "--height" => height = args.next().expect("--height VALUE").parse().expect("u32"),
+ "--port" => port = args.next().expect("--port VALUE").parse().expect("u16"),
+ "--name" => name = args.next().expect("--name VALUE"),
+ "--fps" => fps = args.next().expect("--fps VALUE").parse().expect("u32"),
+ "--continuous" => continuous = true,
+ other => panic!("unknown argument: {other}"),
+ }
+ }
+
+ let instance = gpu::create_instance();
+ let adapter = gpu::create_adapter(&instance, None);
+ let (device, queue) = gpu::create_device(&adapter);
+
+ let mut client = gpu::create_tsv_client(&device);
+ client
+ .init_image(&name, width, height, gpu::TSV_FORMAT, true)
+ .expect("init tsv image");
+
+ let fence = gpu::create_fence(&client);
+ let mut renderer = Renderer::new(&device, &queue, width, height);
+ let mut osc = OscServer::new(&format!("0.0.0.0:{port}")).expect("bind OSC socket");
+
+ let frame_time = Duration::from_secs_f64(1.0 / fps as f64);
+ log::info!(
+ "rendering {width}x{height}{}, tsv image: {name}",
+ if continuous {
+ format!(" at {fps}fps")
+ } else {
+ " on OSC input".to_string()
+ }
+ );
+
+ loop {
+ let frame_start = Instant::now();
+
+ let handle_msg = |msg: rosc::OscMessage| {
+ let path = &msg.addr;
+
+ if path == "/shader" {
+ match &msg.args[..] {
+ [OscType::String(code)] => {
+ if let Err(e) = renderer.load_shader(&device, code) {
+ log::error!("shader error: {e}");
+ }
+ }
+ _ => log::warn!("/shader: expected single string argument"),
+ }
+ } else if let Some(rest) = path.strip_prefix("/uniform/") {
+ if let Err(e) = set_uniform(renderer.uniforms(), rest, &msg.args) {
+ log::warn!("uniform error: {e}");
+ }
+ } else if let Some(tex_name) = path.strip_prefix("/texture/") {
+ match &msg.args[..] {
+ [OscType::String(tsv_name)] => {
+ match renderer.uniforms().texture_slot_mut(tex_name) {
+ Some(slot) => {
+ slot.set_tsv_name(tsv_name.clone());
+ log::info!("texture '{tex_name}' → tsv '{tsv_name}'");
+ }
+ None => log::warn!("texture '{tex_name}' not found"),
+ }
+ }
+ _ => log::warn!("/texture/{tex_name}: expected string (TSV image name)"),
+ }
+ } else if let Some(smp_name) = path.strip_prefix("/sampler/") {
+ match &msg.args[..] {
+ [OscType::String(filter), OscType::String(clamp)] => {
+ if let Err(e) = configure_sampler(
+ renderer.uniforms(),
+ &device,
+ smp_name,
+ filter,
+ clamp,
+ ) {
+ log::warn!("sampler error: {e}");
+ }
+ }
+ _ => log::warn!("/sampler/{smp_name}: expected two strings (filter, clamp)"),
+ }
+ } else {
+ log::debug!("unhandled OSC: {path}");
+ }
+ };
+
+ let dirty = if continuous {
+ osc.poll(handle_msg);
+ true
+ } else {
+ osc.recv(handle_msg)
+ };
+
+ if dirty {
+ refresh_textures(renderer.uniforms(), &device, &mut client, fence);
+ renderer.render(&device, &queue);
+ device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
+
+ let canvas_image = unsafe { gpu::raw_image(renderer.canvas_texture()) };
+ if let Err(e) = client.send_image(
+ &name,
+ canvas_image,
+ vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
+ vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
+ fence,
+ ) {
+ log::warn!("send_image error: {e}");
+ }
+ }
+
+ if continuous {
+ let elapsed = frame_start.elapsed();
+ if elapsed < frame_time {
+ thread::sleep(frame_time - elapsed);
+ }
+ }
+ }
+}
+
+/// Set a uniform value from OSC args, navigating the path.
+fn set_uniform(
+ cache: &mut wgsl_view::uniform::UniformCache,
+ path: &str,
+ args: &[OscType],
+) -> Result<(), Box<dyn std::error::Error>> {
+ let mut parts = path.split('/');
+ let name = parts.next().ok_or("missing uniform name")?;
+
+ let mut uref = cache.get(name).ok_or("uniform not found")?;
+ for component in parts {
+ uref = uref.field(component)?;
+ }
+
+ let scalar = uref.leaf_scalar()?;
+ match scalar.kind {
+ naga::ScalarKind::Float => {
+ let values: Vec<f32> = args
+ .iter()
+ .map(|a| match a {
+ OscType::Float(f) => Ok(*f),
+ OscType::Double(d) => Ok(*d as f32),
+ OscType::Int(i) => Ok(*i as f32),
+ OscType::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
+ _ => Err(UniformError::TypeMismatch),
+ })
+ .collect::<Result<_, _>>()?;
+ uref.set_f32(&values)?;
+ }
+ naga::ScalarKind::Sint => {
+ let values: Vec<i32> = args
+ .iter()
+ .map(|a| match a {
+ OscType::Int(i) => Ok(*i),
+ OscType::Float(f) => Ok(*f as i32),
+ OscType::Double(d) => Ok(*d as i32),
+ OscType::Bool(b) => Ok(if *b { 1 } else { 0 }),
+ _ => Err(UniformError::TypeMismatch),
+ })
+ .collect::<Result<_, _>>()?;
+ uref.set_i32(&values)?;
+ }
+ naga::ScalarKind::Uint => {
+ let values: Vec<u32> = args
+ .iter()
+ .map(|a| match a {
+ OscType::Int(i) => Ok(*i as u32),
+ OscType::Float(f) => Ok(*f as u32),
+ OscType::Double(d) => Ok(*d as u32),
+ OscType::Bool(b) => Ok(if *b { 1 } else { 0 }),
+ _ => Err(UniformError::TypeMismatch),
+ })
+ .collect::<Result<_, _>>()?;
+ uref.set_u32(&values)?;
+ }
+ _ => return Err(Box::new(UniformError::TypeMismatch)),
+ }
+
+ Ok(())
+}
+
+/// Configure a named sampler from string filter/clamp mode names.
+fn configure_sampler(
+ cache: &mut wgsl_view::uniform::UniformCache,
+ device: &wgpu::Device,
+ name: &str,
+ filter: &str,
+ clamp: &str,
+) -> Result<(), String> {
+ let filter_mode = match filter {
+ "linear" => wgpu::FilterMode::Linear,
+ "nearest" => wgpu::FilterMode::Nearest,
+ _ => return Err(format!("unknown filter mode '{filter}' (linear|nearest)")),
+ };
+ let address_mode = match clamp {
+ "clamp" => wgpu::AddressMode::ClampToEdge,
+ "repeat" => wgpu::AddressMode::Repeat,
+ "mirror" => wgpu::AddressMode::MirrorRepeat,
+ _ => return Err(format!("unknown clamp mode '{clamp}' (clamp|repeat|mirror)")),
+ };
+ let slot = cache
+ .sampler_slot_mut(name)
+ .ok_or_else(|| format!("sampler '{name}' not found"))?;
+ slot.configure(device, filter_mode, address_mode);
+ cache.rebuild_bind_group(device);
+ log::info!("sampler '{name}' → {filter}, {clamp}");
+ Ok(())
+}
+
+/// Refresh all texture inputs from TSV shared images.
+fn refresh_textures(
+ cache: &mut wgsl_view::uniform::UniformCache,
+ device: &wgpu::Device,
+ client: &mut texture_share_vk_client::VkClient,
+ fence: vk::Fence,
+) {
+ let mut needs_rebind = false;
+
+ for slot in cache.texture_slots_mut() {
+ let tsv_name = match slot.tsv_name() {
+ Some(n) => n.to_string(),
+ None => continue,
+ };
+
+ if !slot.tsv_registered() {
+ if let Err(e) = client.find_image(&tsv_name, true) {
+ log::debug!("tsv find '{tsv_name}': {e}");
+ continue;
+ }
+
+ match client.find_image_data(&tsv_name, true) {
+ Ok(Some((_lock, data))) => {
+ if slot.resize(device, data.width, data.height) {
+ needs_rebind = true;
+ }
+ }
+ _ => continue,
+ }
+
+ slot.set_tsv_registered();
+ }
+
+ let raw = unsafe { gpu::raw_image(slot.texture()) };
+ match client.recv_image(
+ &tsv_name,
+ raw,
+ vk::ImageLayout::UNDEFINED,
+ vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
+ fence,
+ ) {
+ Ok(Some(())) => log::trace!("texture '{}' updated from '{tsv_name}'", slot.name()),
+ Ok(None) => {}
+ Err(e) => log::warn!("recv_image '{tsv_name}': {e}"),
+ }
+ }
+
+ if needs_rebind {
+ cache.rebuild_bind_group(device);
+ }
+}
diff --git a/src/gpu.rs b/src/gpu.rs
new file mode 100644
index 0000000..e6529f9
--- /dev/null
+++ b/src/gpu.rs
@@ -0,0 +1,141 @@
+use std::time::Duration;
+
+use ash::vk;
+use texture_share_vk_base::vk_device::VkDevice;
+use texture_share_vk_base::vk_entry::VkEntry;
+use texture_share_vk_base::vk_instance::VkInstance;
+use texture_share_vk_base::vk_setup::VkSetup;
+use texture_share_vk_client::VkClient;
+
+const TSV_SERVER_EXECUTABLE: &str = "/usr/bin/texture-share-vk-server";
+const TSV_SERVER_SOCKET_PATH: &str = "/tmp/vk_server/vk_server.sock";
+const TSV_SERVER_LOCK_PATH: &str = "/tmp/vk_server/vk_server.lock";
+const TSV_SHMEM_PREFIX: &str = "shmem_img_";
+
+/// Creates a wgpu instance configured for Vulkan.
+pub fn create_instance() -> wgpu::Instance {
+ wgpu::Instance::new(wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::VULKAN,
+ flags: wgpu::InstanceFlags::default(),
+ backend_options: wgpu::BackendOptions::default(),
+ memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
+ display: None,
+ })
+}
+
+/// Requests a wgpu adapter, optionally compatible with a surface.
+pub fn create_adapter(
+ instance: &wgpu::Instance,
+ surface: Option<&wgpu::Surface<'_>>,
+) -> wgpu::Adapter {
+ pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
+ power_preference: wgpu::PowerPreference::HighPerformance,
+ compatible_surface: surface,
+ force_fallback_adapter: false,
+ }))
+ .expect("find vulkan adapter")
+}
+
+/// Requests a wgpu device + queue.
+pub fn create_device(adapter: &wgpu::Adapter) -> (wgpu::Device, wgpu::Queue) {
+ pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
+ label: Some("device"),
+ ..Default::default()
+ }))
+ .expect("create device")
+}
+
+/// Builds a texture-share-vk VkSetup by importing raw Vulkan handles from a wgpu device.
+/// The returned VkSetup does NOT own the Vulkan objects (import_only = true).
+fn import_vk_setup(device: &wgpu::Device) -> VkSetup {
+ // Extract raw handles from the wgpu HAL device
+ let (raw_instance, raw_device, raw_physical_device, raw_queue, queue_family_index, queue_index) = unsafe {
+ let hal = device
+ .as_hal::<wgpu_hal::api::Vulkan>()
+ .expect("vulkan HAL device");
+
+ let instance_shared = hal.shared_instance();
+ let raw_instance = instance_shared.raw_instance().handle();
+ let raw_device = hal.raw_device().handle();
+ let raw_physical_device = hal.raw_physical_device();
+ let raw_queue = hal.raw_queue();
+ let queue_family_index = hal.queue_family_index();
+ let queue_index = hal.queue_index();
+
+ (
+ raw_instance,
+ raw_device,
+ raw_physical_device,
+ raw_queue,
+ queue_family_index,
+ queue_index,
+ )
+ };
+
+ let vk_entry = Box::new(VkEntry::new().expect("vulkan entry"));
+ let vk_instance =
+ VkInstance::import_vk(Some(vk_entry), raw_instance, true).expect("import vulkan instance");
+ let vk_device = VkDevice::import_vk(
+ &vk_instance,
+ raw_device,
+ raw_physical_device,
+ raw_queue,
+ queue_family_index,
+ queue_index,
+ true,
+ )
+ .expect("import vulkan device");
+
+ VkSetup::new(vk_instance, vk_device)
+}
+
+/// Creates a VkClient connected to the texture-share-vk server,
+/// launching the server if needed.
+pub fn create_tsv_client(device: &wgpu::Device) -> VkClient {
+ let vk_setup = import_vk_setup(device);
+ let timeout = Duration::from_secs(2);
+
+ VkClient::new_with_server_launch(
+ TSV_SERVER_SOCKET_PATH,
+ Box::new(vk_setup),
+ Duration::from_secs(1),
+ TSV_SERVER_EXECUTABLE,
+ TSV_SERVER_LOCK_PATH,
+ TSV_SERVER_SOCKET_PATH,
+ TSV_SHMEM_PREFIX,
+ timeout,
+ timeout,
+ timeout,
+ timeout,
+ timeout,
+ )
+ .expect("connect to texture-share-vk server")
+}
+
+/// Image format for texture-share-vk, matching RGBA8.
+pub const TSV_FORMAT: texture_share_vk_base::ipc::platform::img_data::ImgFormat =
+ texture_share_vk_base::ipc::platform::img_data::ImgFormat::R8G8B8A8;
+
+/// Extracts the raw vk::Image handle from a wgpu Texture.
+///
+/// # Safety
+/// The returned handle must not outlive the texture.
+pub unsafe fn raw_image(texture: &wgpu::Texture) -> vk::Image {
+ let hal = texture
+ .as_hal::<wgpu_hal::api::Vulkan>()
+ .expect("vulkan HAL texture");
+ hal.raw_handle()
+}
+
+/// Creates a vk::Fence on the VkClient's device.
+pub fn create_fence(client: &VkClient) -> vk::Fence {
+ let device = &client.get_vk_setup().device.device;
+ let create_info = vk::FenceCreateInfo::default();
+ unsafe { device.create_fence(&create_info, None) }.expect("create fence")
+}
+
+/// Destroys a vk::Fence.
+pub fn destroy_fence(client: &VkClient, fence: vk::Fence) {
+ let device = &client.get_vk_setup().device.device;
+ unsafe { device.destroy_fence(fence, None) };
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..22a55d8
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,5 @@
+pub mod gpu;
+pub mod osc;
+pub mod renderer;
+pub mod uniform;
+pub mod window;
diff --git a/src/osc.rs b/src/osc.rs
index 6759704..f9a8df1 100644
--- a/src/osc.rs
+++ b/src/osc.rs
@@ -1,11 +1,6 @@
use std::net::UdpSocket;
-use rosc::{OscMessage, OscPacket, OscType};
-
-pub enum OscCommand {
- Shader(String),
- Uniform { path: String, args: Vec<OscType> },
-}
+use rosc::{OscMessage, OscPacket};
pub struct OscServer {
socket: UdpSocket,
@@ -23,15 +18,19 @@ impl OscServer {
})
}
- pub fn poll(&mut self, mut on_command: impl FnMut(OscCommand)) {
+ /// Drains all pending OSC messages, calling `on_message` for each.
+ /// Returns `true` if any messages were dispatched.
+ pub fn poll(&mut self, mut on_message: impl FnMut(OscMessage)) -> bool {
+ let mut received = false;
loop {
match self.socket.recv_from(&mut self.buf) {
Ok((size, _addr)) => {
let data = &self.buf[..size];
match rosc::decoder::decode_udp(data) {
- Ok((_, packet)) => dispatch(packet, &mut on_command),
+ Ok((_, packet)) => dispatch(packet, &mut on_message),
Err(e) => log::warn!("OSC decode error: {}", e),
}
+ received = true;
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
Err(e) => {
@@ -40,34 +39,37 @@ impl OscServer {
}
}
}
+ received
}
-}
-fn dispatch(packet: OscPacket, on_command: &mut impl FnMut(OscCommand)) {
- match packet {
- OscPacket::Message(msg) => dispatch_message(msg, on_command),
- OscPacket::Bundle(bundle) => {
- for p in bundle.content {
- dispatch(p, on_command);
+ /// Blocks until at least one OSC message arrives, then drains all pending.
+ pub fn recv(&mut self, mut on_message: impl FnMut(OscMessage)) -> bool {
+ self.socket.set_nonblocking(false).expect("set blocking");
+ match self.socket.recv_from(&mut self.buf) {
+ Ok((size, _addr)) => {
+ let data = &self.buf[..size];
+ match rosc::decoder::decode_udp(data) {
+ Ok((_, packet)) => dispatch(packet, &mut on_message),
+ Err(e) => log::warn!("OSC decode error: {}", e),
+ }
+ }
+ Err(e) => {
+ log::warn!("OSC recv error: {}", e);
}
}
+ self.socket.set_nonblocking(true).expect("set nonblocking");
+ self.poll(on_message);
+ true
}
}
-fn dispatch_message(msg: OscMessage, on_command: &mut impl FnMut(OscCommand)) {
- let path = &msg.addr;
-
- if path == "/shader" {
- match &msg.args[..] {
- [OscType::String(code)] => on_command(OscCommand::Shader(code.clone())),
- _ => log::warn!("/shader: expected single string argument"),
+fn dispatch(packet: OscPacket, on_message: &mut impl FnMut(OscMessage)) {
+ match packet {
+ OscPacket::Message(msg) => on_message(msg),
+ OscPacket::Bundle(bundle) => {
+ for p in bundle.content {
+ dispatch(p, on_message);
+ }
}
- } else if let Some(rest) = path.strip_prefix("/uniform/") {
- on_command(OscCommand::Uniform {
- path: rest.to_string(),
- args: msg.args,
- });
- } else {
- log::debug!("unhandled OSC message: {}", path);
}
}
diff --git a/src/renderer.rs b/src/renderer.rs
index e3a2acd..454e81a 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -1,5 +1,4 @@
-use crate::uniform::{UniformCache, UniformError};
-use rosc::OscType;
+use crate::uniform::UniformCache;
const CANVAS_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
@@ -32,33 +31,34 @@ fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
";
pub struct Renderer {
+ canvas: wgpu::Texture,
canvas_view: wgpu::TextureView,
vertex_module: wgpu::ShaderModule,
vertex_bgl: wgpu::BindGroupLayout,
vertex_bg: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline,
- uniform_cache: UniformCache,
+ uniforms: UniformCache,
}
impl Renderer {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) -> Self {
- let canvas_view = device
- .create_texture(&wgpu::TextureDescriptor {
- label: Some("canvas"),
- size: wgpu::Extent3d {
- width,
- height,
- depth_or_array_layers: 1,
- },
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: CANVAS_FORMAT,
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT
- | wgpu::TextureUsages::TEXTURE_BINDING,
- view_formats: &[],
- })
- .create_view(&Default::default());
+ let canvas = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("canvas"),
+ size: wgpu::Extent3d {
+ width,
+ height,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: CANVAS_FORMAT,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT
+ | wgpu::TextureUsages::TEXTURE_BINDING
+ | wgpu::TextureUsages::COPY_SRC,
+ view_formats: &[],
+ });
+ let canvas_view = canvas.create_view(&Default::default());
let vertex_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("vertex_shader"),
@@ -101,106 +101,54 @@ impl Renderer {
bytemuck::cast_slice(&[width as f32, height as f32]),
);
- let mut uniform_cache = UniformCache::new();
+ let mut uniforms = UniformCache::new();
let render_pipeline = build_pipeline(
device,
&vertex_module,
&vertex_bgl,
DEFAULT_FRAGMENT,
- &mut uniform_cache,
+ &mut uniforms,
)
.expect("default shader");
Self {
+ canvas,
canvas_view,
vertex_module,
vertex_bgl,
vertex_bg,
render_pipeline,
- uniform_cache,
+ uniforms,
}
}
+ pub fn canvas_texture(&self) -> &wgpu::Texture {
+ &self.canvas
+ }
+
pub fn canvas_view(&self) -> &wgpu::TextureView {
&self.canvas_view
}
+ pub fn uniforms(&mut self) -> &mut UniformCache {
+ &mut self.uniforms
+ }
+
pub fn load_shader(&mut self, device: &wgpu::Device, source: &str) -> Result<(), String> {
let pipeline = build_pipeline(
device,
&self.vertex_module,
&self.vertex_bgl,
source,
- &mut self.uniform_cache,
+ &mut self.uniforms,
)?;
self.render_pipeline = pipeline;
log::info!("shader loaded");
Ok(())
}
- pub fn set_uniform(
- &mut self,
- path: &str,
- args: &[OscType],
- ) -> Result<(), Box<dyn std::error::Error>> {
- let mut parts = path.split('/');
- let name = parts.next().ok_or("missing uniform name")?;
-
- let mut uref = self.uniform_cache.get(name).ok_or("uniform not found")?;
-
- for component in parts {
- uref = uref.field(component)?;
- }
-
- let scalar = uref.leaf_scalar()?;
- match scalar.kind {
- naga::ScalarKind::Float => {
- let values: Vec<f32> = args
- .iter()
- .map(|a| match a {
- OscType::Float(f) => Ok(*f),
- OscType::Double(d) => Ok(*d as f32),
- OscType::Int(i) => Ok(*i as f32),
- OscType::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
- _ => Err(UniformError::TypeMismatch),
- })
- .collect::<Result<_, _>>()?;
- uref.set_f32(&values)?;
- }
- naga::ScalarKind::Sint => {
- let values: Vec<i32> = args
- .iter()
- .map(|a| match a {
- OscType::Int(i) => Ok(*i),
- OscType::Float(f) => Ok(*f as i32),
- OscType::Double(d) => Ok(*d as i32),
- OscType::Bool(b) => Ok(if *b { 1 } else { 0 }),
- _ => Err(UniformError::TypeMismatch),
- })
- .collect::<Result<_, _>>()?;
- uref.set_i32(&values)?;
- }
- naga::ScalarKind::Uint => {
- let values: Vec<u32> = args
- .iter()
- .map(|a| match a {
- OscType::Int(i) => Ok(*i as u32),
- OscType::Float(f) => Ok(*f as u32),
- OscType::Double(d) => Ok(*d as u32),
- OscType::Bool(b) => Ok(if *b { 1 } else { 0 }),
- _ => Err(UniformError::TypeMismatch),
- })
- .collect::<Result<_, _>>()?;
- uref.set_u32(&values)?;
- }
- _ => return Err(Box::new(UniformError::TypeMismatch)),
- }
-
- Ok(())
- }
-
pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
- self.uniform_cache.flush(queue);
+ self.uniforms.flush(queue);
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
@@ -225,7 +173,7 @@ impl Renderer {
pass.set_pipeline(&self.render_pipeline);
pass.set_bind_group(0, &self.vertex_bg, &[]);
- if let Some(ref bg) = self.uniform_cache.bind_group {
+ if let Some(ref bg) = self.uniforms.bind_group {
pass.set_bind_group(1, bg, &[]);
}
pass.draw(0..4, 0..1);
diff --git a/src/uniform.rs b/src/uniform.rs
index c6886b4..57a0a4b 100644
--- a/src/uniform.rs
+++ b/src/uniform.rs
@@ -397,12 +397,196 @@ pub fn types_compatible(
}
}
-/// Manages uniform buffer data for all `var<uniform>` globals in a shader.
+/// A texture input slot, backed by a wgpu::Texture that can be fed from TSV.
+pub struct TextureSlot {
+ name: String,
+ binding: u32,
+ tsv_name: Option<String>,
+ tsv_registered: bool,
+ texture: wgpu::Texture,
+ view: wgpu::TextureView,
+ width: u32,
+ height: u32,
+ view_dimension: wgpu::TextureViewDimension,
+ sample_type: wgpu::TextureSampleType,
+ multisampled: bool,
+}
+
+impl TextureSlot {
+ fn new(
+ device: &wgpu::Device,
+ name: String,
+ binding: u32,
+ view_dimension: wgpu::TextureViewDimension,
+ sample_type: wgpu::TextureSampleType,
+ multisampled: bool,
+ ) -> Self {
+ let (texture, view) = create_input_texture(device, &name, 1, 1);
+ Self {
+ name,
+ binding,
+ tsv_name: None,
+ tsv_registered: false,
+ texture,
+ view,
+ width: 1,
+ height: 1,
+ view_dimension,
+ sample_type,
+ multisampled,
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn tsv_name(&self) -> Option<&str> {
+ self.tsv_name.as_deref()
+ }
+
+ pub fn set_tsv_name(&mut self, name: String) {
+ if self.tsv_name.as_deref() != Some(&name) {
+ self.tsv_name = Some(name);
+ self.tsv_registered = false;
+ }
+ }
+
+ pub fn tsv_registered(&self) -> bool {
+ self.tsv_registered
+ }
+
+ pub fn set_tsv_registered(&mut self) {
+ self.tsv_registered = true;
+ }
+
+ pub fn texture(&self) -> &wgpu::Texture {
+ &self.texture
+ }
+
+ pub fn width(&self) -> u32 {
+ self.width
+ }
+
+ pub fn height(&self) -> u32 {
+ self.height
+ }
+
+ /// Recreate the texture at new dimensions. Returns true if the size actually changed.
+ pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) -> bool {
+ if self.width == width && self.height == height {
+ return false;
+ }
+ let (texture, view) = create_input_texture(device, &self.name, width, height);
+ self.texture = texture;
+ self.view = view;
+ self.width = width;
+ self.height = height;
+ true
+ }
+}
+
+fn create_input_texture(
+ device: &wgpu::Device,
+ label: &str,
+ width: u32,
+ height: u32,
+) -> (wgpu::Texture, wgpu::TextureView) {
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some(label),
+ size: wgpu::Extent3d {
+ width,
+ height,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
+ view_formats: &[],
+ });
+ let view = texture.create_view(&Default::default());
+ (texture, view)
+}
+
+pub struct SamplerSlot {
+ name: String,
+ binding: u32,
+ sampler: wgpu::Sampler,
+ binding_type: wgpu::SamplerBindingType,
+}
+
+impl SamplerSlot {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ /// Recreate the sampler with new filter and address modes.
+ pub fn configure(
+ &mut self,
+ device: &wgpu::Device,
+ filter: wgpu::FilterMode,
+ address_mode: wgpu::AddressMode,
+ ) {
+ self.sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ label: Some(&self.name),
+ mag_filter: filter,
+ min_filter: filter,
+ mipmap_filter: match filter {
+ wgpu::FilterMode::Linear => wgpu::MipmapFilterMode::Linear,
+ wgpu::FilterMode::Nearest => wgpu::MipmapFilterMode::Nearest,
+ },
+ address_mode_u: address_mode,
+ address_mode_v: address_mode,
+ address_mode_w: address_mode,
+ ..Default::default()
+ });
+ }
+}
+
+fn image_view_dimension(dim: naga::ImageDimension, arrayed: bool) -> wgpu::TextureViewDimension {
+ match (dim, arrayed) {
+ (naga::ImageDimension::D1, false) => wgpu::TextureViewDimension::D1,
+ (naga::ImageDimension::D2, false) => wgpu::TextureViewDimension::D2,
+ (naga::ImageDimension::D2, true) => wgpu::TextureViewDimension::D2Array,
+ (naga::ImageDimension::D3, false) => wgpu::TextureViewDimension::D3,
+ (naga::ImageDimension::Cube, false) => wgpu::TextureViewDimension::Cube,
+ (naga::ImageDimension::Cube, true) => wgpu::TextureViewDimension::CubeArray,
+ _ => wgpu::TextureViewDimension::D2,
+ }
+}
+
+fn image_sample_type(class: naga::ImageClass) -> wgpu::TextureSampleType {
+ match class {
+ naga::ImageClass::Sampled { kind, .. } => match kind {
+ naga::ScalarKind::Float => wgpu::TextureSampleType::Float { filterable: true },
+ naga::ScalarKind::Sint => wgpu::TextureSampleType::Sint,
+ naga::ScalarKind::Uint => wgpu::TextureSampleType::Uint,
+ _ => wgpu::TextureSampleType::Float { filterable: true },
+ },
+ naga::ImageClass::Depth { .. } => wgpu::TextureSampleType::Depth,
+ naga::ImageClass::Storage { .. } | naga::ImageClass::External => {
+ wgpu::TextureSampleType::Float { filterable: false }
+ }
+ }
+}
+
+fn image_multisampled(class: naga::ImageClass) -> bool {
+ matches!(
+ class,
+ naga::ImageClass::Sampled { multi: true, .. } | naga::ImageClass::Depth { multi: true }
+ )
+}
+
+/// Manages uniform buffer data and texture/sampler bindings for all globals in a shader.
pub struct UniformCache {
pub module: naga::Module,
pub layouter: Layouter,
uniforms: HashMap<String, UniformMember>,
buffers: Vec<BufferState>,
+ textures: Vec<TextureSlot>,
+ samplers: Vec<SamplerSlot>,
pub bind_group_layout: Option<wgpu::BindGroupLayout>,
pub bind_group: Option<wgpu::BindGroup>,
dirty: bool,
@@ -416,12 +600,17 @@ struct UniformMember {
}
struct BufferState {
- group: u32,
binding: u32,
data: Vec<u8>,
gpu_buffer: Option<wgpu::Buffer>,
}
+impl Default for UniformCache {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl UniformCache {
pub fn new() -> Self {
Self {
@@ -429,13 +618,16 @@ impl UniformCache {
layouter: Layouter::default(),
uniforms: HashMap::new(),
buffers: Vec::new(),
+ textures: Vec::new(),
+ samplers: Vec::new(),
bind_group_layout: None,
bind_group: None,
dirty: false,
}
}
- /// Rebuild from a new shader module, transferring compatible uniform values.
+ /// Rebuild from a new shader module, transferring compatible uniform values
+ /// and texture associations.
pub fn refresh(&mut self, mut new_module: naga::Module, device: &wgpu::Device) {
ensure_component_types(&mut new_module);
let mut new_layouter = Layouter::default();
@@ -444,43 +636,108 @@ impl UniformCache {
let old_module = std::mem::take(&mut self.module);
let old_uniforms = std::mem::take(&mut self.uniforms);
let old_buffers = std::mem::take(&mut self.buffers);
+ let old_textures = std::mem::take(&mut self.textures);
let mut new_uniforms: HashMap<String, UniformMember> = HashMap::new();
let mut new_buffers: Vec<BufferState> = Vec::new();
+ let mut new_textures: Vec<TextureSlot> = Vec::new();
+ let mut new_samplers: Vec<SamplerSlot> = Vec::new();
for (_handle, var) in new_module.global_variables.iter() {
- if var.space != naga::AddressSpace::Uniform {
- continue;
- }
let binding = match &var.binding {
Some(b) => b,
None => continue,
};
- let layout = new_layouter[var.ty];
- let buffer_idx = new_buffers.len();
+ match var.space {
+ naga::AddressSpace::Uniform => {
+ let layout = new_layouter[var.ty];
+ let buffer_idx = new_buffers.len();
- let data = vec![0u8; layout.size as usize];
- new_buffers.push(BufferState {
- group: binding.group,
- binding: binding.binding,
- data,
- gpu_buffer: None,
- });
+ let data = vec![0u8; layout.size as usize];
+ new_buffers.push(BufferState {
+ binding: binding.binding,
+ data,
+ gpu_buffer: None,
+ });
- if let Some(ref name) = var.name {
- new_uniforms.insert(
- name.clone(),
- UniformMember {
- buffer_idx,
- ty: var.ty,
- offset: 0,
- size: layout.size as usize,
- },
- );
+ if let Some(ref name) = var.name {
+ new_uniforms.insert(
+ name.clone(),
+ UniformMember {
+ buffer_idx,
+ ty: var.ty,
+ offset: 0,
+ size: layout.size as usize,
+ },
+ );
+ }
+ }
+ naga::AddressSpace::Handle => {
+ let ty_inner = &new_module.types[var.ty].inner;
+ match *ty_inner {
+ naga::TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ let name = var.name.clone().unwrap_or_default();
+ let view_dimension = image_view_dimension(dim, arrayed);
+ let sample_type = image_sample_type(class);
+ let multisampled = image_multisampled(class);
+
+ let mut slot = TextureSlot::new(
+ device,
+ name.clone(),
+ binding.binding,
+ view_dimension,
+ sample_type,
+ multisampled,
+ );
+
+ // Transfer TSV association from old texture with same name
+ if let Some(old) = old_textures.iter().find(|t| t.name == name) {
+ if old.view_dimension == slot.view_dimension
+ && old.sample_type == slot.sample_type
+ {
+ slot.tsv_name = old.tsv_name.clone();
+ slot.tsv_registered = old.tsv_registered;
+ if old.width > 1 || old.height > 1 {
+ slot.resize(device, old.width, old.height);
+ }
+ }
+ }
+
+ new_textures.push(slot);
+ }
+ naga::TypeInner::Sampler { comparison } => {
+ let binding_type = if comparison {
+ wgpu::SamplerBindingType::Comparison
+ } else {
+ wgpu::SamplerBindingType::Filtering
+ };
+ let name = var.name.clone().unwrap_or_default();
+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ label: Some(&name),
+ mag_filter: wgpu::FilterMode::Linear,
+ min_filter: wgpu::FilterMode::Linear,
+ ..Default::default()
+ });
+ new_samplers.push(SamplerSlot {
+ name,
+ binding: binding.binding,
+ sampler,
+ binding_type,
+ });
+ }
+ _ => {}
+ }
+ }
+ _ => {}
}
}
+ // Transfer compatible buffer data from old shader
for (name, new_member) in &new_uniforms {
if let Some(old_member) = old_uniforms.get(name) {
if old_member.size == new_member.size
@@ -495,9 +752,10 @@ impl UniformCache {
}
}
+ // Create GPU buffers
for buf in &mut new_buffers {
let gpu_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some(&format!("uniform@{}:{}", buf.group, buf.binding)),
+ label: Some(&format!("uniform@{}", buf.binding)),
size: buf.data.len() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
@@ -505,54 +763,118 @@ impl UniformCache {
buf.gpu_buffer = Some(gpu_buffer);
}
- let bind_group_layout = if !new_buffers.is_empty() {
- let entries: Vec<wgpu::BindGroupLayoutEntry> = new_buffers
- .iter()
- .map(|buf| wgpu::BindGroupLayoutEntry {
- binding: buf.binding,
- visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: false,
- min_binding_size: None,
- },
- count: None,
- })
- .collect();
- Some(
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("uniform_bind_group_layout"),
- entries: &entries,
- }),
- )
- } else {
- None
- };
-
- let bind_group = bind_group_layout.as_ref().map(|layout| {
- let entries: Vec<wgpu::BindGroupEntry> = new_buffers
- .iter()
- .map(|buf| wgpu::BindGroupEntry {
- binding: buf.binding,
- resource: buf.gpu_buffer.as_ref().unwrap().as_entire_binding(),
- })
- .collect();
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("uniform_bind_group"),
- layout,
- entries: &entries,
- })
- });
-
self.module = new_module;
self.layouter = new_layouter;
self.uniforms = new_uniforms;
self.buffers = new_buffers;
- self.bind_group_layout = bind_group_layout;
- self.bind_group = bind_group;
+ self.textures = new_textures;
+ self.samplers = new_samplers;
+ self.build_bind_group(device);
self.dirty = true;
}
+ pub fn texture_slot_mut(&mut self, name: &str) -> Option<&mut TextureSlot> {
+ self.textures.iter_mut().find(|t| t.name == name)
+ }
+
+ pub fn texture_slots_mut(&mut self) -> &mut [TextureSlot] {
+ &mut self.textures
+ }
+
+ pub fn sampler_slot_mut(&mut self, name: &str) -> Option<&mut SamplerSlot> {
+ self.samplers.iter_mut().find(|s| s.name == name)
+ }
+
+ /// Rebuild the bind group (e.g. after a texture resize).
+ pub fn rebuild_bind_group(&mut self, device: &wgpu::Device) {
+ self.build_bind_group(device);
+ }
+
+ fn build_bind_group(&mut self, device: &wgpu::Device) {
+ let has_bindings =
+ !self.buffers.is_empty() || !self.textures.is_empty() || !self.samplers.is_empty();
+
+ if !has_bindings {
+ self.bind_group_layout = None;
+ self.bind_group = None;
+ return;
+ }
+
+ let mut layout_entries: Vec<wgpu::BindGroupLayoutEntry> = Vec::new();
+
+ for buf in &self.buffers {
+ layout_entries.push(wgpu::BindGroupLayoutEntry {
+ binding: buf.binding,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ });
+ }
+
+ for tex in &self.textures {
+ layout_entries.push(wgpu::BindGroupLayoutEntry {
+ binding: tex.binding,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: tex.sample_type,
+ view_dimension: tex.view_dimension,
+ multisampled: tex.multisampled,
+ },
+ count: None,
+ });
+ }
+
+ for smp in &self.samplers {
+ layout_entries.push(wgpu::BindGroupLayoutEntry {
+ binding: smp.binding,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(smp.binding_type),
+ count: None,
+ });
+ }
+
+ let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("uniform_bind_group_layout"),
+ entries: &layout_entries,
+ });
+
+ let mut bg_entries: Vec<wgpu::BindGroupEntry> = Vec::new();
+
+ for buf in &self.buffers {
+ bg_entries.push(wgpu::BindGroupEntry {
+ binding: buf.binding,
+ resource: buf.gpu_buffer.as_ref().unwrap().as_entire_binding(),
+ });
+ }
+
+ for tex in &self.textures {
+ bg_entries.push(wgpu::BindGroupEntry {
+ binding: tex.binding,
+ resource: wgpu::BindingResource::TextureView(&tex.view),
+ });
+ }
+
+ for smp in &self.samplers {
+ bg_entries.push(wgpu::BindGroupEntry {
+ binding: smp.binding,
+ resource: wgpu::BindingResource::Sampler(&smp.sampler),
+ });
+ }
+
+ let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("uniform_bind_group"),
+ layout: &bgl,
+ entries: &bg_entries,
+ });
+
+ self.bind_group_layout = Some(bgl);
+ self.bind_group = Some(bg);
+ }
+
pub fn get(&mut self, name: &str) -> Option<UniformRef<'_>> {
// Split the borrow: get member info first, then borrow buffer data
let member_info = self.uniforms.get(name)?;
diff --git a/test.sh b/test.sh
index 54465bc..ccc5e8f 100755
--- a/test.sh
+++ b/test.sh
@@ -1,29 +1,45 @@
#!/bin/bash
+set -euo pipefail
+
HOST=localhost
PORT=9000
+TSV_SRC=test-vid
+
+PIDS=()
+cleanup() {
+ for pid in "${PIDS[@]}"; do
+ kill "$pid" 2>/dev/null || true
+ done
+ wait 2>/dev/null
+}
+trap cleanup EXIT
+
+spawn() {
+ BIN="$1"
+ shift
+ cargo run --bin "$BIN" -- "$@" &
+ PIDS+=($!)
+}
+
+spawn tsv-video-stream --name "$TSV_SRC" -- \
+ -f lavfi -i "testsrc=size=1920x1080:rate=60"
+
+spawn wgsl-render --continuous
+
+sleep 2
+
SHADER='
-struct Uniforms {
- time: f32,
-};
-@group(1) @binding(0) var<uniform> u: Uniforms;
+@group(1) @binding(0) var video: texture_2d<f32>;
+@group(1) @binding(1) var s: sampler;
@fragment
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
- let p = uv * 2.0 - 1.0;
- let col = 0.5 + 0.5 * cos(u.time + vec3<f32>(p.x, p.y, p.x) + vec3<f32>(0.0, 2.0, 4.0));
- return vec4<f32>(col, 1.0);
+ return textureSample(video, s, uv).rrra;
}
'
+oscsend "$HOST" "$PORT" /shader s "$SHADER"
+oscsend "$HOST" "$PORT" /texture/video s "$TSV_SRC"
+oscsend "$HOST" "$PORT" /sampler/sampler ss nearest clamp
-echo "Loading shader..."
-oscsend $HOST $PORT /shader s "$SHADER"
-sleep 0.5
-
-echo "Animating time uniform..."
-t=0.0
-while true; do
- oscsend $HOST $PORT /uniform/u/time f "$t"
- t=$(echo "$t + 0.016" | bc)
- sleep 0.016
-done
+cargo run --bin tsv-view