diff --git a/Cargo.lock b/Cargo.lock index 858fea3..c9a68c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -25,12 +31,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", ] +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "android-activity" version = "0.6.0" @@ -67,6 +82,29 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -106,6 +144,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -121,6 +182,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -136,6 +203,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "block" version = "0.1.6" @@ -151,6 +224,12 @@ dependencies = [ "objc2", ] +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + [[package]] name = "bumpalo" version = "3.19.0" @@ -177,6 +256,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -226,6 +311,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -249,6 +344,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.7" @@ -329,6 +430,34 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -383,6 +512,32 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -399,6 +554,40 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -442,6 +631,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -451,7 +651,17 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", ] [[package]] @@ -537,16 +747,6 @@ dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "gui" -version = "0.1.0" -dependencies = [ - "bytemuck", - "pollster", - "wgpu", - "winit", -] - [[package]] name = "half" version = "2.6.0" @@ -567,6 +767,12 @@ 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" @@ -579,6 +785,45 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "indexmap" version = "2.10.0" @@ -589,6 +834,26 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.21.1" @@ -617,10 +882,16 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom", + "getrandom 0.3.3", "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + [[package]] name = "js-sys" version = "0.3.77" @@ -648,12 +919,28 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.8.8" @@ -715,6 +1002,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -724,6 +1020,16 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.5" @@ -754,6 +1060,22 @@ dependencies = [ "paste", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "naga" version = "26.0.0" @@ -810,6 +1132,69 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1154,6 +1539,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.10.0" @@ -1189,6 +1587,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -1218,6 +1625,34 @@ name = "profiling" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" @@ -1243,18 +1678,118 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "range-alloc" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1279,6 +1814,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1371,12 +1912,36 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "slab" version = "0.4.11" @@ -1464,6 +2029,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "termcolor" version = "1.4.1" @@ -1513,6 +2097,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1538,11 +2133,26 @@ dependencies = [ "strict-num", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -1551,6 +2161,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -1577,6 +2189,17 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "ui" +version = "0.1.0" +dependencies = [ + "bytemuck", + "image", + "pollster", + "wgpu", + "winit", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -1595,6 +2218,23 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -1611,6 +2251,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -1820,6 +2466,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + [[package]] name = "wgpu" version = "26.0.1" @@ -2479,3 +3131,27 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 0cb5735..2101c85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "gui" +name = "ui" version = "0.1.0" edition = "2024" @@ -10,4 +10,5 @@ pollster = "0.4.0" winit = "0.30.11" wgpu = "26.0.1" bytemuck = "1.23.1" +image = "0.25.6" diff --git a/src/core/frame.rs b/src/core/frame.rs index fc9f7fb..a28c130 100644 --- a/src/core/frame.rs +++ b/src/core/frame.rs @@ -8,7 +8,7 @@ pub struct Regioned { impl Widget for Regioned { fn draw(&self, painter: &mut Painter) { painter.region.select(&self.region); - painter.draw(&self.inner); + painter.draw_inner(&self.inner); } } diff --git a/src/core/image.rs b/src/core/image.rs new file mode 100644 index 0000000..1c3f5ff --- /dev/null +++ b/src/core/image.rs @@ -0,0 +1,63 @@ +use image::DynamicImage; + +use crate::{Color, TextureHandle, Widget, WidgetFnRet, render::RectPrimitive}; + +pub struct Image { + handle: Option, +} + +impl Widget for Image { + fn draw(&self, painter: &mut crate::Painter) { + if let Some(handle) = &self.handle { + painter.draw_texture(handle); + } else { + painter.write(RectPrimitive { + color: Color::MAGENTA, + inner_radius: 0.0, + radius: 0.0, + thickness: 0.0, + }); + } + } +} + +pub fn image(image: impl LoadableImage) -> WidgetFnRet!(Image, Ctx) { + let image = match image.get_image() { + Ok(image) => Some(image), + Err(e) => { + println!("Failed to load image: {e}"); + None + } + }; + move |ui| Image { + handle: image.map(|image| ui.add_texture(image)), + } +} + +pub trait LoadableImage { + fn get_image(self) -> Result; +} + +impl LoadableImage for &str { + fn get_image(self) -> Result { + image::open(self).map_err(|e| format!("{e:?}")) + } +} + +impl LoadableImage for String { + fn get_image(self) -> Result { + image::open(self).map_err(|e| format!("{e:?}")) + } +} + +impl LoadableImage for &[u8; LEN] { + fn get_image(self) -> Result { + image::load_from_memory(self).map_err(|e| format!("{e:?}")) + } +} + +impl LoadableImage for DynamicImage { + fn get_image(self) -> Result { + Ok(self) + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index cc63fa6..741f72f 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ mod frame; +mod image; mod num; mod rect; mod sense; @@ -7,6 +8,7 @@ mod stack; mod trait_fns; pub use frame::*; +pub use image::*; pub use num::*; pub use rect::*; pub use sense::*; diff --git a/src/core/rect.rs b/src/core/rect.rs index f0b25e2..0787073 100644 --- a/src/core/rect.rs +++ b/src/core/rect.rs @@ -1,4 +1,4 @@ -use crate::{primitive::RoundedRectData, Painter, UiNum, UiColor, Widget}; +use crate::{Painter, UiColor, UiNum, Widget, render::RectPrimitive}; #[derive(Clone, Copy)] pub struct Rect { @@ -29,7 +29,7 @@ impl Rect { impl Widget for Rect { fn draw(&self, painter: &mut Painter) { - painter.write(RoundedRectData { + painter.write(RectPrimitive { color: self.color, radius: self.radius, thickness: self.thickness, diff --git a/src/core/span.rs b/src/core/span.rs index ac535dc..14398dd 100644 --- a/src/core/span.rs +++ b/src/core/span.rs @@ -1,4 +1,4 @@ -use crate::{Dir, Painter, Sign, UiNum, UiRegion, UIScalar, Widget, WidgetId}; +use crate::{Dir, Painter, Sign, UIScalar, UiNum, UiRegion, Widget, WidgetId}; pub struct Span { pub children: Vec<(WidgetId, SpanLen)>, diff --git a/src/core/stack.rs b/src/core/stack.rs index 994d565..855c7ea 100644 --- a/src/core/stack.rs +++ b/src/core/stack.rs @@ -7,7 +7,7 @@ pub struct Stack { impl Widget for Stack { fn draw(&self, painter: &mut crate::Painter) { for child in &self.children { - painter.draw_within(child, painter.region); + painter.draw(child); } } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 4e8f600..116f28b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,19 +1,21 @@ mod color; +mod id; mod painter; mod region; mod sense; +mod texture; mod ui; mod vec2; mod widget; -mod id; pub use color::*; +pub use id::*; pub use painter::*; pub use region::*; pub use sense::*; +pub use texture::*; pub use ui::*; pub use vec2::*; pub use widget::*; -pub use id::*; pub type UiColor = Color; diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 671d8cb..0b6ea09 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,6 +1,6 @@ use crate::{ - ActiveSensors, SensorMap, UiRegion, WidgetId, Widgets, - primitive::{Primitive, Primitives}, + ActiveSensors, SensorMap, TextureHandle, UiRegion, WidgetId, Widgets, + render::{Primitive, Primitives}, }; pub struct Painter<'a, Ctx: 'static> { @@ -32,7 +32,9 @@ impl<'a, Ctx> Painter<'a, Ctx> { self.primitives.write(data, self.region); } - pub fn draw(&mut self, id: &WidgetId) + /// Draws a widget but does NOT maintain the region. + /// Useful if you only have a single widget to draw. + pub fn draw_inner(&mut self, id: &WidgetId) where Ctx: 'static, { @@ -42,16 +44,34 @@ impl<'a, Ctx> Painter<'a, Ctx> { self.nodes.get_dyn(id).draw(self); } - pub fn draw_within(&mut self, node: &WidgetId, region: UiRegion) + /// Draws a widget and maintains the original region. + /// Useful if you need to draw multiple overlapping child widgets. + pub fn draw(&mut self, id: &WidgetId) + where + Ctx: 'static, + { + let old = self.region; + self.draw_inner(id); + self.region = old; + } + + /// Draws a widget within an inner region and maintains the original region. + /// Useful if you need to draw multiple child widgets in select areas within the original + /// region. + pub fn draw_within(&mut self, id: &WidgetId, region: UiRegion) where Ctx: 'static, { let old = self.region; self.region.select(®ion); - self.draw(node); + self.draw_inner(id); self.region = old; } + pub fn draw_texture(&mut self, handle: &TextureHandle) { + self.write(handle.inner); + } + pub fn finish(self) -> Primitives { self.primitives } diff --git a/src/layout/texture.rs b/src/layout/texture.rs new file mode 100644 index 0000000..8431d54 --- /dev/null +++ b/src/layout/texture.rs @@ -0,0 +1,48 @@ +use image::DynamicImage; + +use crate::render::TexturePrimitive; + +/// TODO: proper resource management +pub struct TextureHandle { + pub inner: TexturePrimitive, +} + +/// a texture manager for a ui +/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped +#[derive(Default)] +pub struct Textures { + /// TODO: these are images, not views rn + views: Vec, + changed: bool, +} + +pub struct TextureUpdates<'a> { + pub images: Option<&'a [DynamicImage]>, +} + +impl Textures { + pub fn add(&mut self, image: DynamicImage) -> TextureHandle { + let view_idx = self.views.len() as u32; + self.views.push(image); + // 0 == default in renderer; TODO: actually create samplers here + let sampler_idx = 0; + self.changed = true; + TextureHandle { + inner: TexturePrimitive { + view_idx, + sampler_idx, + }, + } + } + + pub fn updates(&mut self) -> TextureUpdates<'_> { + if self.changed { + self.changed = false; + TextureUpdates { + images: Some(&self.views), + } + } else { + TextureUpdates { images: None } + } + } +} diff --git a/src/layout/ui.rs b/src/layout/ui.rs index d345812..3520104 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,7 +1,10 @@ +use image::DynamicImage; + use crate::{ - ActiveSensors, HashMap, Painter, SensorMap, Widget, WidgetId, WidgetLike, - primitive::Primitives, - util::{IDTracker, Id}, + ActiveSensors, HashMap, Painter, SensorMap, TextureHandle, TextureUpdates, Textures, Widget, + WidgetId, WidgetLike, + render::Primitives, + util::{Id, IdTracker}, }; use std::{ any::{Any, TypeId}, @@ -10,20 +13,24 @@ use std::{ }; pub struct Ui { - ids: IDTracker, base: Option, widgets: Widgets, updates: Vec, del_recv: Receiver, del_send: Sender, + primitives: Primitives, + textures: Textures, + full_redraw: bool, + pub(super) active_sensors: ActiveSensors, pub(super) sensor_map: SensorMap, - primitives: Primitives, - full_redraw: bool, } #[derive(Default)] -pub struct Widgets(HashMap>>); +pub struct Widgets { + ids: IdTracker, + map: HashMap>>, +} impl Ui { pub fn add, Tag>( @@ -68,7 +75,15 @@ impl Ui { } pub fn id>(&mut self) -> WidgetId { - WidgetId::new(self.ids.next(), TypeId::of::(), self.del_send.clone()) + WidgetId::new( + self.widgets.reserve(), + TypeId::of::(), + self.del_send.clone(), + ) + } + + pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle { + self.textures.add(image) } pub fn redraw_all(&mut self, ctx: &mut Ctx) @@ -83,12 +98,12 @@ impl Ui { &mut self.active_sensors, ); if let Some(base) = &self.base { - painter.draw(base); + painter.draw_inner(base); } self.primitives = painter.finish(); } - pub fn update(&mut self, ctx: &mut Ctx) -> Option<&Primitives> + pub fn update(&mut self, ctx: &mut Ctx) -> UiRenderUpdates where Ctx: 'static, { @@ -99,14 +114,24 @@ impl Ui { if self.full_redraw { self.redraw_all(ctx); self.full_redraw = false; - return Some(&self.primitives); + UiRenderUpdates { + primitives: Some(&self.primitives), + textures: self.textures.updates(), + } + } else if self.updates.is_empty() { + UiRenderUpdates { + primitives: None, + textures: self.textures.updates(), + } + } else { + // TODO: partial updates + self.redraw_all(ctx); + self.updates.drain(..); + UiRenderUpdates { + primitives: Some(&self.primitives), + textures: self.textures.updates(), + } } - if self.updates.is_empty() { - return None; - } - self.redraw_all(ctx); - self.updates.drain(..); - Some(&self.primitives) } pub fn needs_redraw(&self) -> bool { @@ -135,39 +160,51 @@ impl, Ctx> IndexMut<&WidgetId> for Ui { impl Widgets { pub fn new() -> Self { - Self(HashMap::new()) + Self { + ids: IdTracker::default(), + map: HashMap::new(), + } } pub fn get_dyn(&self, id: &WidgetId) -> &dyn Widget { - self.0.get(&id.id).unwrap().as_ref() + self.map.get(&id.id).unwrap().as_ref() } pub fn get>(&self, id: &WidgetId) -> Option<&W> { - self.0.get(&id.id).unwrap().as_any().downcast_ref() + self.map.get(&id.id).unwrap().as_any().downcast_ref() } pub fn get_mut>(&mut self, id: &WidgetId) -> Option<&mut W> { - self.0.get_mut(&id.id).unwrap().as_any_mut().downcast_mut() + self.map + .get_mut(&id.id) + .unwrap() + .as_any_mut() + .downcast_mut() } pub fn insert(&mut self, id: Id, widget: impl Widget) { - self.0.insert(id, Box::new(widget)); + self.map.insert(id, Box::new(widget)); } pub fn insert_any(&mut self, id: Id, widget: Box>) { - self.0.insert(id, widget); + self.map.insert(id, widget); } pub fn delete(&mut self, id: Id) { - self.0.remove(&id); + self.map.remove(&id); + self.ids.free(id); + } + + pub fn reserve(&mut self) -> Id { + self.ids.next() } pub fn len(&self) -> usize { - self.0.len() + self.map.len() } pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.map.is_empty() } } @@ -185,11 +222,11 @@ impl Default for Ui { fn default() -> Self { let (del_send, del_recv) = channel(); Self { - ids: Default::default(), base: Default::default(), widgets: Widgets::new(), updates: Default::default(), primitives: Default::default(), + textures: Textures::default(), full_redraw: false, active_sensors: Default::default(), sensor_map: Default::default(), @@ -198,3 +235,8 @@ impl Default for Ui { } } } + +pub struct UiRenderUpdates<'a> { + pub primitives: Option<&'a Primitives>, + pub textures: TextureUpdates<'a>, +} diff --git a/src/layout/vec2.rs b/src/layout/vec2.rs index c3c1468..2e96d9e 100644 --- a/src/layout/vec2.rs +++ b/src/layout/vec2.rs @@ -2,7 +2,7 @@ use crate::{util::{impl_op, F32Util}, UiNum}; use std::ops::*; #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vec2 { pub x: f32, pub y: f32, @@ -60,3 +60,9 @@ impl From<(T, U)> for Vec2 { } } } + +impl std::fmt::Debug for Vec2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {})", self.x, self.y) + } +} diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 26388ab..8894f47 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -41,8 +41,7 @@ pub(crate) use WidgetFnRet; impl, Ctx, F: FnOnce(&mut Ui) -> W> WidgetLike for F { type Widget = W; fn add(self, ui: &mut Ui) -> WidgetId { - let w = self(ui); - ui.add(w) + self(ui).add(ui) } } diff --git a/src/lib.rs b/src/lib.rs index 5ba7372..f89f25a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,13 @@ #![feature(trait_alias)] #![feature(negative_impls)] -mod layout; -mod render; -mod util; mod core; +mod layout; +pub mod render; +mod util; -pub use layout::*; -pub use render::*; pub use core::*; +pub use layout::*; pub type HashMap = std::collections::HashMap; pub type HashSet = std::collections::HashSet; diff --git a/src/render/mod.rs b/src/render/mod.rs index 08328a6..2c4e3b9 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,6 +1,8 @@ +use std::num::NonZero; + use crate::{ - primitive::{PrimitiveBuffers, Primitives}, - render::{data::PrimitiveInstance, util::ArrBuf}, + Ui, UiRenderUpdates, + render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, }; use data::WindowUniform; use wgpu::{ @@ -10,41 +12,55 @@ use wgpu::{ use winit::dpi::PhysicalSize; mod data; -pub mod primitive; +mod primitive; +mod texture; mod util; +pub use primitive::*; + const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); -pub struct UIRenderNode { - layout0: BindGroupLayout, - group0: BindGroup, +pub struct UiRenderer { + uniform_layout: BindGroupLayout, + uniform_group: BindGroup, primitive_layout: BindGroupLayout, primitive_group: BindGroup, + rsc_layout: BindGroupLayout, + rsc_group: BindGroup, + pipeline: RenderPipeline, window_buffer: Buffer, instance: ArrBuf, primitives: PrimitiveBuffers, + + limits: UiLimits, + textures: GpuTextures, } -impl UIRenderNode { +impl UiRenderer { pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) { - if self.instance.len() != 0 { - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.group0, &[]); - pass.set_bind_group(1, &self.primitive_group, &[]); - pass.set_vertex_buffer(0, self.instance.buffer.slice(..)); - pass.draw(0..4, 0..self.instance.len() as u32); + if self.instance.len() == 0 { + return; } + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.uniform_group, &[]); + pass.set_bind_group(1, &self.primitive_group, &[]); + pass.set_bind_group(2, &self.rsc_group, &[]); + pass.set_vertex_buffer(0, self.instance.buffer.slice(..)); + pass.draw(0..4, 0..self.instance.len() as u32); } - pub fn update(&mut self, device: &Device, queue: &Queue, primitives: Option<&Primitives>) { - if let Some(primitives) = primitives { + pub fn update(&mut self, device: &Device, queue: &Queue, updates: UiRenderUpdates) { + if let Some(primitives) = updates.primitives { self.instance.update(device, queue, &primitives.instances); self.primitives.update(device, queue, &primitives.data); self.primitive_group = Self::primitive_group(device, &self.primitive_layout, self.primitives.buffers()) } + if self.textures.apply(updates.textures) { + self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures) + } } pub fn resize(&mut self, size: &PhysicalSize, queue: &Queue) { @@ -55,7 +71,12 @@ impl UIRenderNode { queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice)); } - pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self { + pub fn new( + device: &Device, + queue: &Queue, + config: &SurfaceConfiguration, + limits: UiLimits, + ) -> Self { let shader = device.create_shader_module(ShaderModuleDescriptor { label: Some("UI Shape Shader"), source: ShaderSource::Wgsl(SHAPE_SHADER.into()), @@ -75,7 +96,7 @@ impl UIRenderNode { ); let primitives = PrimitiveBuffers::new(device); - let layout0 = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX, @@ -89,7 +110,7 @@ impl UIRenderNode { label: Some("window"), }); - let group0 = Self::bind_group_0(device, &layout0, &window_buffer); + let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer); let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| { @@ -110,9 +131,13 @@ impl UIRenderNode { let primitive_group = Self::primitive_group(device, &primitive_layout, primitives.buffers()); + let tex_manager = GpuTextures::new(device, queue); + let rsc_layout = Self::rsc_layout(device, &limits); + let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager); + let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: Some("UI Shape Pipeline Layout"), - bind_group_layouts: &[&layout0, &primitive_layout], + bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout], push_constant_ranges: &[], }); let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { @@ -154,18 +179,22 @@ impl UIRenderNode { }); Self { - layout0, - group0, + uniform_layout, + uniform_group, primitive_layout, primitive_group, + rsc_layout, + rsc_group, pipeline, window_buffer, instance, primitives, + limits, + textures: tex_manager, } } - pub fn bind_group_0( + fn bind_group_0( device: &Device, layout: &BindGroupLayout, window_buffer: &Buffer, @@ -180,18 +209,86 @@ impl UIRenderNode { }) } - pub fn primitive_group( + fn primitive_group( device: &Device, layout: &BindGroupLayout, - buffers: [&Buffer; PrimitiveBuffers::LEN], + buffers: [(u32, &Buffer); PrimitiveBuffers::LEN], ) -> BindGroup { device.create_bind_group(&BindGroupDescriptor { layout, - entries: &buffers.each_ref().map(|b| BindGroupEntry { - binding: 0, - resource: b.as_entire_binding(), + entries: &buffers.map(|(binding, buf)| BindGroupEntry { + binding, + resource: buf.as_entire_binding(), }), label: Some("ui primitives"), }) } + + fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout { + device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: Some(NonZero::new(limits.max_textures).unwrap()), + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::NonFiltering), + count: Some(NonZero::new(limits.max_samplers).unwrap()), + }, + ], + label: Some("ui rsc"), + }) + } + + fn rsc_group( + device: &Device, + layout: &BindGroupLayout, + tex_manager: &GpuTextures, + ) -> BindGroup { + device.create_bind_group(&BindGroupDescriptor { + layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureViewArray(&tex_manager.views()), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::SamplerArray(&tex_manager.samplers()), + }, + ], + label: Some("ui rsc"), + }) + } +} + +pub struct UiLimits { + max_textures: u32, + max_samplers: u32, +} + +impl Default for UiLimits { + fn default() -> Self { + Self { + max_textures: 100000, + max_samplers: 1000, + } + } +} + +impl UiLimits { + pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 { + self.max_textures + self.max_samplers + } + pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 { + self.max_samplers + } } diff --git a/src/render/primitive.rs b/src/render/primitive.rs index ab8980a..6b83721 100644 --- a/src/render/primitive.rs +++ b/src/render/primitive.rs @@ -35,9 +35,9 @@ macro_rules! primitives { impl PrimitiveBuffers { pub const LEN: usize = primitives!(@count $($name)*); - pub fn buffers(&self) -> [&Buffer; Self::LEN] { + pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] { [ - $(&self.$name.buffer)* + $((<$ty>::BINDING, &self.$name.buffer),)* ] } pub fn new(device: &Device) -> Self { @@ -46,7 +46,7 @@ macro_rules! primitives { device, BufferUsages::STORAGE | BufferUsages::COPY_DST, stringify!($name), - ))* + ),)* } } } @@ -62,14 +62,10 @@ macro_rules! primitives { } )* }; - (@count $t1:tt, $($t:tt),+) => { 1 + gen!(@count $($t),+) }; + (@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t),+) }; (@count $t:tt) => { 1 }; } -primitives!( - rects: RoundedRectData => 0, -); - impl Primitives { pub fn write(&mut self, data: P, region: UiRegion) { let vec = P::vec(&mut self.data); @@ -83,11 +79,23 @@ impl Primitives { } } +primitives!( + rects: RectPrimitive => 0, + textures: TexturePrimitive => 1, +); + #[repr(C)] #[derive(Copy, Clone)] -pub struct RoundedRectData { +pub struct RectPrimitive { pub color: Color, pub radius: f32, pub thickness: f32, pub inner_radius: f32, } + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct TexturePrimitive { + pub view_idx: u32, + pub sampler_idx: u32, +} diff --git a/src/render/shader.wgsl b/src/render/shader.wgsl index 5ba65ed..dcd08a6 100644 --- a/src/render/shader.wgsl +++ b/src/render/shader.wgsl @@ -1,9 +1,29 @@ const RECT: u32 = 0; +const TEXTURE: u32 = 1; @group(0) @binding(0) var window: WindowUniform; @group(1) @binding(RECT) -var rects: array; +var rects: array; +@group(1) @binding(TEXTURE) +var textures: array; + +struct Rect { + color: u32, + radius: f32, + thickness: f32, + inner_radius: f32, +} + +struct TextureInfo { + view_idx: u32, + sampler_idx: u32, +} + +@group(2) @binding(0) +var views: binding_array>; +@group(2) @binding(1) +var samplers: binding_array; struct WindowUniform { dim: vec2, @@ -18,23 +38,18 @@ struct InstanceInput { @location(5) idx: u32, } -struct RoundedRect { - color: u32, - radius: f32, - thickness: f32, - inner_radius: f32, -} - struct VertexOutput { @location(0) top_left: vec2, @location(1) bot_right: vec2, - @location(2) binding: u32, - @location(3) idx: u32, + @location(2) uv: vec2, + @location(3) binding: u32, + @location(4) idx: u32, @builtin(position) clip_position: vec4, }; struct Region { pos: vec2, + uv: vec2, top_left: vec2, bot_right: vec2, } @@ -50,12 +65,13 @@ fn vs_main( let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset; let size = bot_right - top_left; - var pos = top_left + vec2( + let uv = vec2( f32(vi % 2u), f32(vi / 2u) - ) * size; - pos = pos / window.dim * 2.0 - 1.0; + ); + let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0; out.clip_position = vec4(pos.x, -pos.y, 0.0, 1.0); + out.uv = uv; out.binding = in.binding; out.idx = in.idx; out.top_left = top_left; @@ -69,19 +85,27 @@ fn fs_main( in: VertexOutput ) -> @location(0) vec4 { let pos = in.clip_position.xy; - let region = Region(pos, in.top_left, in.bot_right); + let region = Region(pos, in.uv, in.top_left, in.bot_right); let i = in.idx; switch in.binding { case RECT: { return draw_rounded_rect(region, rects[i]); } + case TEXTURE: { + return draw_texture(region, textures[i]); + } default: { return vec4(1.0, 0.0, 1.0, 1.0); } } } -fn draw_rounded_rect(region: Region, rect: RoundedRect) -> vec4 { +// TODO: this seems really inefficient (per frag indexing)? +fn draw_texture(region: Region, info: TextureInfo) -> vec4 { + return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv); +} + +fn draw_rounded_rect(region: Region, rect: Rect) -> vec4 { var color = read_color(rect.color); let edge = 0.5; @@ -106,7 +130,7 @@ fn distance_from_rect(pixel_pos: vec2, rect_center: vec2, rect_corner: 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; + return length(max(q, vec2(0.0))) - radius; } fn read_color(c: u32) -> vec4 { diff --git a/src/render/texture.rs b/src/render/texture.rs new file mode 100644 index 0000000..a5e83cf --- /dev/null +++ b/src/render/texture.rs @@ -0,0 +1,98 @@ +use image::{DynamicImage, EncodableLayout}; +use wgpu::{util::DeviceExt, *}; + +use crate::TextureUpdates; + +pub struct GpuTextures { + device: Device, + queue: Queue, + views: Vec, + samplers: Vec, + no_views: Vec, +} + +impl GpuTextures { + pub fn apply(&mut self, updates: TextureUpdates) -> bool { + if let Some(images) = updates.images { + for img in images { + self.add_view(img); + } + true + } else { + false + } + } + fn add_view(&mut self, image: &DynamicImage) { + let image = image.to_rgba8(); + let (width, height) = image.dimensions(); + let texture = self.device.create_texture_with_data( + &self.queue, + &TextureDescriptor { + label: None, + size: Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + wgt::TextureDataOrder::MipMajor, + image.as_bytes(), + ); + let view = texture.create_view(&TextureViewDescriptor::default()); + self.views.push(view); + } + + pub fn new(device: &Device, queue: &Queue) -> Self { + Self { + device: device.clone(), + queue: queue.clone(), + views: Vec::new(), + samplers: vec![default_sampler(device)], + no_views: vec![null_texture_view(device)], + } + } + + pub fn views(&self) -> Vec<&TextureView> { + if self.views.is_empty() { + &self.no_views + } else { + &self.views + } + .iter() + .by_ref() + .collect() + } + + pub fn samplers(&self) -> Vec<&Sampler> { + self.samplers.iter().by_ref().collect() + } +} + +pub fn null_texture_view(device: &Device) -> TextureView { + device + .create_texture(&TextureDescriptor { + label: Some("null"), + size: Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }) + .create_view(&TextureViewDescriptor::default()) +} + +pub fn default_sampler(device: &Device) -> Sampler { + device.create_sampler(&SamplerDescriptor::default()) +} diff --git a/src/render/util/mod.rs b/src/render/util/mod.rs index 8d9b31f..c9d48ff 100644 --- a/src/render/util/mod.rs +++ b/src/render/util/mod.rs @@ -32,7 +32,7 @@ impl ArrBuf { 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); + size = size.max(std::mem::size_of::() as u64); } device.create_buffer(&BufferDescriptor { label: Some(label), diff --git a/src/testing/app.rs b/src/testing/app.rs index a738825..1b7ad89 100644 --- a/src/testing/app.rs +++ b/src/testing/app.rs @@ -1,4 +1,4 @@ -use gui::Ui; +use ui::Ui; use winit::{ application::ApplicationHandler, event::WindowEvent, diff --git a/src/testing/assets/sungals.png b/src/testing/assets/sungals.png new file mode 100755 index 0000000..0070de2 Binary files /dev/null and b/src/testing/assets/sungals.png differ diff --git a/src/testing/input.rs b/src/testing/input.rs index 0971d1f..67c836a 100644 --- a/src/testing/input.rs +++ b/src/testing/input.rs @@ -1,4 +1,4 @@ -use gui::{CursorState, Vec2}; +use ui::{CursorState, Vec2}; use winit::event::WindowEvent; use crate::testing::Client; diff --git a/src/testing/mod.rs b/src/testing/mod.rs index cf288c9..fe8be2f 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use app::App; -use gui::*; use render::Renderer; +use ui::*; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; use crate::testing::input::Input; @@ -76,6 +76,8 @@ impl Client { let span_add_test = ui.add(Span::empty(Dir::RIGHT).id(&span_add)); let main: WidgetId = ui.id(); + let image_test = ui.add(image(include_bytes!("assets/sungals.png"))); + fn switch_button( color: UiColor, main: &WidgetId, @@ -104,8 +106,9 @@ impl Client { switch_button(UiColor::RED, &main, &pad_test), switch_button(UiColor::GREEN, &main, &span_test), switch_button(UiColor::BLUE, &main, &span_add_test), + switch_button(UiColor::MAGENTA, &main, &image_test), ) - .span(Dir::RIGHT, [1, 1, 1]), + .span(Dir::RIGHT, [1, 1, 1, 1]), ); let test_button = Rect::new(Color::PURPLE) .radius(30) @@ -156,8 +159,8 @@ impl Client { match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { - let primitives = ui.update(self); - self.renderer.update(primitives); + let updates = ui.update(self); + self.renderer.update(updates); self.renderer.draw() } WindowEvent::Resized(size) => self.renderer.resize(&size), diff --git a/src/testing/render/mod.rs b/src/testing/render/mod.rs index 60a4dfc..44be282 100644 --- a/src/testing/render/mod.rs +++ b/src/testing/render/mod.rs @@ -1,48 +1,51 @@ -use gui::{UIRenderNode, primitive::Primitives}; use pollster::FutureExt; use std::sync::Arc; -use wgpu::util::StagingBelt; +use ui::{ + UiRenderUpdates, + render::{UiLimits, UiRenderer}, +}; +use wgpu::{util::StagingBelt, *}; use winit::{dpi::PhysicalSize, window::Window}; -pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK; +pub const CLEAR_COLOR: Color = Color::BLACK; pub struct Renderer { window: Arc, - surface: wgpu::Surface<'static>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - encoder: wgpu::CommandEncoder, + surface: Surface<'static>, + device: Device, + queue: Queue, + config: SurfaceConfiguration, + encoder: CommandEncoder, staging_belt: StagingBelt, - ui_node: UIRenderNode, + ui: UiRenderer, } impl Renderer { - pub fn update(&mut self, primitives: Option<&Primitives>) { - self.ui_node.update(&self.device, &self.queue, primitives); + pub fn update(&mut self, updates: UiRenderUpdates) { + self.ui.update(&self.device, &self.queue, updates); } pub fn draw(&mut self) { let output = self.surface.get_current_texture().unwrap(); let view = output .texture - .create_view(&wgpu::TextureViewDescriptor::default()); + .create_view(&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 { + let render_pass = &mut encoder.begin_render_pass(&RenderPassDescriptor { + color_attachments: &[Some(RenderPassColorAttachment { view: &view, resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(CLEAR_COLOR), - store: wgpu::StoreOp::Store, + ops: Operations { + load: LoadOp::Clear(CLEAR_COLOR), + store: StoreOp::Store, }, depth_slice: None, })], ..Default::default() }); - self.ui_node.draw(render_pass); + self.ui.draw(render_pass); } self.queue.submit(std::iter::once(encoder.finish())); @@ -55,11 +58,11 @@ impl Renderer { self.config.width = size.width; self.config.height = size.height; self.surface.configure(&self.device, &self.config); - self.ui_node.resize(size, &self.queue); + self.ui.resize(size, &self.queue); } - fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder { - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + fn create_encoder(device: &Device) -> CommandEncoder { + device.create_command_encoder(&CommandEncoderDescriptor { label: Some("Render Encoder"), }) } @@ -67,8 +70,8 @@ impl Renderer { pub fn new(window: Arc) -> Self { let size = window.inner_size(); - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, + let instance = Instance::new(&InstanceDescriptor { + backends: Backends::PRIMARY, ..Default::default() }); @@ -77,18 +80,28 @@ impl Renderer { .expect("Could not create window surface!"); let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), + .request_adapter(&RequestAdapterOptions { + power_preference: PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, }) .block_on() .expect("Could not get adapter!"); + let ui_limits = UiLimits::default(); + let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), + .request_device(&DeviceDescriptor { + required_features: Features::TEXTURE_BINDING_ARRAY + | Features::PARTIALLY_BOUND_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + required_limits: Limits { + max_binding_array_elements_per_shader_stage: ui_limits + .max_binding_array_elements_per_shader_stage(), + max_binding_array_sampler_elements_per_shader_stage: ui_limits + .max_binding_array_sampler_elements_per_shader_stage(), + ..Default::default() + }, ..Default::default() }) .block_on() @@ -102,12 +115,12 @@ impl Renderer { .find(|f| f.is_srgb()) .unwrap_or(surface_caps.formats[0]); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + let config = SurfaceConfiguration { + usage: TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: size.width, height: size.height, - present_mode: wgpu::PresentMode::AutoVsync, + present_mode: PresentMode::AutoVsync, alpha_mode: surface_caps.alpha_modes[0], desired_maximum_frame_latency: 2, view_formats: vec![], @@ -118,7 +131,7 @@ impl Renderer { let staging_belt = StagingBelt::new(4096 * 4); let encoder = Self::create_encoder(&device); - let shape_pipeline = UIRenderNode::new(&device, &config); + let shape_pipeline = UiRenderer::new(&device, &queue, &config, ui_limits); Self { surface, @@ -127,7 +140,7 @@ impl Renderer { config, encoder, staging_belt, - ui_node: shape_pipeline, + ui: shape_pipeline, window, } } diff --git a/src/util/id.rs b/src/util/id.rs index e76bb65..9b92cac 100644 --- a/src/util/id.rs +++ b/src/util/id.rs @@ -7,12 +7,12 @@ pub struct Id(u64); #[derive(Default)] -pub struct IDTracker { +pub struct IdTracker { free: Vec, cur: u64, } -impl IDTracker { +impl IdTracker { #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Id { if let Some(id) = self.free.pop() {