From 56beeb80f31f49e8f4151aa6bb9fd53ecee04219 Mon Sep 17 00:00:00 2001 From: shadow cat Date: Fri, 8 Aug 2025 18:22:52 -0400 Subject: [PATCH] initial commit --- Cargo.lock | 313 +++++++++---------- Cargo.toml | 5 +- src/layout/mod.rs | 66 ++++ src/layout/node.rs | 164 ++++++++++ src/lib.rs | 10 + src/main.rs | 35 +-- src/render/data.rs | 34 +++ src/render/mod.rs | 331 ++++++++++----------- src/render/primitive.rs | 73 ----- src/render/primitive/color.rs | 47 +++ src/render/primitive/def.rs | 14 + src/render/primitive/format.rs | 97 ++++++ src/render/primitive/mod.rs | 50 ++++ src/render/primitive/point.rs | 84 ++++++ src/render/shader.wgsl | 110 +++++++ src/render/shape/data.rs | 74 ----- src/render/shape/layout.rs | 106 ------- src/render/shape/mod.rs | 44 --- src/render/shape/shader.wgsl | 97 ------ src/render/text/mod.rs | 1 - src/render/text/pipeline.rs | 127 -------- src/render/texture/init.rs | 129 -------- src/render/texture/mod.rs | 22 -- src/render/texture/shader.wgsl | 34 --- src/render/texture/texture.rs | 78 ----- src/render/texture/textures/happy-tree.png | Bin 28134 -> 0 bytes src/render/texture/vertex.rs | 35 --- src/render/util/mod.rs | 48 +++ src/{ => testing}/app.rs | 2 +- src/testing/mod.rs | 58 ++++ src/testing/render/mod.rs | 132 ++++++++ 31 files changed, 1223 insertions(+), 1197 deletions(-) create mode 100644 src/layout/mod.rs create mode 100644 src/layout/node.rs create mode 100644 src/lib.rs create mode 100644 src/render/data.rs delete mode 100644 src/render/primitive.rs create mode 100644 src/render/primitive/color.rs create mode 100644 src/render/primitive/def.rs create mode 100644 src/render/primitive/format.rs create mode 100644 src/render/primitive/mod.rs create mode 100644 src/render/primitive/point.rs create mode 100644 src/render/shader.wgsl delete mode 100644 src/render/shape/data.rs delete mode 100644 src/render/shape/layout.rs delete mode 100644 src/render/shape/mod.rs delete mode 100644 src/render/shape/shader.wgsl delete mode 100644 src/render/text/mod.rs delete mode 100644 src/render/text/pipeline.rs delete mode 100644 src/render/texture/init.rs delete mode 100644 src/render/texture/mod.rs delete mode 100644 src/render/texture/shader.wgsl delete mode 100644 src/render/texture/texture.rs delete mode 100644 src/render/texture/textures/happy-tree.png delete mode 100644 src/render/texture/vertex.rs create mode 100644 src/render/util/mod.rs rename src/{ => testing}/app.rs (89%) create mode 100644 src/testing/mod.rs create mode 100644 src/testing/render/mod.rs 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 fc86db345c14b95728250b8914fa284f34193834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28134 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 D;T5;R 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, + } + } +}