added underdeveloped but working image support (no freeing or samplers)

This commit is contained in:
2025-08-22 23:07:31 -04:00
parent bde929b05a
commit 7dbdcbba42
26 changed files with 1256 additions and 155 deletions

702
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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"

View File

@@ -8,7 +8,7 @@ pub struct Regioned {
impl<Ctx: 'static> Widget<Ctx> for Regioned {
fn draw(&self, painter: &mut Painter<Ctx>) {
painter.region.select(&self.region);
painter.draw(&self.inner);
painter.draw_inner(&self.inner);
}
}

63
src/core/image.rs Normal file
View File

@@ -0,0 +1,63 @@
use image::DynamicImage;
use crate::{Color, TextureHandle, Widget, WidgetFnRet, render::RectPrimitive};
pub struct Image {
handle: Option<TextureHandle>,
}
impl<Ctx> Widget<Ctx> for Image {
fn draw(&self, painter: &mut crate::Painter<Ctx>) {
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<Ctx>(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<DynamicImage, String>;
}
impl LoadableImage for &str {
fn get_image(self) -> Result<DynamicImage, String> {
image::open(self).map_err(|e| format!("{e:?}"))
}
}
impl LoadableImage for String {
fn get_image(self) -> Result<DynamicImage, String> {
image::open(self).map_err(|e| format!("{e:?}"))
}
}
impl<const LEN: usize> LoadableImage for &[u8; LEN] {
fn get_image(self) -> Result<DynamicImage, String> {
image::load_from_memory(self).map_err(|e| format!("{e:?}"))
}
}
impl LoadableImage for DynamicImage {
fn get_image(self) -> Result<DynamicImage, String> {
Ok(self)
}
}

View File

@@ -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::*;

View File

@@ -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<Ctx> Widget<Ctx> for Rect {
fn draw(&self, painter: &mut Painter<Ctx>) {
painter.write(RoundedRectData {
painter.write(RectPrimitive {
color: self.color,
radius: self.radius,
thickness: self.thickness,

View File

@@ -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)>,

View File

@@ -7,7 +7,7 @@ pub struct Stack {
impl<Ctx> Widget<Ctx> for Stack {
fn draw(&self, painter: &mut crate::Painter<Ctx>) {
for child in &self.children {
painter.draw_within(child, painter.region);
painter.draw(child);
}
}
}

View File

@@ -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<u8>;

View File

@@ -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<W>(&mut self, id: &WidgetId<W>)
/// Draws a widget but does NOT maintain the region.
/// Useful if you only have a single widget to draw.
pub fn draw_inner<W>(&mut self, id: &WidgetId<W>)
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<W>(&mut self, id: &WidgetId<W>)
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(&region);
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
}

48
src/layout/texture.rs Normal file
View File

@@ -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<DynamicImage>,
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 }
}
}
}

View File

@@ -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<Ctx> {
ids: IDTracker,
base: Option<WidgetId>,
widgets: Widgets<Ctx>,
updates: Vec<WidgetId>,
del_recv: Receiver<Id>,
del_send: Sender<Id>,
primitives: Primitives,
textures: Textures,
full_redraw: bool,
pub(super) active_sensors: ActiveSensors,
pub(super) sensor_map: SensorMap<Ctx>,
primitives: Primitives,
full_redraw: bool,
}
#[derive(Default)]
pub struct Widgets<Ctx>(HashMap<Id, Box<dyn Widget<Ctx>>>);
pub struct Widgets<Ctx> {
ids: IdTracker,
map: HashMap<Id, Box<dyn Widget<Ctx>>>,
}
impl<Ctx> Ui<Ctx> {
pub fn add<W: Widget<Ctx>, Tag>(
@@ -68,7 +75,15 @@ impl<Ctx> Ui<Ctx> {
}
pub fn id<W: Widget<Ctx>>(&mut self) -> WidgetId<W> {
WidgetId::new(self.ids.next(), TypeId::of::<W>(), self.del_send.clone())
WidgetId::new(
self.widgets.reserve(),
TypeId::of::<W>(),
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<Ctx> Ui<Ctx> {
&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<Ctx> Ui<Ctx> {
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<W: Widget<Ctx>, Ctx> IndexMut<&WidgetId<W>> for Ui<Ctx> {
impl<Ctx> Widgets<Ctx> {
pub fn new() -> Self {
Self(HashMap::new())
Self {
ids: IdTracker::default(),
map: HashMap::new(),
}
}
pub fn get_dyn<W>(&self, id: &WidgetId<W>) -> &dyn Widget<Ctx> {
self.0.get(&id.id).unwrap().as_ref()
self.map.get(&id.id).unwrap().as_ref()
}
pub fn get<W: Widget<Ctx>>(&self, id: &WidgetId<W>) -> 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<W: Widget<Ctx>>(&mut self, id: &WidgetId<W>) -> 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<Ctx>) {
self.0.insert(id, Box::new(widget));
self.map.insert(id, Box::new(widget));
}
pub fn insert_any(&mut self, id: Id, widget: Box<dyn Widget<Ctx>>) {
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<Ctx: 'static> Default for Ui<Ctx> {
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<Ctx: 'static> Default for Ui<Ctx> {
}
}
}
pub struct UiRenderUpdates<'a> {
pub primitives: Option<&'a Primitives>,
pub textures: TextureUpdates<'a>,
}

View File

@@ -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<T: UiNum, U: UiNum> 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)
}
}

View File

@@ -41,8 +41,7 @@ pub(crate) use WidgetFnRet;
impl<W: Widget<Ctx>, Ctx, F: FnOnce(&mut Ui<Ctx>) -> W> WidgetLike<Ctx, FnTag> for F {
type Widget = W;
fn add(self, ui: &mut Ui<Ctx>) -> WidgetId<W> {
let w = self(ui);
ui.add(w)
self(ui).add(ui)
}
}

View File

@@ -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<K, V> = std::collections::HashMap<K, V>;
pub type HashSet<K> = std::collections::HashSet<K>;

View File

@@ -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<PrimitiveInstance>,
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<u32>, 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
}
}

View File

@@ -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<P: Primitive>(&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<u8>,
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,
}

View File

@@ -1,9 +1,29 @@
const RECT: u32 = 0;
const TEXTURE: u32 = 1;
@group(0) @binding(0)
var<uniform> window: WindowUniform;
@group(1) @binding(RECT)
var<storage> rects: array<RoundedRect>;
var<storage> rects: array<Rect>;
@group(1) @binding(TEXTURE)
var<storage> textures: array<TextureInfo>;
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<texture_2d<f32>>;
@group(2) @binding(1)
var samplers: binding_array<sampler>;
struct WindowUniform {
dim: vec2<f32>,
@@ -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<f32>,
@location(1) bot_right: vec2<f32>,
@location(2) binding: u32,
@location(3) idx: u32,
@location(2) uv: vec2<f32>,
@location(3) binding: u32,
@location(4) idx: u32,
@builtin(position) clip_position: vec4<f32>,
};
struct Region {
pos: vec2<f32>,
uv: vec2<f32>,
top_left: vec2<f32>,
bot_right: vec2<f32>,
}
@@ -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<f32>(
let uv = vec2<f32>(
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<f32>(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<f32> {
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<f32> {
// TODO: this seems really inefficient (per frag indexing)?
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
}
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
var color = read_color(rect.color);
let edge = 0.5;
@@ -106,7 +130,7 @@ fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, 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<f32>(0.0, 0.0))) - radius;
return length(max(q, vec2(0.0))) - radius;
}
fn read_color(c: u32) -> vec4<f32> {

98
src/render/texture.rs Normal file
View File

@@ -0,0 +1,98 @@
use image::{DynamicImage, EncodableLayout};
use wgpu::{util::DeviceExt, *};
use crate::TextureUpdates;
pub struct GpuTextures {
device: Device,
queue: Queue,
views: Vec<TextureView>,
samplers: Vec<Sampler>,
no_views: Vec<TextureView>,
}
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())
}

View File

@@ -32,7 +32,7 @@ impl<T: Pod> ArrBuf<T> {
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::<T>() as u64);
}
device.create_buffer(&BufferDescriptor {
label: Some(label),

View File

@@ -1,4 +1,4 @@
use gui::Ui;
use ui::Ui;
use winit::{
application::ApplicationHandler,
event::WindowEvent,

BIN
src/testing/assets/sungals.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,4 +1,4 @@
use gui::{CursorState, Vec2};
use ui::{CursorState, Vec2};
use winit::event::WindowEvent;
use crate::testing::Client;

View File

@@ -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<Regioned> = ui.id();
let image_test = ui.add(image(include_bytes!("assets/sungals.png")));
fn switch_button<To>(
color: UiColor,
main: &WidgetId<Regioned>,
@@ -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),

View File

@@ -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<Window>,
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<Window>) -> 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,
}
}

View File

@@ -7,12 +7,12 @@
pub struct Id(u64);
#[derive(Default)]
pub struct IDTracker {
pub struct IdTracker {
free: Vec<Id>,
cur: u64,
}
impl IDTracker {
impl IdTracker {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Id {
if let Some(id) = self.free.pop() {