diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1c126b8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/target/debug/radar-g", + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_LOG": "info" + } + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5eaab8f..a6deabf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,15 +18,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - [[package]] name = "adler" version = "1.0.2" @@ -55,6 +46,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "aligned-vec" version = "0.6.0" @@ -185,6 +182,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[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 2.0.66", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -216,20 +230,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] -name = "backtrace" -version = "0.3.73" +name = "av1-grain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", ] +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[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" @@ -242,6 +270,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitstream-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" + [[package]] name = "block" version = "0.1.6" @@ -267,6 +301,12 @@ dependencies = [ "objc2", ] +[[package]] +name = "built" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" + [[package]] name = "bumpalo" version = "3.16.0" @@ -299,6 +339,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[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.6.0" @@ -348,6 +394,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.0" @@ -399,46 +455,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "clap" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "clap_lex" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" - [[package]] name = "clipboard-win" version = "3.1.1" @@ -543,18 +559,64 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +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.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "cursor-icon" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -582,20 +644,10 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" dependencies = [ - "itertools", + "itertools 0.11.0", "num-traits", ] -[[package]] -name = "easy-signed-distance-field" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1df5b74971df6cfd9ce883fd96cbe5d6ae46868d681870d72fa518f2184472f" -dependencies = [ - "image", - "ttf-parser 0.15.2", -] - [[package]] name = "either" version = "1.13.0" @@ -661,6 +713,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fdeflate" version = "0.3.4" @@ -686,6 +754,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -713,6 +790,28 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "freetype-rs" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1d1f81b925f09d7040682dbc91eb1b6ad43232f4bc6ee080f518001c05b5415" +dependencies = [ + "bitflags 2.5.0", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb84abd9992afaa8eb9b8bef907a94553504d0a890924e1bf1f1ab1249455af" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "geo" version = "0.28.0" @@ -773,10 +872,14 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.29.0" +name = "gif" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] [[package]] name = "gl_generator" @@ -866,6 +969,16 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hash32" version = "0.3.1" @@ -949,17 +1062,43 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", - "byteorder", + "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.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "imgui" version = "0.12.0" @@ -1035,6 +1174,17 @@ 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 2.0.66", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -1050,6 +1200,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1087,6 +1246,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.69" @@ -1108,12 +1273,29 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" +[[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.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.8.3" @@ -1141,6 +1323,16 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1163,6 +1355,24 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1182,6 +1392,15 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.7.2" @@ -1326,6 +1545,12 @@ 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" @@ -1358,6 +1583,12 @@ dependencies = [ "syn 1.0.109", ] +[[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" @@ -1377,6 +1608,17 @@ dependencies = [ "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 2.0.66", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1407,16 +1649,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" version = "0.7.2" @@ -1489,28 +1721,25 @@ dependencies = [ "objc", ] -[[package]] -name = "object" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox", + "libredox 0.0.2", ] [[package]] @@ -1519,7 +1748,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b41438d2fc63c46c74a2203bf5ccd82c41ba04347b2fcf5754f230b167067d5" dependencies = [ - "ttf-parser 0.21.1", + "ttf-parser", ] [[package]] @@ -1551,6 +1780,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2" +dependencies = [ + "rustc_version", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1597,13 +1845,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-crate" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit", + "toml_edit 0.21.1", ] [[package]] @@ -1615,6 +1869,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.66", +] + +[[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" version = "0.34.0" @@ -1637,40 +1925,124 @@ dependencies = [ name = "radar-g" version = "0.1.0" dependencies = [ - "aligned-vec", + "aligned-vec 0.6.0", "anyhow", "bytemuck", "byteorder", "cgmath", "chrono", "copypasta", - "easy-signed-distance-field", + "dirs", "env_logger", "flate2", + "freetype-rs", "geo", "glow", "glutin", "glutin-winit", + "image", "imgui", "imgui-glow-renderer", "imgui-winit-support", "include_dir", "log", + "lru", "nalgebra 0.33.0", "nalgebra-glm", "ndarray", "nom", "nom-derive", "once_cell", + "pathfinder_geometry", "raw-window-handle 0.5.2", "regex", - "sdf_glyph_renderer", "serde", "serde_json", "thiserror", + "tinyfiledialogs", + "tracker", "winit", ] +[[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", +] + +[[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 0.12.1", + "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", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -1689,6 +2061,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1716,6 +2108,17 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox 0.1.3", + "thiserror", +] + [[package]] name = "regex" version = "1.10.5" @@ -1745,6 +2148,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "rgb" +version = "0.8.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" +dependencies = [ + "bytemuck", +] + [[package]] name = "robust" version = "1.1.0" @@ -1763,10 +2175,13 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] [[package]] name = "rustix" @@ -1837,24 +2252,10 @@ dependencies = [ ] [[package]] -name = "sdf_font" -version = "0.1.0" -dependencies = [ - "clap", - "easy-signed-distance-field", - "num_cpus", - "spmc", - "tokio", -] - -[[package]] -name = "sdf_glyph_renderer" -version = "1.0.1" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef220f6a4fff0c984e9fb3ab12cb5c86960b5bb6ec3b30dd7173e3bf603d94f" -dependencies = [ - "thiserror", -] +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" @@ -1887,6 +2288,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "simba" version = "0.8.1" @@ -1919,6 +2329,15 @@ 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.9" @@ -2001,10 +2420,13 @@ dependencies = [ ] [[package]] -name = "spmc" -version = "0.3.0" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8428da277a8e3a15271d79943e80ccc2ef254e78813a166a08d65e4c3ece5" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "stable_deref_trait" @@ -2018,12 +2440,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "syn" version = "1.0.109" @@ -2046,6 +2462,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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" + [[package]] name = "thiserror" version = "1.0.61" @@ -2066,6 +2501,17 @@ dependencies = [ "syn 2.0.66", ] +[[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" @@ -2092,14 +2538,25 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.38.1" +name = "tinyfiledialogs" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "e25fa0bc43a6566e2cc6d7ac96df3fa5a57beba34445bead1b368ba8fe9ca568" dependencies = [ - "backtrace", - "num_cpus", - "pin-project-lite", + "cc", + "libc", +] + +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.16", ] [[package]] @@ -2107,6 +2564,9 @@ name = "toml_datetime" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2116,7 +2576,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.15", ] [[package]] @@ -2136,10 +2609,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" [[package]] -name = "ttf-parser" -version = "0.15.2" +name = "tracker" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" +checksum = "ce5c98457ff700aaeefcd4a4a492096e78a2af1dd8523c66e94a3adb0fdbd415" +dependencies = [ + "tracker-macros", +] + +[[package]] +name = "tracker-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc19eb2373ccf3d1999967c26c3d44534ff71ae5d8b9dacf78f4b13132229e48" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] [[package]] name = "ttf-parser" @@ -2171,6 +2658,23 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec 0.5.0", + "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.4" @@ -2388,6 +2892,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "wide" version = "0.7.22" @@ -2701,6 +3211,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0" +dependencies = [ + "memchr", +] + [[package]] name = "x11-clipboard" version = "0.9.2" @@ -2793,3 +3312,27 @@ dependencies = [ "quote", "syn 2.0.66", ] + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 4566e43..89906a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,33 +12,40 @@ byteorder = "1.5.0" cgmath = "0.18.0" chrono = "0.4.38" copypasta = "0.10.1" -easy-signed-distance-field = "0.1.1" +dirs = "5.0.1" env_logger = "0.11.3" flate2 = "1.0.30" +# font-kit = {version = "0.14.1"} +freetype-rs = "0.37.0" geo = "0.28.0" glow = "0.13.1" glutin = "0.31.3" glutin-winit = "0.4.2" -imgui = "0.12.0" +image = "0.25.2" +imgui = {version="0.12.0",features = ["tables-api"]} imgui-glow-renderer = "0.12.0" imgui-winit-support = "0.12.0" include_dir = "0.7.4" log = "0.4.22" +lru = "0.12.4" nalgebra = "0.33.0" nalgebra-glm = "0.18.0" ndarray = "0.15.6" nom = "7.1.3" nom-derive = "0.10.1" once_cell = "1.19.0" +pathfinder_geometry = "0.5.1" raw-window-handle = "0.5.2" regex = "1.10.5" -sdf_glyph_renderer = "1.0.1" serde = {version = "1.0.204", features = ["derive"]} serde_json = "1.0.120" thiserror = "1.0.61" winit = "0.29.3" +tinyfiledialogs = "3.0" +tracker = "0.2.2" -[workspace] -members = [ - "sdf_font", -] +[features] +default = ["sdf_font"] + +normal_font = [] +sdf_font = [] diff --git a/sdf_font/Cargo.toml b/sdf_font/Cargo.toml deleted file mode 100644 index 4a0fcba..0000000 --- a/sdf_font/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -edition = "2021" -name = "sdf_font" -version = "0.1.0" - -[dependencies] -clap = { version = "4.5.9", features = ["derive"] } -easy-signed-distance-field = {version = "0.1.1", features = ["font", "ttf-parser", "export", "render"]} -num_cpus = "1.16.0" -spmc = "0.3.0" -tokio = { version = "1.38.1", features = ["fs","rt-multi-thread","sync"] } diff --git a/sdf_font/src/check.rs b/sdf_font/src/check.rs deleted file mode 100644 index aac2ccf..0000000 --- a/sdf_font/src/check.rs +++ /dev/null @@ -1,4 +0,0 @@ -use std::{fs::File, io::Read}; - -#[test] -fn test() {} diff --git a/sdf_font/src/main.rs b/sdf_font/src/main.rs deleted file mode 100644 index b3684b0..0000000 --- a/sdf_font/src/main.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod check; - -use clap::{command, Parser}; -use easy_signed_distance_field as sdf; -use std::fs::{self, create_dir_all, File}; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TOTAL_GLYPHS_RENDERED: AtomicUsize = AtomicUsize::new(0); -const CHARACTERS: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz\ - 0123456789\ - ,.!?@#$%^&*()"; - -#[derive(Parser, Debug)] -#[command(version, author, about)] -struct Args { - /// Sets the source directory to be scanned for fonts. - font_dir: PathBuf, - // / Sets the output directory in which the PBF glyphs will be placed (each font will be placed in a new subdirectory with appropriately named PBF files). - // out_dir: PathBuf, -} - -fn render_worker(path: &PathBuf, output: &PathBuf) { - let mut file = fs::read(path).unwrap(); - let font = sdf::Font::from_bytes(file.as_slice(), Default::default()).unwrap(); - let px = 64.0; - let padding = 2; - let spread = 6.0; - - for c in CHARACTERS.chars() { - if let Some((metrics, glyph_sdf)) = font.sdf_generate(px, padding, spread, c) { - let mut output = output.join(format!("{}.png", c)); - output.parent().map(|p| create_dir_all(p).unwrap()); - sdf::sdf_to_file(output.as_os_str().to_str().unwrap(), &glyph_sdf).unwrap(); - output.set_file_name(format!("fuck_{}.png", c)); - sdf::sdf_render_to_file( - output.as_os_str().to_str().unwrap(), - 0.5, - 0.5, - 0.02, - &glyph_sdf, - ) - .unwrap(); - } - } -} - -fn main() { - let args = Args::parse(); - - let font_dir = &args.font_dir; - - let num_threads = num_cpus::get(); - println!("Starting {num_threads} worker threads..."); - - for dir_entry in font_dir - .read_dir() - .expect("Unable to open font directory") - .flatten() - { - let path = dir_entry.path(); - - if let (Some(stem), Some(extension)) = (path.file_stem(), path.extension()) { - if path.is_file() && (["otf", "ttf"].contains(&extension.to_str().unwrap())) { - println!("Rendering glyphs for font: {}", path.to_str().unwrap()); - let filename = PathBuf::from(format!("test/{}", stem.to_str().unwrap())); - render_worker(&path, &filename) - } - } - } -} diff --git a/shaders/font.frag b/shaders/font.frag new file mode 100644 index 0000000..23a6742 --- /dev/null +++ b/shaders/font.frag @@ -0,0 +1,65 @@ +uniform sampler2D atlas_data; +in vec2 v_texCoord; + +uniform vec4 uClipUV; +// uniform vec2 uSdfUV; +uniform vec4 uSdfConfig; +uniform int uMode; +uniform vec4 uBorder; +uniform vec4 uStroke; +uniform vec4 uFill; + +float getUVScale(vec2 sdfUV) { + float dx = dFdx(sdfUV.x); + float dy = dFdy(sdfUV.y); + return (sqrt(dx * dx + dy * dy) + sqrt(dy * dy + dx * dx)) * 0.5; +} + +struct SDF { + float outer; + float inner; +}; + + +vec4 getTexture(vec2 uv) { + return texture(atlas_data, uv); +} + +vec4 getMask(vec4 color, vec2 uv, vec2 st) { + return color; // 默认不修改颜色 +} + +out vec4 fragColor; + +void main() { + vec4 fillColor = uFill; + vec4 strokeColor = uStroke; + + float scale = getUVScale(v_texCoord); + + vec4 texture = getTexture(v_texCoord); + float sdfRaw = 0.0; + float mark = 0.0; + + float sdf; + + float sdfRadius = uSdfConfig.x; + float expand = uBorder.x; + float bleed = uBorder.y; + + float d = (texture.r - 0.75) * sdfRadius; + float s = (d + expand / uSdfConfig.y) / scale + 0.5 + bleed; + sdf = s; // Assuming SDF returns a single float, adjust as necessary + + if (uMode == -2) { + fillColor = vec4(texture.rgb, fillColor.a); + } + + if (gl_FragCoord.x < uClipUV.x || gl_FragCoord.y < uClipUV.y || gl_FragCoord.x > uClipUV.z || gl_FragCoord.y > uClipUV.w) { + discard; + } + // Compute mask based on SDF + float mask = clamp(sdf, 0.0, 1.0); + // Final color blending logic here + fragColor = vec4(fillColor.rgb + mark, fillColor.a * mask + mark); +} \ No newline at end of file diff --git a/shaders/font.vert b/shaders/font.vert new file mode 100644 index 0000000..e7ba55a --- /dev/null +++ b/shaders/font.vert @@ -0,0 +1,28 @@ +layout(location = 0) in vec3 input_position; +layout(location = 1) in vec2 texcoord; + + +uniform vec2 atlas_shape; + +out float v_scale; +out vec2 v_texCoord; +out vec4 v_color; + + + +const vec2 verts[4] = vec2[4]( + vec2(-0.5f, 0.5f), + vec2(0.5f, 0.5f), + vec2(0.5f,-0.5f), + vec2(-0.5, -0.5f) +); + +void main() { + + // vec4 _position = ; + gl_Position = vec4(verts[gl_VertexID],0.0,1.0); + v_texCoord = texcoord / atlas_shape; + + v_scale = 1.0; + v_color = vec4(1.0, 1.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/shaders/misc/spatial-filters.frag b/shaders/misc/spatial-filters.frag new file mode 100644 index 0000000..85ea67b --- /dev/null +++ b/shaders/misc/spatial-filters.frag @@ -0,0 +1,289 @@ +const float kernel_bias = -0.234377; +const float kernel_scale = 1.241974; +uniform sampler2D u_kernel; + +vec4 filter1D_radius1(sampler2D kernel, float index, float x, vec4 c0, vec4 c1) { + float w; + float w_sum = 0.0; + vec4 r = vec4(0.0,0.0,0.0,0.0); + w = texture(kernel, vec2(0.000000+(x/1.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c0 * w; + w = texture(kernel, vec2(1.000000-(x/1.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r + c1 * w; + return r; +} +vec4 filter2D_radius1(sampler2D _texture, sampler2D kernel, float index, vec2 uv, vec2 pixel) { + vec2 texel = uv/pixel - vec2(0.5,0.5) ; + vec2 f = fract(texel); + texel = (texel-fract(texel)+vec2(0.001,0.001))*pixel; + vec4 t0 = filter1D_radius1(kernel, index, f.x, + texture(_texture, texel + vec2(0,0)*pixel), + texture(_texture, texel + vec2(1,0)*pixel)); + vec4 t1 = filter1D_radius1(kernel, index, f.x, + texture(_texture, texel + vec2(0,1)*pixel), + texture(_texture, texel + vec2(1,1)*pixel)); + return filter1D_radius1(kernel, index, f.y, t0, t1); +} + +vec4 filter1D_radius2(sampler2D kernel, float index, float x, vec4 c0, vec4 c1, vec4 c2, vec4 c3) { + float w; + float w_sum = 0.0; + vec4 r = vec4(0.0,0.0,0.0,0.0); + w = texture(kernel, vec2(0.500000+(x/2.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c0 * w; + w = texture(kernel, vec2(0.500000-(x/2.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c2 * w; + w = texture(kernel, vec2(0.000000+(x/2.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c1 * w; + w = texture(kernel, vec2(1.000000-(x/2.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c3 * w; + return r; +} +vec4 filter2D_radius2(sampler2D _texture, sampler2D kernel, float index, vec2 uv, vec2 pixel) { + vec2 texel = uv/pixel - vec2(0.5,0.5) ; + vec2 f = fract(texel); + texel = (texel-fract(texel)+vec2(0.001,0.001))*pixel; + vec4 t0 = filter1D_radius2(kernel, index, f.x, + texture(_texture, texel + vec2(-1,-1)*pixel), + texture(_texture, texel + vec2(0,-1)*pixel), + texture(_texture, texel + vec2(1,-1)*pixel), + texture(_texture, texel + vec2(2,-1)*pixel)); + vec4 t1 = filter1D_radius2(kernel, index, f.x, + texture(_texture, texel + vec2(-1,0)*pixel), + texture(_texture, texel + vec2(0,0)*pixel), + texture(_texture, texel + vec2(1,0)*pixel), + texture(_texture, texel + vec2(2,0)*pixel)); + vec4 t2 = filter1D_radius2(kernel, index, f.x, + texture(_texture, texel + vec2(-1,1)*pixel), + texture(_texture, texel + vec2(0,1)*pixel), + texture(_texture, texel + vec2(1,1)*pixel), + texture(_texture, texel + vec2(2,1)*pixel)); + vec4 t3 = filter1D_radius2(kernel, index, f.x, + texture(_texture, texel + vec2(-1,2)*pixel), + texture(_texture, texel + vec2(0,2)*pixel), + texture(_texture, texel + vec2(1,2)*pixel), + texture(_texture, texel + vec2(2,2)*pixel)); + return filter1D_radius2(kernel, index, f.y, t0, t1, t2, t3); +} + +vec4 filter1D_radius3(sampler2D kernel, float index, float x, vec4 c0, vec4 c1, vec4 c2, vec4 c3, vec4 c4, vec4 c5) { + float w; + float w_sum = 0.0; + vec4 r = vec4(0.0,0.0,0.0,0.0); + w = texture(kernel, vec2(0.666667+(x/3.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c0 * w; + w = texture(kernel, vec2(0.333333-(x/3.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c3 * w; + w = texture(kernel, vec2(0.333333+(x/3.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c1 * w; + w = texture(kernel, vec2(0.666667-(x/3.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c4 * w; + w = texture(kernel, vec2(0.000000+(x/3.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c2 * w; + w = texture(kernel, vec2(1.000000-(x/3.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c5 * w; + return r; +} +vec4 filter2D_radius3(sampler2D _texture, sampler2D kernel, float index, vec2 uv, vec2 pixel) { + vec2 texel = uv/pixel - vec2(0.5,0.5) ; + vec2 f = fract(texel); + texel = (texel-fract(texel)+vec2(0.001,0.001))*pixel; + vec4 t0 = filter1D_radius3(kernel, index, f.x, + texture(_texture, texel + vec2(-2,-2)*pixel), + texture(_texture, texel + vec2(-1,-2)*pixel), + texture(_texture, texel + vec2(0,-2)*pixel), + texture(_texture, texel + vec2(1,-2)*pixel), + texture(_texture, texel + vec2(2,-2)*pixel), + texture(_texture, texel + vec2(3,-2)*pixel)); + vec4 t1 = filter1D_radius3(kernel, index, f.x, + texture(_texture, texel + vec2(-2,-1)*pixel), + texture(_texture, texel + vec2(-1,-1)*pixel), + texture(_texture, texel + vec2(0,-1)*pixel), + texture(_texture, texel + vec2(1,-1)*pixel), + texture(_texture, texel + vec2(2,-1)*pixel), + texture(_texture, texel + vec2(3,-1)*pixel)); + vec4 t2 = filter1D_radius3(kernel, index, f.x, + texture(_texture, texel + vec2(-2,0)*pixel), + texture(_texture, texel + vec2(-1,0)*pixel), + texture(_texture, texel + vec2(0,0)*pixel), + texture(_texture, texel + vec2(1,0)*pixel), + texture(_texture, texel + vec2(2,0)*pixel), + texture(_texture, texel + vec2(3,0)*pixel)); + vec4 t3 = filter1D_radius3(kernel, index, f.x, + texture(_texture, texel + vec2(-2,1)*pixel), + texture(_texture, texel + vec2(-1,1)*pixel), + texture(_texture, texel + vec2(0,1)*pixel), + texture(_texture, texel + vec2(1,1)*pixel), + texture(_texture, texel + vec2(2,1)*pixel), + texture(_texture, texel + vec2(3,1)*pixel)); + vec4 t4 = filter1D_radius3(kernel, index, f.x, + texture(_texture, texel + vec2(-2,2)*pixel), + texture(_texture, texel + vec2(-1,2)*pixel), + texture(_texture, texel + vec2(0,2)*pixel), + texture(_texture, texel + vec2(1,2)*pixel), + texture(_texture, texel + vec2(2,2)*pixel), + texture(_texture, texel + vec2(3,2)*pixel)); + vec4 t5 = filter1D_radius3(kernel, index, f.x, + texture(_texture, texel + vec2(-2,3)*pixel), + texture(_texture, texel + vec2(-1,3)*pixel), + texture(_texture, texel + vec2(0,3)*pixel), + texture(_texture, texel + vec2(1,3)*pixel), + texture(_texture, texel + vec2(2,3)*pixel), + texture(_texture, texel + vec2(3,3)*pixel)); + return filter1D_radius3(kernel, index, f.y, t0, t1, t2, t3, t4, t5); +} + +vec4 filter1D_radius4(sampler2D kernel, float index, float x, vec4 c0, vec4 c1, vec4 c2, vec4 c3, vec4 c4, vec4 c5, vec4 c6, vec4 c7) { + float w; + float w_sum = 0.0; + vec4 r = vec4(0.0,0.0,0.0,0.0); + w = texture(kernel, vec2(0.750000+(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c0 * w; + w = texture(kernel, vec2(0.250000-(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c4 * w; + w = texture(kernel, vec2(0.500000+(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c1 * w; + w = texture(kernel, vec2(0.500000-(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c5 * w; + w = texture(kernel, vec2(0.250000+(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c2 * w; + w = texture(kernel, vec2(0.750000-(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c6 * w; + w = texture(kernel, vec2(0.000000+(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c3 * w; + w = texture(kernel, vec2(1.000000-(x/4.0),index) ).r; + w = w*kernel_scale + kernel_bias; + r = r+ c7 * w; + return r; +} +vec4 filter2D_radius4(sampler2D _texture, sampler2D kernel, float index, vec2 uv, vec2 pixel) { + vec2 texel = uv/pixel - vec2(0.5,0.5) ; + vec2 f = fract(texel); + texel = (texel-fract(texel)+vec2(0.001,0.001))*pixel; + vec4 t0 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,-3)*pixel), + texture(_texture, texel + vec2(-2,-3)*pixel), + texture(_texture, texel + vec2(-1,-3)*pixel), + texture(_texture, texel + vec2(0,-3)*pixel), + texture(_texture, texel + vec2(1,-3)*pixel), + texture(_texture, texel + vec2(2,-3)*pixel), + texture(_texture, texel + vec2(3,-3)*pixel), + texture(_texture, texel + vec2(4,-3)*pixel)); + vec4 t1 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,-2)*pixel), + texture(_texture, texel + vec2(-2,-2)*pixel), + texture(_texture, texel + vec2(-1,-2)*pixel), + texture(_texture, texel + vec2(0,-2)*pixel), + texture(_texture, texel + vec2(1,-2)*pixel), + texture(_texture, texel + vec2(2,-2)*pixel), + texture(_texture, texel + vec2(3,-2)*pixel), + texture(_texture, texel + vec2(4,-2)*pixel)); + vec4 t2 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,-1)*pixel), + texture(_texture, texel + vec2(-2,-1)*pixel), + texture(_texture, texel + vec2(-1,-1)*pixel), + texture(_texture, texel + vec2(0,-1)*pixel), + texture(_texture, texel + vec2(1,-1)*pixel), + texture(_texture, texel + vec2(2,-1)*pixel), + texture(_texture, texel + vec2(3,-1)*pixel), + texture(_texture, texel + vec2(4,-1)*pixel)); + vec4 t3 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,0)*pixel), + texture(_texture, texel + vec2(-2,0)*pixel), + texture(_texture, texel + vec2(-1,0)*pixel), + texture(_texture, texel + vec2(0,0)*pixel), + texture(_texture, texel + vec2(1,0)*pixel), + texture(_texture, texel + vec2(2,0)*pixel), + texture(_texture, texel + vec2(3,0)*pixel), + texture(_texture, texel + vec2(4,0)*pixel)); + vec4 t4 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,1)*pixel), + texture(_texture, texel + vec2(-2,1)*pixel), + texture(_texture, texel + vec2(-1,1)*pixel), + texture(_texture, texel + vec2(0,1)*pixel), + texture(_texture, texel + vec2(1,1)*pixel), + texture(_texture, texel + vec2(2,1)*pixel), + texture(_texture, texel + vec2(3,1)*pixel), + texture(_texture, texel + vec2(4,1)*pixel)); + vec4 t5 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,2)*pixel), + texture(_texture, texel + vec2(-2,2)*pixel), + texture(_texture, texel + vec2(-1,2)*pixel), + texture(_texture, texel + vec2(0,2)*pixel), + texture(_texture, texel + vec2(1,2)*pixel), + texture(_texture, texel + vec2(2,2)*pixel), + texture(_texture, texel + vec2(3,2)*pixel), + texture(_texture, texel + vec2(4,2)*pixel)); + vec4 t6 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,3)*pixel), + texture(_texture, texel + vec2(-2,3)*pixel), + texture(_texture, texel + vec2(-1,3)*pixel), + texture(_texture, texel + vec2(0,3)*pixel), + texture(_texture, texel + vec2(1,3)*pixel), + texture(_texture, texel + vec2(2,3)*pixel), + texture(_texture, texel + vec2(3,3)*pixel), + texture(_texture, texel + vec2(4,3)*pixel)); + vec4 t7 = filter1D_radius4(kernel, index, f.x, + texture(_texture, texel + vec2(-3,4)*pixel), + texture(_texture, texel + vec2(-2,4)*pixel), + texture(_texture, texel + vec2(-1,4)*pixel), + texture(_texture, texel + vec2(0,4)*pixel), + texture(_texture, texel + vec2(1,4)*pixel), + texture(_texture, texel + vec2(2,4)*pixel), + texture(_texture, texel + vec2(3,4)*pixel), + texture(_texture, texel + vec2(4,4)*pixel)); + return filter1D_radius4(kernel, index, f.y, t0, t1, t2, t3, t4, t5, t6, t7); +} + +vec4 Nearest(sampler2D _texture, vec2 shape, vec2 uv) { return texture(_texture,uv); } + +vec4 Bilinear(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius1(_texture, u_kernel, 0.031250, uv, 1.0/shape); } + +vec4 Hanning(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius1(_texture, u_kernel, 0.093750, uv, 1.0/shape); } + +vec4 Hamming(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius1(_texture, u_kernel, 0.156250, uv, 1.0/shape); } + +vec4 Hermite(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius1(_texture, u_kernel, 0.218750, uv, 1.0/shape); } + +vec4 Kaiser(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius1(_texture, u_kernel, 0.281250, uv, 1.0/shape); } + +vec4 Quadric(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius2(_texture, u_kernel, 0.343750, uv, 1.0/shape); } + +vec4 Bicubic(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius2(_texture, u_kernel, 0.406250, uv, 1.0/shape); } + +vec4 CatRom(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius2(_texture, u_kernel, 0.468750, uv, 1.0/shape); } + +vec4 Mitchell(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius2(_texture, u_kernel, 0.531250, uv, 1.0/shape); } + +vec4 Spline16(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius2(_texture, u_kernel, 0.593750, uv, 1.0/shape); } + +vec4 Spline36(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius3(_texture, u_kernel, 0.656250, uv, 1.0/shape); } + +vec4 Gaussian(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius2(_texture, u_kernel, 0.718750, uv, 1.0/shape); } + +vec4 Bessel(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius4(_texture, u_kernel, 0.781250, uv, 1.0/shape); } + +vec4 Sinc(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius4(_texture, u_kernel, 0.843750, uv, 1.0/shape); } + +vec4 Lanczos(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius4(_texture, u_kernel, 0.906250, uv, 1.0/shape); } + +vec4 Blackman(sampler2D _texture, vec2 shape, vec2 uv) { return filter2D_radius4(_texture, u_kernel, 0.968750, uv, 1.0/shape); } \ No newline at end of file diff --git a/src/_pg.rs b/src/_pg.rs new file mode 100644 index 0000000..d04e033 --- /dev/null +++ b/src/_pg.rs @@ -0,0 +1,745 @@ +use crate::components::Program; +use crate::font_manager::FontManager; +use crate::graphics::colormap::linear::LinearColormap; +use crate::graphics::font::Text; +use crate::graphics::ppi::PPIConfig; +use crate::graphics::threed::ThreeD; +use crate::graphics::transforms::viewport::Viewport; +use crate::graphics::{ppi::PPI, Graphics}; +use crate::graphics::{AttaWithProgram, AttachWithMouse, Config, MouseState}; +use crate::utils::resources::{ + RcGlFramebuffer, RcGlRcResource, RcGlResource, RcGlTexture, Resource, +}; +// use crate::{ +// errors::*, +// ui::{State, GUI}, +// }; +use glow::{HasContext, NativeBuffer, NativeFramebuffer, NativeTexture, NativeVertexArray}; + +use imgui::{ImString, Ui}; +use log::info; +use std::marker::PhantomPinned; +use std::pin::Pin; +use std::ptr::NonNull; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; +type RcGraphic = Rc>; +type ProgramId = &'static str; + +// pub struct App<'a> { +// gui: Option, +// gl: GL, +// viewport: Viewport, +// programs: Option>, +// } + +// impl<'a> App<'a> { +// pub fn new(gl: GL) -> Result { +// let viewport = Viewport::new()?; + +// Ok(Self { +// gui: None, +// viewport, +// programs: None, +// gl, +// }) +// } + +// fn render_for_program( +// gl: &'a glow::Context, +// program_with_window: &HashMap>, +// _windows: &mut HashMap>, +// p: &mut P, +// ) where +// P::Config: From, +// { +// // let mut p = program.borrow_mut(); +// if program_with_window.len() == 0 { +// return; +// } + +// p.mount(&gl).unwrap(); +// program_with_window.get(&P::id).map(|windows| { +// for window in windows.iter() { +// let window_info = &mut *(_windows.get_mut(window).unwrap()); +// // Skip the window if it doesn't need to be redrawn +// if !window_info.need_redraw { +// continue; +// } +// // window_info.re_init = false; +// // If the window needs to be reinitialized, set the config +// for conf in window_info.confs.iter() { +// p.set_config(&gl, &(conf.1.clone()).into()).unwrap(); +// } + +// // Attach the modifer to the program +// if let Some(motion) = window_info.modifer.as_ref() { +// motion.attach_with_program(&gl, p.program_ref()).unwrap(); +// } + +// // Attach the data to the program +// unsafe { +// if window_info.gl_fb_resources.is_none() { +// continue; +// } + +// let framebuffer = window_info.gl_fb_resources.as_ref().unwrap().native(); + +// gl.bind_framebuffer(glow::FRAMEBUFFER, Some(framebuffer)); +// let attach = window_info.attach.get(P::id); + +// if attach.is_some() { +// let vao = attach.unwrap().vao.native(); +// gl.bind_vertex_array(Some(vao)); +// } + +// if attach.is_some() { +// let window_size = window_info.size; +// gl.viewport(0, 0, window_size[0] as i32, window_size[1] as i32); +// p.draw(&gl, attach.as_ref().unwrap().len).unwrap(); +// } + +// if attach.is_some() { +// gl.bind_vertex_array(None); +// } +// gl.bind_framebuffer(glow::FRAMEBUFFER, None); +// window_info.need_redraw = false; +// } +// } +// }); + +// // Unmount the program +// p.unmount(&gl).unwrap(); +// } + +// pub fn render(&mut self) { +// // Self::render_for_program( +// // &self.gl, +// // &self.windows_manager.program_with_window, +// // &mut self.windows_manager.windows, +// // &mut self.programs.ppi_module, +// // ); + +// // Self::render_for_program( +// // &self.gl, +// // &self.windows_manager.program_with_window, +// // &mut self.windows_manager.windows, +// // &mut self.programs.text_module, +// // ); +// } + +// // pub fn render_ui(&mut self, ui: &Ui, window: &winit::window::Window, run: &mut bool) { +// // self.gui.render(ui); +// // self.windows_manager.show_window(ui); +// // // self.windows_manager.destroy_window(); +// // } + +// pub fn programs(&mut self) -> &mut Programs<'a> { +// self.programs.as_mut().unwrap() +// } + +// pub fn prepare(&mut self) { +// self.programs = Some(Programs::new(self.gl.gl(), &self.viewport).unwrap()); +// self.programs().prepare(); +// unsafe { +// // self.gl.enable(glow::DEPTH_TEST); +// } +// } + +// pub fn destroy(&mut self) { +// self.programs().destroy(); +// } +// } + +// pub struct WindowsManager<'a> { +// gl: &'a glow::Context, +// gl_resource_dispatcher: GlResourceDispatcher, +// windows: HashMap>, + +// program_with_window: HashMap>, +// } + +// impl<'a> WindowsManager<'a> { +// fn new(gl: &'a glow::Context, gl_resource_dispatcher: GlResourceDispatcher) -> Self { +// Self { +// gl, +// gl_resource_dispatcher, +// windows: HashMap::with_capacity(30), +// program_with_window: HashMap::with_capacity(30), +// } +// } + +// fn destroy_window(&mut self) { +// for (id, window) in self.windows.iter() { +// if !window.open { +// self.program_with_window.iter_mut().for_each(|(_, v)| { +// v.retain(|v| v != id); +// }); +// } +// } +// self.windows.retain(|_, v| v.open == true); +// } + +// pub fn create_framebuffer( +// &mut self, +// id: &str, +// size: [i32; 2], +// ) -> Result<(RcGlFramebuffer<'a>, RcGlTexture<'a>)> { +// let id = &ImString::new(id); + +// if self.windows.contains_key(id) { +// let info = self.windows.get(id).unwrap(); +// if info.gl_fb_resources.is_some() && info.gl_tex_resources.is_some() { +// return Ok(( +// info.gl_fb_resources.as_ref().unwrap().clone(), +// info.gl_tex_resources.as_ref().unwrap().clone(), +// )); +// } +// } + +// let gl = self.gl; +// let tex = unsafe { +// let framebuffer = self +// .gl_resource_dispatcher +// .create_new::(); +// // let framebuffer = gl.create_framebuffer().unwrap(); +// gl.bind_framebuffer(glow::FRAMEBUFFER, Some(framebuffer.native())); + +// let texture = self.gl_resource_dispatcher.create_new::(); +// gl.bind_texture(glow::TEXTURE_2D, Some(texture.native())); +// gl.tex_image_2d( +// glow::TEXTURE_2D, +// 0, +// glow::RGB8 as i32, +// size[0], +// size[1], +// 0, +// glow::RGB, +// glow::UNSIGNED_BYTE, +// None, +// ); +// gl.tex_parameter_i32( +// glow::TEXTURE_2D, +// glow::TEXTURE_MIN_FILTER, +// glow::LINEAR as i32, +// ); +// gl.tex_parameter_i32( +// glow::TEXTURE_2D, +// glow::TEXTURE_MAG_FILTER, +// glow::LINEAR as i32, +// ); + +// gl.framebuffer_texture_2d( +// glow::FRAMEBUFFER, +// glow::COLOR_ATTACHMENT0, +// glow::TEXTURE_2D, +// Some(texture.native()), +// 0, +// ); + +// assert_eq!( +// gl.check_framebuffer_status(glow::FRAMEBUFFER), +// glow::FRAMEBUFFER_COMPLETE +// ); + +// gl.bind_framebuffer(glow::FRAMEBUFFER, None); +// gl.bind_texture(glow::TEXTURE_2D, None); +// (framebuffer, texture) +// }; + +// Ok(tex) +// } +// pub fn create_render_window( +// &mut self, +// title: &str, +// size: [f32; 2], +// ) -> Result<&mut WindowData<'a>> { +// // Insert the window data into the windows hashmap +// let id = ImString::new(title); +// let mut data = WindowData::new(&self.gl, id.clone(), size, None); +// let (fb, tex) = +// self.create_framebuffer(title, [size[0].floor() as i32, size[1].floor() as i32])?; +// data.gl_fb_resources = Some(fb); +// data.gl_tex_resources = Some(tex); +// self.windows.insert(id.clone(), data); +// let window = self.windows.get_mut(&id).unwrap(); +// Ok(window) +// } + +// fn show_window(&mut self, ui: &Ui) { +// let mut need_resize = vec![]; + +// for (id, window) in self.windows.iter_mut() { +// let mut window = window; +// ui.window(&window.title) +// .size(window.size, imgui::Condition::FirstUseEver) +// .opened(&mut window.open) +// .flags(imgui::WindowFlags::NO_SCROLLBAR) +// .build(|| { +// if ui.is_mouse_clicked(imgui::MouseButton::Left) { +// let io = ui.io(); +// let pos = io.mouse_pos; + +// let window_pos = ui.window_pos(); +// window.last_mouse_position = +// [pos[0] - window_pos[0], pos[1] - window_pos[1]]; +// } + +// if ui.is_mouse_dragging(imgui::MouseButton::Left) { +// if ui.is_window_hovered() { +// let delta = ui.mouse_drag_delta(); +// window.last_mouse_delta = delta; +// window.accmulate_mouse_delta = [ +// window.accmulate_mouse_delta[0] + delta[0], +// window.accmulate_mouse_delta[1] + delta[1], +// ]; +// window.motion = Some(MouseState::Drag { +// from: window.last_mouse_position, +// delta: delta, +// }); + +// println!( +// "Dragging: {:?} {:?}", +// window.last_mouse_position, window.accmulate_mouse_delta +// ); +// window.modifer.as_mut().map(|v| { +// v.exec(window.motion.as_ref().unwrap()); +// }); +// window.need_redraw = true; +// } +// } + +// if ui.is_mouse_released(imgui::MouseButton::Left) { +// if window.size != ui.window_size() { +// window.size = ui.window_size(); +// info!("resized: {:?}", window.size); +// need_resize.push((window.title.clone(), ui.window_size())); +// } +// } + +// if let Some(texture) = window.gl_tex_resources.as_ref() { +// let cursor = ui.cursor_pos(); +// imgui::Image::new(texture.native2imguiid(), ui.window_size()).build(ui); +// ui.set_cursor_pos(cursor); +// if ui.invisible_button(&window.title, ui.window_size()) { +// let io = ui.io(); +// let pos = io.mouse_pos; +// let window_pos = ui.window_pos(); +// let related_pos = [pos[0] - window_pos[0], pos[1] - window_pos[1]]; +// } +// } +// }); +// } + +// for (id, size) in need_resize.iter() { +// self.reset_window_size(id, *size); +// } +// } + +// fn reset_window_size(&mut self, id: &ImString, size: [f32; 2]) { +// let window_info = self.windows.get_mut(id).unwrap(); +// window_info.need_redraw = true; + +// info!("resize: {:?}", size); +// let tex = unsafe { +// self.gl.bind_framebuffer( +// glow::FRAMEBUFFER, +// window_info.gl_fb_resources.as_ref().map(|v| v.native()), +// ); +// let texture = self.gl.create_texture().unwrap(); +// self.gl.bind_texture(glow::TEXTURE_2D, Some(texture)); +// self.gl.tex_image_2d( +// glow::TEXTURE_2D, +// 0, +// glow::RGB8 as i32, +// size[0].floor() as i32, +// size[1].floor() as i32, +// 0, +// glow::RGB, +// glow::UNSIGNED_BYTE, +// None, +// ); +// self.gl.tex_parameter_i32( +// glow::TEXTURE_2D, +// glow::TEXTURE_MIN_FILTER, +// glow::LINEAR as i32, +// ); +// self.gl.tex_parameter_i32( +// glow::TEXTURE_2D, +// glow::TEXTURE_MAG_FILTER, +// glow::LINEAR as i32, +// ); + +// self.gl.framebuffer_texture_2d( +// glow::FRAMEBUFFER, +// glow::COLOR_ATTACHMENT0, +// glow::TEXTURE_2D, +// Some(texture), +// 0, +// ); + +// assert_eq!( +// self.gl.check_framebuffer_status(glow::FRAMEBUFFER), +// glow::FRAMEBUFFER_COMPLETE +// ); + +// self.gl.bind_framebuffer(glow::FRAMEBUFFER, None); +// self.gl.bind_texture(glow::TEXTURE_2D, None); + +// texture +// }; + +// window_info.gl_tex_resources = Some(RcGlResource::new(self.gl, tex)); +// } + +// pub fn set_window_program(&mut self, window: ImString) { +// self.program_with_window +// .entry(P::id) +// .and_modify(|v| v.push(window.clone())) +// .or_insert(vec![window]); +// } +// } + +// pub struct Programs<'a> { +// gl: &'a glow::Context, +// pub text_module: NonNull>, +// pub ppi_module: PPI, +// } + +impl<'a> Programs<'a> { + fn new(gl: &'a glow::Context, viewport: &Viewport) -> Result { + let font_manager = FontManager::new()?; + let mut cmap = LinearColormap::new()?; + cmap.set_colors(vec![ + [170, 170, 170, 255], + [0, 34, 255, 255], + [1, 160, 246, 255], + [0, 236, 236, 255], + [0, 216, 0, 255], + [1, 144, 0, 255], + [255, 255, 0, 255], + [231, 192, 0, 255], + [255, 144, 0, 255], + [255, 0, 0, 255], + [214, 0, 0, 255], + [192, 0, 0, 255], + [255, 0, 240, 255], + [150, 0, 180, 255], + ]); + cmap.set_range(0.0, 70.0); + let cmap = Box::new(cmap); + + let mut ppi = PPI::new()?; + ppi.set_viewport(&viewport); + ppi.set_colormap(cmap); + + let mut text_module = Text::new(gl, font_manager)?; + text_module.set_viewport(&viewport); + + Ok(Self { + gl, + ppi_module: ppi, + text_module, + }) + } + + fn prepare(&mut self) { + self.ppi_module.compile(&self.gl).unwrap(); + self.text_module.compile(&self.gl).unwrap(); + } + + fn destroy(&mut self) { + self.ppi_module.destroy(&self.gl).unwrap(); + self.text_module.destroy(&self.gl).unwrap(); + } + + pub fn create_ppi_render(&self, config: Option) -> Attach<'a> { + self.create_render(&self.ppi_module) + } + + pub fn create_text_render(&self) -> Attach<'a> { + self.create_render(&self.text_module) + } + + fn create_render(&self, p: &P) -> Attach<'a> { + let (vao, vbo, ebo) = p.init(&self.gl); + Attach { + vao: RcGlResource::new(&self.gl, vao), + vbo: RcGlResource::new(&self.gl, vbo), + ebo: ebo.map(|ebo| RcGlResource::new(&self.gl, ebo)), + len: 0, + } + } +} + +// pub struct WindowProgram<'a, 'sl, 'pro, P: Graphics> { +// window: &'sl mut WindowData<'a>, +// program: &'pro P, +// } + +// impl<'a, 's, 'b, P: Graphics> WindowProgram<'a, 's, 'b, P> { +// pub fn bind_data(&mut self, data: &P::Data) -> Result { +// use bytemuck::cast_slice; +// let data = self.program.bake(data)?; +// let attach = self.window.attach.get_mut(P::id).unwrap(); +// attach.len = data.2; + +// let gl = self.window.gl; +// unsafe { +// gl.bind_vertex_array(Some(attach.vao.native())); +// // gl.bind_buffer(glow::VERTEX_ARRAY, Some(attach.vbo.native())); +// gl.buffer_data_u8_slice( +// glow::ARRAY_BUFFER, +// cast_slice(data.0.as_slice()), +// glow::STATIC_DRAW, +// ); +// if let Some(_) = attach.ebo.as_ref() { +// // gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo.native())); +// gl.buffer_data_u8_slice( +// glow::ELEMENT_ARRAY_BUFFER, +// cast_slice(&data.1.as_ref().unwrap()), +// glow::STATIC_DRAW, +// ); + +// // gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); +// } +// // gl.bind_buffer(glow::VERTEX_ARRAY, None); + +// gl.bind_vertex_array(None); +// } + +// Ok(()) +// } + +// pub fn set_config(&mut self, config: P::Config) +// where +// Config: From, +// { +// let config = Config::from(config); +// self.window +// .confs +// .entry(P::id) +// .and_modify(|v| *v = config.clone()) +// .or_insert(config); +// } +// } + +// pub struct WindowData<'a> { +// gl: &'a glow::Context, +// pub title: ImString, +// pub open: bool, +// pub copy_from: Option, +// pub size: [f32; 2], +// gl_fb_resources: Option>, +// gl_tex_resources: Option>, +// need_redraw: bool, +// attach: HashMap>, +// confs: HashMap, + +// re_init: bool, +// last_mouse_position: [f32; 2], +// last_mouse_delta: [f32; 2], +// accmulate_mouse_delta: [f32; 2], +// mouse_position: [f32; 2], +// motion: Option, +// modifer: Option, +// } + +// impl<'a> WindowData<'a> { +// fn new( +// gl: &'a glow::Context, +// title: ImString, +// size: [f32; 2], +// modifer: Option, +// ) -> Self { +// Self { +// gl, +// title, +// open: true, +// copy_from: None, +// size, +// last_mouse_position: [0.0, 0.0], +// last_mouse_delta: [0.0, 0.0], +// accmulate_mouse_delta: [0.0, 0.0], +// mouse_position: [0.0, 0.0], +// motion: None, + +// gl_fb_resources: None, +// gl_tex_resources: None, +// need_redraw: true, +// attach: HashMap::with_capacity(10), +// re_init: true, +// confs: HashMap::with_capacity(5), +// modifer, +// } +// } + +// fn set(&mut self, f: F) +// where +// F: FnOnce(&mut Self), +// { +// f(self); +// self.need_redraw = true; +// // let v = self.program_with_window.entry(PPI::id).or_insert(vec![]); +// // v.push(id.clone()); +// } + +// pub fn set_attach(&mut self, attach: Attach<'a>) { +// self.set(move |w| { +// w.attach.insert(P::id, attach); +// }); +// } + +// pub fn with_program<'b, 's, P: Graphics>( +// &'b mut self, +// program: &'s P, +// ) -> WindowProgram<'a, 'b, 's, P> +// where +// 's: 'b, +// 'a: 's, +// { +// WindowProgram { +// program, +// window: self, +// } +// } + +// pub fn set_window_modifer(&mut self, modifier: Option) { +// self.set(|w| w.modifer = modifier); +// } + +// pub fn id(&self) -> &ImString { +// &self.title +// } + +// fn set_current_mouse_delta(&mut self, delta: [f32; 2]) { +// self.last_mouse_delta = delta; +// self.accmulate_mouse_delta = [ +// self.accmulate_mouse_delta[0] + delta[0], +// self.accmulate_mouse_delta[1] + delta[1], +// ]; +// } + +// fn set_mouse_postion(&mut self, pos: [f32; 2]) { +// self.mouse_position = pos; +// } + +// fn set_motion(&mut self, motion: MouseState) { +// self.motion = Some(motion); +// } + +// fn set_need_redraw(&mut self) { +// self.need_redraw = true; +// } + +// fn set_re_init(&mut self) { +// self.re_init = true; +// } + +// fn on_mouse_drag(&mut self) { +// let state = MouseState::Drag { +// from: self.last_mouse_position, +// delta: self.last_mouse_delta, +// }; +// self.set_motion(state.clone()); + +// self.modifer.as_mut().map(|m| {}); + +// self.set_need_redraw(); +// } +// } + +// macro_rules! modifer_exec { +// ($(($t:ty => $b:tt),)+) => { +// impl ModiferType { +// pub fn exec(&mut self, motion: &MouseState) { +// match self { +// $( +// ModiferType::$b(b) => { +// b.attach_with_mouse(motion); +// } +// )+ +// _ => {} +// } +// } +// } + +// impl AttaWithProgram for ModiferType { +// fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result { +// match self { +// $( + +// ModiferType::$b(b) => { +// b.attach_with_program(gl, program)?; +// } + +// )+ +// _ => { +// } +// } +// Ok(()) +// } + +// } + +// $( +// impl From<$t> for ModiferType { +// fn from(t: $t) -> Self { +// ModiferType::$b(t) +// } +// } +// )+ +// } + +// } + +// pub enum ModiferType { +// ThreeD(ThreeD), +// TwoD, +// } + +// modifer_exec!((ThreeD => ThreeD),); + +// #[derive(Debug, Clone)] +// pub struct GlResourceDispatcher { +// gl: Rc, +// } + +// impl GlResourceDispatcher { +// pub fn new(gl: Rc) -> Self { +// Self { gl } +// } + +// pub fn create_new<'a, T: Resource>(&'a self) -> RcGlResource<'a, T> { +// RcGlResource::new(&self.gl, T::create(&self.gl)) +// } + +// pub fn create_new_rc(&self) -> RcGlRcResource { +// RcGlRcResource::new(self.gl.clone(), T::create(&self.gl)) +// } + +// pub fn gl(&self) -> &Rc { +// &self.gl +// } +// } + +// pub struct Manager { +// pub gl_resource_dispatcher: GlResourceDispatcher, +// } + +// pub struct GL { +// gl: Rc, +// } + +// impl GL { +// pub fn new(gl: Rc) -> Self { +// Self { gl } +// } + +// fn gl(&self) -> &glow::Context { +// &self.gl +// } + +// fn rc_gl(&self) -> Rc { +// self.gl.clone() +// } +// } diff --git a/src/components/program.rs b/src/components/program.rs index 0e0f1f9..e844b23 100644 --- a/src/components/program.rs +++ b/src/components/program.rs @@ -41,6 +41,10 @@ impl Program { &self.fragment } + pub fn geometry(&self) -> Option<&Shader> { + self.geometry.as_ref() + } + pub fn set_hook(&mut self, hook: &str, code: &Snippet) { self.vertex.set_hook(hook, code); self.fragment.set_hook(hook, code); diff --git a/src/components/shader.rs b/src/components/shader.rs index 5b16341..5606656 100644 --- a/src/components/shader.rs +++ b/src/components/shader.rs @@ -30,7 +30,7 @@ impl Shader { let code = match code { CodeType::Code(code) => code.borrow().to_string(), CodeType::Path(path) => { - let code = find_file(path).expect("Failed to find file"); + let code = find_file(path).expect(&format!("Failed to find file {}", path)); code } }; @@ -217,7 +217,13 @@ mod utils { body.push_str("}\n\n"); let input = header + &body; - let result = Snippet::new("fetch_uniform", CodeType::Code(input), false, None).unwrap(); + let result = Snippet::new( + "fetch_uniform", + CodeType::::Code(input), + false, + None, + ) + .unwrap(); result } diff --git a/src/components/snippets/mod.rs b/src/components/snippets/mod.rs index 3ef013f..71c7357 100644 --- a/src/components/snippets/mod.rs +++ b/src/components/snippets/mod.rs @@ -3,6 +3,7 @@ use std::{ cell::{Ref, RefCell}, collections::{HashMap, HashSet}, ops::Add, + path::Path, rc::Rc, sync::atomic::{AtomicUsize, Ordering}, }; @@ -26,9 +27,9 @@ pub enum InputType { Other(Variable), } -pub enum CodeType> { +pub enum CodeType = &'static str, P: AsRef = &'static str> { Code(S), - Path(std::path::PathBuf), + Path(P), } #[derive(Debug, Clone)] @@ -43,9 +44,9 @@ pub struct Snippet { } impl Snippet { - pub fn new>( + pub fn new, P: AsRef>( name: &'static str, - code: CodeType, + code: CodeType, mangling: bool, main: Option, ) -> Result { @@ -120,7 +121,11 @@ impl Snippet { pub fn call(&self, paras: &Vec) -> Option { (self.main.as_ref()).map(|name| { if let Some(link) = &self.link { - let call_name = self.alias.as_ref().map(|a| a.get(name).unwrap()).unwrap_or(name); + let call_name = self + .alias + .as_ref() + .map(|a| a.get(name).unwrap()) + .unwrap_or(name); let c = link.call(paras); if let Some(c) = c { return format!("{}({})", call_name, c); @@ -128,7 +133,11 @@ impl Snippet { return format!("{}()", call_name); } } else { - let call_name = self.alias.as_ref().map(|a| a.get(name).unwrap()).unwrap_or(name); + let call_name = self + .alias + .as_ref() + .map(|a| a.get(name).unwrap()) + .unwrap_or(name); // let call_name = self.alias.get(name).unwrap(); format!("{}({})", call_name, paras.join(", ")) } @@ -164,7 +173,13 @@ impl Add for Snippet { let code = rhs.parsed.to_string(); raw_code.push_str(&code); - Snippet::new(self.name, CodeType::Code(raw_code),false, None).unwrap() + Snippet::new( + self.name, + CodeType::::Code(raw_code), + false, + None, + ) + .unwrap() } } @@ -192,9 +207,11 @@ mod tests { } "#; - let snippet = Snippet::new("polar", CodeType::Code(code), true, None).unwrap(); + let snippet = + Snippet::new("polar", CodeType::<&'static str>::Code(code), true, None).unwrap(); - let snippet2 = Snippet::new("polar2", CodeType::Code(code),true, None).unwrap(); + let snippet2 = + Snippet::new("polar2", CodeType::<&'static str>::Code(code), true, None).unwrap(); let snippet3 = snippet.clone() + snippet2.clone(); diff --git a/src/errors.rs b/src/errors.rs index 42aeb2e..4a1cefc 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -21,4 +21,10 @@ pub enum Error { #[error("Invalid CoordType")] InvalidDataType, + + #[error("Init Error, cause of {0}")] + InitError(anyhow::Error), + + #[error("Invalid Font {0}")] + FontError(String), } diff --git a/src/font_manager/mod.rs b/src/font_manager/mod.rs new file mode 100644 index 0000000..33a58a0 --- /dev/null +++ b/src/font_manager/mod.rs @@ -0,0 +1,215 @@ +use crate::errors::*; +use dirs::font_dir; +use freetype::face::LoadFlag; +use freetype::{Face, GlyphMetrics, Library}; + +use image::GrayImage; +use image::ImageBuffer; +use log::*; +use std::collections::HashMap; +pub struct FontManager { + library: Library, + fonts: HashMap, +} + +impl FontManager { + pub fn new() -> Result { + // let source = fk::source::SystemSource::new(); + let library = Library::init().map_err(|e| Error::InitError(e.into()))?; + + let root_path = font_dir().map_or_else( + || { + error!("Font dir not found"); + Err(Error::InitError(anyhow::anyhow!("Font dir not found"))) + }, + |font_dir| { + info!("Font dir: {:?}", font_dir); + Ok(font_dir) + }, + )?; + + Ok(Self { + library, + fonts: HashMap::new(), + }) + } + + pub fn get_font_or_insert(&mut self, path: &str) -> Option<&Font> { + if self.fonts.contains_key(path) { + return self.fonts.get(path); + } + + let font = self.library.new_face(path, 0).ok()?; + let font = Font::new(font).ok()?; + self.fonts.insert(path.to_string(), font); + + self.fonts.get(path) + } +} + +pub struct Font { + font: Face, +} + +pub struct CharImg { + img: Vec, + width: usize, + height: usize, +} + +impl CharImg { + pub fn to_image(&self) -> GrayImage { + ImageBuffer::from_raw(self.width as u32, self.height as u32, self.img.clone()) + .map(|img| img.into()) + .unwrap() + } + + pub fn pixels(&self) -> &[u8] { + &self.img + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } +} + +impl Font { + fn new(font: Face) -> Result { + Ok(Self { font }) + } + + pub fn get_char(&self, c: char, size: isize) -> Result { + self.font.set_char_size(0, size * 64, 0, 96).unwrap(); + + if let Err(_) = self.font.load_char(c as usize, LoadFlag::RENDER) { + self.font.load_char(0, LoadFlag::RENDER).unwrap(); + } + let glyph = self.font.glyph(); + let bitmap = glyph.bitmap(); + if bitmap.width() == 0 || bitmap.rows() == 0 { + return Err(Error::FontError("bitmap is empty".to_string())); + } + + Ok(CharImg { + img: bitmap.buffer().to_vec(), + width: bitmap.width() as usize, + height: bitmap.rows() as usize, + }) + } + + pub fn set_char_size(&self, size: isize) { + self.font.set_char_size(0, size * 64, 0, 96).unwrap(); + } + + pub fn set_char(&self, c: char) { + self.font.load_char(c as usize, LoadFlag::RENDER).unwrap(); + } + + pub fn glyph(&self) -> &freetype::GlyphSlot { + self.font.glyph() + } + + pub fn get_advance(&self) -> (f32, f32) { + let advance = self.font.glyph().advance(); + (advance.x as f32 / 64.0, advance.y as f32 / 64.0) + } + + pub fn get_kerning(&self, prev: char, c: char) -> (f32, f32) { + let prev = self.font.get_char_index(prev as usize).unwrap(); + let curr = self.font.get_char_index(c as usize).unwrap(); + let kerning = self + .font + .get_kerning(prev, curr, freetype::face::KerningMode::KerningDefault) + .unwrap(); + (kerning.x as f32 / 64.0, kerning.y as f32 / 64.0) + } + + pub fn get_line_height(&self) -> i16 { + self.font.height() + } + + pub fn get_metrics(&self, c: char) -> GlyphMetrics { + self.font.glyph().metrics() + } +} + +#[derive(Debug, Clone)] +pub enum FontSize { + Absolute(f32), + WindowScale(f32), + DistanceScale(f32), +} + +#[derive(Debug, Clone)] +pub struct FontStyle { + pub postscript_name: String, + pub size: FontSize, + pub color: [f32; 4], + pub style: f32, +} + +impl Default for FontStyle { + fn default() -> Self { + Self { + postscript_name: "resources/Roboto-Regular.ttf".to_string(), + size: FontSize::Absolute(12.0), + color: [1.0, 1.0, 1.0, 1.0], + style: 0.0, + } + } +} + +#[derive(Debug, Default)] +pub struct FontStyleBuilder { + style: FontStyle, +} + +impl FontStyleBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn build(self) -> FontStyle { + self.style + } + + pub fn postscript_name(mut self, name: &str) -> Self { + self.style.postscript_name = name.to_string(); + self + } + + pub fn size(mut self, size: FontSize) -> Self { + self.style.size = size; + self + } + + pub fn color(mut self, color: [f32; 4]) -> Self { + self.style.color = color; + self + } +} + +impl FontStyle { + pub fn buider() -> FontStyleBuilder { + FontStyleBuilder::default() + } +} + +mod test { + use super::{FontManager, FontStyle}; + + #[test] + fn test() { + let mut fon = FontManager::new().unwrap(); + let font = + fon.get_font_or_insert("/Users/tsuki/projects/radar-gi/resources/Dokdo-Regular.ttf"); + + if let Some(font) = font { + font.get_char('g', 64).unwrap(); + } + } +} diff --git a/src/graphics/collections/agg_fast_path.rs b/src/graphics/collections/agg_fast_path.rs index 86ac83c..2bc4259 100644 --- a/src/graphics/collections/agg_fast_path.rs +++ b/src/graphics/collections/agg_fast_path.rs @@ -36,7 +36,7 @@ impl AggFastPath { let input_snippet = Snippet::new( "input", - CodeType::Code( + CodeType::<&'static str>::Code( " layout(location = 0) in vec3 prev; layout(location = 1) in vec3 curr; @@ -126,6 +126,8 @@ impl Colletion for AggFastPath { } impl Graphics for AggFastPath { + const id: &'static str = "AggPath"; + type Config = (); fn compile(&mut self, gl: &glow::Context) -> Result<()> { use bytemuck::cast_slice; self.program.compile(gl)?; @@ -214,7 +216,7 @@ impl Graphics for AggFastPath { &mut self.program } - fn set_config(&mut self, gl: &glow::Context, config: Option<&Config>) -> Result<()> { + fn set_config(&mut self, gl: &glow::Context, config: &Self::Config) -> Result<()> { Ok(()) } @@ -237,7 +239,11 @@ impl Graphics for AggFastPath { impl AttaWithBuffer for AggFastPath { type Data = (); - fn bake(&self, data: &Self::Data) -> Result<(Vec, Option>, i32)> { + fn bake( + &self, + data: &Self::Data, + config: &::Config, + ) -> Result<(Vec, Option>, i32)> { Ok((vec![], None, 0)) } diff --git a/src/graphics/font/esdt/esdt.rs b/src/graphics/font/esdt/esdt.rs new file mode 100644 index 0000000..21d3002 --- /dev/null +++ b/src/graphics/font/esdt/esdt.rs @@ -0,0 +1,740 @@ +//! Rust port of the ESDT ("Euclidean Subpixel Distance Transform") algorithm. +//! +//! +//! This algorithm was originally published as the [`@use-gpu/glyph`](https://www.npmjs.com/package/@use-gpu/glyph) +//! `npm` package, and was described in . + +use super::img::{Bitmap, Image2d, NDCursor, NDCursorExt as _, Unorm8}; + +// HACK(eddyb) only exists to allow toggling precision for testing purposes. +#[cfg(sdfer_use_f64_instead_of_f32)] +type f32 = f64; + +#[derive(Copy, Clone, Debug)] +pub struct Params { + pub pad: usize, + pub radius: f32, + pub cutoff: f32, + pub solidify: bool, + pub preprocess: bool, + // FIXME(eddyb) implement. + // pub postprocess: bool, +} + +impl Default for Params { + fn default() -> Self { + Self { + pad: 4, + radius: 3.0, + cutoff: 0.25, + solidify: true, + preprocess: false, + // FIXME(eddyb) implement. + // postprocess: false, + } + } +} + +/// Opaque `struct` allowing buffer reuse between SDF computations, instead of +/// reallocating all the buffers every time. +#[derive(Default)] +pub struct ReusableBuffers(ReusableBuffers2d, ReusableBuffers1d); + +// Convert grayscale glyph to SDF +pub fn glyph_to_sdf( + glyph: &mut Image2d + AsRef<[Unorm8]>>, + params: Params, + reuse_bufs: Option, +) -> (Image2d, ReusableBuffers) { + if params.solidify { + solidify_alpha(glyph.reborrow_mut()); + } + glyph_to_esdt(glyph.reborrow_mut(), params, reuse_bufs) +} + +// Solidify semi-transparent areas +fn solidify_alpha(mut glyph: Image2d) { + let (w, h) = (glyph.width(), glyph.height()); + + let mut mask: Image2d = Image2d::new(w, h); + + let get_data = |x: isize, y: isize| { + if x >= 0 && (x as usize) < w && y >= 0 && (y as usize) < h { + glyph[(x as usize, y as usize)] + } else { + Unorm8::MIN + } + }; + + let mut masked = 0; + + // Mask pixels whose alpha matches their 4 adjacent neighbors (within 16 steps) + // and who don't have black or white neighbors. + for y in 0..(h as isize) { + for x in 0..(w as isize) { + let a = get_data(x, y); + // FIXME(eddyb) audit all comparisons with `254` and try removing them. + if a == Unorm8::MIN || a >= Unorm8::from_bits(254) { + continue; + } + + let l = get_data(x - 1, y); + let r = get_data(x + 1, y); + let t = get_data(x, y - 1); + let b = get_data(x, y + 1); + + let (min, max) = [a, l, r, t, b] + .into_iter() + .map(|x| (x, x)) + .reduce(|(a_min, a_max), (b_min, b_max)| (a_min.min(b_min), a_max.max(b_max))) + .unwrap(); + + let [a, min, max] = [a, min, max].map(Unorm8::to_bits); + + // FIXME(eddyb) audit all comparisons with `254` and try removing them. + if (max - min) < 16 && min > 0 && max < 254 { + // NOTE(eddyb) `min > 0` guarantees all neighbors are in-bounds. + let (x, y) = (x as usize, y as usize); + + // Spread to 4 neighbors with max + mask[(x - 1, y)] = mask[(x - 1, y)].max(a); + mask[(x, y - 1)] = mask[(x, y - 1)].max(a); + mask[(x, y)] = a; + mask[(x + 1, y)] = mask[(x + 1, y)].max(a); + mask[(x, y + 1)] = mask[(x, y + 1)].max(a); + masked += 1; + } + } + } + + if masked == 0 { + return; + } + + let get_mask = |x: isize, y: isize| { + if x >= 0 && (x as usize) < w && y >= 0 && (y as usize) < h { + mask[(x as usize, y as usize)] + } else { + 0 + } + }; + + // Sample 3x3 area for alpha normalization factor + for y in 0..(h as isize) { + for x in 0..(w as isize) { + let a = &mut glyph[(x as usize, y as usize)]; + // FIXME(eddyb) audit all comparisons with `254` and try removing them. + if *a == Unorm8::MIN || *a >= Unorm8::from_bits(254) { + continue; + } + + let c = get_mask(x, y); + + let l = get_mask(x - 1, y); + let r = get_mask(x + 1, y); + let t = get_mask(x, y - 1); + let b = get_mask(x, y + 1); + + let tl = get_mask(x - 1, y - 1); + let tr = get_mask(x + 1, y - 1); + let bl = get_mask(x - 1, y + 1); + let br = get_mask(x + 1, y + 1); + + if let Some(m) = [c, l, r, t, b, tl, tr, bl, br] + .into_iter() + .find(|&x| x != 0) + { + *a = Unorm8::from_bits((a.to_bits() as f32 / m as f32 * 255.0) as u8); + } + } + } +} + +// Convert grayscale or color glyph to SDF using subpixel distance transform +fn glyph_to_esdt( + mut glyph: Image2d, + params: Params, + reuse_bufs: Option, +) -> (Image2d, ReusableBuffers) { + // FIXME(eddyb) use `Params` itself directly in more places. + let Params { + pad, + radius, + cutoff, + solidify: _, + preprocess, + } = params; + + let wp = glyph.width() + pad * 2; + let hp = glyph.height() + pad * 2; + + let mut state = State::from_glyph(glyph.reborrow_mut(), params, reuse_bufs); + + state.esdt_outer_and_inner(wp, hp); + + // FIXME(eddyb) implement. + // if postprocess { state.relax_subpixel_offsets(glyph, pad); } + + let mut sdf = Image2d::from_fn(wp, hp, |x, y| { + let i = y * wp + x; + let ReusableBuffers2d { xo, yo, xi, yi, .. } = &state.bufs_2d; + let outer = ((xo[i].powi(2) + yo[i].powi(2)).sqrt() - 0.5).max(0.0); + let inner = ((xi[i].powi(2) + yi[i].powi(2)).sqrt() - 0.5).max(0.0); + let d = if outer >= inner { outer } else { -inner }; + Unorm8::encode(1.0 - (d / radius + cutoff)) + }); + + if !preprocess { + paint_into_distance_field(&mut sdf, glyph.reborrow(), params); + } + + (sdf, ReusableBuffers(state.bufs_2d, state.reuse_bufs_1d)) +} + +// Helpers +fn is_black(x: f32) -> bool { + x == 0.0 +} +fn is_white(x: f32) -> bool { + x == 1.0 +} +fn is_solid(x: f32) -> bool { + x == 0.0 || x == 1.0 +} + +// Paint original alpha channel into final SDF when gray +fn paint_into_distance_field( + sdf: &mut Image2d, + glyph: Image2d, + params: Params, +) { + let Params { + pad, + radius, + cutoff, + .. + } = params; + + for y in 0..glyph.height() { + for x in 0..glyph.width() { + let a = glyph[(x, y)].decode(); + if !is_solid(a) { + let d = 0.5 - a; + sdf[(x + pad, y + pad)] = Unorm8::encode(1.0 - (d / radius + cutoff)); + } + } + } +} + +/// 2D buffers, which get reused (see also `ReusableBuffers` itself). +#[derive(Default)] +struct ReusableBuffers2d { + // FIXME(eddyb) group `outer` with `{x,y}o`. + outer: Bitmap, + // FIXME(eddyb) group `inner` with `{x,y}i``. + inner: Bitmap, + + xo: Vec, + yo: Vec, + xi: Vec, + yi: Vec, +} + +struct State { + // FIXME(eddyb) do the grouping suggested in `ReusableBuffers2d`, to have + // `outer` and `inner` fields in here, to use instead of `ReusableBuffers2d`. + bufs_2d: ReusableBuffers2d, + reuse_bufs_1d: ReusableBuffers1d, +} + +impl State { + fn from_glyph( + mut glyph: Image2d, + params: Params, + reuse_bufs: Option, + ) -> Self { + let Params { + pad, + // FIXME(eddyb) should this still be taken as a separate `bool`? + preprocess: relax, + .. + } = params; + + let wp = glyph.width() + pad * 2; + let hp = glyph.height() + pad * 2; + let np = wp * hp; + + let ReusableBuffers(bufs_2d, reuse_bufs_1d) = reuse_bufs.unwrap_or_default(); + let mut state = Self { + bufs_2d, + reuse_bufs_1d, + }; + let ReusableBuffers2d { + outer, + inner, + xo, + yo, + xi, + yi, + } = &mut state.bufs_2d; + + outer.resize_and_fill_with(wp, hp, true); + inner.resize_and_fill_with(wp, hp, false); + for buf2d in [&mut *xo, yo, xi, yi] { + buf2d.clear(); + buf2d.resize(np, 0.0); + } + + for y in 0..glyph.height() { + for x in 0..glyph.width() { + let a = &mut glyph[(x, y)]; + if *a == Unorm8::MIN { + continue; + } + + // FIXME(eddyb) audit all comparisons with `254` and try removing them, + // especially this step that modifies the `glyph` itself. + if *a >= Unorm8::from_bits(254) { + // Fix for bad rasterizer rounding + *a = Unorm8::MAX; + + outer.at(x + pad, y + pad).set(false); + inner.at(x + pad, y + pad).set(true); + } else { + outer.at(x + pad, y + pad).set(false); + inner.at(x + pad, y + pad).set(false); + } + } + } + + // + // Generate subpixel offsets for all border pixels + // + + let get_data = |x: isize, y: isize| { + if x >= 0 && (x as usize) < glyph.width() && y >= 0 && (y as usize) < glyph.height() { + glyph[(x as usize, y as usize)].decode() + } else { + 0.0 + } + }; + + // Make vector from pixel center to nearest boundary + for y in 0..(glyph.height() as isize) { + for x in 0..(glyph.width() as isize) { + let c = get_data(x, y); + // NOTE(eddyb) `j - 1` (X-) / `j - wp` (Y-) positive (`pad >= 1`). + let j = ((y as usize) + pad) * wp + (x as usize) + pad; + + if !is_solid(c) { + let dc = c - 0.5; + + // NOTE(eddyb) l(eft) r(ight) t(op) b(ottom) + let l = get_data(x - 1, y); + let r = get_data(x + 1, y); + let t = get_data(x, y - 1); + let b = get_data(x, y + 1); + + let tl = get_data(x - 1, y - 1); + let tr = get_data(x + 1, y - 1); + let bl = get_data(x - 1, y + 1); + let br = get_data(x + 1, y + 1); + + let ll = (tl + l * 2.0 + bl) / 4.0; + let rr = (tr + r * 2.0 + br) / 4.0; + let tt = (tl + t * 2.0 + tr) / 4.0; + let bb = (bl + b * 2.0 + br) / 4.0; + + let (min, max) = [l, r, t, b, tl, tr, bl, br] + .into_iter() + .map(|x| (x, x)) + .reduce(|(a_min, a_max), (b_min, b_max)| { + (a_min.min(b_min), a_max.max(b_max)) + }) + .unwrap(); + + if min > 0.0 { + // Interior creases + inner.at(x as usize + pad, y as usize + pad).set(true); + continue; + } + if max < 1.0 { + // Exterior creases + outer.at(x as usize + pad, y as usize + pad).set(true); + continue; + } + + let mut dx = rr - ll; + let mut dy = bb - tt; + let dl = 1.0 / (dx.powi(2) + dy.powi(2)).sqrt(); + dx *= dl; + dy *= dl; + + xo[j] = -dc * dx; + yo[j] = -dc * dy; + } else if is_white(c) { + // NOTE(eddyb) l(eft) r(ight) t(op) b(ottom) + let l = get_data(x - 1, y); + let r = get_data(x + 1, y); + let t = get_data(x, y - 1); + let b = get_data(x, y + 1); + + if is_black(l) { + xo[j - 1] = 0.4999; + outer.at(x as usize + pad - 1, y as usize + pad).set(false); + inner.at(x as usize + pad - 1, y as usize + pad).set(false); + } + if is_black(r) { + xo[j + 1] = -0.4999; + outer.at(x as usize + pad + 1, y as usize + pad).set(false); + inner.at(x as usize + pad + 1, y as usize + pad).set(false); + } + + if is_black(t) { + yo[j - wp] = 0.4999; + outer.at(x as usize + pad, y as usize + pad - 1).set(false); + inner.at(x as usize + pad, y as usize + pad - 1).set(false); + } + if is_black(b) { + yo[j + wp] = -0.4999; + outer.at(x as usize + pad, y as usize + pad + 1).set(false); + inner.at(x as usize + pad, y as usize + pad + 1).set(false); + } + } + } + } + + // Blend neighboring offsets but preserve normal direction + // Uses xo as input, xi as output + // Improves quality slightly, but slows things down. + if relax { + let check_cross = |nx, ny, dc, dl, dr, dxl, dyl, dxr, dyr| { + ((dxl * nx + dyl * ny) * (dc * dl) > 0.0) + && ((dxr * nx + dyr * ny) * (dc * dr) > 0.0) + && ((dxl * dxr + dyl * dyr) * (dl * dr) > 0.0) + }; + + for y in 0..(glyph.height() as isize) { + for x in 0..(glyph.width() as isize) { + // NOTE(eddyb) `j - 1` (X-) / `j - wp` (Y-) positive (`pad >= 1`). + let j = ((y as usize) + pad) * wp + (x as usize) + pad; + + let nx = xo[j]; + let ny = yo[j]; + if nx == 0.0 && ny == 0.0 { + continue; + } + + // NOTE(eddyb) c(enter) l(eft) r(ight) t(op) b(ottom) + let c = get_data(x, y); + let l = get_data(x - 1, y); + let r = get_data(x + 1, y); + let t = get_data(x, y - 1); + let b = get_data(x, y + 1); + + let dxl = xo[j - 1]; + let dxr = xo[j + 1]; + let dxt = xo[j - wp]; + let dxb = xo[j + wp]; + + let dyl = yo[j - 1]; + let dyr = yo[j + 1]; + let dyt = yo[j - wp]; + let dyb = yo[j + wp]; + + let mut dx = nx; + let mut dy = ny; + let mut dw = 1.0; + + let dc = c - 0.5; + let dl = l - 0.5; + let dr = r - 0.5; + let dt = t - 0.5; + let db = b - 0.5; + + if !is_solid(l) && !is_solid(r) { + if check_cross(nx, ny, dc, dl, dr, dxl, dyl, dxr, dyr) { + dx += (dxl + dxr) / 2.0; + dy += (dyl + dyr) / 2.0; + dw += 1.0; + } + } + + if !is_solid(t) && !is_solid(b) { + if check_cross(nx, ny, dc, dt, db, dxt, dyt, dxb, dyb) { + dx += (dxt + dxb) / 2.0; + dy += (dyt + dyb) / 2.0; + dw += 1.0; + } + } + + if !is_solid(l) && !is_solid(t) { + if check_cross(nx, ny, dc, dl, dt, dxl, dyl, dxt, dyt) { + dx += (dxl + dxt - 1.0) / 2.0; + dy += (dyl + dyt - 1.0) / 2.0; + dw += 1.0; + } + } + + if !is_solid(r) && !is_solid(t) { + if check_cross(nx, ny, dc, dr, dt, dxr, dyr, dxt, dyt) { + dx += (dxr + dxt + 1.0) / 2.0; + dy += (dyr + dyt - 1.0) / 2.0; + dw += 1.0; + } + } + + if !is_solid(l) && !is_solid(b) { + if check_cross(nx, ny, dc, dl, db, dxl, dyl, dxb, dyb) { + dx += (dxl + dxb - 1.0) / 2.0; + dy += (dyl + dyb + 1.0) / 2.0; + dw += 1.0; + } + } + + if !is_solid(r) && !is_solid(b) { + if check_cross(nx, ny, dc, dr, db, dxr, dyr, dxb, dyb) { + dx += (dxr + dxb + 1.0) / 2.0; + dy += (dyr + dyb + 1.0) / 2.0; + dw += 1.0; + } + } + + let nn = (nx * nx + ny * ny).sqrt(); + let ll = (dx * nx + dy * ny) / nn; + + dx = nx * ll / dw / nn; + dy = ny * ll / dw / nn; + + xi[j] = dx; + yi[j] = dy; + } + } + } + + // Produce zero points for positive and negative DF, at +0.5 / -0.5. + // Splits xs into xo/xi + for y in 0..(glyph.height() as isize) { + for x in 0..(glyph.width() as isize) { + // NOTE(eddyb) `j - 1` (X-) / `j - wp` (Y-) positive (`pad >= 1`). + let j = ((y as usize) + pad) * wp + (x as usize) + pad; + + // NOTE(eddyb) `if relax` above changed `xs`/`ys` in the original. + let (nx, ny) = if relax { + (xi[j], yi[j]) + } else { + (xo[j], yo[j]) + }; + if nx == 0.0 && ny == 0.0 { + continue; + } + + let nn = (nx.powi(2) + ny.powi(2)).sqrt(); + + let sx = if ((nx / nn).abs() - 0.5) > 0.0 { + nx.signum() as isize + } else { + 0 + }; + let sy = if ((ny / nn).abs() - 0.5) > 0.0 { + ny.signum() as isize + } else { + 0 + }; + + let c = get_data(x, y); + let d = get_data(x + sx, y + sy); + // FIXME(eddyb) is this inefficient? (was `Math.sign(d - c)`) + let s = (d - c).total_cmp(&0.0) as i8 as f32; + + let dlo = (nn + 0.4999 * s) / nn; + let dli = (nn - 0.4999 * s) / nn; + + xo[j] = nx * dlo; + yo[j] = ny * dlo; + xi[j] = nx * dli; + yi[j] = ny * dli; + } + } + + state + } + + fn esdt_outer_and_inner(&mut self, w: usize, h: usize) { + { + let Self { + bufs_2d: + ReusableBuffers2d { + outer, + inner, + xo, + yo, + xi, + yi, + }, + reuse_bufs_1d, + } = self; + esdt(outer, xo, yo, w, h, reuse_bufs_1d); + esdt(inner, xi, yi, w, h, reuse_bufs_1d); + } + } +} + +// 2D subpixel distance transform by unconed +// extended from Felzenszwalb & Huttenlocher https://cs.brown.edu/~pff/papers/dt-final.pdf +fn esdt( + mask: &mut Bitmap, + xs: &mut [f32], + ys: &mut [f32], + w: usize, + h: usize, + reuse_bufs_1d: &mut ReusableBuffers1d, +) { + reuse_bufs_1d.critical_minima.clear(); + reuse_bufs_1d.critical_minima.reserve(w.max(h)); + + let mut xs = Image2d::from_storage(w, h, xs); + let mut ys = Image2d::from_storage(w, h, ys); + + for x in 0..w { + let mut mask_xy_cursor = mask + .cursor_at(0, 0) + .zip( + // FIXME(eddyb) combine `xs` and `ys` into the same `Image2d`. + ys.cursor_at(0, 0).zip(xs.cursor_at(0, 0)), + ) + .map_abs_and_rel(move |y| (x, y), |dy| (0, dy)); + mask_xy_cursor.reset(0); + + esdt1d(mask_xy_cursor, h, reuse_bufs_1d) + } + for y in 0..h { + let mut mask_xy_cursor = mask + .cursor_at(0, 0) + .zip( + // FIXME(eddyb) combine `xs` and `ys` into the same `Image2d`. + xs.cursor_at(0, 0).zip(ys.cursor_at(0, 0)), + ) + .map_abs_and_rel(move |x| (x, y), |dx| (dx, 0)); + mask_xy_cursor.reset(0); + + esdt1d(mask_xy_cursor, w, reuse_bufs_1d) + } +} + +/// 1D buffers (for `esdt1d`), which get reused between calls. +// +// FIXME(eddyb) the name is outdated now that there's only one buffer. +#[derive(Default)] +struct ReusableBuffers1d { + critical_minima: Vec, +} + +// FIXME(eddyb) clean up the names after all the refactors. +struct CriticalMinimum { + // FIXME(eddyb) this is really just a position, since it's not used to + // index anything indirectly anymore, but rather indicates the original `q`, + // and is used to compare against it in the second iteration of `esdt1d`. + v: usize, // Array index + + z: f32, // Voronoi threshold + f: f32, // Squared distance + b: f32, // Subpixel offset parallel + t: f32, // Subpixel offset perpendicular +} + +// 1D subpixel distance transform +fn esdt1d( + mut mask_xy_cursor: impl for<'a> NDCursor< + 'a, + usize, + RefMut = (super::img::BitmapEntry<'a>, (&'a mut f32, &'a mut f32)), + >, + // FIXME(eddyb) provide this through the cursor, maybe? + length: usize, + reuse_bufs_1d: &mut ReusableBuffers1d, +) { + // FIXME(eddyb) this is a pretty misleading name. + const INF: f32 = 1e10; + + let cm = &mut reuse_bufs_1d.critical_minima; + cm.clear(); + + { + let (mask, (&mut dx, &mut dy)) = mask_xy_cursor.get_mut(); + cm.push(CriticalMinimum { + v: 0, + z: -INF, + f: if mask.get() { INF } else { dy.powi(2) }, + + b: dx, + t: dy, + }); + mask_xy_cursor.advance(1); + } + + // Scan along array and build list of critical minima + for q in 1..length { + // Perpendicular + let (mask, (&mut dx, &mut dy)) = mask_xy_cursor.get_mut(); + let fq = if mask.get() { INF } else { dy.powi(2) }; + mask_xy_cursor.advance(1); + + // Parallel + let qs = q as f32 + dx; + let q2 = qs.powi(2); + + // Remove any minima eclipsed by this one + let mut s; + loop { + let r = &cm[cm.len() - 1]; + + s = (fq - r.f + q2 - r.b.powi(2)) / (qs - r.b) / 2.0; + + if !(s <= r.z) { + break; + } + + cm.pop(); + if cm.len() == 0 { + break; + } + } + + // Add to minima list + cm.push(CriticalMinimum { + v: q, + z: s, + f: fq, + b: qs, + t: dy, + }); + } + + mask_xy_cursor.reset(0); + + // Resample array based on critical minima + { + let mut k = 0; + for q in 0..length { + // Skip eclipsed minima + while k + 1 < cm.len() && cm[k + 1].z < q as f32 { + k += 1; + } + + let r = &cm[k]; + + // Distance from integer index to subpixel location of minimum + let rq = r.b - q as f32; + + let (mut mask, (dx, dy)) = mask_xy_cursor.get_mut(); + *dx = rq; + *dy = r.t; + // Mark cell as having propagated + if r.v != q { + mask.set(false); + } + mask_xy_cursor.advance(1); + } + } +} diff --git a/src/graphics/font/esdt/img.rs b/src/graphics/font/esdt/img.rs new file mode 100644 index 0000000..11af9b9 --- /dev/null +++ b/src/graphics/font/esdt/img.rs @@ -0,0 +1,411 @@ +// NOTE(eddyb) this is a separate module so that privacy affects sibling modules. +// FIXME(eddyb) deduplicate with `image` crate? + +use std::marker::PhantomData; +use std::ops::{Index, IndexMut}; + +// HACK(eddyb) only exists to allow toggling precision for testing purposes. +#[cfg(sdfer_use_f64_instead_of_f32)] +type f32 = f64; + +/// `[0, 1]` represented by uniformly spaced `u8` values (`0..=255`), +/// i.e. `Unorm8(byte)` corresponds to the `f32` value `byte as f32 / 255.0`. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct Unorm8(u8); + +impl Unorm8 { + pub const MIN: Self = Self::from_bits(0); + pub const MAX: Self = Self::from_bits(u8::MAX); + + #[inline(always)] + pub fn encode(x: f32) -> Self { + // NOTE(eddyb) manual `clamp` not needed, `(_: f32) as u8` will saturate: + // https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast + Self((x * 255.0).round() as u8) + } + + #[inline(always)] + pub fn decode(self) -> f32 { + self.0 as f32 / 255.0 + } + + #[inline(always)] + pub const fn from_bits(bits: u8) -> Self { + Self(bits) + } + + #[inline(always)] + pub const fn to_bits(self) -> u8 { + self.0 + } +} + +#[derive(Default, Copy, Clone)] +pub struct Image2d = Vec> { + width: usize, + height: usize, + data: Storage, + _marker: PhantomData, +} + +impl> Image2d { + pub fn new(width: usize, height: usize) -> Self + where + T: Default, + Storage: FromIterator, + { + Self::from_fn(width, height, |_, _| T::default()) + } + + pub fn from_fn(width: usize, height: usize, mut f: impl FnMut(usize, usize) -> T) -> Self + where + Storage: FromIterator, + { + Self::from_storage( + width, + height, + (0..height) + .flat_map(|y| (0..width).map(move |x| (x, y))) + .map(|(x, y)| f(x, y)) + .collect(), + ) + } + + pub fn from_storage(width: usize, height: usize, storage: Storage) -> Self { + assert_eq!(storage.as_ref().len(), width * height); + Self { + width, + height, + data: storage, + _marker: PhantomData, + } + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn reborrow(&self) -> Image2d { + Image2d { + width: self.width, + height: self.height, + data: self.data.as_ref(), + _marker: PhantomData, + } + } + + pub fn reborrow_mut(&mut self) -> Image2d + where + Storage: AsMut<[T]>, + { + Image2d { + width: self.width, + height: self.height, + data: self.data.as_mut(), + _marker: PhantomData, + } + } + + pub fn cursor_at(&mut self, x: usize, y: usize) -> Image2dCursor<'_, T> + where + Storage: AsMut<[T]>, + { + let mut cursor = Image2dCursor { + image: self.reborrow_mut(), + xy_offset: 0, + }; + cursor.reset((x, y)); + cursor + } +} + +impl> Index<(usize, usize)> for Image2d { + type Output = T; + + fn index(&self, (x, y): (usize, usize)) -> &T { + &self.data.as_ref()[y * self.width..][..self.width][x] + } +} + +impl + AsRef<[T]>> IndexMut<(usize, usize)> for Image2d { + fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut T { + &mut self.data.as_mut()[y * self.width..][..self.width][x] + } +} + +impl From for Image2d { + fn from(img: image::GrayImage) -> Self { + Self { + width: img.width().try_into().unwrap(), + height: img.height().try_into().unwrap(), + // HACK(eddyb) this should be a noop if the right specializations + // all kick in, and LLVM optimizes out the in-place transformation. + data: img.into_vec().into_iter().map(Unorm8::from_bits).collect(), + _marker: PhantomData, + } + } +} + +impl From> for image::GrayImage { + fn from(img: Image2d) -> Self { + image::GrayImage::from_vec( + img.width().try_into().unwrap(), + img.height().try_into().unwrap(), + // HACK(eddyb) this should be a noop if the right specializations + // all kick in, and LLVM optimizes out the in-place transformation. + img.data.into_iter().map(Unorm8::to_bits).collect(), + ) + .unwrap() + } +} + +impl From> for ndarray::Array2 { + fn from(value: Image2d) -> Self { + ndarray::Array2::from_shape_vec( + [value.height(), value.width()], + value.data.into_iter().map(Unorm8::to_bits).collect(), + ) + .unwrap() + } +} + +impl Image2d { + fn resize_and_fill_with(&mut self, width: usize, height: usize, initial: T) { + self.width = width; + self.height = height; + self.data.clear(); + self.data.resize(width * height, initial); + } +} + +#[derive(Default)] +pub struct Bitmap { + width: usize, + height: usize, + bit_8x8_blocks: Image2d, +} + +pub struct BitmapEntry<'a> { + bit_8x8_block: &'a mut u64, + mask: u64, +} + +impl Bitmap { + #[inline(always)] + pub fn new(width: usize, height: usize) -> Self { + let mut r = Self::default(); + r.resize_and_fill_with(width, height, false); + r + } + + #[inline(always)] + pub(crate) fn resize_and_fill_with(&mut self, width: usize, height: usize, initial: bool) { + self.width = width; + self.height = height; + self.bit_8x8_blocks.resize_and_fill_with( + width.div_ceil(8), + height.div_ceil(8), + if initial { !0 } else { 0 }, + ); + } + + #[inline(always)] + pub fn width(&self) -> usize { + self.width + } + + #[inline(always)] + pub fn height(&self) -> usize { + self.height + } + + const BW: usize = 8; + const BH: usize = 8; + + #[inline(always)] + const fn bit_8x8_block_xy_and_mask(x: usize, y: usize) -> ((usize, usize), u64) { + ( + (x / Self::BW, y / Self::BH), + 1 << ((y % Self::BH) * Self::BW + x % Self::BW), + ) + } + + #[inline(always)] + pub fn get(&self, x: usize, y: usize) -> bool { + let (block_xy, mask) = Self::bit_8x8_block_xy_and_mask(x, y); + (self.bit_8x8_blocks[block_xy] & mask) != 0 + } + + #[inline(always)] + pub fn at(&mut self, x: usize, y: usize) -> BitmapEntry<'_> { + let (block_xy, mask) = Self::bit_8x8_block_xy_and_mask(x, y); + BitmapEntry { + bit_8x8_block: &mut self.bit_8x8_blocks[block_xy], + mask, + } + } + + #[inline(always)] + pub fn cursor_at(&mut self, x: usize, y: usize) -> BitmapCursor<'_> { + let mut cursor = BitmapCursor { + bit_8x8_blocks: self.bit_8x8_blocks.cursor_at(0, 0), + intra_block_xy: (0, 0), + }; + cursor.reset((x, y)); + cursor + } +} + +impl BitmapEntry<'_> { + #[inline(always)] + pub fn get(&self) -> bool { + (*self.bit_8x8_block & self.mask) != 0 + } + + #[inline(always)] + pub fn set(&mut self, value: bool) { + if value { + *self.bit_8x8_block |= self.mask; + } else { + *self.bit_8x8_block &= !self.mask; + } + } +} + +// FIXME(eddyb) this doesn't really belong here, and should use GATs. +pub trait NDCursor<'a, P> { + type RefMut; + fn reset(&'a mut self, position: P); + fn get_mut(&'a mut self) -> Self::RefMut; + fn advance(&'a mut self, delta: P); +} + +pub trait NDCursorExt

: for<'a> NDCursor<'a, P> { + fn zip>(self, other: C2) -> NDCursorZip + where + Self: Sized, + { + NDCursorZip(self, other) + } + + // FIXME(eddyb) this is a really bad API but a whole coordinate system would be overkill. + fn map_abs_and_rel P, FR: Fn(P2) -> P>( + self, + fa: FA, + fr: FR, + ) -> NDCursorMapPos + where + Self: Sized, + { + NDCursorMapPos(self, fa, fr) + } +} +impl NDCursor<'a, P>> NDCursorExt

for C {} + +pub struct NDCursorZip(C1, C2); +impl<'a, P: Copy, C1: NDCursor<'a, P>, C2: NDCursor<'a, P>> NDCursor<'a, P> + for NDCursorZip +{ + type RefMut = (C1::RefMut, C2::RefMut); + #[inline(always)] + fn reset(&'a mut self, position: P) { + self.0.reset(position); + self.1.reset(position); + } + #[inline(always)] + fn get_mut(&'a mut self) -> Self::RefMut { + (self.0.get_mut(), self.1.get_mut()) + } + #[inline(always)] + fn advance(&'a mut self, delta: P) { + self.0.advance(delta); + self.1.advance(delta); + } +} + +pub struct NDCursorMapPos(C, FA, FR); +impl<'a, C: NDCursor<'a, P>, P, P2, FA: Fn(P2) -> P, FR: Fn(P2) -> P> NDCursor<'a, P2> + for NDCursorMapPos +{ + type RefMut = C::RefMut; + #[inline(always)] + fn reset(&'a mut self, position: P2) { + self.0.reset((self.1)(position)); + } + #[inline(always)] + fn get_mut(&'a mut self) -> Self::RefMut { + self.0.get_mut() + } + #[inline(always)] + fn advance(&'a mut self, delta: P2) { + self.0.advance((self.2)(delta)); + } +} + +pub struct Image2dCursor<'a, T> { + // FIXME(eddyb) find a way to use something closer to `slice::IterMut` here. + image: Image2d, + xy_offset: usize, +} + +impl<'a, T: 'a> NDCursor<'a, (usize, usize)> for Image2dCursor<'_, T> { + type RefMut = &'a mut T; + #[inline(always)] + fn reset(&'a mut self, (x, y): (usize, usize)) { + self.xy_offset = y * self.image.width + x; + } + #[inline(always)] + fn get_mut(&'a mut self) -> Self::RefMut { + &mut self.image.data[self.xy_offset] + } + #[inline(always)] + fn advance(&'a mut self, (dx, dy): (usize, usize)) { + // FIXME(eddyb) check for edge conditions? (should be more like an iterator) + self.xy_offset += dy * self.image.width + dx; + } +} + +pub struct BitmapCursor<'a> { + bit_8x8_blocks: Image2dCursor<'a, u64>, + // FIXME(eddyb) because of this we can't just use `bit_8x8_block_xy_and_mask`. + intra_block_xy: (u8, u8), +} + +impl<'a> NDCursor<'a, (usize, usize)> for BitmapCursor<'_> { + type RefMut = BitmapEntry<'a>; + #[inline(always)] + fn reset(&'a mut self, (x, y): (usize, usize)) { + self.bit_8x8_blocks.reset((x / Bitmap::BW, y / Bitmap::BH)); + self.intra_block_xy = ((x % Bitmap::BW) as u8, (y % Bitmap::BH) as u8); + } + #[inline(always)] + fn get_mut(&'a mut self) -> Self::RefMut { + let bxy = self.intra_block_xy; + let (_, mask) = Bitmap::bit_8x8_block_xy_and_mask(bxy.0 as usize, bxy.1 as usize); + BitmapEntry { + bit_8x8_block: self.bit_8x8_blocks.get_mut(), + mask, + } + } + #[inline(always)] + fn advance(&'a mut self, (dx, dy): (usize, usize)) { + // FIXME(eddyb) check for edge conditions? (should be more like an iterator) + let bxy = self.intra_block_xy; + let new_bxy = (bxy.0 as usize + dx, bxy.1 as usize + dy); + + let whole_block_dxy = (new_bxy.0 / Bitmap::BW, new_bxy.1 / Bitmap::BH); + if whole_block_dxy != (0, 0) { + self.bit_8x8_blocks.advance(whole_block_dxy); + } + + self.intra_block_xy = ( + (new_bxy.0 % Bitmap::BW) as u8, + (new_bxy.1 % Bitmap::BH) as u8, + ); + } +} diff --git a/src/graphics/font/esdt/mod.rs b/src/graphics/font/esdt/mod.rs new file mode 100644 index 0000000..870fa07 --- /dev/null +++ b/src/graphics/font/esdt/mod.rs @@ -0,0 +1,5 @@ +mod esdt; +mod img; + +pub use esdt::*; +pub use img::{Image2d, Unorm8}; diff --git a/src/graphics/font/mod.rs b/src/graphics/font/mod.rs index 09db769..c99a827 100644 --- a/src/graphics/font/mod.rs +++ b/src/graphics/font/mod.rs @@ -1,15 +1,316 @@ -pub struct FontManager { - fonts: Vec, +mod esdt; +use esdt::{Image2d, Unorm8}; +use glow::{HasContext, TEXTURE_2D}; +use std::{cell::RefCell, collections::HashMap}; +use text_items::{LineStyle, Text as TextTrait}; +mod text_items; +pub use text_items::{Anchor, PositionText, TextLine}; + +use crate::{ + components::{CodeType, Program, Shader}, + errors::*, + font_manager::{FontManager, FontStyle}, + utils::resources::RcGlTexture, +}; + +use super::{threed::ThreeD, transforms::viewport::Viewport, AttaWithBuffer, Config, Graphics}; +pub struct Text<'a> { + gl: &'a glow::Context, + font_manager: RefCell, + cache: RefCell>>, + items: Vec, + program: Program, } -impl FontManager { - pub fn new() -> Self { - Self { fonts: Vec::new() } +pub struct Cache<'a> { + gl: &'a glow::Context, + cache: HashMap, + // cache_tex: Vec, + width: usize, + height: usize, + last_pos: [usize; 2], + tex: RcGlTexture<'a>, +} + +impl<'a> Cache<'a> { + pub fn new(gl: &'a glow::Context) -> Self { + let tex = unsafe { + let tex = gl.create_texture().unwrap(); + gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as i32, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as i32, + ); + + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::R8 as i32, + 1024, + 1024, + 0, + glow::RED, + glow::UNSIGNED_BYTE, + None, + ); + tex + }; + + Self { + gl, + cache: HashMap::new(), + // cache_tex: vec![0; 1024 * 1024], + width: 1024, + height: 1024, + last_pos: [0, 0], + tex: RcGlTexture::new(gl, tex), + } } - pub fn add_font(&mut self, font: Font) { - self.fonts.push(font); + fn get(&self, c: char) -> Option<&TextVType> { + self.cache.get(&c) + } + + fn insert_glyph(&mut self, tex: Image2d, c: char) -> &TextVType { + // use image::GrayImage; + + use ndarray::{s, Array2}; + let width = tex.width(); + let height = tex.height(); + + let data: Array2 = tex.into(); + + let x = self.last_pos[0]; + let y = self.last_pos[1]; + + use glow::PixelUnpackData; + + unsafe { + self.gl + .bind_texture(glow::TEXTURE_2D, Some(self.tex.native())); + self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + + self.gl.tex_sub_image_2d( + glow::TEXTURE_2D, + 0, + x as i32, + y as i32, + width as i32, + height as i32, + glow::RED, + glow::UNSIGNED_BYTE, + PixelUnpackData::Slice(&data.as_slice().unwrap()), + ); + println!("{} {} {} {}", x, y, width, height); + println!("size: {}", &data.len()); + self.gl.bind_texture(glow::TEXTURE_2D, None); + } + + self.cache.insert( + c, + TextVType { + tex_coords: [ + x as f32, + (y + height - 1) as f32, + (x + width - 1) as f32, + (y + height - 1) as f32, + (x + width - 1) as f32, + y as f32, + x as f32, + y as f32, + ], + // tex_coords: [0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], + }, + ); + if x + width >= 1024 { + self.last_pos[0] = 0; + self.last_pos[1] += height; + } else { + self.last_pos[0] += width; + } + + if y + height > self.height { + self.height += 1024; + // self.cache_tex.extend(vec![0; 1024 * 1024]); + } + + self.cache.get(&c).unwrap() } } -pub struct Font {} +#[derive(Clone)] +pub struct TextVType { + tex_coords: [f32; 8], +} + +impl<'a> Text<'a> { + pub fn new(gl: &'a glow::Context, font_manager: FontManager) -> Result { + let vertex = Shader::new( + glow::VERTEX_SHADER, + crate::components::CodeType::<&str>::Path("font.vert"), + )?; + + let fragment = Shader::new(glow::FRAGMENT_SHADER, CodeType::<&str>::Path("font.frag"))?; + + let transform = ThreeD::new(1.0, 0.1, 100.0, 45.0)?; + let mut program = Program::new(vertex, fragment, None, "330 core"); + program.set_transform(&transform); + + Ok(Self { + gl, + font_manager: RefCell::new(font_manager), + cache: RefCell::new(HashMap::new()), + items: Vec::new(), + program, + }) + } + + pub fn set_viewport(&mut self, viewport: &Viewport) { + self.program.set_viewport(viewport); + } + + fn set_uniforms(&self) { + let conf = self.program.get_uniform_location(&self.gl, "uSdfConfig"); + let u_mode = self.program.get_uniform_location(&self.gl, "uMode"); + let u_border = self.program.get_uniform_location(&self.gl, "uBorder"); + let u_stroke = self.program.get_uniform_location(&self.gl, "uStroke"); + let u_fill = self.program.get_uniform_location(&self.gl, "uFill"); + + unsafe { + self.gl.uniform_4_f32(conf.as_ref(), 5.0, 0.0, 0.0, 0.0); + self.gl.uniform_1_i32(u_mode.as_ref(), -1); + self.gl.uniform_4_f32(u_border.as_ref(), 0.0, 0.0, 0.0, 0.0); + self.gl.uniform_4_f32(u_stroke.as_ref(), 1.0, 1.0, 1.0, 1.0); + self.gl.uniform_4_f32(u_fill.as_ref(), 1.0, 1.0, 1.0, 1.0); + } + } +} + +impl<'a> Graphics for Text<'a> { + const id: &'static str = "Text"; + type Config = FontConfig; + fn compile(&mut self, gl: &glow::Context) -> Result<()> { + self.program.compile(gl) + } + + fn destroy(&mut self, gl: &glow::Context) -> Result<()> { + self.program.destroy(gl); + Ok(()) + } + + fn draw(&self, gl: &glow::Context, count: i32) -> Result<()> { + unsafe { + gl.clear(glow::COLOR_BUFFER_BIT); + // gl.polygon_mode(glow::FRONT_AND_BACK, glow::LINE); + + let loc = self.program.get_uniform_location(gl, "atlas_data"); + gl.uniform_1_i32(loc.as_ref(), 0); + + gl.active_texture(glow::TEXTURE0); + gl.bind_texture( + glow::TEXTURE_2D, + self.cache + .borrow() + .get("resources/Roboto-Regular.ttf") + .map(|v| v.tex.native()), + ); + + let width_loc = self.program.get_uniform_location(gl, "atlas_shape"); + gl.uniform_2_f32(width_loc.as_ref(), 1024.0, 1024.0); + + self.set_uniforms(); + + gl.draw_elements(glow::TRIANGLES, count / 2 * 3, glow::UNSIGNED_INT, 0); + } + Ok(()) + } + + fn program_mut(&mut self) -> &mut Program { + &mut self.program + } + + fn program_ref(&self) -> &Program { + &self.program + } + + fn set_config(&mut self, gl: &glow::Context, config: &Self::Config) -> Result<()> { + Ok(()) + } +} + +impl<'a> AttaWithBuffer for Text<'a> { + type Data = PositionText; + + fn bake( + &self, + data: &Self::Data, + config: &::Config, + ) -> Result<(Vec, Option>, i32)> { + let v = data.bake( + &self.gl, + &mut *self.font_manager.borrow_mut(), + &mut *self.cache.borrow_mut(), + )?; + + let mut ebos = Vec::with_capacity(v.len() / 2 * 3); + + for i in 0..v.len() / 4 { + let i = i as u32; + ebos.push(i * 4); + ebos.push(i * 4 + 1); + ebos.push(i * 4 + 3); + + ebos.push(i * 4 + 1); + ebos.push(i * 4 + 2); + ebos.push(i * 4 + 3); + } + Ok((v.vertex(), Some(ebos), v.len() as i32)) + } + + fn init( + &self, + gl: &glow::Context, + ) -> ( + glow::NativeVertexArray, + glow::NativeBuffer, + Option, + ) { + unsafe { + let vao = gl.create_vertex_array().unwrap(); + gl.bind_vertex_array(Some(vao)); + let vbo = gl.create_buffer().unwrap(); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo)); + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, 20, 0); + + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, 20, 12); + + let ebo = gl.create_buffer().unwrap(); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo)); + + gl.bind_vertex_array(None); + + (vao, vbo, Some(ebo)) + } + } +} + +#[derive(Clone, Debug)] +pub enum FontConfig { + Textline(LineStyle, FontStyle), +} + +mod test { + #[test] + fn test() { + use super::*; + let mut font_manager = FontManager::new().unwrap(); + } +} diff --git a/src/graphics/font/text_items.rs b/src/graphics/font/text_items.rs new file mode 100644 index 0000000..64ec599 --- /dev/null +++ b/src/graphics/font/text_items.rs @@ -0,0 +1,256 @@ +use super::esdt::{Image2d, Params, Unorm8}; +use super::TextVType; +use super::{esdt::glyph_to_sdf, Cache}; +use crate::components::merge_includes; +use crate::font_manager::{CharImg, FontManager, FontSize}; +use crate::{errors::*, font_manager::FontStyle}; +use bytemuck::{Pod, Zeroable}; +use geo::kernels; +use std::collections::HashMap; + +const SDF_PARAM: Params = Params { + radius: 5.0, + pad: 4, + cutoff: 0.25, + solidify: true, + preprocess: false, +}; + +#[derive(Debug, Default, Clone)] +pub struct LineStyle { + line_height: f32, + line_spacing: f32, +} + +#[derive(Clone)] +pub struct PositionText { + item: TextLine, + position: [f32; 3], + anchor: Anchor, +} + +impl PositionText { + pub fn new(item: TextLine, position: [f32; 3], anchor: Anchor) -> Self { + Self { + item, + position, + anchor, + } + } +} + +impl Text for PositionText { + fn bake<'a>( + &self, + gl: &'a glow::Context, + font_manager: &mut FontManager, + cache: &mut HashMap>, + ) -> Result { + self.item.bake(gl, font_manager, cache) + } +} + +#[derive(Clone)] +pub struct TextLine { + text: String, + font_style: FontStyle, + line_style: LineStyle, +} + +impl TextLine { + pub fn new>( + text: P, + font_style: Option, + line_style: Option, + ) -> Self { + Self { + text: text.into(), + font_style: font_style.unwrap_or_default(), + line_style: line_style.unwrap_or_default(), + } + } +} + +#[derive(Clone, Copy)] +pub enum Anchor { + TopLeft, + TopCenter, + TopRight, + CenterLeft, + Center, + CenterRight, + BottomLeft, + BottomCenter, + BottomRight, +} + +#[derive(Clone, Copy)] +pub enum TextAlign { + Left, + Center, + Right, +} + +impl Default for TextAlign { + fn default() -> Self { + Self::Left + } +} + +#[repr(C)] +#[derive(Clone, Copy, Zeroable, Pod)] +pub struct TextVertexItem { + position: [f32; 3], + tex_coords: [f32; 2], +} + +pub struct TextVertexArray { + points: Vec, +} + +impl TextVertexArray { + pub fn new() -> Self { + Self { + points: Vec::with_capacity(30), + } + } + + pub fn len(&self) -> usize { + self.points.len() + } + + pub fn push(&mut self, item: TextVertexItem) { + self.points.push(item); + } + + pub fn insert_text(&mut self, tex_coords: TextVType, positions: [[f32; 3]; 4]) { + for i in 0..4 { + self.push(TextVertexItem { + position: positions[i], + tex_coords: [ + tex_coords.tex_coords[i * 2], + tex_coords.tex_coords[i * 2 + 1], + ], + }); + } + } + pub fn vertex(&self) -> Vec { + let mut result = Vec::with_capacity(self.len() * 6); + self.points.iter().for_each(|v| { + result.extend_from_slice(&v.position); + result.extend_from_slice(&v.tex_coords); + }); + result + } + + pub fn to_bits(&self) -> &[u8] { + bytemuck::cast_slice(&self.points) + } +} + +pub trait Text: Sized { + fn bake<'a>( + &self, + gl: &'a glow::Context, + font_manager: &mut FontManager, + cache: &mut HashMap>, + ) -> Result; +} + +impl Text for TextLine { + fn bake<'a>( + &self, + gl: &'a glow::Context, + font_manager: &mut FontManager, + cache: &mut HashMap>, + ) -> Result { + let font_style = &self.font_style; + + let font = font_manager.get_font_or_insert(&font_style.postscript_name); + + if let Some(font) = font { + cache + .entry(font_style.postscript_name.clone()) + .or_insert_with(|| Cache::new(&gl)); + + let cache = cache.get_mut(&font_style.postscript_name).unwrap(); + let mut baked = TextVertexArray::new(); + let mut pen = [0.0, 0.0]; + + let mut prev: Option = None; + + for char in self.text.chars() { + if char == '\n' { + break; + } + + if char == ' ' { + pen[0] += 10.0; + continue; + } + + let font_size = match &font_style.size { + FontSize::Absolute(s) => *s, + FontSize::DistanceScale(_) => panic!(""), + FontSize::WindowScale(_) => panic!(""), + }; + + font.set_char_size(font_size.floor() as isize); + font.set_char(char); + + let (x_advanced, y_advanced) = font.get_advance(); + let (x_kerning, _) = prev.map_or((0.0, 0.0), |v| { + let kerning = font.get_kerning(v, char); + kerning + }); + + let metrics = font.get_metrics(char); + let bear_x = metrics.horiBearingX; + let bear_y = metrics.horiBearingY; + + let x0 = pen[0] + bear_x as f32 + x_kerning; + let y0 = pen[1] - bear_y as f32; + let x1 = pen[0] + metrics.width as f32 + x_kerning; + let y1 = pen[1] + metrics.height as f32; + + let position = [[x0, y0, 0.0], [x1, y0, 0.0], [x1, y1, 0.0], [x1, y0, 0.0]]; + + if let Some(cache) = cache.get(char) { + baked.insert_text(cache.to_owned(), position); + } else { + let char_glyph = font.get_char(char, 64)?; + let mut img = char_glyph.into(); + let (result, _) = glyph_to_sdf(&mut img, SDF_PARAM, None); + let b = cache.insert_glyph(result, char); + baked.insert_text(b.to_owned(), position); + } + + pen[0] += x_advanced; + pen[1] += y_advanced; + prev = Some(char); + } + Ok(baked) + } else { + Err(Error::FontError(format!( + "Font {} not found", + font_style.postscript_name + ))) + } + } +} + +impl From for Image2d { + fn from(value: CharImg) -> Self { + // let img = Image2d::new(value.width() as u32, value.height() as u32, value.pixels); + let img = Image2d::from_storage( + value.width(), + value.height(), + value + .pixels() + .iter() + .map(|v| Unorm8::from_bits(*v)) + .collect(), + ); + img + } +} diff --git a/src/graphics/hello.rs b/src/graphics/hello.rs index cb37be8..0d99f46 100644 --- a/src/graphics/hello.rs +++ b/src/graphics/hello.rs @@ -53,6 +53,8 @@ impl Hello { } impl Graphics for Hello { + const id: &'static str = "Hello"; + type Config = (); fn compile(&mut self, gl: &glow::Context) -> Result<()> { self.program.compile(gl)?; unsafe { @@ -124,7 +126,7 @@ impl Graphics for Hello { &mut self.program } - fn set_config(&mut self, gl: &glow::Context, config: Option<&Config>) -> Result<()> { + fn set_config(&mut self, gl: &glow::Context, config: &()) -> Result<()> { Ok(()) } @@ -147,7 +149,11 @@ impl Graphics for Hello { impl AttaWithBuffer for Hello { type Data = (); - fn bake(&self, data: &Self::Data) -> Result<(Vec, Option>, i32)> { + fn bake( + &self, + data: &Self::Data, + config: &::Config, + ) -> Result<(Vec, Option>, i32)> { return Ok((vec![], None, 0)); } diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 523c2da..7e6db79 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -8,12 +8,16 @@ pub mod threed; pub mod tools; pub mod transforms; pub mod ty; -use crate::{components::Program, errors::*}; -use glow::{NativeBuffer, NativeVertexArray}; +use crate::{components::Program, errors::*, graphics::font::FontConfig}; + +use glow::{HasContext, NativeBuffer, NativeVertexArray}; use ppi::PPIConfig; -pub trait Graphics: AttaWithBuffer { +pub trait Graphics { + const id: &'static str; + type Config; + fn draw(&self, gl: &glow::Context, count: i32) -> Result<()>; fn compile(&mut self, gl: &glow::Context) -> Result<()>; @@ -24,21 +28,37 @@ pub trait Graphics: AttaWithBuffer { fn program_mut(&mut self) -> &mut Program; - fn mount(&mut self, gl: &glow::Context) -> Result<()>; + fn mount(&mut self, gl: &glow::Context) -> Result<()> { + unsafe { + gl.use_program(self.program_ref().native_program.clone()); + } - fn unmount(&mut self, gl: &glow::Context) -> Result<()>; + Ok(()) + } - fn set_config(&mut self, gl: &glow::Context, config: Option<&Config>) -> Result<()>; + fn unmount(&mut self, gl: &glow::Context) -> Result<()> { + unsafe { + gl.use_program(None); + } + + Ok(()) + } + + fn set_config(&mut self, gl: &glow::Context, config: &Self::Config) -> Result<()>; } pub trait AttaWithProgram { fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result<()>; } -pub trait AttaWithBuffer { +pub trait AttaWithBuffer: Graphics { type Data; - fn bake(&self, data: &Self::Data) -> Result<(Vec, Option>, i32)>; + fn bake( + &self, + data: &Self::Data, + config: &::Config, + ) -> Result<(Vec, Option>, i32)>; fn init(&self, gl: &glow::Context) -> (NativeVertexArray, NativeBuffer, Option); } @@ -50,17 +70,47 @@ macro_rules! config_for_everyitem { Self::$name(value) } } + + impl From for $conf { + fn from(value: Config) -> Self { + if let Config::$name(value) = value { + value + } else { + panic!("error transfer"); + } + } + } + + impl<'a> From<&'a Config> for &'a $conf { + fn from(value: &'a Config) -> &'a $conf { + if let Config::$name(value) = value { + &value + } else { + panic!("error transfer"); + } + } + } + + )+ + impl From<()> for Config { + fn from(_: ()) -> Self { + Self::None + } + } + }; } #[derive(Debug, Clone)] pub enum Config { PPI(PPIConfig), + Font(FontConfig), + None, } -config_for_everyitem!({PPIConfig => PPI},); +config_for_everyitem!({PPIConfig => PPI},{FontConfig => Font}, ); pub trait AttachWithMouse { fn attach_with_mouse(&mut self, state: &MouseState); @@ -69,4 +119,5 @@ pub trait AttachWithMouse { #[derive(Debug, Clone)] pub enum MouseState { Drag { from: [f32; 2], delta: [f32; 2] }, + None, } diff --git a/src/graphics/ppi.rs b/src/graphics/ppi.rs index 1394a16..fb36b27 100644 --- a/src/graphics/ppi.rs +++ b/src/graphics/ppi.rs @@ -1,19 +1,16 @@ -use glow::{HasContext, NativeBuffer, NativeVertexArray}; - +use super::colormap::ColorMap; +use super::threed::ThreeD; +use super::transforms::viewport::Viewport; +use super::{transforms, AttaWithBuffer, AttaWithProgram, AttachWithMouse, Config, Graphics}; use crate::components::{CodeType, Program, Shader}; use crate::data_loader::{CoordType, Data, DataType}; use crate::errors::*; +use crate::graphics::colormap::linear::LinearColormap; use crate::graphics::transforms::Transform; - -use super::colormap::ColorMap; -use super::threed::ThreeD; -use super::transforms::position::Position; -use super::transforms::viewport::Viewport; -use super::{transforms, AttaWithBuffer, AttaWithProgram, AttachWithMouse, Config, Graphics}; +use glow::{HasContext, NativeBuffer, NativeVertexArray}; pub struct PPI { program: Program, - layer: isize, cmap: Option>, } @@ -34,19 +31,34 @@ impl PPI { CodeType::::Path("ppi.frag".into()), )?; + let mut cmap = LinearColormap::new()?; + cmap.set_colors(vec![ + [170, 170, 170, 255], + [0, 34, 255, 255], + [1, 160, 246, 255], + [0, 236, 236, 255], + [0, 216, 0, 255], + [1, 144, 0, 255], + [255, 255, 0, 255], + [231, 192, 0, 255], + [255, 144, 0, 255], + [255, 0, 0, 255], + [214, 0, 0, 255], + [192, 0, 0, 255], + [255, 0, 240, 255], + [150, 0, 180, 255], + ]); + cmap.set_range(0.0, 70.0); + let transform = ThreeD::new(1.0, 0.1, 100.0, 45.0)?; - println!("{}", transform.snippet().call(&vec![]).unwrap()); - - // let transform = Position::new()?; - let mut program = Program::new(vertex, fragment, Some(geom), "330 core"); program.set_transform(&transform); + program.set_hook("colormap", cmap.snippet_ref()); Ok(Self { program, - layer: 0, - cmap: None, + cmap: Some(Box::new(cmap)), }) } @@ -54,11 +66,6 @@ impl PPI { self.program.set_viewport(viewport); } - pub fn set_colormap(&mut self, colormap: Box) { - self.program.set_hook("colormap", colormap.snippet_ref()); - self.cmap = Some(colormap); - } - pub fn program(&mut self) -> &mut Program { &mut self.program } @@ -70,37 +77,9 @@ impl PPI { } } - fn bake_data(&self, data: &Data) -> Result<(Vec, i32)> { - let first_block = data.blocks.get(0).unwrap(); - let first_block_data = first_block.data.view(); - if let CoordType::Polar { - r_range, - azimuth, - r, - .. - } = &first_block.coord_type - { - let azimuth_len = azimuth.len(); - let r_len = r.len(); + // fn bake_data(&self, data: &Data, layer: usize) -> Result<(Vec, i32)> { - let mut vertices = Vec::with_capacity(azimuth_len * r_len); - - for azi_idx in 0..azimuth_len { - for r_idx in 0..r_len { - let azi = azimuth.get(azi_idx).unwrap(); - let r = r.get(r_idx).unwrap() / r_range[1] as f32; - let dt = first_block_data - .get([self.layer as usize, azi_idx, r_idx]) - .unwrap(); - vertices.extend([r, *azi, *dt]); - } - } - let len = vertices.len() as i32 / 3; - return Ok((vertices, len)); - } else { - return Err(Error::InvalidDataType); - } - } + // } pub fn data_info(&self, data: &Data) -> Result<(f32, f32, DataType)> { let first_block = data.blocks.get(0).unwrap(); @@ -168,6 +147,8 @@ fn max_step(data: &Vec) -> f32 { } impl Graphics for PPI { + const id: &'static str = "PPI"; + type Config = PPIConfig; fn compile(&mut self, gl: &glow::Context) -> Result<()> { self.program.compile(gl)?; Ok(()) @@ -200,15 +181,8 @@ impl Graphics for PPI { &mut self.program } - fn set_config(&mut self, gl: &glow::Context, config: Option<&Config>) -> Result<()> { - if let Some(config) = config { - if let Config::PPI(config) = config { - self.init(gl, config); - } else { - panic!("Errr config type"); - } - } - + fn set_config(&mut self, gl: &glow::Context, config: &Self::Config) -> Result<()> { + self.init(gl, config); Ok(()) } @@ -230,10 +204,39 @@ impl Graphics for PPI { impl AttaWithBuffer for PPI { type Data = Data; - fn bake(&self, data: &Self::Data) -> Result<(Vec, Option>, i32)> { - // let (rdpi, adpi, data_type) = self.data_info(data)?; - let baked_buffer = self.bake_data(data)?; - Ok((baked_buffer.0, None, baked_buffer.1)) + fn bake( + &self, + data: &Self::Data, + config: &::Config, + ) -> Result<(Vec, Option>, i32)> { + let layer = config.layer; + let first_block = data.blocks.get(0).unwrap(); + let first_block_data = first_block.data.view(); + if let CoordType::Polar { + r_range, + azimuth, + r, + .. + } = &first_block.coord_type + { + let azimuth_len = azimuth.len(); + let r_len = r.len(); + + let mut vertices = Vec::with_capacity(azimuth_len * r_len); + + for azi_idx in 0..azimuth_len { + for r_idx in 0..r_len { + let azi = azimuth.get(azi_idx).unwrap(); + let r = r.get(r_idx).unwrap() / r_range[1] as f32; + let dt = first_block_data.get([layer, azi_idx, r_idx]).unwrap(); + vertices.extend([r, *azi, *dt]); + } + } + let len = vertices.len() as i32 / 3; + return Ok((vertices, None, len)); + } else { + return Err(Error::InvalidDataType); + } } fn init(&self, gl: &glow::Context) -> (NativeVertexArray, NativeBuffer, Option) { diff --git a/src/graphics/threed.rs b/src/graphics/threed.rs index 79009d0..aca5b13 100644 --- a/src/graphics/threed.rs +++ b/src/graphics/threed.rs @@ -36,11 +36,27 @@ impl ThreeD { projection, }) } + + pub fn set_aspect(&mut self, aspect: f32) { + self.projection = nalgebra_glm::perspective(aspect, 45.0f32.to_radians(), 0.1, 100.0); + } + + pub fn set_fov(&mut self, fov: f32) { + self.projection = nalgebra_glm::perspective(1.0, fov.to_radians(), 0.1, 100.0); + } + + pub fn set_z_near(&mut self, z_near: f32) { + self.projection = nalgebra_glm::perspective(1.0, 45.0f32.to_radians(), z_near, 100.0); + } + + pub fn set_z_far(&mut self, z_far: f32) { + self.projection = nalgebra_glm::perspective(1.0, 45.0f32.to_radians(), 0.1, z_far); + } } impl Default for ThreeD { fn default() -> Self { - Self::new(1.0, 2.0, 1000.0, 45.0).unwrap() + Self::new(16.0 / 9.0, 0.1, 1000.0, 45.0).unwrap() } } @@ -53,9 +69,7 @@ impl AttaWithProgram for ThreeD { unsafe { let view = program.get_uniform_location(gl, "trackball_view"); let projection = program.get_uniform_location(gl, "trackball_projection"); - let model = program.get_uniform_location(gl, "trackball_model"); - // self.trackball.attach_with_program(gl, program)?; let view_mat = nalgebra_glm::translation(&nalgebra_glm::vec3(0.0, 0.0, -3.0)); @@ -67,28 +81,42 @@ impl AttaWithProgram for ThreeD { // self.camera.get_view_matrix().as_slice(), ); - println!("projection: {:?}", self.projection); - - let id: nalgebra_glm::Mat4 = nalgebra_glm::identity(); - let scale_factor = nalgebra_glm::vec3(1.5, 1.5, 1.0); - let res = nalgebra_glm::scaling(&scale_factor); - - let rotate = - nalgebra_glm::rotation(45.0f32.to_radians(), &nalgebra_glm::vec3(1.0, 0.0, 0.0)); + // println!("projection: {:?}", self.projection); + // let scale_factor = nalgebra_glm::vec3(1.5, 1.5, 1.0); + // let res = nalgebra_glm::scaling(&scale_factor); + let ident: nalgebra_glm::Mat4 = nalgebra_glm::identity(); gl.uniform_matrix_4_f32_slice( model.as_ref(), false, - res.as_slice(), // nalgebra::Matrix4::identity().as_slice(), + ident.as_slice(), + // nalgebra::Matrix4::identity().as_slice(), ); - gl.uniform_matrix_4_f32_slice(projection.as_ref(), false, self.projection.as_slice()); + gl.uniform_matrix_4_f32_slice( + view.as_ref(), + false, + ident.as_slice(), + // nalgebra::Matrix4::identity().as_slice(), + ); + + gl.uniform_matrix_4_f32_slice( + projection.as_ref(), + false, + ident.as_slice(), + // nalgebra::Matrix4::identity().as_slice(), + ); + + // let rotate = + // nalgebra_glm::rotation(45.0f32.to_radians(), &nalgebra_glm::vec3(1.0, 0.0, 0.0)); // gl.uniform_matrix_4_f32_slice( - // projection.as_ref(), + // model.as_ref(), // false, - // nalgebra::Matrix4::identity().as_slice(), + // rotate.as_slice(), // nalgebra::Matrix4::identity().as_slice(), // ); + + // gl.uniform_matrix_4_f32_slice(projection.as_ref(), false, self.projection.as_slice()); } Ok(()) } diff --git a/src/graphics/tools/mod.rs b/src/graphics/tools/mod.rs index e69de29..8b13789 100644 --- a/src/graphics/tools/mod.rs +++ b/src/graphics/tools/mod.rs @@ -0,0 +1 @@ + diff --git a/src/main.rs b/src/main.rs index adcea6a..7e5b703 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ -mod ui; -use pg::App; +#![allow(unused)] mod camera; mod components; mod data_loader; mod errors; +mod font_manager; mod graphics; mod pg; mod support; +mod ui; mod utils; + +use pg::App; use support::supporter::run; fn main() { diff --git a/src/pg.rs b/src/pg.rs deleted file mode 100644 index f5b130d..0000000 --- a/src/pg.rs +++ /dev/null @@ -1,704 +0,0 @@ -use crate::components::Program; -use crate::data_loader::Data; -use crate::graphics::colormap::linear::LinearColormap; -use crate::graphics::ppi::PPIConfig; -use crate::graphics::threed::ThreeD; -use crate::graphics::transforms::viewport::Viewport; -use crate::graphics::{ppi::PPI, Graphics}; -use crate::graphics::{AttaWithBuffer, AttaWithProgram, AttachWithMouse, Config, MouseState}; -use crate::{errors::*, ui::base}; -use glow::{HasContext, NativeBuffer, NativeFramebuffer, NativeTexture, NativeVertexArray}; -use imgui::{ImStr, ImString, Textures, Ui}; -use log::info; -use std::hash::Hash; -use std::ops::Deref; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; -pub type Graphic = Rc>>; -type RcGraphic = Rc>; - -pub struct App<'a> { - pub ui_state: State, - gl: &'a glow::Context, - viewport: Viewport, - windows: HashMap>, - programs: [Graphic; 1], - pub ppi_module: RcGraphic, - program_with_window: HashMap>, -} - -impl<'a> App<'a> { - pub fn new(gl: &'a glow::Context) -> Result { - let viewport = Viewport::new()?; - - let mut cmap = LinearColormap::new().unwrap(); - cmap.set_colors(vec![ - [170, 170, 170, 255], - [0, 34, 255, 255], - [1, 160, 246, 255], - [0, 236, 236, 255], - [0, 216, 0, 255], - [1, 144, 0, 255], - [255, 255, 0, 255], - [231, 192, 0, 255], - [255, 144, 0, 255], - [255, 0, 0, 255], - [214, 0, 0, 255], - [192, 0, 0, 255], - [255, 0, 240, 255], - [150, 0, 180, 255], - ]); - cmap.set_range(0.0, 70.0); - let cmap = Box::new(cmap); - - let mut ppi = PPI::new()?; - ppi.set_viewport(&viewport); - ppi.set_colormap(cmap); - - let ppi = Rc::new(RefCell::new(ppi)); - let programs = [ppi.clone() as Graphic]; - - Ok(Self { - ui_state: State {}, - viewport, - programs, - windows: HashMap::with_capacity(30), - ppi_module: ppi, - gl, - program_with_window: HashMap::with_capacity(5), - }) - } - - pub fn render(&mut self) { - let mut need_clean = false; - for (id, program) in self.programs.iter().enumerate() { - let mut p = program.borrow_mut(); - - if self.program_with_window.len() == 0 { - return; - } - - p.mount(&self.gl).unwrap(); - self.program_with_window.get(&id).map(|windows| { - for window in windows.iter() { - let window_info = self.windows.get_mut(window).unwrap(); - - // Skip the window if it doesn't need to be redrawn - if !window_info.need_redraw { - continue; - } - - // If the window needs to be reinitialized, set the config - { - let conf = if window_info.re_init { - window_info.config.as_ref() - } else { - None - }; - - p.set_config(self.gl, conf).unwrap(); - window_info.re_init = false; - } - - // Attach the modifer to the program - if let Some(motion) = window_info.modifer.as_ref() { - motion - .attach_with_program(&self.gl, p.program_ref()) - .unwrap(); - } - - // Attach the data to the program - unsafe { - let framebuffer = window_info.gl_fb_resources.as_ref().map(|v| v.native()); - - // Skip the window if the framebuffer is None - if framebuffer.is_none() { - continue; - } - - self.gl.bind_framebuffer(glow::FRAMEBUFFER, framebuffer); - let attach = window_info.attach.get(&id); - - if attach.is_some() { - let vao = attach.unwrap().vao.native(); - self.gl.bind_vertex_array(Some(vao)); - } - - if attach.is_some() { - let window_size = window_info.size; - self.gl - .viewport(0, 0, window_size[0] as i32, window_size[1] as i32); - p.draw(&self.gl, attach.as_ref().unwrap().len).unwrap(); - } - - if attach.is_some() { - self.gl.bind_vertex_array(None); - } - self.gl.bind_framebuffer(glow::FRAMEBUFFER, None); - window_info.need_redraw = false; - need_clean = true; - } - } - }); - - // Unmount the program - p.unmount(&self.gl).unwrap(); - } - } - - pub fn render_ui(&mut self, ui: &Ui, window: &winit::window::Window, run: &mut bool) { - base(ui, window, run, self); - } - - pub fn create_framebuffer( - &mut self, - id: &str, - size: [i32; 2], - ) -> Result<(RcGlFramebuffer<'a>, RcGlTexture<'a>)> { - let id = &ImString::new(id); - - if self.windows.contains_key(id) { - let info = self.windows.get(id).unwrap(); - if info.gl_fb_resources.is_some() && info.gl_tex_resources.is_some() { - return Ok(( - info.gl_fb_resources.as_ref().unwrap().clone(), - info.gl_tex_resources.as_ref().unwrap().clone(), - )); - } - } - - let gl = self.gl; - let tex = unsafe { - let framebuffer = gl.create_framebuffer().unwrap(); - gl.bind_framebuffer(glow::FRAMEBUFFER, Some(framebuffer)); - - let texture = gl.create_texture().unwrap(); - gl.bind_texture(glow::TEXTURE_2D, Some(texture)); - gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::RGB8 as i32, - size[0], - size[1], - 0, - glow::RGB, - glow::UNSIGNED_BYTE, - None, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::LINEAR as i32, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::LINEAR as i32, - ); - - gl.framebuffer_texture_2d( - glow::FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::TEXTURE_2D, - Some(texture), - 0, - ); - - assert_eq!( - gl.check_framebuffer_status(glow::FRAMEBUFFER), - glow::FRAMEBUFFER_COMPLETE - ); - - gl.bind_framebuffer(glow::FRAMEBUFFER, None); - gl.bind_texture(glow::TEXTURE_2D, None); - ( - RcGlResource::new(&self.gl, framebuffer), - RcGlResource::new(&self.gl, texture), - ) - }; - - Ok(tex) - } - - pub fn prepare(&mut self) { - for program in self.programs.iter() { - let mut p = program.borrow_mut(); - p.compile(&self.gl).unwrap(); - } - unsafe { - self.gl.enable(glow::DEPTH_TEST); - } - } - - pub fn destroy(&mut self) { - for p in self.programs.iter() { - let mut p = p.borrow_mut(); - p.unmount(&self.gl).unwrap(); - p.destroy(&self.gl).unwrap(); - } - } - - pub fn create_ppi_render(&mut self, id: &str, config: Option) { - let id = &ImString::new(id); - let (vao, vbo, ebo) = self.ppi_module.borrow().init(&self.gl); - self.windows.get_mut(id).map(|w| { - w.attach.insert( - 0, - Attach { - vao: RcGlResource::new(&self.gl, vao), - vbo: RcGlResource::new(&self.gl, vbo), - ebo: ebo.map(|ebo| RcGlResource::new(&self.gl, ebo)), - len: 0, - }, - ); - w.need_redraw = true; - w.program = 0; - w.config = Some(config.unwrap_or_default().into()); - }); - - let v = self.program_with_window.entry(0).or_insert(vec![]); - v.push(id.clone()); - } - - pub fn bind_data(&mut self, program_idx: usize, id: &str, data: &Data) -> Result { - let id = &ImString::new(id); - use bytemuck::cast_slice; - - let window = self.windows.get_mut(id).unwrap(); - let program = window.program; - let program = self.programs[program].borrow(); - - let data = program.bake(data)?; - - let attach = window.attach.get_mut(&program_idx).unwrap(); - attach.len = data.2; - - unsafe { - self.gl - .bind_buffer(glow::VERTEX_ARRAY, Some(attach.vbo.native())); - self.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - cast_slice(data.0.as_slice()), - glow::STATIC_DRAW, - ); - if let Some(ebo) = attach.ebo.as_ref() { - self.gl - .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo.native())); - self.gl.buffer_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - cast_slice(&data.1.as_ref().unwrap()), - glow::STATIC_DRAW, - ); - - self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - } - self.gl.bind_buffer(glow::VERTEX_ARRAY, None); - } - - Ok(()) - } - - pub fn show_window(&mut self, ui: &Ui) { - let mut need_resize = vec![]; - - for (id, window) in self.windows.iter_mut() { - ui.window(&window.title) - .size(window.size, imgui::Condition::FirstUseEver) - .opened(&mut window.open) - .flags(imgui::WindowFlags::NO_SCROLLBAR) - .build(|| { - if ui.is_mouse_clicked(imgui::MouseButton::Left) { - let io = ui.io(); - let pos = io.mouse_pos; - - let window_pos = ui.window_pos(); - window.last_mouse_position = - [pos[0] - window_pos[0], pos[1] - window_pos[1]]; - } - - if ui.is_mouse_dragging(imgui::MouseButton::Left) { - if ui.is_window_hovered() { - let delta = ui.mouse_drag_delta(); - window.last_mouse_delta = delta; - window.accmulate_mouse_delta = [ - window.accmulate_mouse_delta[0] + delta[0], - window.accmulate_mouse_delta[1] + delta[1], - ]; - window.motion = Some(MouseState::Drag { - from: window.last_mouse_position, - delta: delta, - }); - - println!( - "Dragging: {:?} {:?}", - window.last_mouse_position, window.accmulate_mouse_delta - ); - window.modifer.as_mut().map(|v| { - v.exec(window.motion.as_ref().unwrap()); - }); - window.need_redraw = true; - } - } - - if ui.is_mouse_released(imgui::MouseButton::Left) { - if window.size != ui.window_size() { - window.size = ui.window_size(); - println!("resized: {:?}", window.size); - need_resize.push((window.title.clone(), ui.window_size())); - } - } - - if let Some(texture) = window.gl_tex_resources.as_ref() { - let cursor = ui.cursor_pos(); - imgui::Image::new(texture.native2imguiid(), ui.window_size()).build(ui); - ui.set_cursor_pos(cursor); - if ui.invisible_button(&window.title, ui.window_size()) { - let io = ui.io(); - let pos = io.mouse_pos; - let window_pos = ui.window_pos(); - let related_pos = [pos[0] - window_pos[0], pos[1] - window_pos[1]]; - } - } - }); - } - - for (id, size) in need_resize.iter() { - self.reset_window_size(id, *size); - } - } - - pub fn create_render_window(&mut self, title: &str, size: [f32; 2]) -> Result { - // Insert the window data into the windows hashmap - let id = ImString::new(title); - let mut data = WindowData::new(id.clone(), size, None); - let (fb, tex) = - self.create_framebuffer(title, [size[0].floor() as i32, size[1].floor() as i32])?; - data.gl_fb_resources = Some(fb); - data.gl_tex_resources = Some(tex); - self.windows.insert(id, data); - Ok(()) - } - - pub fn set_window_modifer(&mut self, id: &str, modifer: Option) { - let id = &ImString::new(id); - self.windows.get_mut(id).map(|v| { - v.modifer = modifer; - }); - } - - pub fn set_config(&mut self, id: &str) -> Option<&mut Config> { - let id = &ImString::new(id); - self.windows - .get_mut(id) - .map(|v| { - v.re_init = true; - v.config.as_mut() - }) - .flatten() - } - - fn reset_window_size(&mut self, id: &ImString, size: [f32; 2]) { - let window_info = self.windows.get_mut(id).unwrap(); - window_info.need_redraw = true; - let tex = unsafe { - self.gl.bind_framebuffer( - glow::FRAMEBUFFER, - window_info.gl_fb_resources.as_ref().map(|v| v.native()), - ); - let texture = self.gl.create_texture().unwrap(); - self.gl.bind_texture(glow::TEXTURE_2D, Some(texture)); - self.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::RGB8 as i32, - size[0].floor() as i32, - size[1].floor() as i32, - 0, - glow::RGB, - glow::UNSIGNED_BYTE, - None, - ); - self.gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::LINEAR as i32, - ); - self.gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::LINEAR as i32, - ); - - self.gl.framebuffer_texture_2d( - glow::FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::TEXTURE_2D, - Some(texture), - 0, - ); - - assert_eq!( - self.gl.check_framebuffer_status(glow::FRAMEBUFFER), - glow::FRAMEBUFFER_COMPLETE - ); - - self.gl.bind_framebuffer(glow::FRAMEBUFFER, None); - self.gl.bind_texture(glow::TEXTURE_2D, None); - - texture - }; - - window_info.gl_tex_resources = Some(RcGlResource::new(self.gl, tex)); - } - - pub fn destroy_window(&mut self) { - for (id, window) in self.windows.iter() { - if !window.open { - self.program_with_window.get_mut(&window.program).map(|v| { - v.retain(|k| k != id); - }); - } - } - self.windows.retain(|k, v| v.open == true); - } -} - -#[derive(Debug, Clone)] -pub struct Attach<'a> { - pub vao: RcGlResource<'a, NativeVertexArray>, - pub vbo: RcGlResource<'a, NativeBuffer>, - pub ebo: Option>, - pub len: i32, -} - -pub struct State {} - -pub struct WindowData<'a> { - pub title: ImString, - pub open: bool, - pub copy_from: Option, - pub size: [f32; 2], - gl_fb_resources: Option>, - gl_tex_resources: Option>, - need_redraw: bool, - program: usize, - attach: HashMap>, - - re_init: bool, - last_mouse_position: [f32; 2], - last_mouse_delta: [f32; 2], - accmulate_mouse_delta: [f32; 2], - mouse_position: [f32; 2], - motion: Option, - config: Option, - modifer: Option, -} - -impl<'a> WindowData<'a> { - pub fn new(title: ImString, size: [f32; 2], modifer: Option) -> Self { - Self { - title, - open: true, - copy_from: None, - size, - last_mouse_position: [0.0, 0.0], - last_mouse_delta: [0.0, 0.0], - accmulate_mouse_delta: [0.0, 0.0], - mouse_position: [0.0, 0.0], - motion: None, - - gl_fb_resources: None, - gl_tex_resources: None, - need_redraw: false, - program: 0, - attach: HashMap::with_capacity(10), - re_init: false, - config: None, - modifer, - } - } - - fn set_current_mouse_delta(&mut self, delta: [f32; 2]) { - self.last_mouse_delta = delta; - self.accmulate_mouse_delta = [ - self.accmulate_mouse_delta[0] + delta[0], - self.accmulate_mouse_delta[1] + delta[1], - ]; - } - - fn set_mouse_postion(&mut self, pos: [f32; 2]) { - self.mouse_position = pos; - } - - fn set_motion(&mut self, motion: MouseState) { - self.motion = Some(motion); - } - - fn set_need_redraw(&mut self) { - self.need_redraw = true; - } - - fn set_config(&mut self, config: Config) { - self.config = Some(config); - } - - fn set_re_init(&mut self) { - self.re_init = true; - } - - fn on_mouse_drag(&mut self) { - let state = MouseState::Drag { - from: self.last_mouse_position, - delta: self.last_mouse_delta, - }; - self.set_motion(state.clone()); - - self.modifer.as_mut().map(|m| {}); - - self.set_need_redraw(); - } -} - -macro_rules! modifer_exec { - ($(($t:ty => $b:tt),)+) => { - impl ModiferType { - pub fn exec(&mut self, motion: &MouseState) { - match self { - $( - ModiferType::$b(b) => { - b.attach_with_mouse(motion); - } - )+ - _ => {} - } - } - } - - impl AttaWithProgram for ModiferType { - fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result { - match self { - $( - - ModiferType::$b(b) => { - b.attach_with_program(gl, program)?; - } - - )+ - _ => { - } - } - Ok(()) - } - - } - - $( - impl From<$t> for ModiferType { - fn from(t: $t) -> Self { - ModiferType::$b(t) - } - } - )+ - } - -} - -pub enum ModiferType { - ThreeD(ThreeD), - TwoD, -} - -modifer_exec!((ThreeD => ThreeD),); - -pub type RcGlFramebuffer<'a> = RcGlResource<'a, NativeFramebuffer>; -pub type RcGlTexture<'a> = RcGlResource<'a, NativeTexture>; -pub type RcGlVertexArray<'a> = RcGlResource<'a, NativeVertexArray>; -pub type RcGlBuffer<'a> = RcGlResource<'a, NativeBuffer>; - -// pub type RcGlResource<'a, T> = Rc>; -#[derive(Debug, Clone)] -pub struct RcGlResource<'a, T: Resource>(Rc>); - -impl<'a, T: Resource> RcGlResource<'a, T> { - pub fn new(gl: &'a glow::Context, resource: T) -> Self { - Self(Rc::new(GlResource::new(resource, gl))) - } - - pub fn native(&self) -> T { - self.0.resource.clone() - } -} - -impl<'a, T: Resource> Deref for RcGlResource<'a, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - self.0.deref() - } -} - -trait Resource: Clone + std::fmt::Debug { - fn drop_self(&self, gl: &glow::Context); -} - -impl Resource for NativeVertexArray { - fn drop_self(&self, gl: &glow::Context) { - unsafe { - gl.delete_vertex_array(*self); - } - } -} - -impl Resource for NativeBuffer { - fn drop_self(&self, gl: &glow::Context) { - unsafe { - gl.delete_buffer(*self); - } - } -} - -impl Resource for NativeFramebuffer { - fn drop_self(&self, gl: &glow::Context) { - unsafe { - gl.delete_framebuffer(*self); - } - } -} - -impl Resource for NativeTexture { - fn drop_self(&self, gl: &glow::Context) { - unsafe { - gl.delete_texture(*self); - } - } -} - -#[derive(Debug)] -struct GlResource<'a, T: Resource> { - gl: &'a glow::Context, - resource: T, -} - -impl Deref for GlResource<'_, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.resource - } -} - -impl<'a, T: Resource> GlResource<'a, T> { - pub fn new(resource: T, gl: &'a glow::Context) -> Self { - Self { resource, gl } - } -} - -impl Drop for GlResource<'_, T> { - fn drop(&mut self) { - self.resource.drop_self(self.gl); - info!("Dropping resource: {:?}", self.resource); - } -} - -impl<'a> RcGlResource<'a, NativeTexture> { - fn native2imguiid(&self) -> imgui::TextureId { - imgui::TextureId::new(self.native().0.get() as usize) - } -} diff --git a/src/pg/app.rs b/src/pg/app.rs new file mode 100644 index 0000000..5570074 --- /dev/null +++ b/src/pg/app.rs @@ -0,0 +1,110 @@ +use log::*; +use std::{cell::RefCell, path::PathBuf, rc::Rc}; + +use crate::{ + data_loader::Data, + errors::*, + graphics::{ + colormap::linear::LinearColormap, ppi::PPI, threed::ThreeD, AttaWithBuffer, + AttaWithProgram, Graphics, + }, + ui::{State, GUI}, + utils::{ + cache::{Cache, CachedData}, + resources::{ + ManagedResource, RcGlBuffer, RcGlFramebuffer, RcGlRcFramebuffer, RcGlRcRenderbuffer, + RcGlRcResource, RcGlRenderbuffer, RcGlResource, RcGlVertexArray, GL, + }, + }, +}; +use glow::HasContext; +use imgui::Ui; + +use super::app_type::{self, AppType}; +use super::{ModulePackage, Programs}; +use crate::{font_manager::FontManager, graphics::font::Text}; + +pub struct App<'gl> { + gl: &'gl GL, + context: Context<'gl>, + gui: GUI, + app_type: AppType<'gl>, +} + +impl<'gl> App<'gl> { + pub fn new(gl: &'gl GL) -> Result { + let programs = Programs::new(gl).unwrap(); + let mut default_state = State::default(); + + let mut gui = GUI::new(State::default()); + let app_type = gui.apptype(gl); + + let context = Context::new(gl, Cache::new(), programs); + Ok(Self { + gui, + gl, + context, + app_type, + }) + } + + pub fn init<'a>(&'a mut self) -> AppType { + self.gui.apptype(&self.gl) + } + + pub fn prepare(&mut self) { + if let Err(e) = self.context.programs.prepare() { + error!("prepare failed: {:?}", e); + } + } + + fn gl(&self) -> &GL { + &self.context.gl + } + + fn datapool(&mut self) -> &mut Cache> { + &mut self.context.datapool + } + + pub fn render<'a>(&'a mut self) { + if let Err(e) = self.app_type.draw_program(&mut self.context) { + error!("draw_program failed: {:?}", e); + } + } + + pub fn render_ui<'a>(&'a mut self, ui: &Ui, window: &winit::window::Window, run: &mut bool) { + *run = self.gui.render(ui, &mut self.context, &mut self.app_type); + } + + pub fn destroy(&mut self) { + self.context.programs.destroy().unwrap(); + } + pub fn resize(&mut self, size: [f32; 2]) {} +} + +pub struct Context<'gl> { + pub gl: &'gl GL, + pub programs: Programs<'gl>, + pub datapool: Cache>, +} + +impl<'gl> Context<'gl> { + fn new( + gl: &'gl GL, + datapool: Cache>, + programs: Programs<'gl>, + ) -> Self { + let context = Context { + datapool, + programs, + gl, + }; + context + } + + pub fn load_data>(&mut self, path: P) -> Result> { + let data = self.datapool.get_or_insert(path.into())?; + let module_cursor = self.programs.load_data(data)?; + Ok(module_cursor) + } +} diff --git a/src/pg/app_type.rs b/src/pg/app_type.rs new file mode 100644 index 0000000..1e92a85 --- /dev/null +++ b/src/pg/app_type.rs @@ -0,0 +1,145 @@ +use crate::errors::*; +use crate::graphics::ty; +use crate::pg::{Context, ModulePackage}; +use crate::ui::typ::{LayoutAppType, MainLoadTyp}; +use crate::utils::resources::{ManagedResource, RcGlRcFramebuffer, RcGlRcRenderbuffer, GL}; +use glow::HasContext; + +const RBO_WIDTH: i32 = 1920; +const RBO_HEIGHT: i32 = 1080; + +pub struct ViewPort { + renderer_size: [f32; 2], + main_fbo: RcGlRcFramebuffer, + _main_rbo: RcGlRcRenderbuffer, +} + +impl ViewPort { + pub fn new(gl: &GL) -> Self { + let main_fbo: RcGlRcFramebuffer = gl.create_resource_rc(); + let main_rbo: RcGlRcRenderbuffer = gl.create_resource_rc(); + + main_fbo.bind(glow::FRAMEBUFFER); + main_rbo.bind(glow::RENDERBUFFER); + + unsafe { + gl.renderbuffer_storage(glow::RENDERBUFFER, glow::RGBA8, RBO_WIDTH, RBO_HEIGHT); + gl.framebuffer_renderbuffer( + glow::FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::RENDERBUFFER, + Some(main_rbo.native()), + ); + // gl.clear_color(1.0, 0.0, 0.0, 1.0); + // gl.clear(glow::COLOR_BUFFER_BIT); + // 检查帧缓冲是否完整 + if gl.check_framebuffer_status(glow::FRAMEBUFFER) != glow::FRAMEBUFFER_COMPLETE { + panic!("Framebuffer is not complete!"); + } + } + main_fbo.unbind(glow::FRAMEBUFFER); + + Self { + renderer_size: [0.0, 0.0], + main_fbo, + _main_rbo: main_rbo, + } + } + + pub fn bind(&self, gl: &glow::Context) { + self.main_fbo.bind(glow::FRAMEBUFFER); + unsafe { + gl.viewport( + 0, + 0, + self.renderer_size[0] as i32, + self.renderer_size[1] as i32, + ); + } + } + + pub fn unbind(&self) { + self.main_fbo.unbind(glow::FRAMEBUFFER); + } + + pub fn set_size(&mut self, size: [f32; 2]) -> bool { + if size != self.renderer_size { + self.renderer_size = size; + true + } else { + false + } + } + + pub fn fbo(&self) -> &RcGlRcFramebuffer { + &self.main_fbo + } +} + +macro_rules! app_type_into { + ($({$t: ty => $b:tt},)+) => { + $( + impl<'gl> From<$t> for AppType<'gl> { + fn from(t: $t) -> Self { + Self::$b(t) + } + } + + impl<'a,'gl> From<&'a AppType<'gl>> for &'a $t { + fn from(t: &'a AppType<'gl>) -> Self { + if let AppType::$b(t) = t { + t + } else { + panic!("AppType is not {}", stringify!($b)); + } + } + } + + impl<'a,'gl> From<&'a mut AppType<'gl>> for &'a mut $t { + fn from(t: &'a mut AppType<'gl>) -> Self { + if let AppType::$b(t) = t { + t + } else { + panic!("AppType is not {}", stringify!($b)); + } + } + } + + )+ + }; +} + +pub enum AppType<'gl> { + MainLoad(MainLoadTyp<'gl>), + Other, +} + +app_type_into!({ + MainLoadTyp<'gl> => MainLoad +},); + +impl<'gl> AppType<'gl> { + pub fn draw_program<'b>(&mut self, mut context: &mut Context<'gl>) -> Result<()> + where + 'gl: 'b, + { + let programs = &mut context.programs; + let gl = programs.gl; + match self { + Self::MainLoad(typ) => { + programs.draw(typ)?; + } + Self::Other => {} + } + Ok(()) + } + + pub fn deal_with_cursor(&mut self, package: ModulePackage<'gl>) { + match self { + Self::MainLoad(typ) => { + typ.deal_with_cursor(package); + } + Self::Other => {} + } + } +} diff --git a/src/pg/mod.rs b/src/pg/mod.rs new file mode 100644 index 0000000..2efb065 --- /dev/null +++ b/src/pg/mod.rs @@ -0,0 +1,166 @@ +mod app; +pub mod app_type; +mod modules; + +use crate::font_manager::FontManager; +use crate::graphics::font::Text; +use crate::graphics::ppi::PPI; +use crate::graphics::threed::ThreeD; +use crate::ui::typ::LayoutAppType; +use crate::utils::cache::CachedData; +use crate::utils::resources::GL; +use crate::{errors::*, graphics::Graphics}; +pub use app::{App, Context}; +pub use modules::{Module, ModuleCursor, ModuleData, PPIModule, PPIPackage}; + +pub(super) struct Programs<'gl> { + gl: &'gl GL, + _ppi: PPI, + _text: Text<'gl>, +} + +impl<'gl> Programs<'gl> { + pub fn new(gl: &'gl GL) -> Result { + let font_manager = FontManager::new()?; + + Ok(Self { + gl, + _ppi: PPI::new()?, + _text: Text::new(gl, font_manager)?, + }) + } + + pub fn prepare(&mut self) -> Result<()> { + self._ppi.program().compile(&self.gl)?; + Ok(()) + } + + pub fn ppi<'b>(&'b mut self) -> modules::PPIModule<'b, 'gl> { + modules::PPIModule::new(&self.gl, &mut self._ppi, &mut self._text).unwrap() + } + + pub fn destroy(&mut self) -> Result<()> { + self._ppi.destroy(&self.gl)?; + self._text.destroy(&self.gl)?; + Ok(()) + } + + pub fn load_data( + &mut self, + data: &CachedData, + ) -> Result> { + let cdata = data.borrow(); + if cdata.blocks.len() == 0 { + return Err(Error::InvalidDataType); + } + let first_block = cdata.blocks.first().unwrap(); + + match first_block.coord_type { + crate::data_loader::CoordType::Polar { .. } => { + let package: PPIPackage<'gl> = self.ppi().load_data(data)?; + return Ok(package.into()); + } + _ => { + unimplemented!("Only polar data is supported for now"); + } + } + } + + pub fn draw_modules( + &mut self, + modules: &mut ModulePackage<'gl>, + init_info: &ThreeD, + ) -> Result<()> { + // if !modules.need_update { + // return Ok(()); + // } + match &mut modules.modules { + _ModulePackage::PPI(ppi) => { + self.ppi().start(); + self.ppi().init(init_info); + self.ppi().render(ppi)?; + self.ppi().end(); + } + } + modules.need_update = false; + Ok(()) + } + + pub fn draw>(&mut self, ty: &mut M) -> Result<()> { + ty.draw_program(self.gl, self) + } +} + +pub enum Data<'a> { + Data(&'a crate::data_loader::Data), + Other, +} + +macro_rules! impl_module_data { + ($({$t:ty => $b: tt}),+) => { + $( + impl ModuleData for $t { + fn to_data(&self) -> Data { + Data::$b(self) + } + + } + + )+ + }; +} + +macro_rules! impl_module_package { + ($({$t:ty => $b: tt}),+) => { + $( + + impl<'a> From<$t> for _ModulePackage<'a> { + fn from(t: $t) -> Self { + _ModulePackage::$b(t) + } + } + + + )+ + }; +} + +impl_module_data!({ + crate::data_loader::Data => Data +}); + +pub struct ModulePackage<'gl> { + need_update: bool, + modules: _ModulePackage<'gl>, +} + +pub enum _ModulePackage<'gl> { + PPI(PPIPackage<'gl>), +} + +impl_module_package!({ + PPIPackage<'a> => PPI +}); + +impl<'gl, T> From for ModulePackage<'gl> +where + T: ModuleCursor + Into<_ModulePackage<'gl>>, +{ + fn from(t: T) -> Self { + Self { + modules: t.into(), + need_update: true, + } + } +} + +impl ModulePackage<'_> { + pub fn ui_build(&mut self, ui: &imgui::Ui) -> Result<()> { + match &mut self.modules { + _ModulePackage::PPI(ppi) => { + ppi.ui_build(ui); + } + } + Ok(()) + } +} diff --git a/src/pg/modules/mod.rs b/src/pg/modules/mod.rs new file mode 100644 index 0000000..f7c741e --- /dev/null +++ b/src/pg/modules/mod.rs @@ -0,0 +1,92 @@ +use crate::utils::{ + cache::CachedData, + resources::{ManagedResource, RcGlBuffer, RcGlVertexArray}, +}; +use glow::{HasContext, NativeBuffer, NativeVertexArray}; +mod ppi; +use crate::errors::*; +pub use ppi::{PPIModule, PPIPackage}; + +use super::Data; + +#[derive(Debug, Clone)] +struct Attach<'a> { + gl: &'a glow::Context, + pub vao: RcGlVertexArray<'a>, + pub vbo: RcGlBuffer<'a>, + pub ebo: Option>, + pub len: i32, +} + +impl<'a> Attach<'a> { + fn new( + gl: &'a glow::Context, + vao: NativeVertexArray, + vbo: NativeBuffer, + ebo: Option, + len: Option, + ) -> Self { + Self { + gl, + vao: RcGlVertexArray::new(gl, vao), + vbo: RcGlBuffer::new(gl, vbo), + ebo: ebo.map(|ebo| RcGlBuffer::new(gl, ebo)), + len: len.unwrap_or(0), + } + } + + fn bind_data(&mut self, vbo: &Vec, ebo: Option<&Vec>, len: i32) { + use bytemuck::cast_slice; + self.vao.bind(glow::VERTEX_ARRAY); + unsafe { + self.gl + .buffer_data_u8_slice(glow::ARRAY_BUFFER, cast_slice(&vbo), glow::STATIC_DRAW); + if let Some(ebo) = ebo { + self.ebo.as_ref().unwrap().bind(glow::ELEMENT_ARRAY_BUFFER); + self.gl.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + cast_slice(&ebo), + glow::STATIC_DRAW, + ); + } + } + + self.vao.unbind(glow::VERTEX_ARRAY); + self.len = len; + } + + fn len(&self) -> i32 { + self.len + } +} + +pub trait Module: Sized { + type Cursor: ModuleCursor; + type Data; + type InitInfo; + + fn render(&mut self, cursor: &mut Self::Cursor) -> Result<()>; + fn resize(&self, size: [f32; 2]); + fn destroy(&self); + + fn start(&mut self); + fn init(&mut self, info: &Self::InitInfo) -> Result<()>; + fn end(&mut self); + fn load_data<'dt>(&self, data: &CachedData) -> Result; +} + +pub trait ModuleData: Sized { + fn to_data(&self) -> Data; +} + +pub trait ModuleCursor { + type Module: Module; + type Data: ModuleData; + type Config; + + fn set_config(&mut self, f: F) + where + F: FnOnce(&mut Self::Config); + + fn ui_build(&mut self, ui: &imgui::Ui); +} diff --git a/src/pg/modules/ppi.rs b/src/pg/modules/ppi.rs new file mode 100644 index 0000000..0161091 --- /dev/null +++ b/src/pg/modules/ppi.rs @@ -0,0 +1,159 @@ +use std::rc::Rc; +use tracker::track; + +use crate::{ + data_loader::Data, + errors::*, + font_manager::FontManager, + ui::typ, + utils::{cache::CachedData, resources::ManagedResource}, +}; +use imgui::VerticalSlider; +use ppi::{PPIConfig, PPI}; +use threed::ThreeD; + +use crate::graphics::{font::Text, *}; + +use super::{Attach, Module, ModuleCursor}; +pub struct PPIModule<'b, 'a: 'b> { + gl: &'a glow::Context, + ppi_program: &'b mut PPI, + text_program: &'b mut Text<'a>, +} + +impl<'b, 'a: 'b> PPIModule<'b, 'a> { + pub fn new(gl: &'a glow::Context, ppi: &'b mut PPI, text: &'b mut Text<'a>) -> Result { + let config = PPIConfig::default(); + Ok(Self { + gl, + ppi_program: ppi, + text_program: text, + }) + } + + fn rebind(&self, attach: &mut Attach, data: &Data, config: &PPIModuleConfig) -> Result<()> { + let (vbo, ebo, len) = self.ppi_program.bake( + data, + &PPIConfig { + layer: config.layer, + rdpi: config.rdpi, + adpi: config.adpi, + }, + )?; + attach.bind_data(&vbo, ebo.as_ref(), len); + Ok(()) + } +} + +impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> { + type Cursor = PPIPackage<'a>; + type Data = Data; + type InitInfo = ThreeD; + + fn start(&mut self) { + self.ppi_program.mount(&self.gl).unwrap(); + } + + fn end(&mut self) { + self.ppi_program.unmount(&self.gl).unwrap(); + } + + fn render<'dt>(&mut self, cursor: &mut Self::Cursor) -> Result<()> { + let attach = &mut cursor.ppi_attach; + let data = &cursor.ppi_data.borrow(); + let config = &mut cursor.ppi_config; + self.ppi_program.set_config( + &self.gl, + &PPIConfig { + layer: config.layer, + rdpi: config.rdpi, + adpi: config.adpi, + }, + )?; + + if config.changed_layer() { + self.rebind(attach, data, config); + config.reset(); + } + attach.vao.bind(glow::VERTEX_ARRAY); + self.ppi_program.draw(&self.gl, attach.len())?; + attach.vao.unbind(glow::VERTEX_ARRAY); + Ok(()) + } + + fn resize(&self, size: [f32; 2]) {} + + fn destroy(&self) {} + + fn load_data<'dt>(&self, data: &CachedData) -> Result { + let _data = data.borrow(); + if _data.blocks.len() == 0 { + return Err(Error::InvalidDataType); + } + let (vao, vbo, ebo) = self.ppi_program.init(&self.gl); + let mut attach = Attach::new(&self.gl, vao, vbo, ebo, None); + let (r, a, t) = self.ppi_program.data_info(&_data)?; + let config = PPIModuleConfig { + layer: 0, + rdpi: r, + adpi: a, + tracker: 0, + }; + + self.rebind(&mut attach, &data.borrow(), &config); + + Ok(PPIPackage::new(config, attach, data)) + } + + fn init(&mut self, info: &Self::InitInfo) -> Result<()> { + info.attach_with_program(&self.gl, &self.ppi_program.program()); + Ok(()) + } +} + +#[track] +pub struct PPIModuleConfig { + pub layer: usize, + #[do_not_track] + pub rdpi: f32, + #[do_not_track] + pub adpi: f32, +} + +pub struct PPIPackage<'gl> { + ppi_config: PPIModuleConfig, + ppi_attach: Attach<'gl>, + ppi_data: CachedData, +} + +impl<'gl> PPIPackage<'gl> { + fn new(ppi_config: PPIModuleConfig, ppi_attach: Attach<'gl>, data: &CachedData) -> Self { + Self { + ppi_config, + ppi_attach, + ppi_data: Rc::clone(data), + } + } +} + +impl<'gl> ModuleCursor for PPIPackage<'gl> { + type Module = PPIModule<'gl, 'gl>; + type Data = Data; + type Config = PPIModuleConfig; + + fn set_config(&mut self, f: F) + where + F: FnOnce(&mut Self::Config), + { + f(&mut self.ppi_config); + } + + fn ui_build(&mut self, ui: &imgui::Ui) { + let mut layer = self.ppi_config.layer; + ui.text("PPI Data Config"); + ui.slider("Layer", 0, 10, &mut layer); + ui.separator(); + + self.ppi_config.set_layer(layer); + } +} diff --git a/src/support/supporter.rs b/src/support/supporter.rs index 15f2cd0..2f0a845 100644 --- a/src/support/supporter.rs +++ b/src/support/supporter.rs @@ -1,13 +1,5 @@ -use crate::pg::App; -use crate::{ - graphics::{ - collections::agg_fast_path::AggFastPath, - transforms::{ - polar::Polar, position::Position, trackball::Trackball, viewport::Viewport, Transform, - }, - }, - utils::Triangler, -}; +use crate::{pg::App, utils::resources::GL}; + use glow::HasContext; use glutin::{ config::ConfigTemplateBuilder, @@ -15,24 +7,21 @@ use glutin::{ display::{GetGlDisplay, GlDisplay}, surface::{GlSurface, Surface, SurfaceAttributesBuilder, WindowSurface}, }; -use imgui::Ui; use imgui_winit_support::{ winit::{ - dpi::{LogicalSize, PhysicalPosition}, + dpi::LogicalSize, event_loop::EventLoop, window::{Window, WindowBuilder}, }, HiDpiMode, WinitPlatform, }; -use nalgebra_glm::perspective; -use nalgebra_glm::Vec3; use raw_window_handle::HasRawWindowHandle; use std::num::NonZeroU32; use std::time::Instant; pub fn run(mut app: F) where - F: for<'b> FnMut(&'b glow::Context) -> App<'b>, + F: for<'b> FnMut(&'b GL) -> App<'b>, { // Create Window let (event_loop, window, surface, context) = create_window(); @@ -47,10 +36,10 @@ where let gl_app = ig_renderer.gl_context().clone(); let gl_context = ig_renderer.gl_context().clone(); + let gl = GL::new(gl_app); - // let mut run = true; - let mut app = app(&gl_app); - + // App instance + let mut app = app(&gl); let mut run = true; // Prepare @@ -82,10 +71,14 @@ where // Render your custom scene, note we need to borrow the OpenGL // context from the `AutoRenderer`, which takes ownership of it. unsafe { - gl_context.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT); + gl_context.clear_color(0.0, 0.0, 0.0, 1.0); + gl_context.clear(glow::COLOR_BUFFER_BIT); + gl_context.clear(glow::COLOR_BUFFER_BIT); } + // Render Offscreen app.render(); + let ui = imgui_context.frame(); app.render_ui(&ui, &window, &mut run); @@ -120,6 +113,7 @@ where ); } winit_platform.handle_event(imgui_context.io_mut(), &window, &event); + app.resize([new_size.width as f32, new_size.height as f32]); } winit::event::Event::LoopExiting => { app.destroy(); @@ -142,6 +136,7 @@ fn create_window() -> ( let window_builder = WindowBuilder::new() .with_title("TEST") + .with_maximized(true) .with_inner_size(LogicalSize::new(1024, 768)); let (window, cfg) = glutin_winit::DisplayBuilder::new() @@ -211,7 +206,9 @@ fn imgui_init(window: &Window) -> (WinitPlatform, imgui::Context) { .fonts() .add_font(&[imgui::FontSource::DefaultFontData { config: None }]); - imgui_context.io_mut().font_global_scale = (1.0 / winit_platform.hidpi_factor()) as f32; + println!("imgui global scale: {}", winit_platform.hidpi_factor()); + + imgui_context.io_mut().font_global_scale = (1.0 / 1.5) as f32; (winit_platform, imgui_context) } diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index 9d2b2d9..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::errors::*; -use crate::graphics::threed::ThreeD; -use crate::graphics::Config; -use crate::pg::App; -use crate::{data_loader::Data, pg::WindowData}; -use glow::NativeTexture; -use imgui::{ImString, Ui}; - -pub fn base(ui: &Ui, window: &winit::window::Window, run: &mut bool, app: &mut App) { - ui.window("test") - .size([300.0, 200.0], imgui::Condition::FirstUseEver) - .build(|| { - if ui.button("PPI") { - let data = - load_data(r#"/Users/tsuki/Desktop/ZJSXAA_20230113070200_R.dat.gz"#).unwrap(); - app.create_render_window("ppi", [300.0, 300.0]).unwrap(); - app.create_ppi_render("ppi", None); - app.set_window_modifer( - "ppi", - Some(ThreeD::new(1.0, 0.1, 100.0, 45.0).unwrap().into()), - ); - app.bind_data(0, "ppi", &data).unwrap(); - - let (rdpi, adpi, _) = { - let ppi = app.ppi_module.borrow_mut(); - ppi.data_info(&data).unwrap() - }; - - app.set_config("ppi").map(|config| { - if let Config::PPI(config) = config { - config.rdpi = rdpi; - config.adpi = adpi; - } - }); - } - }); - - app.show_window(ui); - app.destroy_window(); -} - -fn create_display_window(title: &str, size: [f32; 2], copy_from: Option) -> WindowData { - WindowData::new(ImString::new(title), size, Some(ThreeD::default().into())) -} - -fn load_data(path: impl AsRef) -> Result { - let data = Data::from_path(path)?; - Ok(data) -} diff --git a/src/ui/io.rs b/src/ui/io.rs new file mode 100644 index 0000000..5afc84a --- /dev/null +++ b/src/ui/io.rs @@ -0,0 +1,43 @@ +use imgui::Ui; + +pub struct MouseIO { + pub position: [f32; 2], // 鼠标当前位置 + pub drag_delta: Option<[f32; 2]>, // 拖动开始时的鼠标位置 + pub is_dragging: bool, // 是否正在拖动 + pub left_button_pressed: bool, // 左键是否被按下 + pub right_button_pressed: bool, // 右键是否被按下 + pub wheel_delta: f32, // 鼠标滚轮变化值 +} + +pub struct KeyboardIO { + pub keys: [bool; 512], // 键盘按键状态 +} + +pub struct IO { + pub mouse: MouseIO, + pub keyboard: KeyboardIO, +} + +impl IO { + pub fn new(ui: &Ui) -> Self { + let io = ui.io(); + + let delta = if ui.is_mouse_dragging(imgui::MouseButton::Left) { + Some(ui.mouse_drag_delta_with_button(imgui::MouseButton::Left)) + } else { + None + }; + + IO { + mouse: MouseIO { + position: io.mouse_pos, + drag_delta: delta, + is_dragging: ui.is_mouse_dragging(imgui::MouseButton::Left), + left_button_pressed: io.mouse_down[0], + right_button_pressed: io.mouse_down[1], + wheel_delta: io.mouse_wheel, + }, + keyboard: KeyboardIO { keys: [false; 512] }, + } + } +} diff --git a/src/ui/layout.rs b/src/ui/layout.rs new file mode 100644 index 0000000..bda1954 --- /dev/null +++ b/src/ui/layout.rs @@ -0,0 +1,165 @@ +use imgui::{Condition, Ui}; + +pub struct Layout { + size: [f32; 2], + origin: [f32; 2], +} + +impl Layout { + pub(super) fn new() -> Self { + Layout { + size: [0.0, 0.0], + origin: [0.0, 0.0], + } + } + + pub(super) fn set_size(&mut self, size: [f32; 2]) { + self.size = size; + } + + pub(super) fn set_origin(&mut self, origin: [f32; 2]) { + self.origin = origin; + } + + pub(super) fn grid(&self, cols: usize, rows: usize, gap: [f32; 2]) -> GridLayout { + GridLayout::new(self.size, self.origin, cols, rows, gap) + } +} + +pub enum Size { + Percent(f32), + Fixed(f32), + Fill, +} + +#[macro_export] +macro_rules! size { + ($float:literal %) => { + Size::Percent($float) + }; + + ($float:literal px) => { + Size::Fixed($float) + }; + + (fill) => { + Size::Fill + }; +} + +pub struct GridLayout { + size: [f32; 2], + origin: [f32; 2], + cols: usize, + rows: usize, + gap: [f32; 2], + cursor: Vec<[f32; 2]>, + widths: Vec, + hgts: Vec, +} + +impl GridLayout { + fn new(size: [f32; 2], origin: [f32; 2], cols: usize, rows: usize, gap: [f32; 2]) -> Self { + GridLayout { + size, + origin, + cols, + rows, + gap, + cursor: vec![[0.0, 0.0]; cols * rows], + widths: vec![0.0; cols], + hgts: vec![0.0; rows], + } + } + + fn build(&mut self, sizes: &[Size], idx: usize) { + let mut start = 0.0; + let s = sizes + .iter() + .filter(|v| !matches!(v, Size::Fill)) + .fold(0.0, |start, v| match v { + Size::Percent(p) => start + self.size[idx] * p / 100.0, + Size::Fixed(f) => start + *f, + _ => start, + }); + + let fill = self.size[idx] - s; + + for (i, v) in sizes.iter().enumerate() { + let hgt = match v { + Size::Percent(p) => self.size[idx] * p / 100.0, + Size::Fixed(f) => *f, + Size::Fill => fill, + }; + + if idx == 0 { + for j in 0..self.rows { + self.cursor[j * self.cols + i][0] = start; + } + + self.widths[i] = hgt; + } else { + self.cursor[i * self.cols..(i + 1) * self.cols] + .iter_mut() + .for_each(|v| { + v[1] = start; + }); + + self.hgts[i] = hgt; + } + + start += hgt; + } + } + + pub(super) fn set_row_size(&mut self, sizes: &[Size]) { + assert_eq!(sizes.len(), self.rows); + assert!(sizes.iter().filter(|v| matches!(v, Size::Fill)).count() <= 1); + self.build(sizes, 1); + } + + pub(super) fn set_col_size(&mut self, sizes: &[Size]) { + assert_eq!(sizes.len(), self.cols); + assert!(sizes.iter().filter(|v| matches!(v, Size::Fill)).count() <= 1); + self.build(sizes, 0); + } + + pub(super) fn with(&mut self, row: usize, col: usize, span: [usize; 2]) -> GridBlock { + let curr = self.cursor[row * self.cols + col]; + let width: f32 = self.widths.iter().skip(col).take(span[1]).sum(); + let hgt: f32 = self.hgts.iter().skip(row).take(span[0]).sum(); + + GridBlock { + size: [width, hgt], + origin: [curr[0] + self.origin[0], curr[1] + self.origin[1]], + } + } +} + +pub struct GridBlock { + size: [f32; 2], + origin: [f32; 2], +} + +impl GridBlock { + fn pos(&self, x: f32, y: f32) -> [f32; 2] { + [self.origin[0] + x, self.origin[1] + y] + } + + pub fn build(&self, ui: &Ui, f: F) + where + F: FnOnce(&Ui, [f32; 2], [f32; 2]), + { + f(ui, self.size, self.origin); + } + + pub fn start_window<'a, Label: AsRef>( + &self, + ui: &'a Ui, + title: Label, + ) -> imgui::Window<'a, 'a, Label> { + ui.window(title) + .size(self.size, Condition::Always) + .position(self.origin, Condition::Always) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..a912485 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,102 @@ +use std::path::PathBuf; + +use log::*; + +use crate::data_loader::Data; +use crate::errors::*; +use crate::pg::app_type::AppType; +use crate::pg::{ModulePackage, Programs}; +use crate::utils::cache::Cache; +use crate::utils::resources::{RcGlRcFramebuffer, RcGlRcRenderbuffer, RcGlRcResource, GL}; +use glow::HasContext; +use glow::{NativeFramebuffer, NativeRenderbuffer}; +use imgui::*; +#[macro_use] +mod layout; +pub mod io; +mod state; +pub mod typ; +pub use layout::*; +pub use state::State; + +pub struct GUI { + state: State, + layout: layout::Layout, + renderer_size: [f32; 2], +} + +impl GUI { + pub fn new(state: State) -> Self { + GUI { + state, + layout: layout::Layout::new(), + renderer_size: [0.0, 0.0], + } + } + + pub fn apptype<'a, 'gl>(&'a mut self, gl: &'gl GL) -> AppType<'gl> { + self.state.app_type.init_apptype(gl) + } + + pub fn render<'b, 'gl>( + &mut self, + ui: &Ui, + context: &'b mut crate::pg::Context<'gl>, + app_type: &'b mut AppType<'gl>, + ) -> bool { + // Menu bar + self.menu_bar(ui, context, app_type); + let menu_bar_height = ui.frame_height(); + let display_size = ui.io().display_size; + + // Layout Size Reset + self.layout.set_origin([0.0, menu_bar_height]); + self.layout + .set_size([display_size[0], display_size[1] - menu_bar_height]); + + // Render + self.state + .app_type + .render(ui, context, &mut self.layout, app_type); + + true + } + + fn menu_bar<'b, 'gl>( + &self, + ui: &Ui, + context: &'b mut crate::pg::Context<'gl>, + app_type: &mut AppType<'gl>, + ) { + if let Some(top_menu) = ui.begin_main_menu_bar() { + if let Some(menu) = ui.begin_menu("File") { + if ui.menu_item("Open") { + match tinyfiledialogs::open_file_dialog("Open", "password.txt", None) { + Some(file) => match context.load_data(file.clone()) { + Ok(data) => { + info!("Data loaded: {:?}", file); + app_type.deal_with_cursor(data); + } + Err(e) => { + error!("Failed to get or insert data: {:?}", e); + } + }, + None => { + error!("No file selected"); + } + } + } + + if ui.menu_item("Save") { + println!("Save clicked"); + } + + if ui.menu_item("Exit") {} + + menu.end(); + } + + top_menu.end(); + } + } +} diff --git a/src/ui/state.rs b/src/ui/state.rs new file mode 100644 index 0000000..82473c0 --- /dev/null +++ b/src/ui/state.rs @@ -0,0 +1,60 @@ +use super::typ::{LayoutMod, MainLoad}; +use crate::pg::ModulePackage; +use crate::utils::resources::GL; + +macro_rules! uit { + ($s:ident,$({$b: tt => $exp:ident($($gl: ident),+)},)*) => { + { + match $s { + $( + Self::$b(v) => v.$exp($($gl),+), + )* + _ => { + panic!("UiType not found"); + } + } + } + } +} + +#[derive(Default)] +pub struct State { + pub example: u32, + pub i32_value: i32, + pub u8_value: u8, + pub f32_value: f32, + pub f64_formatted: f64, + pub array: [u8; 4], + pub render_closable: bool, + pub notify_text: &'static str, + pub app_type: UiType, +} + +pub enum UiType { + Mainload(MainLoad), + Other, +} + +impl UiType { + pub(super) fn render<'b, 'gl: 'b>( + &mut self, + ui: &imgui::Ui, + context: &mut crate::pg::Context<'gl>, + layout: &mut crate::ui::layout::Layout, + app_typ: &mut crate::pg::app_type::AppType<'gl>, + ) { + let gl = context.gl; + let app_typ = app_typ.into(); + uit!(self, {Mainload => render(ui,context,layout,app_typ)},); + } + + pub fn init_apptype<'gl>(&mut self, gl: &'gl GL) -> crate::pg::app_type::AppType<'gl> { + uit!(self, {Mainload => init(gl)},).into() + } +} + +impl Default for UiType { + fn default() -> Self { + Self::Mainload(MainLoad::new()) + } +} diff --git a/src/ui/typ/main_load.rs b/src/ui/typ/main_load.rs new file mode 100644 index 0000000..a703d1d --- /dev/null +++ b/src/ui/typ/main_load.rs @@ -0,0 +1,158 @@ +use super::{CameraOP, Layout, LayoutAppType, LayoutMod, Size}; +use crate::graphics::threed::ThreeD; +use crate::graphics::{AttaWithProgram, AttachWithMouse, MouseState}; +use crate::pg::ModulePackage; +use crate::pg::{_ModulePackage, app_type::ViewPort}; +use crate::ui::io::IO; +use crate::utils::resources::GL; +use glow::HasContext; +use imgui::{Condition, Ui, WindowFlags}; + +pub struct MainLoad {} + +impl LayoutMod for MainLoad { + type AppType<'gl> = MainLoadTyp<'gl>; + fn render<'b, 'gl>( + &mut self, + ui: &imgui::Ui, + context: &mut crate::pg::Context<'gl>, + layout: &mut Layout, + apptyp: &mut Self::AppType<'gl>, + ) where + 'gl: 'b, + { + let mut grid = layout.grid(2, 1, [0.0, 0.0]); + grid.set_col_size(&[size!(20.0%), size!(fill)]); + grid.set_row_size(&[size!(fill)]); + let io = ui.io(); + + let window_flag = WindowFlags::empty() + | WindowFlags::NO_MOVE + | WindowFlags::NO_RESIZE + | WindowFlags::NO_COLLAPSE + | WindowFlags::NO_TITLE_BAR; + + grid.with(0, 0, [1, 1]) + .start_window(ui, "MainLoad") + .build(|| { + for package in apptyp.packages.iter_mut() { + package.ui_build(ui); + } + }); + + grid.with(0, 1, [1, 1]) + .build(ui, |ui, window_size, origin| { + let window_size = [window_size[0], window_size[0] / 16.0 * 9.0]; + ui.window("Renderer") + .size(window_size, Condition::Always) + .flags(window_flag) + .position(origin, Condition::Always) + .build(|| { + let gl = context.gl.gl_rc(); + let scale = ui.io().display_framebuffer_scale; + let pos = ui.cursor_screen_pos(); + let dsp_size = ui.io().display_size; + + let content_size = ui.content_region_avail(); + apptyp.main_viewport.set_size(content_size); + + // ui.set_cursor_screen_pos([pos[0] + size[0], pos[1] + size[1]]); + let draws = ui.get_window_draw_list(); + let fbo = apptyp.main_viewport.fbo(); + let fbo = fbo.native(); + + draws + .add_callback({ + move || unsafe { + let x = pos[0] * scale[0]; + let y = (dsp_size[1] - pos[1]) * scale[1]; + + // gl.enable(glow::SCISSOR_TEST); + // gl.scissor(0, 0, size[0] as i32, size[1] as i32); + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(fbo)); + gl.blit_framebuffer( + 0, + 0, + (content_size[0]) as i32, + (content_size[1]) as i32, + x as i32, + y as i32, + (x + content_size[0] * scale[0]) as i32, + (y - content_size[1] * scale[1]) as i32, + glow::COLOR_BUFFER_BIT, + glow::NEAREST, + ); + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None); + } + }) + .build(); + + if ui.is_window_hovered() { + let cio = IO::new(ui); + apptyp.deal_with_camera(&cio); + } + }); + }); + } + + fn init<'gl>(&mut self, gl: &'gl GL) -> Self::AppType<'gl> { + let typ = MainLoadTyp { + main_viewport: ViewPort::new(gl), + packages: Vec::new(), + camera_op: ThreeD::default(), + }; + typ + } +} + +impl MainLoad { + pub fn new() -> Self { + MainLoad {} + } + + fn render_window(&mut self, ui: &Ui, context: &mut crate::pg::Context) {} +} + +pub struct MainLoadTyp<'gl> { + main_viewport: ViewPort, + camera_op: ThreeD, + packages: Vec>, +} + +impl CameraOP for MouseState { + fn from_context(context: &IO) -> Self { + if context.mouse.is_dragging { + Self::Drag { + from: context.mouse.position, + delta: context.mouse.drag_delta.unwrap(), + } + } else { + Self::None + } + } +} + +impl<'gl> LayoutAppType<'gl> for MainLoadTyp<'gl> { + type CameraOP = MouseState; + fn deal_with_cursor(&mut self, package: ModulePackage<'gl>) { + self.packages.push(package); + } + + fn draw_program( + &mut self, + gl: &glow::Context, + programs: &mut crate::pg::Programs<'gl>, + ) -> crate::errors::Result<()> { + self.main_viewport.bind(gl); + for package in self.packages.iter_mut() { + programs.draw_modules(package, &self.camera_op)?; + } + self.main_viewport.unbind(); + Ok(()) + } + + fn _deal_with_camera(&mut self, camera_op: Self::CameraOP) { + // println!("Camera Op: {:?}", camera_op); + self.camera_op.attach_with_mouse(&camera_op); + } +} diff --git a/src/ui/typ/mod.rs b/src/ui/typ/mod.rs new file mode 100644 index 0000000..8ddebc5 --- /dev/null +++ b/src/ui/typ/mod.rs @@ -0,0 +1,49 @@ +use std::fmt::Debug; + +use super::io::IO; +use super::layout::{Layout, Size}; +mod main_load; +use crate::pg::app_type::AppType; +use crate::pg::{self, ModuleCursor, ModulePackage, Programs}; +use crate::utils::resources::GL; +pub use main_load::*; + +pub trait LayoutMod { + type AppType<'gl>: Into> + LayoutAppType<'gl>; + fn render<'dt, 'b, 'gl>( + &mut self, + ui: &imgui::Ui, + context: &mut crate::pg::Context<'gl>, + layout: &mut Layout, + apptyp: &mut Self::AppType<'gl>, + ) where + 'gl: 'b; + fn init<'dt, 'gl>(&mut self, gl: &'gl GL) -> Self::AppType<'gl>; +} + +pub trait LayoutAppType<'gl> { + type CameraOP: CameraOP + Debug; + fn deal_with_cursor(&mut self, package: ModulePackage<'gl>); + + fn deal_with_camera(&mut self, camera_op: &IO) { + self._deal_with_camera(CameraOP::from_context(camera_op)); + } + + fn _deal_with_camera(&mut self, camera_op: Self::CameraOP); + + fn draw_program( + &mut self, + gl: &glow::Context, + programs: &mut Programs<'gl>, + ) -> crate::errors::Result<()>; +} + +pub trait CameraOP { + fn from_context(context: &IO) -> Self; +} + +impl CameraOP for () { + fn from_context(context: &IO) -> Self { + () + } +} diff --git a/src/utils/cache.rs b/src/utils/cache.rs new file mode 100644 index 0000000..9149d3e --- /dev/null +++ b/src/utils/cache.rs @@ -0,0 +1,61 @@ +use crate::errors::*; +use std::{cell::RefCell, hash::Hash, num::NonZeroUsize, rc::Rc}; + +use lru::LruCache; + +use crate::data_loader::Data; +pub struct Cache { + cache: LruCache, +} + +impl Cache { + pub fn new() -> Self { + Self { + cache: LruCache::new(NonZeroUsize::new(10).unwrap()), + } + } + pub fn get(&mut self, key: &K) -> Option<&V> { + self.cache.get(key) + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.cache.get_mut(key) + } +} + +impl Cache +where + K: AsRef + Hash + Eq + Clone, + V: CacheData, +{ + pub fn insert(&mut self, key: K) -> Result<()> { + let value = V::from_path(&key)?; + self.cache.put(key, value); + Ok(()) + } + + pub fn get_or_insert(&mut self, key: K) -> Result<&V> { + if !self.cache.contains(&key) { + self.insert(key.clone())?; + } + Ok(self.cache.get(&key).unwrap()) + } +} + +pub type CachedData = Rc>; + +pub trait CacheData: Sized { + fn from_path(path: impl AsRef) -> Result; +} + +impl CacheData for Data { + fn from_path(path: impl AsRef) -> Result { + Data::from_path(path) + } +} + +impl CacheData for CachedData { + fn from_path(path: impl AsRef) -> Result { + Ok(Rc::new(RefCell::new(T::from_path(path)?))) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 23b5d6f..5dfe3f1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,9 @@ mod parser; +pub mod resources; use include_dir::{include_dir, Dir}; pub use parser::*; use std::{num::NonZeroU32, path::Path}; +pub mod cache; pub mod geo_tools; use glow::HasContext; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 77c7363..109eb01 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -514,6 +514,9 @@ impl Code { Code::PreprocessorDirective(Rc::new(RefCell::new(pre)), Box::new(inner)) }, ), + map(terminated(parse_struct, pair(space0, char(';'))), |v| { + Code::Struct(Rc::new(RefCell::new(v))) + }), map(parse_macro, |v| Code::Macro(Rc::new(RefCell::new(v)))), map(include, |v| Code::Include(Rc::new(RefCell::new(v)))), map(function, |v| Code::Function(Rc::new(RefCell::new(v)))), @@ -1037,6 +1040,7 @@ fn parse_struct_member_access(input: &str) -> IResult<&str, Expression> { // parse_struct_pointer_access, map(hook, Expression::Hook), parse_array_access, + parse_function_call, map(identifier, |v| Expression::Variable(v.to_string())), )), char('.'), @@ -1244,6 +1248,7 @@ pub struct SnippetCode { pub includes: Vec>, pub variables: Vec>, pub functions: Vec>, + pub structs: Vec>, pub preprocessor_directives: HashMap>, } @@ -1562,6 +1567,14 @@ impl SnippetCode { }) .collect(); + let structs = codes + .iter() + .filter_map(|v| match v { + Code::Struct(v) => Some(v.clone()), + _ => None, + }) + .collect(); + let mut preprocessor_directives = vec![]; for code in codes.into_iter() { @@ -1579,6 +1592,7 @@ impl SnippetCode { includes: includes, variables: variables, functions: functions, + structs: structs, preprocessor_directives: preprocessor_directives.into_iter().collect(), }) } @@ -1838,60 +1852,173 @@ impl SnippetCode { #[test] fn test() { let input = r#" - #include "transform/polar.glsl" + uniform sampler2D atlas_data; +uniform vec2 v_texCoord; - layout(points) in; - layout(triangle_strip, max_vertices = 4) out; +uniform vec4 uClipUV; +uniform vec2 uSdfUV; +uniform vec4 uSdfConfig; +uniform int uRepeat; +uniform int uMode; +uniform vec4 uShape; +uniform vec4 uRadius; +uniform vec4 uBorder; +uniform vec4 uStroke; +uniform vec4 uFill; - // conf: Elevation, Range, Resolution, 0.0 - uniform vec4 conf; +float getUVScale(vec2 sdfUV) { + float dx = dFdx(sdfUV.x); + float dy = dFdy(sdfUV.y); + return (sqrt(dx * dx + dy * dy) + sqrt(dy * dy + dx * dx)) * 0.5; +} - out float eth; - out float rng; +struct SDF { + float outer; + float inner; +}; - out vec4 vrange; +float getBoxCorner(vec2 xy, vec2 offset) { + vec2 clip = xy - offset; + return min(clip.x, clip.y); +} +float getCircleCorner(vec2 xy, float r) { + vec2 clip = max(vec2(0.0), r - xy); + float neg = min(0.0, max(-xy.x, -xy.y) + r); + return r - length(clip) - neg; +} - void main() { +SDF getBoxSDF(vec2 box, vec2 uv, float scale) { + vec2 nearest = round(uv); + vec2 xy = (abs(uv - 0.5) - 0.5) * box; + float d1 = max(xy.x, xy.y); + float outer = -d1; + float o = outer / scale; + return SDF(o, o); +} - // Resolution - vec4 reso = vec4(conf.x/2.0, conf.y/2.0, 0.0, 0.0); - float c = cos(reso.x); +SDF getBorderBoxSDF(vec2 box, vec4 border, vec2 uv, float scale) { + vec2 nearest = round(uv); + vec2 xy = (abs(uv - 0.5) - 0.5) * box; + float d1 = max(xy.x, xy.y); + float outer = -d1; - vec4 po = gl_in[0].gl_Position; + vec4 flipUV = vec4(uv, 1.0 - uv); + vec2 tl = flipUV.xy * box; + vec2 tr = flipUV.zy * box; + vec2 bl = flipUV.xw * box; + vec2 br = flipUV.zw * box; - vrange = vec4(po.x - reso.x, po.y - reso.y, po.x + reso.x, po.y + reso.y); + vec4 i4 = vec4( + getBoxCorner(tl, vec2(border.x, border.y)), + getBoxCorner(tr, vec2(border.z, border.y)), + getBoxCorner(bl, vec2(border.x, border.w)), + getBoxCorner(br, vec2(border.z, border.w)) + ); + float inner = min(min(i4.x, i4.y), min(i4.z, i4.w)); - gl_Position = po - reso; - eth = gl_Position.x; - rng = gl_Position.y; - gl_Position = ; - EmitVertex(); + return SDF(outer / scale, inner / scale); +} - gl_Position = po + vec4(-reso.x, reso.y, 0.0, 0.0); - gl_Position.y = gl_Position.y / c; - eth = gl_Position.x; - rng = gl_Position.y; - gl_Position = ; - EmitVertex(); +SDF getRoundedBorderBoxSDF(vec2 box, vec4 border, vec4 radius, vec2 uv, float scale) { + vec4 flipUV = vec4(uv, 1.0 - uv); + vec2 tl = flipUV.xy * box; + vec2 tr = flipUV.zy * box; + vec2 bl = flipUV.xw * box; + vec2 br = flipUV.zw * box; - gl_Position = po + reso; - gl_Position.y = gl_Position.y / c; - eth = gl_Position.x; - rng = gl_Position.y; - gl_Position = ; - EmitVertex(); + vec4 o4 = vec4( + getCircleCorner(tl, radius.x), + getCircleCorner(tr, radius.y), + getCircleCorner(bl, radius.w), + getCircleCorner(br, radius.z) + ); + float outer = min(min(o4.x, o4.y), min(o4.z, o4.w)); - gl_Position = po + vec4(reso.x, -reso.y, 0.0, 0.0); - eth = gl_Position.x; - rng = gl_Position.y; - gl_Position = ; - EmitVertex(); + tl = tl - border.xy; + tr = tl - border.zy; + bl = tl - border.xw; + br = tl - border.zw; - EndPrimitive(); + vec4 innerRadius = max( + vec4(0.0), + radius - vec4( + max(border.x, border.y), + max(border.z, border.y), + max(border.x, border.w), + max(border.z, border.w) + ) + ); + vec4 i4 = vec4( + getCircleCorner(tl, innerRadius.x), + getCircleCorner(tr, innerRadius.y), + getCircleCorner(bl, innerRadius.w), + getCircleCorner(br, innerRadius.z) + ); + float inner = min(min(i4.x, i4.y), min(i4.z, i4.w)); + + return SDF(outer / scale, inner / scale); +} + +vec4 getTexture(vec2 uv) { + return texture(uTexture, uv); +} + +vec4 getMask(vec4 color, vec2 uv, vec2 st) { + return color; // 默认不修改颜色 +} + +out vec4 fragColor; + +void main() { + vec4 fillColor = uFill; + vec4 strokeColor = uStroke; + + float scale = getUVScale(uSdfUV); + + vec4 texture = getTexture(uTextureUV); + float sdfRaw = 0.0; + float mark = 0.0; + + if (uMode == -1 || uMode == -2) { + float sdfRadius = uSdfConfig.x; + float expand = uBorder.x; + float bleed = uBorder.y; + + float d = (texture.a - 0.75) * sdfRadius; + float s = (d + expand / uSdfConfig.y) / scale + 0.5 + bleed; + float sdf = s; // Assuming SDF returns a single float, adjust as necessary + + if (uMode == -2) { + fillColor = vec4(texture.rgb, fillColor.a); + } + } else { + // More complex logic for other modes + if (texture.a > 0.0) { + if (((uRepeat == 0 || uRepeat == 1) && (uTextureUV.x < 0.0 || uTextureUV.x > 1.0)) || + ((uRepeat == 0 || uRepeat == 2) && (uTextureUV.y < 0.0 || uTextureUV.y > 1.0))) { + texture.a = 0.0; + } + + if (texture.a > 0.0) { + fillColor = vec4(mix(fillColor.rgb * (1.0 - texture.a), texture.rgb, texture.a), mix(fillColor.a, 1.0, texture.a)); + } + } else { + fillColor = fillColor; // Use premultiply function if needed + } } + if (gl_FragCoord.x < uClipUV.x || gl_FragCoord.y < uClipUV.y || gl_FragCoord.x > uClipUV.z || gl_FragCoord.y > uClipUV.w) { + discard; + } + + // Compute mask based on SDF + float mask = clamp(sdf, 0.0, 1.0); + + // Final color blending logic here + fragColor = vec4(fillColor.rgb + mark, fillColor.a * mask + mark); // Simplified for clarity +} "#; let result = CodeBlock::new(input).unwrap(); @@ -1974,15 +2101,49 @@ fn test_shared_variable() { #[test] fn test_function() { let input = r#" - float norm_rad(float rad) { - float result = mod(angle, M_2_PI); - if(result < 0.0) { - result = M_2_PI + result; - } - return result; + SDF getRoundedBorderBoxSDF(vec2 box, vec4 border, vec4 radius, vec2 uv, float scale) { + vec4 flipUV = vec4(uv, 1.0 - uv); + vec2 tl = flipUV.xy * box; + vec2 tr = flipUV.zy * box; + vec2 bl = flipUV.xw * box; + vec2 br = flipUV.zw * box; + + vec4 o4 = vec4( + getCircleCorner(tl, radius.x), + getCircleCorner(tr, radius.y), + getCircleCorner(bl, radius.w), + getCircleCorner(br, radius.z) + ); + float outer = min(min(o4.x, o4.y), min(o4.z, o4.w)); + + tl = tl - border.xy; + tr = tl - border.zy; + bl = tl - border.xw; + br = tl - border.zw; + + vec4 innerRadius = max( + vec4(0.0), + radius - vec4( + max(border.x, border.y), + max(border.z, border.y), + max(border.x, border.w), + max(border.z, border.w) + ) + ); + + vec4 i4 = vec4( + getCircleCorner(tl, innerRadius.x), + getCircleCorner(tr, innerRadius.y), + getCircleCorner(bl, innerRadius.w), + getCircleCorner(br, innerRadius.z) + ); + float inner = min(min(i4.x, i4.y), min(i4.z, i4.w)); + + return SDF(outer / scale, inner / scale); } - + + "#; let result = ws(function)(input).unwrap(); diff --git a/src/utils/resources.rs b/src/utils/resources.rs new file mode 100644 index 0000000..3950efa --- /dev/null +++ b/src/utils/resources.rs @@ -0,0 +1,314 @@ +use glow::{ + HasContext, NativeBuffer, NativeFramebuffer, NativeRenderbuffer, NativeTexture, + NativeVertexArray, +}; +use log::info; +use std::{borrow::Borrow, ops::Deref, rc::Rc}; + +pub type RcGlFramebuffer<'a> = RcGlResource<'a, NativeFramebuffer>; +pub type RcGlRenderbuffer<'a> = RcGlResource<'a, NativeRenderbuffer>; +pub type RcGlRcFramebuffer = RcGlRcResource; +pub type RcGlRcRenderbuffer = RcGlRcResource; +pub type RcGlTexture<'a> = RcGlResource<'a, NativeTexture>; +pub type RcGlVertexArray<'a> = RcGlResource<'a, NativeVertexArray>; +pub type RcGlBuffer<'a> = RcGlResource<'a, NativeBuffer>; + +// pub type RcGlResource<'a, T> = Rc>; +pub trait ManagedResource { + fn bind(&self, target: u32); + fn unbind(&self, target: u32); +} +#[derive(Debug, Clone)] +pub struct RcGlResource<'a, T: Resource>(Rc>); + +#[derive(Clone)] +pub struct RcGlRcResource(Rc>); + +impl<'a, T: Resource> RcGlResource<'a, T> { + pub fn new(gl: &'a glow::Context, resource: T) -> Self { + Self(Rc::new(GlResource::new(resource, gl))) + } + + pub fn native(&self) -> T { + self.0.resource.clone() + } +} + +impl<'a, T> From<&'a GlRcResource> for GlResource<'a, T> +where + T: Resource, +{ + fn from(value: &'a GlRcResource) -> Self { + GlResource::new(value.resource.clone(), &value.gl) + } +} + +impl<'a, T: Resource> Deref for RcGlResource<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl RcGlRcResource { + pub fn new(gl: Rc, resource: T) -> Self { + Self(Rc::new(GlRcResource::new(resource, gl))) + } + + pub fn native(&self) -> T { + self.0.resource.clone() + } +} + +impl Deref for RcGlRcResource { + type Target = T; + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +trait Resource: Clone + std::fmt::Debug { + fn drop_self(&self, gl: &glow::Context); + + fn create(gl: &glow::Context) -> Self; + + fn bind(&self, gl: &glow::Context, target: u32); + + fn unbind(&self, gl: &glow::Context, target: u32); +} + +impl Resource for NativeVertexArray { + fn drop_self(&self, gl: &glow::Context) { + unsafe { + gl.delete_vertex_array(*self); + } + } + + fn create(gl: &glow::Context) -> Self { + unsafe { gl.create_vertex_array().unwrap() } + } + + fn bind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_vertex_array(Some(*self)); + } + } + + fn unbind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_vertex_array(Some(*self)); + } + } +} + +impl Resource for NativeBuffer { + fn drop_self(&self, gl: &glow::Context) { + unsafe { + gl.delete_buffer(*self); + } + } + + fn create(gl: &glow::Context) -> Self { + unsafe { gl.create_buffer().unwrap() } + } + + fn bind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_buffer(target, Some(*self)); + } + } + + fn unbind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_buffer(target, None); + } + } +} + +impl Resource for NativeFramebuffer { + fn drop_self(&self, gl: &glow::Context) { + unsafe { + gl.delete_framebuffer(*self); + } + } + + fn create(gl: &glow::Context) -> Self { + unsafe { gl.create_framebuffer().unwrap() } + } + fn bind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_framebuffer(target, Some(*self)); + } + } + fn unbind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_framebuffer(target, None); + } + } +} + +impl Resource for NativeRenderbuffer { + fn drop_self(&self, gl: &glow::Context) { + unsafe { + gl.delete_renderbuffer(*self); + } + } + + fn create(gl: &glow::Context) -> Self { + unsafe { gl.create_renderbuffer().unwrap() } + } + + fn bind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_renderbuffer(target, Some(*self)); + } + } + fn unbind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_renderbuffer(target, None); + } + } +} + +impl Resource for NativeTexture { + fn drop_self(&self, gl: &glow::Context) { + unsafe { + gl.delete_texture(*self); + } + } + + fn create(gl: &glow::Context) -> Self { + unsafe { gl.create_texture().unwrap() } + } + + fn bind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_texture(target, Some(*self)); + } + } + + fn unbind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_texture(target, None); + } + } +} + +#[derive(Debug)] +struct GlResource<'a, T: Resource> { + gl: &'a glow::Context, + resource: T, +} + +struct GlRcResource { + gl: Rc, + resource: T, +} +impl Deref for GlRcResource { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.resource + } +} + +impl<'a, T> AsRef> for RcGlResource<'a, T> +where + T: Resource, +{ + fn as_ref(&self) -> &GlResource<'a, T> { + &self.0 + } +} + +impl GlRcResource { + pub fn new(resource: T, gl: Rc) -> Self { + Self { resource, gl } + } +} + +impl Drop for GlRcResource { + fn drop(&mut self) { + self.resource.drop_self(&*self.gl); + info!("Dropping resource: {:?}", self.resource); + } +} + +impl Deref for GlResource<'_, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.resource + } +} + +impl<'a, T: Resource> GlResource<'a, T> { + pub fn new(resource: T, gl: &'a glow::Context) -> Self { + Self { resource, gl } + } +} + +impl Drop for GlResource<'_, T> { + fn drop(&mut self) { + self.resource.drop_self(self.gl); + info!("Dropping resource: {:?}", self.resource); + } +} + +impl<'a> RcGlResource<'a, NativeTexture> { + pub fn native2imguiid(&self) -> imgui::TextureId { + imgui::TextureId::new(self.native().0.get() as usize) + } +} + +#[derive(Debug, Clone)] +pub struct GL { + gl: Rc, +} + +impl GL { + pub fn new(gl: Rc) -> Self { + Self { gl } + } + pub fn gl<'a>(&'a self) -> &'a glow::Context { + &self.gl + } + + pub fn gl_rc(&self) -> Rc { + Rc::clone(&self.gl) + } + + pub fn create_resource<'a, T: Resource>(&'a self) -> RcGlResource<'a, T> { + RcGlResource::new(self.gl(), T::create(self.gl())) + } + + pub fn create_resource_rc(&self) -> RcGlRcResource { + RcGlRcResource::new(self.gl_rc(), T::create(self.gl())) + } +} + +impl Deref for GL { + type Target = glow::Context; + + fn deref(&self) -> &Self::Target { + &self.gl + } +} + +impl ManagedResource for RcGlResource<'_, T> { + fn bind(&self, target: u32) { + self.0.resource.bind(self.0.gl, target); + } + + fn unbind(&self, target: u32) { + self.0.resource.unbind(&self.0.gl, target); + } +} + +impl ManagedResource for RcGlRcResource { + fn bind(&self, target: u32) { + self.0.resource.bind(&self.0.gl, target); + } + + fn unbind(&self, target: u32) { + self.0.resource.unbind(&self.0.gl, target); + } +} diff --git a/test.png b/test.png new file mode 100644 index 0000000..f42ca9c Binary files /dev/null and b/test.png differ