diff --git a/Cargo.lock b/Cargo.lock index 2788c70..33618ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" [[package]] name = "ahash" @@ -48,7 +48,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "thiserror 1.0.69", ] @@ -154,24 +154,24 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.27" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "jobserver", "libc", @@ -299,6 +299,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -312,8 +322,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] @@ -325,7 +335,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", "libc", ] @@ -337,9 +358,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cursor-icon" @@ -399,18 +420,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - [[package]] name = "foldhash" version = "0.1.5" @@ -497,15 +506,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "glam" -version = "0.30.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" -dependencies = [ - "bytemuck", -] - [[package]] name = "glow" version = "0.16.0" @@ -583,7 +583,6 @@ name = "gui" version = "0.1.0" dependencies = [ "bytemuck", - "glam", "notify", "pollster", "rhai", @@ -604,19 +603,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] -[[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.5.2" @@ -631,9 +624,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -760,7 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -771,13 +764,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", ] [[package]] @@ -794,9 +787,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -831,22 +824,22 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] [[package]] name = "metal" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.9.1", "block", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types", "log", "objc", @@ -867,25 +860,26 @@ dependencies = [ [[package]] name = "naga" -version = "25.0.1" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" dependencies = [ "arrayvec", "bit-set", "bitflags 2.9.1", + "cfg-if", "cfg_aliases", "codespan-reporting", "half", "hashbrown", "hexf-parse", "indexmap", + "libm", "log", "num-traits", "once_cell", "rustc-hash", "spirv", - "strum", "thiserror 2.0.12", "unicode-ident", ] @@ -899,7 +893,7 @@ dependencies = [ "bitflags 2.9.1", "jni-sys", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -911,15 +905,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -931,12 +916,11 @@ dependencies = [ [[package]] name = "notify" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.9.1", - "filetime", "fsevent-sys", "inotify", "kqueue", @@ -945,7 +929,7 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1218,18 +1202,18 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.6.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" dependencies = [ "num-traits", ] [[package]] name = "owned_ttf_parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ "ttf-parser", ] @@ -1252,7 +1236,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] @@ -1303,17 +1287,16 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" -version = "3.8.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -1328,6 +1311,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "presser" version = "0.3.1" @@ -1405,9 +1397,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -1421,7 +1413,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "rhai" version = "1.22.2" -source = "git+https://github.com/rhaiscript/rhai#08baaaf4e2d6767f2da4d89a4f91352f8637b45a" +source = "git+https://github.com/rhaiscript/rhai#9508db6a828d8d8b2ac868054d985098c62ba904" dependencies = [ "ahash", "bitflags 2.9.1", @@ -1436,8 +1428,8 @@ dependencies = [ [[package]] name = "rhai_codegen" -version = "3.0.0" -source = "git+https://github.com/rhaiscript/rhai#08baaaf4e2d6767f2da4d89a4f91352f8637b45a" +version = "3.1.0" +source = "git+https://github.com/rhaiscript/rhai#9508db6a828d8d8b2ac868054d985098c62ba904" dependencies = [ "proc-macro2", "quote", @@ -1465,15 +1457,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1544,9 +1536,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -1629,28 +1621,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "syn" version = "2.0.104" @@ -1912,13 +1882,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.44", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -1926,12 +1896,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.1", - "rustix 0.38.44", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -1949,20 +1919,20 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.8", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -1972,9 +1942,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -1985,9 +1955,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -1998,9 +1968,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml", @@ -2009,9 +1979,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -2041,12 +2011,13 @@ dependencies = [ [[package]] name = "wgpu" -version = "25.0.2" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" dependencies = [ "arrayvec", "bitflags 2.9.1", + "cfg-if", "cfg_aliases", "document-features", "hashbrown", @@ -2069,9 +2040,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "25.0.2" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" +checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" dependencies = [ "arrayvec", "bit-set", @@ -2100,36 +2071,36 @@ dependencies = [ [[package]] name = "wgpu-core-deps-apple" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "25.0.2" +version = "26.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" +checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e" dependencies = [ "android_system_properties", "arrayvec", @@ -2140,7 +2111,7 @@ dependencies = [ "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types", + "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -2154,11 +2125,12 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys 0.5.0+25.2.9519653", + "ndk-sys", "objc", "ordered-float", "parking_lot", "portable-atomic", + "portable-atomic-util", "profiling", "range-alloc", "raw-window-handle", @@ -2174,9 +2146,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "25.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" dependencies = [ "bitflags 2.9.1", "bytemuck", @@ -2240,6 +2212,12 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-result" version = "0.2.0" @@ -2292,7 +2270,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2343,10 +2321,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2539,9 +2518,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" -version = "0.30.11" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", @@ -2552,7 +2531,7 @@ dependencies = [ "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -2591,9 +2570,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -2641,9 +2620,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xkbcommon-dl" @@ -2666,9 +2645,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index 0086cff..776bc92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bytemuck = "1.23.1" -glam = { version = "0.30.4", features = ["bytemuck"] } pollster = "0.4.0" -wgpu = "25.0.2" winit = "0.30.11" rhai = { git = "https://github.com/rhaiscript/rhai", features = ["f32_float"] } notify = "8.0.0" +wgpu = "26.0.1" +bytemuck = "1.23.1" diff --git a/src/layout/mod.rs b/src/layout/mod.rs new file mode 100644 index 0000000..8845245 --- /dev/null +++ b/src/layout/mod.rs @@ -0,0 +1,66 @@ +mod node; +pub use node::*; + +use crate::primitive::{Color, Painter}; + +pub type UIColor = Color; + +pub trait UINode: 'static { + fn draw(&self, painter: &mut Painter); +} + +pub struct UI { + base: Box, +} + +impl UI { + pub fn to_primitives(&self) -> Painter { + let mut painter = Painter::default(); + self.base.draw(&mut painter); + painter + } +} + +impl From for UI { + fn from(node: N) -> Self { + Self { + base: Box::new(node), + } + } +} + +impl UINode for Box { + fn draw(&self, painter: &mut Painter) { + self.as_ref().draw(painter); + } +} + +pub trait NodeArray { + fn to_arr(self) -> [Box; LEN]; +} + +// I hate this language it's so bad why do I even use it +macro_rules! impl_node_arr { + ($n:expr;$($T:tt)+) => { + impl<$($T: UINode,)*> NodeArray<$n> for ($($T,)*) { + fn to_arr(self) -> [Box; $n] { + #[allow(non_snake_case)] + let ($($T,)*) = self; + [$(Box::new($T),)*] + } + } + }; +} + +impl_node_arr!(1;A); +impl_node_arr!(2;A B); +impl_node_arr!(3;A B C); +impl_node_arr!(4;A B C D); +impl_node_arr!(5;A B C D E); +impl_node_arr!(6;A B C D E F); +impl_node_arr!(7;A B C D E F G); +impl_node_arr!(8;A B C D E F G H); +impl_node_arr!(9;A B C D E F G H I); +impl_node_arr!(10;A B C D E F G H I J); +impl_node_arr!(11;A B C D E F G H I J K); +impl_node_arr!(12;A B C D E F G H I J K L); diff --git a/src/layout/node.rs b/src/layout/node.rs new file mode 100644 index 0000000..c3ae1b7 --- /dev/null +++ b/src/layout/node.rs @@ -0,0 +1,164 @@ +use std::ops::Range; + +use crate::{ + primitive::{Axis, Painter, RoundedRectData, UIRegion}, + NodeArray, UIColor, UINode, +}; + +#[derive(Clone, Copy)] +pub struct RoundedRect { + pub color: UIColor, + pub radius: f32, + pub thickness: f32, + pub inner_radius: f32, +} + +impl RoundedRect { + pub fn color(mut self, color: UIColor) -> Self { + self.color = color; + self + } +} + +impl UINode for RoundedRect { + fn draw(&self, painter: &mut Painter) { + painter.write(RoundedRectData { + color: self.color, + radius: self.radius, + thickness: self.thickness, + inner_radius: self.inner_radius, + }); + } +} + +pub struct Span { + pub elements: Vec<(Range, Box)>, + pub axis: Axis, +} + +impl UINode for Span { + fn draw(&self, painter: &mut Painter) { + for (span, child) in &self.elements { + let mut sub_region = UIRegion::full(); + let view = sub_region.axis_mut(self.axis); + *view.top_left.anchor = span.start; + *view.bot_right.anchor = span.end; + painter.draw_within(child, sub_region); + } + } +} + +impl Span { + pub fn proportioned( + axis: Axis, + ratios: [impl UINum; LEN], + elements: impl NodeArray, + ) -> Self { + let ratios = ratios.map(|r| r.to_f32()); + let total: f32 = ratios.iter().sum(); + let mut start = 0.0; + Self { + elements: elements + .to_arr() + .into_iter() + .zip(ratios) + .map(|(e, r)| { + let end = start + r / total; + let res = (start..end, e); + start = end; + res + }) + .collect(), + axis, + } + } +} + +pub struct Regioned { + region: UIRegion, + inner: N, +} + +impl UINode for Regioned { + fn draw(&self, painter: &mut Painter) { + painter.region.select(&self.region); + self.inner.draw(painter); + } +} + +pub struct Padding { + left: f32, + right: f32, + top: f32, + bottom: f32, +} + +impl Padding { + pub fn uniform(amt: f32) -> Self { + Self { + left: amt, + right: amt, + top: amt, + bottom: amt, + } + } + pub fn region(&self) -> UIRegion { + let mut region = UIRegion::full(); + region.top_left.offset.x += self.left; + region.top_left.offset.y += self.top; + region.bot_right.offset.x -= self.right; + region.bot_right.offset.y -= self.bottom; + region + } +} + +impl From for Padding { + fn from(amt: T) -> Self { + Self::uniform(amt.to_f32()) + } +} + +pub trait NodeUtil: Sized + UINode { + fn pad(self, padding: impl Into) -> Regioned; +} + +impl NodeUtil for T { + fn pad(self, padding: impl Into) -> Regioned { + Regioned { + region: padding.into().region(), + inner: self, + } + } +} + +pub trait NodeArrayUtil { + fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span; +} + +impl, const LEN: usize> NodeArrayUtil for T { + fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span { + Span::proportioned(axis, ratios, self) + } +} + +pub trait UINum { + fn to_f32(self) -> f32; +} + +impl UINum for f32 { + fn to_f32(self) -> f32 { + self + } +} + +impl UINum for u32 { + fn to_f32(self) -> f32 { + self as f32 + } +} + +impl UINum for i32 { + fn to_f32(self) -> f32 { + self as f32 + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e2ed257 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +#![feature(macro_metavar_expr_concat)] +#![feature(const_ops)] +#![feature(const_trait_impl)] +#![feature(const_from)] + +mod layout; +mod render; + +pub use layout::*; +pub use render::*; diff --git a/src/main.rs b/src/main.rs index e7795ac..3d74eae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,5 @@ -use std::sync::Arc; - -use app::App; -use render::Renderer; -use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; - -mod app; -mod render; +mod testing; fn main() { - App::run(); -} - -pub struct Client { - window: Arc, - renderer: Renderer, -} - -impl Client { - pub fn new(window: Arc) -> Self { - let renderer = Renderer::new(window.clone()); - Self { - window, - renderer, - } - } - - pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { - match event { - WindowEvent::CloseRequested => event_loop.exit(), - WindowEvent::RedrawRequested => self.renderer.draw(), - WindowEvent::Resized(size) => self.renderer.resize(&size), - _ => (), - } - } + testing::main(); } diff --git a/src/render/data.rs b/src/render/data.rs new file mode 100644 index 0000000..32cf6bb --- /dev/null +++ b/src/render/data.rs @@ -0,0 +1,34 @@ +use crate::primitive::UIRegion; +use wgpu::VertexAttribute; + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct PrimitiveInstance { + pub region: UIRegion, + pub ptr: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] +pub struct WindowUniform { + pub width: f32, + pub height: f32, +} + +impl PrimitiveInstance { + const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![ + 0 => Float32x2, + 1 => Float32x2, + 2 => Float32x2, + 3 => Float32x2, + 4 => Uint32, + ]; + + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &Self::ATTRIBS, + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index ad5b591..1391ad8 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,199 +1,184 @@ -use std::sync::{ - mpsc::{channel, Receiver}, - Arc, +use crate::{ + render::{data::PrimitiveInstance, util::ArrBuf}, + UI, }; - -use notify::{ - event::{CreateKind, ModifyKind}, - EventKind, RecommendedWatcher, Watcher, +use data::WindowUniform; +use wgpu::{ + util::{BufferInitDescriptor, DeviceExt}, + *, }; -use pollster::FutureExt; -use primitive::{RoundedRect, UIPos}; -use shape::ShapePipeline; -use wgpu::util::StagingBelt; -use winit::{dpi::PhysicalSize, window::Window}; +use winit::dpi::PhysicalSize; +mod data; pub mod primitive; -mod shape; +mod util; -pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK; +const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); -pub struct Renderer { - surface: wgpu::Surface<'static>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - adapter: wgpu::Adapter, - encoder: wgpu::CommandEncoder, - staging_belt: StagingBelt, - shape_pipeline: ShapePipeline, - recv: Receiver>, +pub struct UIRenderNode { + bind_group_layout: BindGroupLayout, + bind_group: BindGroup, + pipeline: RenderPipeline, + + window_buffer: Buffer, + instance: ArrBuf, + data: ArrBuf, } -impl Renderer { - pub fn new(window: Arc) -> Self { - let size = window.inner_size(); +impl UIRenderNode { + pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + if self.instance.len() != 0 { + pass.set_vertex_buffer(0, self.instance.buffer.slice(..)); + pass.draw(0..4, 0..self.instance.len() as u32); + } + } - let (send, recv) = channel(); + pub fn update(&mut self, device: &Device, queue: &Queue, ui: &UI) { + let primitives = ui.to_primitives(); + self.instance.update(device, queue, &primitives.instances); + self.data.update(device, queue, &primitives.data); + self.bind_group = Self::bind_group( + device, + &self.bind_group_layout, + &self.window_buffer, + &self.data.buffer, + ) + } - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("data/test.rhai"); - let w2 = window.clone(); - std::thread::spawn(move || { - let (tx, rx) = channel(); + pub fn resize(&mut self, size: &PhysicalSize, queue: &Queue) { + let slice = &[WindowUniform { + width: size.width as f32, + height: size.height as f32, + }]; + queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice)); + } - let mut watcher = RecommendedWatcher::new(tx, Default::default()).unwrap(); - watcher - .watch("data".as_ref(), notify::RecursiveMode::Recursive) - .unwrap(); - - for res in rx { - let Ok(ev) = res else { - continue; - }; - if !ev.paths.contains(&path) { - continue; - } - if !matches!( - ev.kind, - EventKind::Create(CreateKind::File) | EventKind::Modify(ModifyKind::Data(_)) - ) { - continue; - } - println!("reloaded {ev:?}"); - let mut engine = rhai::Engine::new(); - engine.build_type::(); - engine.register_type::(); - engine.register_fn("anchor_offset", UIPos::anchor_offset); - engine.register_fn("rect", RoundedRect::default); - let Ok(code) = std::fs::read_to_string(&path) else { - continue; - }; - let rects = engine - .eval::(&code) - .map_err(|e| println!("{e:?}")) - .unwrap_or_default() - .into_iter() - .filter_map(|d| match rhai::Dynamic::try_cast(d.clone()) { - Some(v) => Some(v), - None => { - println!("{d:?} is not a RoundedRect"); - None - } - }) - .collect::>(); - send.send(rects).unwrap(); - w2.request_redraw(); - } + pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self { + let shader = device.create_shader_module(ShaderModuleDescriptor { + label: Some("UI Shape Shader"), + source: ShaderSource::Wgsl(SHAPE_SHADER.into()), }); - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() + let window_uniform = WindowUniform::default(); + let window_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: Some("Camera Buffer"), + contents: bytemuck::cast_slice(&[window_uniform]), + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, }); - let surface = instance - .create_surface(window.clone()) - .expect("Could not create window surface!"); + let instance = ArrBuf::new( + device, + BufferUsages::VERTEX | BufferUsages::COPY_DST, + "instance", + ); + let data = ArrBuf::new( + device, + BufferUsages::STORAGE | BufferUsages::COPY_DST, + "data", + ); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .block_on() - .expect("Could not get adapter!"); + let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + label: Some("camera_bind_group_layout"), + }); - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - ..Default::default() - }) - .block_on() - .expect("Could not get device!"); + let bind_group = Self::bind_group(device, &bind_group_layout, &window_buffer, &data.buffer); - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - desired_maximum_frame_latency: 2, - view_formats: vec![], - }; - - surface.configure(&device, &config); - - let staging_belt = StagingBelt::new(4096 * 4); - let encoder = Self::create_encoder(&device); - - let shape_pipeline = ShapePipeline::new(&device, &config); + let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("UI Shape Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("UI Shape Pipeline"), + layout: Some(&pipeline_layout), + vertex: VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[PrimitiveInstance::desc()], + compilation_options: Default::default(), + }, + fragment: Some(FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(ColorTargetState { + format: config.format, + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: PrimitiveState { + topology: PrimitiveTopology::TriangleStrip, + strip_index_format: None, + front_face: FrontFace::Cw, + cull_mode: Some(Face::Back), + polygon_mode: PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); Self { - surface, - device, - queue, - config, - adapter, - encoder, - staging_belt, - shape_pipeline, - recv, + bind_group_layout, + bind_group, + pipeline, + window_buffer, + instance, + data, } } - fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder { - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), + pub fn bind_group( + device: &Device, + layout: &BindGroupLayout, + window_buffer: &Buffer, + data: &Buffer, + ) -> BindGroup { + device.create_bind_group(&BindGroupDescriptor { + layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: window_buffer.as_entire_binding(), + }, + BindGroupEntry { + binding: 1, + resource: data.as_entire_binding(), + }, + ], + label: Some("ui_bind_group"), }) } - - pub fn draw(&mut self) { - if let Some(rects) = self.recv.try_iter().last() { - self.shape_pipeline - .update(&self.device, &self.queue, &rects); - } - let output = self.surface.get_current_texture().unwrap(); - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device)); - { - let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(CLEAR_COLOR), - store: wgpu::StoreOp::Store, - }, - })], - ..Default::default() - }); - self.shape_pipeline.draw(render_pass); - } - - self.queue.submit(std::iter::once(encoder.finish())); - self.staging_belt.finish(); - output.present(); - self.staging_belt.recall(); - } - - pub fn resize(&mut self, size: &PhysicalSize) { - self.config.width = size.width; - self.config.height = size.height; - self.surface.configure(&self.device, &self.config); - self.shape_pipeline.resize(size, &self.queue); - } } diff --git a/src/render/primitive.rs b/src/render/primitive.rs deleted file mode 100644 index 92090c6..0000000 --- a/src/render/primitive.rs +++ /dev/null @@ -1,73 +0,0 @@ -use glam::Vec2; -use rhai::{CustomType, TypeBuilder}; - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, CustomType)] -pub struct RoundedRect { - pub top_left: UIPos, - pub bottom_right: UIPos, - pub colors: [[f32; 4]; 4], - pub radius: f32, - pub inner_radius: f32, - pub thickness: f32, -} - -impl Default for RoundedRect { - fn default() -> Self { - Self { - top_left: Default::default(), - bottom_right: Default::default(), - colors: [ - [1.0, 0.0, 0.0, 1.0], - [0.0, 1.0, 0.0, 1.0], - [0.0, 0.0, 1.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ], - radius: Default::default(), - inner_radius: Default::default(), - thickness: Default::default(), - } - } -} - -#[derive(PartialEq, Clone)] -pub struct Text { - pub content: String, - pub align: Align, - pub pos: Vec2, - pub bounds: (f32, f32), -} - -impl Text { - pub fn empty() -> Self { - Self { - content: String::new(), - align: Align::Left, - pos: Vec2::default(), - bounds: (0.0, 0.0), - } - } -} - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] -pub struct UIPos { - pub anchor: Vec2, - pub offset: Vec2, -} - -impl UIPos { - pub fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self { - Self { - anchor: Vec2::new(anchor_x, anchor_y), - offset: Vec2::new(offset_x, offset_y), - } - } -} - -#[derive(PartialEq, Clone)] -pub enum Align { - Left, - Right, - Center, -} diff --git a/src/render/primitive/color.rs b/src/render/primitive/color.rs new file mode 100644 index 0000000..131c1ba --- /dev/null +++ b/src/render/primitive/color.rs @@ -0,0 +1,47 @@ +#![allow(clippy::multiple_bound_locations)] + +#[repr(C)] +#[derive(Clone, Copy, bytemuck::Zeroable)] +pub struct Color { + r: T, + g: T, + b: T, + a: T, +} + +impl Color { + pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN); + pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX); + + pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN); + pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN); + pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN); + pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN); + pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN); + pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX); + pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX); + pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX); +} + +impl Color { + pub const fn new(r: T, g: T, b: T, a: T) -> Self { + Self { r, g, b, a } + } + pub const fn rgb(r: T, g: T, b: T) -> Self { + Self { r, g, b, a: T::MAX } + } +} + +pub trait ColorNum { + const MIN: Self; + const MID: Self; + const MAX: Self; +} + +impl ColorNum for u8 { + const MIN: Self = u8::MIN; + const MID: Self = u8::MAX / 2; + const MAX: Self = u8::MAX; +} + +unsafe impl bytemuck::Pod for Color {} diff --git a/src/render/primitive/def.rs b/src/render/primitive/def.rs new file mode 100644 index 0000000..b390d73 --- /dev/null +++ b/src/render/primitive/def.rs @@ -0,0 +1,14 @@ +use crate::primitive::{Color, PrimitiveData}; + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct RoundedRectData { + pub color: Color, + pub radius: f32, + pub thickness: f32, + pub inner_radius: f32, +} + +impl PrimitiveData for RoundedRectData { + const DISCRIM: u32 = 0; +} diff --git a/src/render/primitive/format.rs b/src/render/primitive/format.rs new file mode 100644 index 0000000..a9578cb --- /dev/null +++ b/src/render/primitive/format.rs @@ -0,0 +1,97 @@ +use crate::primitive::{point::point, Point}; + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] +pub struct UIPos { + pub anchor: Point, + pub offset: Point, +} + +impl UIPos { + pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self { + Self { + anchor: point(anchor_x, anchor_y), + offset: point(offset_x, offset_y), + } + } + + pub const fn top_left() -> Self { + Self::anchor_offset(0.0, 0.0, 0.0, 0.0) + } + + pub const fn bottom_right() -> Self { + Self::anchor_offset(1.0, 1.0, 0.0, 0.0) + } + + pub const fn within(&self, region: &UIRegion) -> UIPos { + let range = region.bot_right.anchor - region.top_left.anchor; + let region_offset = region + .top_left + .offset + .lerp(region.bot_right.offset, self.anchor); + UIPos { + anchor: region.top_left.anchor + self.anchor * range, + offset: self.offset + region_offset, + } + } + + pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> { + match axis { + Axis::X => UIPosAxisView { + anchor: &mut self.anchor.x, + offset: &mut self.offset.x, + }, + Axis::Y => UIPosAxisView { + anchor: &mut self.anchor.y, + offset: &mut self.offset.y, + }, + } + } +} + +pub struct UIPosAxisView<'a> { + pub anchor: &'a mut f32, + pub offset: &'a mut f32, +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct UIRegion { + pub top_left: UIPos, + pub bot_right: UIPos, +} + +impl UIRegion { + pub const fn full() -> Self { + Self { + top_left: UIPos::top_left(), + bot_right: UIPos::bottom_right(), + } + } + pub fn within(&self, parent: &Self) -> Self { + Self { + top_left: self.top_left.within(parent), + bot_right: self.bot_right.within(parent), + } + } + pub fn select(&mut self, inner: &Self) { + *self = inner.within(self); + } + pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> { + UIRegionAxisView { + top_left: self.top_left.axis_mut(axis), + bot_right: self.bot_right.axis_mut(axis), + } + } +} + +pub struct UIRegionAxisView<'a> { + pub top_left: UIPosAxisView<'a>, + pub bot_right: UIPosAxisView<'a>, +} + +#[derive(Copy, Clone)] +pub enum Axis { + X, + Y, +} diff --git a/src/render/primitive/mod.rs b/src/render/primitive/mod.rs new file mode 100644 index 0000000..e0b6ae5 --- /dev/null +++ b/src/render/primitive/mod.rs @@ -0,0 +1,50 @@ +mod color; +mod def; +mod format; +mod point; + +pub use color::*; +pub use def::*; +pub use format::*; +pub use point::*; + +use crate::{render::data::PrimitiveInstance, UINode}; +use bytemuck::Pod; + +pub struct Painter { + pub region: UIRegion, + pub instances: Vec, + pub data: Vec, +} + +impl Default for Painter { + fn default() -> Self { + Self { + region: UIRegion::full(), + instances: Default::default(), + data: Default::default(), + } + } +} + +/// NOTE: Self must have at least u32 alignment +pub trait PrimitiveData: Pod { + const DISCRIM: u32; +} + +impl Painter { + pub fn write(&mut self, data: D) { + let ptr = self.data.len() as u32; + let region = self.region; + self.instances.push(PrimitiveInstance { region, ptr }); + self.data.push(D::DISCRIM); + self.data + .extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data])); + } + pub fn draw_within(&mut self, node: &impl UINode, region: UIRegion) { + let old = self.region; + self.region.select(®ion); + node.draw(self); + self.region = old; + } +} diff --git a/src/render/primitive/point.rs b/src/render/primitive/point.rs new file mode 100644 index 0000000..a84244f --- /dev/null +++ b/src/render/primitive/point.rs @@ -0,0 +1,84 @@ +use std::ops::*; + +#[repr(C)] +#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Point { + pub x: f32, + pub y: f32, +} + +pub const fn point(x: f32, y: f32) -> Point { + Point::new(x, y) +} + +impl Point { + pub const fn new(x: f32, y: f32) -> Self { + Self { x, y } + } + + pub const fn lerp(self, to: Self, amt: impl const Into) -> Self { + let amt = amt.into(); + Self { + x: lerp(self.x, to.x, amt.x), + y: lerp(self.y, to.y, amt.y), + } + } +} + +const fn lerp(x: f32, y: f32, amt: f32) -> f32 { + (1.0 - amt) * x + y * amt +} + +impl const From for Point { + fn from(v: f32) -> Self { + Self { x: v, y: v } + } +} + +macro_rules! impl_op_inner { + ($op:ident $fn:ident $opa:ident $fna:ident) => { + impl const $op for Point { + type Output = Self; + + fn $fn(self, rhs: Self) -> Self::Output { + Self { + x: self.x.$fn(rhs.x), + y: self.y.$fn(rhs.y), + } + } + } + impl $opa for Point { + fn $fna(&mut self, rhs: Self) { + self.x.$fna(rhs.x); + self.y.$fna(rhs.y); + } + } + impl const $op for Point { + type Output = Self; + + fn $fn(self, rhs: f32) -> Self::Output { + Self { + x: self.x.$fn(rhs), + y: self.y.$fn(rhs), + } + } + } + impl $opa for Point { + fn $fna(&mut self, rhs: f32) { + self.x.$fna(rhs); + self.y.$fna(rhs); + } + } + }; +} + +macro_rules! impl_op { + ($op:ident $fn:ident) => { + impl_op_inner!($op $fn ${concat($op,Assign)} ${concat($fn,_assign)}); + }; +} + +impl_op!(Add add); +impl_op!(Sub sub); +impl_op!(Mul mul); +impl_op!(Div div); diff --git a/src/render/shader.wgsl b/src/render/shader.wgsl new file mode 100644 index 0000000..b48ff5a --- /dev/null +++ b/src/render/shader.wgsl @@ -0,0 +1,110 @@ +@group(0) @binding(0) +var window: WindowUniform; +@group(0) @binding(1) +var data: array; + +struct WindowUniform { + dim: vec2, +}; + +struct InstanceInput { + @location(0) top_left_anchor: vec2, + @location(1) top_left_offset: vec2, + @location(2) bottom_right_anchor: vec2, + @location(3) bottom_right_offset: vec2, + @location(4) pointer: u32, +} + +struct RoundedRect { + color: u32, + radius: f32, + thickness: f32, + inner_radius: f32, +} + +struct VertexOutput { + @location(0) pointer: u32, + @location(1) top_left: vec2, + @location(2) bot_right: vec2, + @builtin(position) clip_position: vec4, +}; + +struct Region { + pos: vec2, + top_left: vec2, + bot_right: vec2, +} + +@vertex +fn vs_main( + @builtin(vertex_index) vi: u32, + in: InstanceInput, +) -> VertexOutput { + var out: VertexOutput; + + let top_left = in.top_left_anchor * window.dim + in.top_left_offset; + let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset; + let size = bot_right - top_left; + + var pos = top_left + vec2( + f32(vi % 2u), + f32(vi / 2u) + ) * size; + pos = pos / window.dim * 2.0 - 1.0; + out.clip_position = vec4(pos.x, -pos.y, 0.0, 1.0); + out.pointer = in.pointer; + out.top_left = top_left; + out.bot_right = bot_right; + + return out; +} + +@fragment +fn fs_main( + in: VertexOutput +) -> @location(0) vec4 { + let pos = in.clip_position.xy; + let ty = data[in.pointer]; + let dp = in.pointer + 1u; + let region = Region(pos, in.top_left, in.bot_right); + switch ty { + case 0u: { + return draw_rounded_rect(region, RoundedRect( + data[dp + 0u], + bitcast(data[dp + 1u]), + bitcast(data[dp + 2u]), + bitcast(data[dp + 3u]), + )); + } + default: {} + } + return vec4(1.0, 0.0, 1.0, 1.0); +} + +fn draw_rounded_rect(region: Region, rect: RoundedRect) -> vec4 { + var color = unpack4x8unorm(rect.color); + + let edge = 0.5; + + let size = region.bot_right - region.top_left; + let corner = size / 2.0; + let center = region.top_left + corner; + + let dist = distance_from_rect(region.pos, center, corner, rect.radius); + color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist); + + if rect.thickness > 0.0 { + let dist2 = distance_from_rect(region.pos, center, corner - rect.thickness, rect.inner_radius); + color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2); + } + + return color; +} + +fn distance_from_rect(pixel_pos: vec2, rect_center: vec2, rect_corner: vec2, radius: f32) -> f32 { + // vec from center to pixel + let p = pixel_pos - rect_center; + // vec from inner rect corner to pixel + let q = abs(p) - (rect_corner - radius); + return length(max(q, vec2(0.0, 0.0))) - radius; +} diff --git a/src/render/shape/data.rs b/src/render/shape/data.rs deleted file mode 100644 index 0200909..0000000 --- a/src/render/shape/data.rs +++ /dev/null @@ -1,74 +0,0 @@ -use wgpu::{RenderPass, VertexAttribute}; - -use crate::render::primitive::RoundedRect; - -pub struct RoundedRectBuffer { - buffer: wgpu::Buffer, - len: usize, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] -pub struct WindowUniform { - pub width: f32, - pub height: f32, -} - - -impl RoundedRectBuffer { - pub fn new(device: &wgpu::Device) -> Self { - Self { - buffer: Self::init_buf(device, 0), - len: 0, - } - } - pub fn update( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - rects: &[RoundedRect], - ) { - if self.len != rects.len() { - self.len = rects.len(); - self.buffer = Self::init_buf(device, std::mem::size_of_val(rects)); - } - queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(rects)); - } - fn init_buf(device: &wgpu::Device, size: usize) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Instance Buffer"), - size: size as u64, - mapped_at_creation: false, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }) - } - pub fn len(&self) -> usize { - self.len - } - pub fn set_in<'a>(&'a self, pass: &mut RenderPass<'a>) { - pass.set_vertex_buffer(0, self.buffer.slice(..)); - } -} - -impl RoundedRect { - const ATTRIBS: [VertexAttribute; 11] = wgpu::vertex_attr_array![ - 0 => Float32x2, - 1 => Float32x2, - 2 => Float32x2, - 3 => Float32x2, - 4 => Float32x4, - 5 => Float32x4, - 6 => Float32x4, - 7 => Float32x4, - 8 => Float32, - 9 => Float32, - 10 => Float32, - ]; - pub fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &Self::ATTRIBS, - } - } -} diff --git a/src/render/shape/layout.rs b/src/render/shape/layout.rs deleted file mode 100644 index f39ee13..0000000 --- a/src/render/shape/layout.rs +++ /dev/null @@ -1,106 +0,0 @@ -use wgpu::{ - util::{BufferInitDescriptor, DeviceExt}, - BufferUsages, -}; - -use crate::render::primitive::RoundedRect; - -use super::{ - data::{RoundedRectBuffer, WindowUniform}, - ShapeBuffers, ShapePipeline, SHAPE_SHADER, -}; - -impl ShapePipeline { - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self { - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("UI Shape Shader"), - source: wgpu::ShaderSource::Wgsl(SHAPE_SHADER.into()), - }); - - let window_uniform = WindowUniform::default(); - let window_buffer = device.create_buffer_init(&BufferInitDescriptor { - label: Some("Camera Buffer"), - contents: bytemuck::cast_slice(&[window_uniform]), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - }); - - let instance_buffer = RoundedRectBuffer::new(device); - - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("camera_bind_group_layout"), - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: window_buffer.as_entire_binding(), - }], - label: Some("camera_bind_group"), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("UI Shape Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("UI Shape Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[RoundedRect::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - strip_index_format: None, - front_face: wgpu::FrontFace::Cw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let buffers = ShapeBuffers { - window: window_buffer, - instance: instance_buffer, - }; - - Self { - bind_group, - pipeline, - buffers, - } - } -} diff --git a/src/render/shape/mod.rs b/src/render/shape/mod.rs deleted file mode 100644 index 4681abe..0000000 --- a/src/render/shape/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::render::primitive::RoundedRect; -use data::{RoundedRectBuffer, WindowUniform}; -use wgpu::{BindGroup, Buffer, RenderPass, RenderPipeline}; -use winit::dpi::PhysicalSize; - -mod data; -mod layout; - -pub const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); - -pub struct ShapeBuffers { - pub window: Buffer, - pub instance: RoundedRectBuffer, -} - -pub struct ShapePipeline { - pub bind_group: BindGroup, - pub pipeline: RenderPipeline, - - pub buffers: ShapeBuffers, -} - -impl ShapePipeline { - pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.bind_group, &[]); - if self.buffers.instance.len() != 0 { - self.buffers.instance.set_in(pass); - pass.draw(0..4, 0..self.buffers.instance.len() as u32); - } - } - - pub fn update(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, rects: &[RoundedRect]) { - self.buffers.instance.update(device, queue, rects); - } - - pub fn resize(&mut self, size: &PhysicalSize, queue: &wgpu::Queue) { - let slice = &[WindowUniform { - width: size.width as f32, - height: size.height as f32, - }]; - queue.write_buffer(&self.buffers.window, 0, bytemuck::cast_slice(slice)); - } -} diff --git a/src/render/shape/shader.wgsl b/src/render/shape/shader.wgsl deleted file mode 100644 index 016219c..0000000 --- a/src/render/shape/shader.wgsl +++ /dev/null @@ -1,97 +0,0 @@ - -// vertex shader - -struct VertexOutput { - @location(0) color: vec4, - @location(1) center: vec2, - @location(2) corner: vec2, - @location(3) radius: f32, - @location(4) inner_radius: f32, - @location(5) thickness: f32, - @builtin(position) clip_position: vec4, -}; - -struct WindowUniform { - dim: vec2, -}; - -struct InstanceInput { - @location(0) top_left_anchor: vec2, - @location(1) top_left_offset: vec2, - @location(2) bottom_right_anchor: vec2, - @location(3) bottom_right_offset: vec2, - @location(4) top_right_color: vec4, - @location(5) top_left_color: vec4, - @location(6) bottom_right_color: vec4, - @location(7) bottom_left_color: vec4, - @location(8) radius: f32, - @location(9) inner_radius: f32, - @location(10) thickness: f32, -} - -@group(0) @binding(0) -var window: WindowUniform; - -@vertex -fn vs_main( - @builtin(vertex_index) vi: u32, - in: InstanceInput, -) -> VertexOutput { - var out: VertexOutput; - - let top_left = in.top_left_anchor * window.dim + in.top_left_offset; - let bottom_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset; - let size = bottom_right - top_left; - - var pos = top_left + vec2( - f32(vi % 2u), - f32(vi / 2u) - ) * size; - pos = pos / window.dim * 2.0 - 1.0; - out.clip_position = vec4(pos.x, -pos.y, 0.0, 1.0); - - if vi == 0u { - out.color = in.top_left_color; - } else if vi == 1u { - out.color = in.top_right_color; - } else if vi == 2u { - out.color = in.bottom_left_color; - } else if vi == 3u { - out.color = in.bottom_right_color; - } - - out.corner = size / 2.0; - out.center = top_left + out.corner; - out.radius = in.radius; - out.inner_radius = in.inner_radius; - out.thickness = in.thickness; - - return out; -} - -@fragment -fn fs_main( - in: VertexOutput -) -> @location(0) vec4 { - var color = in.color; - - let edge = 0.5; - - let dist = distance_from_rect(in.clip_position.xy, in.center, in.corner, in.radius); - color.a *= 1.0 - smoothstep(-min(edge, in.radius), edge, dist); - - if in.thickness > 0.0 { - let dist2 = distance_from_rect(in.clip_position.xy, in.center, in.corner - in.thickness, in.inner_radius); - color.a *= smoothstep(-min(edge, in.inner_radius), edge, dist2); - } - - return color; -} - -fn distance_from_rect(pixel_pos: vec2, rect_center: vec2, rect_corner: vec2, radius: f32) -> f32 { - // vec from center to pixel - let p = pixel_pos - rect_center; - // vec from inner rect corner to pixel - let q = abs(p) - (rect_corner - radius); - return length(max(q, vec2(0.0, 0.0))) - radius; -} diff --git a/src/render/text/mod.rs b/src/render/text/mod.rs deleted file mode 100644 index 626c2e4..0000000 --- a/src/render/text/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod pipeline; diff --git a/src/render/text/pipeline.rs b/src/render/text/pipeline.rs deleted file mode 100644 index c6c4bd0..0000000 --- a/src/render/text/pipeline.rs +++ /dev/null @@ -1,127 +0,0 @@ -use glyphon::{ - Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea, - TextAtlas, TextBounds, TextRenderer, -}; -use wgpu::{MultisampleState, RenderPass}; - -use crate::{ - client::ui::element::Align, - render::{primitive::TextPrimitive, surface::RenderSurface}, -}; - -pub struct TextPipeline { - pub renderer: glyphon::TextRenderer, - pub font_system: glyphon::FontSystem, - pub atlas: glyphon::TextAtlas, - pub cache: glyphon::SwashCache, - pub text_buffers: Vec, - pub old_text: Vec, -} - -impl TextPipeline { - pub fn new(surface: &RenderSurface) -> Self { - let RenderSurface { - device, - config, - queue, - .. - } = surface; - - let font_system = FontSystem::new(); - let cache = SwashCache::new(); - let mut atlas = TextAtlas::new(&device, &queue, config.format); - let renderer = TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None); - - Self { - font_system, - atlas, - cache, - renderer, - text_buffers: Vec::new(), - old_text: Vec::new(), - } - } - - pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { - self.renderer.render(&self.atlas, pass).unwrap(); - } - - pub fn update(&mut self, surface: &RenderSurface, text: &[TextPrimitive]) { - let buffers = &mut self.text_buffers; - if buffers.len() < text.len() { - self.old_text.resize(text.len(), TextPrimitive::empty()); - buffers.resize_with(text.len(), || { - Buffer::new(&mut self.font_system, Metrics::new(20.0, 25.0)) - }) - } - for ((buffer, text), old) in buffers.iter_mut().zip(text).zip(&mut self.old_text) { - if text != old { - *old = text.clone(); - buffer.set_size(&mut self.font_system, f32::MAX, f32::MAX); - buffer.set_text( - &mut self.font_system, - &text.content, - Attrs::new().family(Family::SansSerif), - Shaping::Basic, - ); - } - } - let color = Color::rgb(255, 255, 255); - let areas = buffers.iter().zip(text).map(|(buffer, text)| { - let width = measure(&buffer).0; - let mut left = text.pos.x - - match text.align { - Align::Left => 0.0, - Align::Center => width / 2.0, - Align::Right => width, - }; - let x = text.pos.x; - let w = text.bounds.0; - let x_bounds = match text.align { - Align::Left => (x, x + w), - Align::Center => (x - w / 2.0, x + w / 2.0), - Align::Right => (x - w, x), - }; - if left < x_bounds.0 { - left = x_bounds.0; - } - TextArea { - buffer: &buffer, - left, - top: text.pos.y, - scale: 1.0, - bounds: TextBounds { - left: x_bounds.0 as i32, - top: text.pos.y as i32, - right: x_bounds.1 as i32, - bottom: (text.pos.y + text.bounds.1) as i32, - }, - default_color: color, - } - }); - self.renderer - .prepare( - &surface.device, - &surface.queue, - &mut self.font_system, - &mut self.atlas, - Resolution { - width: surface.config.width, - height: surface.config.height, - }, - areas, - &mut self.cache, - ) - .unwrap(); - } -} - -fn measure(buffer: &glyphon::Buffer) -> (f32, f32) { - let (width, total_lines) = buffer - .layout_runs() - .fold((0.0, 0usize), |(width, total_lines), run| { - (run.line_w.max(width), total_lines + 1) - }); - - (width, total_lines as f32 * buffer.metrics().line_height) -} diff --git a/src/render/texture/init.rs b/src/render/texture/init.rs deleted file mode 100644 index 2c85a6c..0000000 --- a/src/render/texture/init.rs +++ /dev/null @@ -1,129 +0,0 @@ -use wgpu::{ - util::DeviceExt, - BindGroup, BindGroupLayout, Device, Queue, -}; - -use crate::render::surface::RenderSurface; - -use super::{ - pipeline::{TexturePipeline, TEXTURE_SHADER}, - vertex::{TextureVertex, TEXTURE_VERTICES}, texture::GameTexture, -}; - -impl TexturePipeline { - pub fn new(surface: &RenderSurface) -> Self { - let RenderSurface { - device, - config, - queue, - .. - } = surface; - - let (bind_group_layout, diffuse_bind_group) = Self::init_textures(device, queue); - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("UI Texture Shader"), - source: wgpu::ShaderSource::Wgsl(TEXTURE_SHADER.into()), - }); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Texture Vertex Buffer"), - contents: bytemuck::cast_slice(TEXTURE_VERTICES), - usage: wgpu::BufferUsages::VERTEX, - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("UI Texture Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("UI Texture Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[TextureVertex::desc()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - - Self { - pipeline, - vertex_buffer, - diffuse_bind_group, - } - } - - fn init_textures(device: &Device, queue: &Queue) -> (BindGroupLayout, BindGroup) { - let diffuse_bytes = include_bytes!("./textures/happy-tree.png"); - let diffuse_texture = - GameTexture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap(); - - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - (texture_bind_group_layout, diffuse_bind_group) - } -} diff --git a/src/render/texture/mod.rs b/src/render/texture/mod.rs deleted file mode 100644 index b5ab845..0000000 --- a/src/render/texture/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -mod init; -mod vertex; -mod texture; - -use wgpu::{RenderPass, RenderPipeline}; - -pub const TEXTURE_SHADER: &str = include_str!("./shader.wgsl"); - -pub struct TexturePipeline { - pub pipeline: RenderPipeline, - pub vertex_buffer: wgpu::Buffer, - pub diffuse_bind_group: wgpu::BindGroup, -} - -impl TexturePipeline { - pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.diffuse_bind_group, &[]); - pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - pass.draw(0..4, 0..1); - } -} diff --git a/src/render/texture/shader.wgsl b/src/render/texture/shader.wgsl deleted file mode 100644 index 6d6cf95..0000000 --- a/src/render/texture/shader.wgsl +++ /dev/null @@ -1,34 +0,0 @@ -// Vertex shader - -struct VertexInput { - @location(0) position: vec2, - @location(1) tex_coords: vec2, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.clip_position = vec4(model.position, 0.0, 1.0); - return out; -} - -// Fragment shader - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0)@binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return textureSample(t_diffuse, s_diffuse, in.tex_coords); -} - diff --git a/src/render/texture/texture.rs b/src/render/texture/texture.rs deleted file mode 100644 index d3ee531..0000000 --- a/src/render/texture/texture.rs +++ /dev/null @@ -1,78 +0,0 @@ -use image::{GenericImageView, ImageResult}; - -pub struct GameTexture { - pub texture: wgpu::Texture, - pub view: wgpu::TextureView, - pub sampler: wgpu::Sampler, -} - -impl GameTexture { - pub fn from_bytes( - device: &wgpu::Device, - queue: &wgpu::Queue, - bytes: &[u8], - label: &str - ) -> ImageResult { - let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label)) - } - - pub fn from_image( - device: &wgpu::Device, - queue: &wgpu::Queue, - img: &image::DynamicImage, - label: Option<&str> - ) -> ImageResult { - let rgba = img.to_rgba8(); - let dimensions = img.dimensions(); - - let size = wgpu::Extent3d { - width: dimensions.0, - height: dimensions.1, - depth_or_array_layers: 1, - }; - let texture = device.create_texture( - &wgpu::TextureDescriptor { - label, - size, - 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: &[], - } - ); - - queue.write_texture( - wgpu::ImageCopyTexture { - aspect: wgpu::TextureAspect::All, - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - }, - &rgba, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * dimensions.0), - rows_per_image: Some(dimensions.1), - }, - size, - ); - - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler( - &wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - } - ); - - Ok(Self { texture, view, sampler }) - } -} diff --git a/src/render/texture/textures/happy-tree.png b/src/render/texture/textures/happy-tree.png deleted file mode 100644 index fc86db3..0000000 Binary files a/src/render/texture/textures/happy-tree.png and /dev/null differ diff --git a/src/render/texture/vertex.rs b/src/render/texture/vertex.rs deleted file mode 100644 index 2b164ae..0000000 --- a/src/render/texture/vertex.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct TextureVertex { - position: [f32; 2], - tex_coords: [f32; 2], -} - -impl TextureVertex { - const ATTRIBS: [wgpu::VertexAttribute; 2] = - wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2]; - - pub fn desc() -> wgpu::VertexBufferLayout<'static> { - use std::mem; - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBS, - } - } -} - -pub const TEXTURE_VERTICES: &[TextureVertex] = &[ - TextureVertex { position: [0.0, 0.0], tex_coords: [0.0, 1.0], }, - TextureVertex { position: [0.5, 0.0], tex_coords: [1.0, 1.0], }, - TextureVertex { position: [0.0, 0.5], tex_coords: [0.0, 0.0], }, - TextureVertex { position: [0.5, 0.5], tex_coords: [1.0, 0.0], }, -]; - -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct ShapeVertex { - position: [f32; 2], - tex_coords: [f32; 2], -} - diff --git a/src/render/util/mod.rs b/src/render/util/mod.rs new file mode 100644 index 0000000..8d9b31f --- /dev/null +++ b/src/render/util/mod.rs @@ -0,0 +1,48 @@ +use std::marker::PhantomData; + +use bytemuck::Pod; +use wgpu::*; + +pub struct ArrBuf { + label: &'static str, + usage: BufferUsages, + pub buffer: Buffer, + len: usize, + _pd: PhantomData, +} + +impl ArrBuf { + pub fn new(device: &Device, usage: BufferUsages, label: &'static str) -> Self { + Self { + label, + usage, + buffer: Self::init_buf(device, 0, usage, label), + len: 0, + _pd: PhantomData, + } + } + pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) { + if self.len != data.len() { + self.len = data.len(); + self.buffer = + Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label); + } + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data)); + } + fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer { + let mut size = size as u64; + if usage.contains(BufferUsages::STORAGE) { + size = size.max(1); + } + device.create_buffer(&BufferDescriptor { + label: Some(label), + size, + mapped_at_creation: false, + usage, + }) + } + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len + } +} diff --git a/src/app.rs b/src/testing/app.rs similarity index 89% rename from src/app.rs rename to src/testing/app.rs index afe9a73..5d097d1 100644 --- a/src/app.rs +++ b/src/testing/app.rs @@ -30,7 +30,7 @@ impl ApplicationHandler for App { } } - fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { self.client.as_mut().unwrap().event(event, event_loop); } } diff --git a/src/testing/mod.rs b/src/testing/mod.rs new file mode 100644 index 0000000..5512d15 --- /dev/null +++ b/src/testing/mod.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use app::App; +use gui::{primitive::Axis, NodeArrayUtil, NodeUtil, RoundedRect, UIColor}; +use render::Renderer; +use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; + +mod app; +mod render; + +pub fn main() { + App::run(); +} + +pub struct Client { + renderer: Renderer, +} + +impl Client { + pub fn new(window: Arc) -> Self { + let mut renderer = Renderer::new(window); + let rect = RoundedRect { + color: UIColor::WHITE, + radius: 10.0, + thickness: 0.0, + inner_radius: 0.0, + }; + let ui = ( + ( + rect.color(UIColor::BLUE), + ( + rect.color(UIColor::RED), + (rect.color(UIColor::ORANGE), rect.color(UIColor::LIME)) + .proportioned(Axis::Y, [1, 1]), + rect.color(UIColor::YELLOW), + ) + .proportioned(Axis::X, [2, 2, 1]) + .pad(10), + ) + .proportioned(Axis::X, [1, 3]), + rect.color(UIColor::GREEN), + ) + .proportioned(Axis::Y, [3, 1]) + .pad(10) + .into(); + renderer.update(&ui); + Self { renderer } + } + + pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::RedrawRequested => self.renderer.draw(), + WindowEvent::Resized(size) => self.renderer.resize(&size), + _ => (), + } + } +} diff --git a/src/testing/render/mod.rs b/src/testing/render/mod.rs new file mode 100644 index 0000000..0a1b0e6 --- /dev/null +++ b/src/testing/render/mod.rs @@ -0,0 +1,132 @@ +use gui::{UIRenderNode, UI}; +use pollster::FutureExt; +use std::sync::Arc; +use wgpu::util::StagingBelt; +use winit::{dpi::PhysicalSize, window::Window}; + +pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK; + +pub struct Renderer { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + encoder: wgpu::CommandEncoder, + staging_belt: StagingBelt, + ui_node: UIRenderNode, +} + +impl Renderer { + pub fn update(&mut self, ui: &UI) { + self.ui_node.update(&self.device, &self.queue, ui); + } + + pub fn draw(&mut self) { + let output = self.surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device)); + { + let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(CLEAR_COLOR), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + ..Default::default() + }); + self.ui_node.draw(render_pass); + } + + self.queue.submit(std::iter::once(encoder.finish())); + self.staging_belt.finish(); + output.present(); + self.staging_belt.recall(); + } + + pub fn resize(&mut self, size: &PhysicalSize) { + self.config.width = size.width; + self.config.height = size.height; + self.surface.configure(&self.device, &self.config); + self.ui_node.resize(size, &self.queue); + } + + fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder { + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }) + } + + pub fn new(window: Arc) -> Self { + let size = window.inner_size(); + + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance + .create_surface(window.clone()) + .expect("Could not create window surface!"); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .block_on() + .expect("Could not get adapter!"); + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + ..Default::default() + }) + .block_on() + .expect("Could not get device!"); + + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: surface_caps.alpha_modes[0], + desired_maximum_frame_latency: 2, + view_formats: vec![], + }; + + surface.configure(&device, &config); + + let staging_belt = StagingBelt::new(4096 * 4); + let encoder = Self::create_encoder(&device); + + let shape_pipeline = UIRenderNode::new(&device, &config); + + Self { + surface, + device, + queue, + config, + encoder, + staging_belt, + ui_node: shape_pipeline, + } + } +}