diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-04-16 15:29:50 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-05-14 14:46:13 +0000 |
| commit | 5925137f3b41cb3a87f098604ef482bced91341e (patch) | |
| tree | 613ee305358e319bab5d88a85cc923b4870d73f1 | |
| parent | refactor (diff) | |
| download | wgsl-view-5925137f3b41cb3a87f098604ef482bced91341e.tar.gz wgsl-view-5925137f3b41cb3a87f098604ef482bced91341e.zip | |
tsv integration, split binaries
| -rw-r--r-- | Cargo.lock | 870 | ||||
| -rw-r--r-- | Cargo.toml | 22 | ||||
| -rw-r--r-- | rust-toolchain.toml | 2 | ||||
| -rw-r--r-- | src/bin/tsv_video_stream.rs | 185 | ||||
| -rw-r--r-- | src/bin/tsv_view.rs (renamed from src/main.rs) | 166 | ||||
| -rw-r--r-- | src/bin/wgsl_render.rs | 281 | ||||
| -rw-r--r-- | src/gpu.rs | 141 | ||||
| -rw-r--r-- | src/lib.rs | 5 | ||||
| -rw-r--r-- | src/osc.rs | 60 | ||||
| -rw-r--r-- | src/renderer.rs | 122 | ||||
| -rw-r--r-- | src/uniform.rs | 456 | ||||
| -rwxr-xr-x | test.sh | 52 |
12 files changed, 2042 insertions, 320 deletions
@@ -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" @@ -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; @@ -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)?; @@ -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 |
