diff --git a/.builds/build.yml b/.builds/build.yml index 51bf9e8e..17a1ad96 100644 --- a/.builds/build.yml +++ b/.builds/build.yml @@ -4,7 +4,7 @@ sources: tasks: - install: | sudo pacman -Syu --noconfirm - sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon + sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon cmake rustup toolchain install stable - build: | cd jay diff --git a/.builds/test.yml b/.builds/test.yml index 0c104a85..4f15cbe1 100644 --- a/.builds/test.yml +++ b/.builds/test.yml @@ -4,7 +4,7 @@ sources: tasks: - install: | sudo pacman -Syu --noconfirm - sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor + sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake rustup toolchain install stable - configure: | sudo rmmod bochs diff --git a/.builds/unit-tests.yml b/.builds/unit-tests.yml index 596570b9..e9a98dc4 100644 --- a/.builds/unit-tests.yml +++ b/.builds/unit-tests.yml @@ -4,7 +4,7 @@ sources: tasks: - install: | sudo pacman -Syu --noconfirm - sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor + sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake rustup toolchain install stable - test: | cd jay diff --git a/Cargo.lock b/Cargo.lock index 9f2e16cc..398f0d7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,14 +19,15 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if 1.0.0", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -62,9 +63,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -76,43 +77,58 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] [[package]] name = "autocfg" @@ -162,15 +178,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bstr" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", @@ -211,23 +227,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.0", ] [[package]] name = "clap" -version = "4.4.6" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -235,9 +251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -248,30 +264,39 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.3" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7" +checksum = "abb745187d7f4d76267b37485a65e0149edd0e91a4cfcdd3f27524ad86cee9f3" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "cmake" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] [[package]] name = "colorchoice" @@ -281,9 +306,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "default-config" @@ -313,47 +338,53 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-macro", @@ -365,9 +396,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if 1.0.0", "libc", @@ -376,9 +407,45 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.4.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-ash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2424bc9be88170e1a56e57c25d3d0e2dfdd22e8f328e892786aeb4da1415732" +dependencies = [ + "ash", + "gpu-alloc-types", + "tinyvec", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.4.2", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -394,9 +461,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -415,6 +482,16 @@ dependencies = [ "cc", ] +[[package]] +name = "indexmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "isnt" version = "0.1.0" @@ -428,9 +505,11 @@ dependencies = [ "ahash", "algorithms", "anyhow", + "arrayvec", + "ash", "backtrace", "bincode", - "bitflags 2.4.1", + "bitflags 2.4.2", "bstr", "byteorder", "chrono", @@ -439,10 +518,13 @@ dependencies = [ "default-config", "dirs", "futures-util", + "gpu-alloc", + "gpu-alloc-ash", "humantime", + "indexmap", "isnt", "jay-config", - "libloading", + "libloading 0.8.1", "log", "num-derive", "num-traits", @@ -451,6 +533,7 @@ dependencies = [ "pin-project", "rand", "repc", + "shaderc", "smallvec", "thiserror", "uapi", @@ -466,9 +549,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -481,9 +564,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] [[package]] name = "libloading" @@ -492,14 +585,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", ] [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -519,15 +623,15 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -540,7 +644,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -554,18 +658,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "option-ext" @@ -591,29 +695,29 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.4.1", + "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -636,18 +740,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -682,15 +786,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -702,20 +797,20 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -725,9 +820,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -755,6 +850,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e110b7d5a1335c2e801176c42a626a905c23eecdee104d9bdfbd6ea5f0b8368" +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -763,15 +867,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.20" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -782,22 +886,43 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", +] + +[[package]] +name = "shaderc" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e07913ada18607bb60d12431cbe3358d3bbebbe95948e1618851dc01e63b7b" +dependencies = [ + "libc", + "shaderc-sys", +] + +[[package]] +name = "shaderc-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73120d240fe22196300f39ca8547ca2d014960f27b19b47b21288b396272f7f7" +dependencies = [ + "cmake", + "libc", + "roxmltree", ] [[package]] @@ -811,9 +936,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "strsim" @@ -834,9 +959,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -850,29 +975,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "uapi" version = "0.2.10" @@ -931,9 +1071,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -941,24 +1081,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -966,30 +1106,52 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.0", ] [[package]] @@ -998,7 +1160,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1007,13 +1178,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1022,38 +1208,106 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/Cargo.toml b/Cargo.toml index 1077ef70..cfad0d4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,37 +15,43 @@ panic = "abort" [dependencies] uapi = "0.2.10" -thiserror = "1.0.30" -ahash = "0.8.2" -log = { version = "0.4.16", features = ["std"] } -futures-util = "0.3.19" -num-traits = "0.2.14" +thiserror = "1.0.56" +ahash = "0.8.7" +log = { version = "0.4.20", features = ["std"] } +futures-util = "0.3.30" +num-traits = "0.2.17" num-derive = "0.4.1" -bitflags = "2.4.1" +bitflags = "2.4.2" libloading = "0.8.1" -bstr = { version = "1.1.0", default-features = false, features = ["std"] } +bstr = { version = "1.9.0", default-features = false, features = ["std"] } isnt = "0.1.0" -once_cell = "1.9.0" -rand = "0.8.4" -smallvec = { version = "1.8.0", features = ["const_generics", "const_new", "union"] } -byteorder = "1.4.3" -bincode = "2.0.0-rc.1" +once_cell = "1.19.0" +rand = "0.8.5" +smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "union"] } +byteorder = "1.5.0" +bincode = "2.0.0-rc.3" jay-config = { path = "jay-config" } default-config = { path = "default-config" } algorithms = { path = "algorithms" } -pin-project = "1.0.10" -clap = { version = "4.0.29", features = ["derive", "wrap_help"] } -clap_complete = "4.0.6" +pin-project = "1.1.4" +clap = { version = "4.4.18", features = ["derive", "wrap_help"] } +clap_complete = "4.4.10" humantime = "2.1.0" dirs = "5.0.1" -backtrace = "0.3.64" -chrono = "0.4.19" +backtrace = "0.3.69" +chrono = "0.4.33" parking_lot = "0.12.1" +arrayvec = "0.7.4" +indexmap = "2.2.0" +ash = "0.37.3" +gpu-alloc = "0.6.0" +gpu-alloc-ash = "0.6.0" [build-dependencies] repc = "0.1.1" -anyhow = "1.0.52" -bstr = { version = "1.1.0", default-features = false, features = ["std"] } +anyhow = "1.0.79" +bstr = { version = "1.9.0", default-features = false, features = ["std"] } +shaderc = "0.8.3" #[profile.dev.build-override] #opt-level = 3 diff --git a/build/build.rs b/build/build.rs index 254ca1fc..85580ae8 100644 --- a/build/build.rs +++ b/build/build.rs @@ -25,6 +25,7 @@ use std::{ mod egl; mod enums; mod tokens; +mod vulkan; mod wire; mod wire_dbus; mod wire_xcon; @@ -47,6 +48,7 @@ fn main() -> anyhow::Result<()> { wire_xcon::main()?; enums::main()?; egl::main()?; + vulkan::main()?; println!("cargo:rerun-if-changed=build/build.rs"); Ok(()) diff --git a/build/vulkan.rs b/build/vulkan.rs new file mode 100644 index 00000000..69f41aab --- /dev/null +++ b/build/vulkan.rs @@ -0,0 +1,38 @@ +use { + crate::open, + anyhow::{bail, Context}, + std::{io::Write, path::Path}, +}; + +const ROOT: &str = "src/gfx_apis/vulkan/shaders"; + +pub fn main() -> anyhow::Result<()> { + println!("cargo:rerun-if-changed={}", ROOT); + for shader in std::fs::read_dir(ROOT)? { + let shader = shader?; + let name = shader.file_name().to_string_lossy().into_owned(); + compile_shader(&name).context(name)?; + } + Ok(()) +} + +fn compile_shader(name: &str) -> anyhow::Result<()> { + let stage = match Path::new(name) + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + { + "frag" => shaderc::ShaderKind::Fragment, + "vert" => shaderc::ShaderKind::Vertex, + n => bail!("Unknown shader stage {}", n), + }; + let src = std::fs::read_to_string(format!("{}/{}", ROOT, name))?; + let compiler = shaderc::Compiler::new().unwrap(); + let binary = compiler + .compile_into_spirv(&src, stage, name, "main", None) + .unwrap(); + let mut file = open(&format!("{}.spv", name))?; + file.write_all(binary.as_binary_u8())?; + file.flush()?; + Ok(()) +} diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 1a952540..8da2c089 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -15,7 +15,7 @@ use { timer::Timer, video::{ connector_type::{ConnectorType, CON_UNKNOWN}, - Connector, DrmDevice, Mode, + Connector, DrmDevice, GfxApi, Mode, }, Axis, Direction, ModifiedKeySym, PciId, Workspace, }, @@ -506,6 +506,10 @@ impl Client { self.send(&ClientMessage::MakeRenderDevice { device }); } + pub fn set_gfx_api(&self, device: Option, api: GfxApi) { + self.send(&ClientMessage::SetGfxApi { device, api }); + } + pub fn connector_connected(&self, connector: Connector) -> bool { let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); get_response!(res, false, ConnectorConnected { connected }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index fdf7041a..7becf1de 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -5,7 +5,7 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable, Color}, timer::Timer, - video::{connector_type::ConnectorType, Connector, DrmDevice}, + video::{connector_type::ConnectorType, Connector, DrmDevice, GfxApi}, Axis, Direction, PciId, Workspace, }, bincode::{BorrowDecode, Decode, Encode}, @@ -334,6 +334,10 @@ pub enum ClientMessage<'a> { device: InputDevice, enabled: bool, }, + SetGfxApi { + device: Option, + api: GfxApi, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 5f9d494b..6d89c33a 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -362,4 +362,30 @@ impl DrmDevice { pub fn make_render_device(self) { get!().make_render_device(self); } + + /// Sets the preferred graphics API for this device. + /// + /// If the API cannot be used, the compositor will try other APIs. + pub fn set_gfx_api(self, gfx_api: GfxApi) { + get!().set_gfx_api(Some(self), gfx_api); + } +} + +/// A graphics API. +#[non_exhaustive] +#[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum GfxApi { + OpenGl, + Vulkan, +} + +/// Sets the default graphics API. +/// +/// If the API cannot be used, the compositor will try other APIs. +/// +/// This setting can be overwritten per-device with [DrmDevice::set_gfx_api]. +/// +/// This call has no effect on devices that have already been initialized. +pub fn set_gfx_api(gfx_api: GfxApi) { + get!().set_gfx_api(None, gfx_api); } diff --git a/src/backend.rs b/src/backend.rs index 47b604ac..318d8c4b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -6,6 +6,7 @@ use { ifs::wl_seat::wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL}, video::drm::{ConnectorType, DrmError, DrmVersion}, }, + jay_config::video::GfxApi, std::{ any::Any, error::Error, @@ -217,6 +218,7 @@ pub enum InputEvent { pub enum DrmEvent { #[allow(dead_code)] Removed, + GfxApiChanged, } pub trait BackendDrmDevice { @@ -224,6 +226,8 @@ pub trait BackendDrmDevice { fn event(&self) -> Option; fn on_change(&self, cb: Rc); fn dev_t(&self) -> c::dev_t; - fn make_render_device(self: Rc); + fn make_render_device(&self); + fn set_gfx_api(&self, api: GfxApi); + fn gtx_api(&self) -> GfxApi; fn version(&self) -> Result; } diff --git a/src/backends/metal.rs b/src/backends/metal.rs index f827087c..b3c3a212 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -108,6 +108,14 @@ pub enum MetalError { DevicePauseSignalHandler(#[source] DbusError), #[error("Could not create device-resumed signal handler")] DeviceResumeSignalHandler(#[source] DbusError), + #[error("Device render context does not support required format {0}")] + MissingDevFormat(&'static str), + #[error("Render context does not support required format {0}")] + MissingRenderFormat(&'static str), + #[error("Device cannot scan out any buffers writable by its GFX API (format {0})")] + MissingDevModifier(&'static str), + #[error("Device GFX API cannot read any buffers writable by the render GFX API (format {0})")] + MissingRenderModifier(&'static str), } pub struct MetalBackend { diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 978a5c9f..5b977135 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -9,7 +9,6 @@ use { edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, gfx_api::{GfxContext, GfxFramebuffer, GfxTexture}, - gfx_apis::create_gfx_context, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, renderer::RenderResult, state::State, @@ -28,11 +27,13 @@ use { DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, }, gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING, GBM_BO_USE_SCANOUT}, - ModifiedFormat, INVALID_MODIFIER, + Modifier, INVALID_MODIFIER, }, }, ahash::{AHashMap, AHashSet}, bstr::{BString, ByteSlice}, + indexmap::{indexset, IndexSet}, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ffi::CString, @@ -74,7 +75,8 @@ pub struct MetalDrmDevice { pub cursor_height: u64, pub gbm: GbmDevice, pub handle_events: HandleEvents, - pub ctx: Rc, + pub ctx: CloneCell>, + pub on_change: OnChange, } impl BackendDrmDevice for MetalDrmDevice { @@ -83,19 +85,27 @@ impl BackendDrmDevice for MetalDrmDevice { } fn event(&self) -> Option { - None + self.on_change.events.pop() } - fn on_change(&self, _cb: Rc) { - // nothing + fn on_change(&self, cb: Rc) { + self.on_change.on_change.set(Some(cb)); } fn dev_t(&self) -> dev_t { self.devnum } - fn make_render_device(self: Rc) { - self.backend.make_render_device(&self, true); + fn make_render_device(&self) { + self.backend.make_render_device(&self, false); + } + + fn set_gfx_api(&self, api: GfxApi) { + self.backend.set_gfx_api(self, api) + } + + fn gtx_api(&self) -> GfxApi { + self.ctx.get().gfx.gfx_api() } fn version(&self) -> Result { @@ -161,8 +171,6 @@ pub struct MetalConnector { pub connector_id: ConnectorId, - pub events: SyncQueue, - pub buffers: CloneCell>>, pub next_buffer: NumCell, @@ -181,7 +189,7 @@ pub struct MetalConnector { pub crtc: CloneCell>>, - pub on_change: OnChange, + pub on_change: OnChange, pub present_trigger: AsyncEvent, @@ -270,12 +278,30 @@ impl Debug for ConnectorFutures { } } -#[derive(Default)] -pub struct OnChange { +pub struct OnChange { pub on_change: CloneCell>>, + pub events: SyncQueue, +} + +impl OnChange { + pub fn send_event(&self, event: T) { + self.events.push(event); + if let Some(cb) = self.on_change.get() { + cb(); + } + } +} + +impl Default for OnChange { + fn default() -> Self { + Self { + on_change: Default::default(), + events: Default::default(), + } + } } -impl Debug for OnChange { +impl Debug for OnChange { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.on_change.get() { None => f.write_str("None"), @@ -310,7 +336,8 @@ impl MetalConnector { }) as _), _ => None, }; - self.send_event(ConnectorEvent::HardwareCursor(hc)); + self.on_change + .send_event(ConnectorEvent::HardwareCursor(hc)); } fn connected(&self) -> bool { @@ -320,13 +347,6 @@ impl MetalConnector { && self.primary_plane.get().is_some() } - fn send_event(&self, event: ConnectorEvent) { - self.events.push(event); - if let Some(oc) = self.on_change.on_change.get() { - oc(); - } - } - pub fn schedule_present(&self) { self.present_trigger.trigger(); } @@ -360,17 +380,16 @@ impl MetalConnector { if let Some(node) = self.state.root.outputs.get(&self.connector_id) { let mut rr = self.render_result.borrow_mut(); let render_fb = buffer.render_fb(); - render_fb.render( + render_fb.render_node( &*node, &self.state, Some(node.global.pos.get()), - true, - &mut rr, + Some(&mut rr), node.preferred_scale.get(), !self.cursor_enabled.get(), ); if let Some(tex) = &buffer.dev_tex { - buffer.dev_fb.copy_texture(&self.state, tex, 0, 0, false); + buffer.dev_fb.copy_texture(tex, 0, 0); } for fr in rr.frame_requests.drain(..) { fr.send_done(); @@ -393,20 +412,21 @@ impl MetalConnector { let buffer = &buffers[self.cursor_front_buffer.get() % buffers.len()]; if swap_buffer { if let Some(tex) = &buffer.dev_tex { - buffer.dev_fb.copy_texture(&self.state, tex, 0, 0, true); + buffer.dev_fb.copy_texture(tex, 0, 0); } } + let (width, height) = buffer.dev_fb.size(); changes.change_object(plane.id, |c| { c.change(plane.fb_id, buffer.drm.id().0 as _); c.change(plane.crtc_id.id, crtc.id.0 as _); c.change(plane.crtc_x.id, self.cursor_x.get() as _); c.change(plane.crtc_y.id, self.cursor_y.get() as _); - c.change(plane.crtc_w.id, buffer.render_tex.width() as _); - c.change(plane.crtc_h.id, buffer.render_tex.height() as _); + c.change(plane.crtc_w.id, width as _); + c.change(plane.crtc_h.id, height as _); c.change(plane.src_x.id, 0); c.change(plane.src_y.id, 0); - c.change(plane.src_w.id, (buffer.render_tex.width() as u64) << 16); - c.change(plane.src_h.id, (buffer.render_tex.height() as u64) << 16); + c.change(plane.src_w.id, (width as u64) << 16); + c.change(plane.src_h.id, (height as u64) << 16); }); } else { changes.change_object(plane.id, |c| { @@ -444,7 +464,7 @@ impl Connector for MetalConnector { } fn event(&self) -> Option { - self.events.pop() + self.on_change.events.pop() } fn on_change(&self, cb: Rc) { @@ -506,6 +526,12 @@ pub enum PlaneType { Cursor, } +#[derive(Debug)] +pub struct PlaneFormat { + _format: &'static Format, + modifiers: IndexSet, +} + #[derive(Debug)] pub struct MetalPlane { pub id: DrmPlane, @@ -514,7 +540,7 @@ pub struct MetalPlane { pub ty: PlaneType, pub possible_crtcs: u32, - pub formats: AHashMap, + pub formats: AHashMap, pub assigned: Cell, @@ -570,7 +596,6 @@ fn create_connector( dev: dev.clone(), backend: backend.clone(), connector_id: backend.state.connector_ids.next(), - events: Default::default(), buffers: Default::default(), next_buffer: Default::default(), enabled: Cell::new(true), @@ -756,19 +781,36 @@ fn create_crtc( fn create_plane(plane: DrmPlane, master: &Rc) -> Result { let info = master.get_plane_info(plane)?; + let props = collect_properties(master, plane)?; let mut formats = AHashMap::new(); - for format in info.format_types { - if let Some(f) = crate::format::formats().get(&format) { - formats.insert(format, *f); - } else { - // log::warn!( - // "{:?} supports unknown format '{:?}'", - // plane, - // crate::format::debug(format) - // ); + if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { + for format in master.get_in_formats(*v as _)? { + if format.modifiers.is_empty() { + continue; + } + if let Some(f) = crate::format::formats().get(&format.format) { + formats.insert( + format.format, + PlaneFormat { + _format: f, + modifiers: format.modifiers, + }, + ); + } + } + } else { + for format in info.format_types { + if let Some(f) = crate::format::formats().get(&format) { + formats.insert( + format, + PlaneFormat { + _format: f, + modifiers: indexset![INVALID_MODIFIER], + }, + ); + } } } - let props = collect_properties(master, plane)?; let ty = match props.props.get(b"type".as_bstr()) { Some((def, val)) => match &def.ty { DrmPropertyType::Enum { values, .. } => 'ty: { @@ -886,7 +928,7 @@ impl MetalBackend { if let Some(r) = ctx .gfx .reset_status() - .or_else(|| dev.ctx.gfx.reset_status()) + .or_else(|| dev.ctx.get().gfx.reset_status()) { fatal!("EGL context has been reset: {:?}", r); } @@ -964,9 +1006,9 @@ impl MetalBackend { dev.futures.remove(&c); if let Some(c) = dev.connectors.remove(&c) { if c.connect_sent.get() { - c.send_event(ConnectorEvent::Disconnected); + c.on_change.send_event(ConnectorEvent::Disconnected); } - c.send_event(ConnectorEvent::Removed); + c.on_change.send_event(ConnectorEvent::Removed); } } let mut preserve = Preserve::default(); @@ -988,7 +1030,7 @@ impl MetalBackend { || old.connection != ConnectorStatus::Connected || !old.is_same_monitor(&dd) { - c.send_event(ConnectorEvent::Disconnected); + c.on_change.send_event(ConnectorEvent::Disconnected); c.connect_sent.set(false); } else if preserve_any { preserve.connectors.insert(c.id); @@ -1030,15 +1072,17 @@ impl MetalBackend { modes.push(mode); } } - connector.send_event(ConnectorEvent::Connected(MonitorInfo { - modes, - manufacturer: dd.monitor_manufacturer.clone(), - product: dd.monitor_name.clone(), - serial_number: dd.monitor_serial_number.clone(), - initial_mode: dd.mode.clone().unwrap().to_backend(), - width_mm: dd.mm_width as _, - height_mm: dd.mm_height as _, - })); + connector + .on_change + .send_event(ConnectorEvent::Connected(MonitorInfo { + modes, + manufacturer: dd.monitor_manufacturer.clone(), + product: dd.monitor_name.clone(), + serial_number: dd.monitor_serial_number.clone(), + initial_mode: dd.mode.clone().unwrap().to_backend(), + width_mm: dd.mm_width as _, + height_mm: dd.mm_height as _, + })); connector.connect_sent.set(true); connector.send_hardware_cursor(); } @@ -1091,7 +1135,7 @@ impl MetalBackend { } } - let gfx = match create_gfx_context(master) { + let gfx = match self.state.create_gfx_context(master, None) { Ok(r) => r, Err(e) => return Err(MetalError::CreateRenderContex(e)), }; @@ -1124,7 +1168,8 @@ impl MetalBackend { handle_events: HandleEvents { handle_events: Cell::new(None), }, - ctx, + ctx: CloneCell::new(ctx), + on_change: Default::default(), }); let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?; @@ -1416,28 +1461,68 @@ impl MetalBackend { } } - fn make_render_device(&self, dev: &Rc, log: bool) -> bool { - if let Some(ctx) = self.ctx.get() { - if ctx.dev_id == dev.id { - return true; + fn make_render_device(&self, dev: &MetalDrmDevice, force: bool) { + if !force { + if let Some(ctx) = self.ctx.get() { + if ctx.dev_id == dev.id { + return; + } } } - self.state.set_render_ctx(Some(dev.ctx.gfx.clone())); - self.ctx.set(Some(dev.ctx.clone())); - let mut preserve = Preserve::default(); + let ctx = dev.ctx.get(); + self.state.set_render_ctx(Some(ctx.gfx.clone())); + self.ctx.set(Some(ctx)); for dev in self.device_holder.drm_devices.lock().values() { - if let Err(e) = self.init_drm_device(dev, &mut preserve) { - if log { - log::error!("Could not initialize device: {}", ErrorFmt(e)); - } + self.re_init_drm_device(&dev); + } + } + + fn set_gfx_api(&self, dev: &MetalDrmDevice, api: GfxApi) { + if dev.ctx.get().gfx.gfx_api() == api { + return; + } + let gfx = match self.state.create_gfx_context(&dev.master, Some(api)) { + Ok(r) => r, + Err(e) => { + log::error!( + "Could not create a new graphics context for device {:?}: {}", + dev.devnode, + ErrorFmt(e) + ); + return; } - for connector in dev.connectors.lock().values() { - if connector.connected() { - self.start_connector(connector, false); - } + }; + dev.on_change + .send_event(crate::backend::DrmEvent::GfxApiChanged); + dev.ctx.set(Rc::new(MetalRenderContext { + dev_id: dev.id, + gfx, + })); + let mut is_render_ctx = false; + if let Some(render_ctx) = self.ctx.get() { + if render_ctx.dev_id == dev.id { + is_render_ctx = true; + } + } + if is_render_ctx { + self.make_render_device(dev, true); + } else { + if let Some(dev) = self.device_holder.drm_devices.get(&dev.devnum) { + self.re_init_drm_device(&dev); + } + } + } + + fn re_init_drm_device(&self, dev: &Rc) { + let mut preserve = Preserve::default(); + if let Err(e) = self.init_drm_device(dev, &mut preserve) { + log::error!("Could not initialize device: {}", ErrorFmt(e)); + } + for connector in dev.connectors.lock().values() { + if connector.connected() { + self.start_connector(connector, false); } } - true } fn init_drm_device( @@ -1570,30 +1655,52 @@ impl MetalBackend { fn create_scanout_buffers( &self, dev: &Rc, - format: &ModifiedFormat, + format: &Format, + plane_modifiers: &IndexSet, width: i32, height: i32, ctx: &MetalRenderContext, cursor: bool, ) -> Result<[RenderBuffer; 2], MetalError> { - let create = || self.create_scanout_buffer(dev, format, width, height, ctx, cursor); + let create = + || self.create_scanout_buffer(dev, format, plane_modifiers, width, height, ctx, cursor); Ok([create()?, create()?]) } fn create_scanout_buffer( &self, dev: &Rc, - format: &ModifiedFormat, + format: &Format, + plane_modifiers: &IndexSet, width: i32, height: i32, render_ctx: &MetalRenderContext, cursor: bool, ) -> Result { + let ctx = dev.ctx.get(); + let dev_gfx_formats = ctx.gfx.formats(); + let dev_gfx_format = match dev_gfx_formats.get(&format.drm) { + None => return Err(MetalError::MissingDevFormat(format.name)), + Some(f) => f, + }; + let possible_modifiers: Vec<_> = dev_gfx_format + .write_modifiers + .iter() + .filter(|m| plane_modifiers.contains(*m)) + .copied() + .collect(); + if possible_modifiers.is_empty() { + log::warn!("Scanout modifiers: {:?}", plane_modifiers); + log::warn!("DEV GFX modifiers: {:?}", dev_gfx_format.write_modifiers); + return Err(MetalError::MissingDevModifier(format.name)); + } let mut usage = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; if cursor { usage |= GBM_BO_USE_LINEAR; }; - let dev_bo = dev.gbm.create_bo(width, height, format, usage); + let dev_bo = dev + .gbm + .create_bo(width, height, format, &possible_modifiers, usage); let dev_bo = match dev_bo { Ok(b) => b, Err(e) => return Err(MetalError::ScanoutBuffer(e)), @@ -1602,7 +1709,7 @@ impl MetalBackend { Ok(fb) => Rc::new(fb), Err(e) => return Err(MetalError::Framebuffer(e)), }; - let dev_img = match dev.ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { + let dev_img = match ctx.gfx.clone().dmabuf_img(dev_bo.dmabuf()) { Ok(img) => img, Err(e) => return Err(MetalError::ImportImage(e)), }; @@ -1619,8 +1726,31 @@ impl MetalBackend { (None, render_tex, None) } else { // Create a _bridge_ BO in the render device + let render_gfx_formats = render_ctx.gfx.formats(); + let render_gfx_format = match render_gfx_formats.get(&format.drm) { + None => return Err(MetalError::MissingRenderFormat(format.name)), + Some(f) => f, + }; + let possible_modifiers: Vec<_> = render_gfx_format + .write_modifiers + .iter() + .filter(|m| dev_gfx_format.read_modifiers.contains(*m)) + .copied() + .collect(); + if possible_modifiers.is_empty() { + log::warn!( + "Render GFX modifiers: {:?}", + render_gfx_format.write_modifiers + ); + log::warn!("DEV GFX modifiers: {:?}", dev_gfx_format.read_modifiers); + return Err(MetalError::MissingRenderModifier(format.name)); + } usage = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; - let render_bo = render_ctx.gfx.gbm().create_bo(width, height, format, usage); + let render_bo = + render_ctx + .gfx + .gbm() + .create_bo(width, height, format, &possible_modifiers, usage); let render_bo = match render_bo { Ok(b) => b, Err(e) => return Err(MetalError::ScanoutBuffer(e)), @@ -1640,7 +1770,7 @@ impl MetalBackend { }; // Import the bridge BO into the current device - let dev_img = match dev.ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { + let dev_img = match ctx.gfx.clone().dmabuf_img(render_bo.dmabuf()) { Ok(img) => img, Err(e) => return Err(MetalError::ImportImage(e)), }; @@ -1717,46 +1847,45 @@ impl MetalBackend { return Ok(()); } }; - let primary_plane = 'primary_plane: { + let (primary_plane, primary_modifiers) = 'primary_plane: { for plane in crtc.possible_planes.values() { - if plane.ty == PlaneType::Primary - && !plane.assigned.get() - && plane.formats.contains_key(&XRGB8888.drm) - { - break 'primary_plane plane.clone(); + if plane.ty == PlaneType::Primary && !plane.assigned.get() { + if let Some(format) = plane.formats.get(&XRGB8888.drm) { + break 'primary_plane (plane.clone(), &format.modifiers); + } } } return Err(MetalError::NoPrimaryPlaneForConnector); }; let buffers = Rc::new(self.create_scanout_buffers( &connector.dev, - &ModifiedFormat { - format: XRGB8888, - modifier: INVALID_MODIFIER, - }, + XRGB8888, + primary_modifiers, mode.hdisplay as _, mode.vdisplay as _, ctx, false, )?); let mut cursor_plane = None; + let mut cursor_modifiers = &IndexSet::new(); for plane in crtc.possible_planes.values() { if plane.ty == PlaneType::Cursor && !plane.assigned.get() && plane.formats.contains_key(&ARGB8888.drm) { - cursor_plane = Some(plane.clone()); - break; + if let Some(format) = plane.formats.get(&ARGB8888.drm) { + cursor_plane = Some(plane.clone()); + cursor_modifiers = &format.modifiers; + break; + } } } let mut cursor_buffers = None; if cursor_plane.is_some() { let res = self.create_scanout_buffers( &connector.dev, - &ModifiedFormat { - format: ARGB8888, - modifier: INVALID_MODIFIER, - }, + ARGB8888, + cursor_modifiers, connector.dev.cursor_width as _, connector.dev.cursor_height as _, ctx, diff --git a/src/backends/x.rs b/src/backends/x.rs index d2a96bb7..7a5812df 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -10,7 +10,6 @@ use { fixed::Fixed, format::XRGB8888, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, - gfx_apis::create_gfx_context, renderer::RenderResult, state::State, time::now_usec, @@ -20,8 +19,8 @@ use { }, video::{ drm::{ConnectorType, Drm, DrmError, DrmVersion}, - gbm::{GbmDevice, GbmError, GBM_BO_USE_RENDERING}, - ModifiedFormat, INVALID_MODIFIER, + gbm::{GbmDevice, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, + INVALID_MODIFIER, LINEAR_MODIFIER, }, wire_xcon::{ ChangeProperty, ChangeWindowAttributes, ConfigureNotify, CreateCursor, CreatePixmap, @@ -50,6 +49,7 @@ use { Event, XEvent, Xcon, XconError, }, }, + jay_config::video::GfxApi, std::{ any::Any, borrow::Cow, @@ -118,6 +118,8 @@ pub enum XBackendError { QueryDevice(#[source] XconError), #[error("Could not fstat the drm device")] DrmDeviceFstat(#[source] Errno), + #[error("Render device does not support XRGB8888 format")] + XRGB8888, } pub async fn create(state: &Rc) -> Result, XBackendError> { @@ -179,7 +181,7 @@ pub async fn create(state: &Rc) -> Result, XBackendError> { Err(e) => return Err(XBackendError::DrmDeviceFstat(e)), }; let gbm = GbmDevice::new(&drm)?; - let ctx = match create_gfx_context(&drm) { + let ctx = match state.create_gfx_context(&drm, None) { Ok(r) => r, Err(e) => return Err(XBackendError::CreateEgl(e)), }; @@ -376,15 +378,25 @@ impl XBackend { width: i32, height: i32, ) -> Result<[XImage; 2], XBackendError> { - let format = ModifiedFormat { - format: XRGB8888, - modifier: INVALID_MODIFIER, - }; let mut images = [None, None]; + let formats = self.ctx.formats(); + let format = match formats.get(&XRGB8888.drm) { + Some(f) => f, + None => return Err(XBackendError::XRGB8888), + }; + let mut usage = GBM_BO_USE_RENDERING; + let modifier = if format.write_modifiers.contains(&LINEAR_MODIFIER) { + &[LINEAR_MODIFIER] + } else if format.write_modifiers.contains(&INVALID_MODIFIER) { + usage |= GBM_BO_USE_LINEAR; + &[INVALID_MODIFIER] + } else { + panic!("Neither linear nor invalid modifier is supported"); + }; for image in &mut images { let bo = self .gbm - .create_bo(width, height, &format, GBM_BO_USE_RENDERING)?; + .create_bo(width, height, XRGB8888, modifier, usage)?; let dma = bo.dmabuf(); assert!(dma.planes.len() == 1); let plane = dma.planes.first().unwrap(); @@ -723,12 +735,11 @@ impl XBackend { if let Some(node) = self.state.root.outputs.get(&output.id) { let mut rr = self.render_result.borrow_mut(); let fb = image.fb.get(); - fb.render( + fb.render_node( &*node, &self.state, Some(node.global.pos.get()), - true, - rr.deref_mut(), + Some(rr.deref_mut()), node.preferred_scale.get(), true, ); @@ -965,11 +976,20 @@ impl BackendDrmDevice for XDrmDevice { self.dev } - fn make_render_device(self: Rc) { + fn make_render_device(&self) { log::warn!("make_render_device is not supported by the X backend"); // nothing } + fn set_gfx_api(&self, _api: GfxApi) { + log::warn!("set_gfx_api is not supported by the X backend"); + // nothing + } + + fn gtx_api(&self) -> GfxApi { + self.backend.ctx.gfx_api() + } + fn version(&self) -> Result { self.backend.gbm.drm.version() } diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index e8dfea8d..91222eba 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -5,10 +5,9 @@ use { tools::tool_client::{with_tool_client, Handle, ToolClient}, utils::{errorfmt::ErrorFmt, queue::AsyncQueue}, video::{ - dmabuf::{DmaBuf, DmaBufPlane}, + dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, drm::Drm, gbm::{GbmDevice, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, - INVALID_MODIFIER, }, wire::{ jay_compositor::TakeScreenshot, @@ -81,16 +80,18 @@ pub fn buf_to_qoi(buf: &Dmabuf) -> Vec { fatal!("Could not create a gbm device: {}", ErrorFmt(e)); } }; + let mut planes = PlaneVec::new(); + planes.push(DmaBufPlane { + offset: buf.offset, + stride: buf.stride, + fd: buf.fd.clone(), + }); let dmabuf = DmaBuf { width: buf.width as _, height: buf.height as _, format: XRGB8888, - modifier: INVALID_MODIFIER, - planes: vec![DmaBufPlane { - offset: buf.offset, - stride: buf.stride, - fd: buf.fd.clone(), - }], + modifier: (buf.modifier_hi as u64) << 32 | (buf.modifier_lo as u64), + planes, }; let bo = match gbm.import_dmabuf(&dmabuf, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING) { Ok(bo) => Rc::new(bo), diff --git a/src/compositor.rs b/src/compositor.rs index 2524bbf1..917ef34e 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -39,6 +39,7 @@ use { }, ahash::AHashSet, forker::ForkerProxy, + jay_config::video::GfxApi, std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}, thiserror::Error, uapi::c, @@ -197,6 +198,7 @@ fn start_compositor2( render_ctx_watchers: Default::default(), workspace_watchers: Default::default(), default_workspace_capture: Cell::new(true), + default_gfx_api: Cell::new(GfxApi::OpenGl), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -410,6 +412,7 @@ fn create_dummy_output(state: &Rc) { desired_output: CloneCell::new(dummy_output.global.output_id.clone()), jay_workspaces: Default::default(), capture: Cell::new(false), + title_texture: Cell::new(None), }); dummy_workspace.output_link.set(Some( dummy_output.workspaces.add_last(dummy_workspace.clone()), diff --git a/src/config/handler.rs b/src/config/handler.rs index 347ca39c..a285bfc6 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -40,7 +40,7 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, - video::{Connector, DrmDevice}, + video::{Connector, DrmDevice, GfxApi}, Axis, Direction, Workspace, }, libloading::Library, @@ -582,6 +582,14 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_gfx_api(&self, device: Option, api: GfxApi) -> Result<(), CphError> { + match device { + Some(dev) => self.get_drm_device(dev)?.dev.set_gfx_api(api), + _ => self.state.default_gfx_api.set(api), + } + Ok(()) + } + fn handle_get_default_workspace_capture(&self) { self.respond(Response::GetDefaultWorkspaceCapture { capture: self.state.default_workspace_capture.get(), @@ -1309,6 +1317,9 @@ impl ConfigProxyHandler { ClientMessage::SetNaturalScrollingEnabled { device, enabled } => self .handle_set_natural_scrolling_enabled(device, enabled) .wrn("set_natural_scrolling_enabled")?, + ClientMessage::SetGfxApi { device, api } => { + self.handle_set_gfx_api(device, api).wrn("set_gfx_api")? + } } Ok(()) } diff --git a/src/cursor.rs b/src/cursor.rs index e4447700..a5ca594c 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -310,7 +310,7 @@ impl CursorImageScaled { extents: Rect::new_sized(-xhot, -yhot, width, height).unwrap(), tex: ctx .clone() - .shmem_texture(data, ARGB8888, width, height, width * 4)?, + .shmem_texture(None, data, ARGB8888, width, height, width * 4)?, })) } } @@ -363,7 +363,6 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed &img.tex, extents.x1(), extents.y1(), - ARGB8888, None, None, scale, @@ -384,7 +383,6 @@ impl Cursor for StaticCursor { &img.tex, 0, 0, - ARGB8888, None, None, renderer.scale(), @@ -422,7 +420,6 @@ impl Cursor for AnimatedCursor { &img.tex, 0, 0, - ARGB8888, None, None, renderer.scale(), diff --git a/src/drm_feedback.rs b/src/drm_feedback.rs index 4f54a608..381ab2c0 100644 --- a/src/drm_feedback.rs +++ b/src/drm_feedback.rs @@ -42,7 +42,7 @@ impl DrmFeedback { fn create_fd_data(ctx: &dyn GfxContext) -> Vec { let mut vec = vec![]; for (format, info) in &*ctx.formats() { - for modifier in info.modifiers.keys() { + for modifier in &info.read_modifiers { vec.write_u32::(*format).unwrap(); vec.write_u32::(0).unwrap(); vec.write_u64::(*modifier).unwrap(); diff --git a/src/edid.rs b/src/edid.rs index 7d976cb6..d5623118 100644 --- a/src/edid.rs +++ b/src/edid.rs @@ -1,5 +1,7 @@ use { - crate::utils::{bitflags::BitflagsExt, ptr_ext::PtrExt, stack::Stack}, + crate::utils::{ + bitflags::BitflagsExt, clonecell::UnsafeCellCloneSafe, ptr_ext::PtrExt, stack::Stack, + }, bstr::{BString, ByteSlice}, std::{ fmt::{Debug, Formatter}, @@ -393,6 +395,8 @@ pub enum EdidParseContext { VideoInputDefinition, } +unsafe impl UnsafeCellCloneSafe for EdidParseContext {} + struct EdidPushedContext { stack: Rc>, } diff --git a/src/format.rs b/src/format.rs index 1d1f9bd0..6de070e9 100644 --- a/src/format.rs +++ b/src/format.rs @@ -3,21 +3,23 @@ use { gfx_apis::gl::sys::{GLint, GL_BGRA_EXT, GL_RGBA, GL_UNSIGNED_BYTE}, pipewire::pw_pod::{ SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SpaVideoFormat, SPA_VIDEO_FORMAT_BGRA, - SPA_VIDEO_FORMAT_NV12, SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_RGBA, }, utils::debug_fn::debug_fn, }, ahash::AHashMap, + ash::vk, once_cell::sync::Lazy, std::fmt::{Debug, Write}, }; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq)] pub struct Format { pub name: &'static str, pub bpp: u32, pub gl_format: GLint, pub gl_type: GLint, + pub vk_format: vk::Format, pub drm: u32, pub wl_id: Option, pub external_only_guess: bool, @@ -26,6 +28,12 @@ pub struct Format { pub pipewire: SpaVideoFormat, } +impl PartialEq for Format { + fn eq(&self, other: &Self) -> bool { + self.drm == other.drm + } +} + static FORMATS_MAP: Lazy> = Lazy::new(|| { let mut map = AHashMap::new(); for format in FORMATS { @@ -80,39 +88,44 @@ pub fn map_wayland_format_id(id: u32) -> u32 { } #[allow(dead_code)] -pub static ARGB8888: &Format = &FORMATS[0]; -pub static XRGB8888: &Format = &FORMATS[1]; +pub static ARGB8888: &Format = &Format { + name: "argb8888", + bpp: 4, + gl_format: GL_BGRA_EXT, + gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::B8G8R8A8_SRGB, + drm: ARGB8888_DRM, + wl_id: Some(ARGB8888_ID), + external_only_guess: false, + has_alpha: true, + shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_BGRA, +}; + +pub static XRGB8888: &Format = &Format { + name: "xrgb8888", + bpp: 4, + gl_format: GL_BGRA_EXT, + gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::B8G8R8A8_SRGB, + drm: XRGB8888_DRM, + wl_id: Some(XRGB8888_ID), + external_only_guess: false, + has_alpha: false, + shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_BGRx, +}; pub static FORMATS: &[Format] = &[ - Format { - name: "argb8888", - bpp: 4, - gl_format: GL_BGRA_EXT, - gl_type: GL_UNSIGNED_BYTE, - drm: ARGB8888_DRM, - wl_id: Some(ARGB8888_ID), - external_only_guess: false, - has_alpha: true, - shm_supported: true, - pipewire: SPA_VIDEO_FORMAT_BGRA, - }, - Format { - name: "xrgb8888", - bpp: 4, - gl_format: GL_BGRA_EXT, - gl_type: GL_UNSIGNED_BYTE, - drm: XRGB8888_DRM, - wl_id: Some(XRGB8888_ID), - external_only_guess: false, - has_alpha: false, - shm_supported: true, - pipewire: SPA_VIDEO_FORMAT_BGRx, - }, + *ARGB8888, + *XRGB8888, + // *NV12, Format { name: "abgr8888", bpp: 4, gl_format: GL_RGBA, gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::R8G8B8A8_SRGB, drm: fourcc_code('A', 'B', '2', '4'), wl_id: None, external_only_guess: false, @@ -125,6 +138,7 @@ pub static FORMATS: &[Format] = &[ bpp: 4, gl_format: GL_RGBA, gl_type: GL_UNSIGNED_BYTE, + vk_format: vk::Format::R8G8B8A8_SRGB, drm: fourcc_code('X', 'B', '2', '4'), wl_id: None, external_only_guess: false, @@ -132,18 +146,18 @@ pub static FORMATS: &[Format] = &[ shm_supported: true, pipewire: SPA_VIDEO_FORMAT_RGBx, }, - Format { - name: "nv12", - bpp: 1, // wrong but only used for shm - gl_format: 0, // wrong but only used for shm - gl_type: GL_UNSIGNED_BYTE, // wrong but only used for shm - drm: fourcc_code('N', 'V', '1', '2'), - wl_id: None, - external_only_guess: true, - has_alpha: false, - shm_supported: false, - pipewire: SPA_VIDEO_FORMAT_NV12, - }, + // Format { + // name: "nv12", + // bpp: 1, // wrong but only used for shm + // gl_format: 0, // wrong but only used for shm + // gl_type: GL_UNSIGNED_BYTE, // wrong but only used for shm + // drm: fourcc_code('N', 'V', '1', '2'), + // wl_id: None, + // external_only_guess: true, + // has_alpha: false, + // shm_supported: false, + // pipewire: SPA_VIDEO_FORMAT_NV12, + // }, // Format { // id: fourcc_code('C', '8', ' ', ' '), // name: "c8", diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 00e8971f..36c28f01 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,16 +1,19 @@ use { crate::{ cursor::Cursor, + fixed::Fixed, format::Format, rect::Rect, - renderer::{renderer_base::RendererBase, RenderResult}, + renderer::{renderer_base::RendererBase, RenderResult, Renderer}, scale::Scale, state::State, theme::Color, tree::Node, - video::{dmabuf::DmaBuf, gbm::GbmDevice}, + video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, }, ahash::AHashMap, + indexmap::IndexSet, + jay_config::video::GfxApi, std::{ any::Any, cell::Cell, @@ -24,7 +27,6 @@ use { pub enum GfxApiOpt { Sync, - Clear(Clear), FillRect(FillRect), CopyTexture(CopyTexture), } @@ -79,6 +81,7 @@ impl BufferPoints { } } +#[derive(Debug)] pub struct AbsoluteRect { pub x1: f32, pub x2: f32, @@ -86,10 +89,7 @@ pub struct AbsoluteRect { pub y2: f32, } -pub struct Clear { - pub color: Color, -} - +#[derive(Debug)] pub struct FillRect { pub rect: AbsoluteRect, pub color: Color, @@ -97,7 +97,6 @@ pub struct FillRect { pub struct CopyTexture { pub tex: Rc, - pub format: &'static Format, pub source: BufferPoints, pub target: AbsoluteRect, } @@ -113,18 +112,11 @@ pub enum ResetStatus { pub trait GfxFramebuffer: Debug { fn as_any(&self) -> &dyn Any; - fn clear(&self); + fn take_render_ops(&self) -> Vec; - fn clear_with(&self, r: f32, g: f32, b: f32, a: f32); + fn size(&self) -> (i32, i32); - fn copy_texture( - &self, - state: &State, - texture: &Rc, - x: i32, - y: i32, - alpha: bool, - ); + fn render(&self, ops: Vec, clear: Option<&Color>); fn copy_to_shm( &self, @@ -134,22 +126,122 @@ pub trait GfxFramebuffer: Debug { height: i32, format: &Format, shm: &[Cell], - ); + ) -> Result<(), GfxError>; - fn render_custom(&self, scale: Scale, f: &mut dyn FnMut(&mut RendererBase)); + fn format(&self) -> &'static Format; +} - fn render( +impl dyn GfxFramebuffer { + pub fn clear(&self) { + self.clear_with(0.0, 0.0, 0.0, 0.0); + } + + pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) { + let ops = self.take_render_ops(); + self.render(ops, Some(&Color { r, g, b, a })); + } + + pub fn copy_texture(&self, texture: &Rc, x: i32, y: i32) { + let mut ops = self.take_render_ops(); + let scale = Scale::from_int(1); + let mut renderer = RendererBase { + ops: &mut ops, + scaled: false, + scale, + scalef: 1.0, + }; + renderer.render_texture(texture, x, y, None, None, scale, i32::MAX, i32::MAX); + let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); + self.render(ops, clear); + } + + pub fn render_custom( + &self, + scale: Scale, + clear: Option<&Color>, + f: &mut dyn FnMut(&mut RendererBase), + ) { + let mut ops = self.take_render_ops(); + let mut renderer = RendererBase { + ops: &mut ops, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + }; + f(&mut renderer); + self.render(ops, clear); + } + + pub fn render_node( &self, node: &dyn Node, state: &State, cursor_rect: Option, - on_output: bool, - result: &mut RenderResult, + result: Option<&mut RenderResult>, scale: Scale, render_hardware_cursor: bool, - ); + ) { + let mut ops = self.take_render_ops(); + let (width, height) = self.size(); + let mut renderer = Renderer { + base: RendererBase { + ops: &mut ops, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + }, + state, + result, + logical_extents: node.node_absolute_position().at_point(0, 0), + physical_extents: Rect::new(0, 0, width, height).unwrap(), + }; + node.node_render(&mut renderer, 0, 0, i32::MAX, i32::MAX); + if let Some(rect) = cursor_rect { + let seats = state.globals.lock_seats(); + for seat in seats.values() { + if let Some(cursor) = seat.get_cursor() { + let (mut x, mut y) = seat.get_position(); + if let Some(dnd_icon) = seat.dnd_icon() { + let extents = dnd_icon.extents.get().move_( + x.round_down() + dnd_icon.buf_x.get(), + y.round_down() + dnd_icon.buf_y.get(), + ); + if extents.intersects(&rect) { + let (x, y) = rect.translate(extents.x1(), extents.y1()); + renderer.render_surface(&dnd_icon, x, y, i32::MAX, i32::MAX); + } + } + if render_hardware_cursor || !seat.hardware_cursor() { + cursor.tick(); + x -= Fixed::from_int(rect.x1()); + y -= Fixed::from_int(rect.y1()); + cursor.render(&mut renderer, x, y); + } + } + } + } + let c = state.theme.colors.background.get(); + self.render(ops, Some(&c)); + } - fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale); + pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) { + let mut ops = self.take_render_ops(); + let (width, height) = self.size(); + let mut renderer = Renderer { + base: RendererBase { + ops: &mut ops, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + }, + state, + result: None, + logical_extents: Rect::new_empty(0, 0), + physical_extents: Rect::new(0, 0, width, height).unwrap(), + }; + cursor.render_hardware_cursor(&mut renderer); + self.render(ops, Some(&Color::TRANSPARENT)); + } } pub trait GfxImage { @@ -162,28 +254,37 @@ pub trait GfxImage { } pub trait GfxTexture: Debug { - fn width(&self) -> i32; - fn height(&self) -> i32; + fn size(&self) -> (i32, i32); fn as_any(&self) -> &dyn Any; + fn into_any(self: Rc) -> Rc; + fn read_pixels( + self: Rc, + x: i32, + y: i32, + width: i32, + height: i32, + stride: i32, + format: &'static Format, + shm: &[Cell], + ) -> Result<(), GfxError>; } pub trait GfxContext: Debug { - fn take_render_ops(&self) -> Vec; - fn reset_status(&self) -> Option; - fn supports_external_texture(&self) -> bool; - fn render_node(&self) -> Rc; fn formats(&self) -> Rc>; - fn dmabuf_fb(self: Rc, buf: &DmaBuf) -> Result, GfxError>; + fn dmabuf_fb(self: Rc, buf: &DmaBuf) -> Result, GfxError> { + self.dmabuf_img(buf)?.to_framebuffer() + } fn dmabuf_img(self: Rc, buf: &DmaBuf) -> Result, GfxError>; fn shmem_texture( self: Rc, + old: Option>, data: &[Cell], format: &'static Format, width: i32, @@ -192,19 +293,15 @@ pub trait GfxContext: Debug { ) -> Result, GfxError>; fn gbm(&self) -> &GbmDevice; + + fn gfx_api(&self) -> GfxApi; } #[derive(Debug)] pub struct GfxFormat { pub format: &'static Format, - pub implicit_external_only: bool, - pub modifiers: AHashMap, -} - -#[derive(Debug)] -pub struct GfxModifier { - pub modifier: u64, - pub external_only: bool, + pub read_modifiers: IndexSet, + pub write_modifiers: IndexSet, } #[derive(Error)] diff --git a/src/gfx_apis.rs b/src/gfx_apis.rs index d6450911..5cc1dd3a 100644 --- a/src/gfx_apis.rs +++ b/src/gfx_apis.rs @@ -1,13 +1,49 @@ use { crate::{ + async_engine::AsyncEngine, gfx_api::{GfxContext, GfxError}, + io_uring::IoUring, + utils::errorfmt::ErrorFmt, video::drm::Drm, }, + jay_config::video::GfxApi, std::rc::Rc, }; pub mod gl; +mod vulkan; -pub fn create_gfx_context(drm: &Drm) -> Result, GfxError> { - gl::create_gfx_context(drm) +pub fn create_gfx_context( + eng: &Rc, + ring: &Rc, + drm: &Drm, + api: GfxApi, +) -> Result, GfxError> { + let mut apis = [GfxApi::OpenGl, GfxApi::Vulkan]; + apis.sort_by_key(|&a| if a == api { -1 } else { a as i32 }); + let mut last_err = None; + for api in apis { + let res = create_gfx_context_(eng, ring, drm, api); + match res { + Ok(_) => return res, + Err(e) => { + log::warn!("Could not create {:?} API: {}", api, ErrorFmt(&e)); + last_err = Some(e); + } + } + } + Err(last_err.unwrap()) +} + +fn create_gfx_context_( + eng: &Rc, + ring: &Rc, + drm: &Drm, + api: GfxApi, +) -> Result, GfxError> { + match api { + GfxApi::OpenGl => gl::create_gfx_context(drm), + GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm), + _ => unreachable!(), + } } diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index f8423ead..f080d1a0 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -20,7 +20,6 @@ macro_rules! egl_transparent { use { crate::{ - format::Format, gfx_api::{ BufferPoints, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, }, @@ -28,11 +27,10 @@ use { gl::texture::image_target, renderer::{context::GlRenderContext, framebuffer::Framebuffer, texture::Texture}, sys::{ - glActiveTexture, glBindTexture, glClear, glClearColor, glDisable, - glDisableVertexAttribArray, glDrawArrays, glEnable, glEnableVertexAttribArray, - glTexParameteri, glUniform1i, glUniform4f, glUseProgram, glVertexAttribPointer, - GL_BLEND, GL_COLOR_BUFFER_BIT, GL_FALSE, GL_FLOAT, GL_LINEAR, GL_TEXTURE0, - GL_TEXTURE_MIN_FILTER, GL_TRIANGLES, GL_TRIANGLE_STRIP, + glActiveTexture, glBindTexture, glDisable, glDisableVertexAttribArray, + glDrawArrays, glEnable, glEnableVertexAttribArray, glTexParameteri, glUniform1i, + glUniform4f, glUseProgram, glVertexAttribPointer, GL_BLEND, GL_FALSE, GL_FLOAT, + GL_LINEAR, GL_TEXTURE0, GL_TEXTURE_MIN_FILTER, GL_TRIANGLES, GL_TRIANGLE_STRIP, }, }, theme::Color, @@ -127,6 +125,10 @@ enum RenderError { ExternalOnly, #[error("OpenGL context does not support external textures")] ExternalUnsupported, + #[error("OpenGL context does not support any formats")] + NoSupportedFormats, + #[error("Unsupported operation")] + UnsupportedOperation, } #[derive(Default)] @@ -164,13 +166,6 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { break; } } - GfxApiOpt::Clear(c) => { - if has_ops!() { - break; - } - clear(&c.color); - i += 1; - } GfxApiOpt::FillRect(f) => { fill_rect.push(f); i += 1; @@ -220,27 +215,11 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { let y1 = 2.0 * (tex.target.y1 / height) - 1.0; let x2 = 2.0 * (tex.target.x2 / width) - 1.0; let y2 = 2.0 * (tex.target.y2 / height) - 1.0; - render_texture( - &fb.ctx, - &tex.tex.as_gl(), - tex.format, - x1, - y1, - x2, - y2, - &tex.source, - ) + render_texture(&fb.ctx, &tex.tex.as_gl(), x1, y1, x2, y2, &tex.source) } } } -fn clear(c: &Color) { - unsafe { - glClearColor(c.r, c.g, c.b, c.a); - glClear(GL_COLOR_BUFFER_BIT); - } -} - fn fill_boxes3(ctx: &GlRenderContext, boxes: &[f32], color: &Color) { unsafe { glUseProgram(ctx.fill_prog.prog); @@ -262,7 +241,6 @@ fn fill_boxes3(ctx: &GlRenderContext, boxes: &[f32], color: &Color) { fn render_texture( ctx: &GlRenderContext, texture: &Texture, - format: &Format, x1: f32, y1: f32, x2: f32, @@ -288,7 +266,7 @@ fn render_texture( }, false => &ctx.tex_internal, }; - let prog = match format.has_alpha { + let prog = match texture.gl.format.has_alpha { true => { glEnable(GL_BLEND); &progs.alpha diff --git a/src/gfx_apis/gl/egl/context.rs b/src/gfx_apis/gl/egl/context.rs index c71f7176..8a90ef91 100644 --- a/src/gfx_apis/gl/egl/context.rs +++ b/src/gfx_apis/gl/egl/context.rs @@ -1,6 +1,6 @@ use { crate::{ - gfx_api::ResetStatus, + gfx_api::{GfxFormat, ResetStatus}, gfx_apis::gl::{ egl::{ display::EglDisplay, @@ -17,6 +17,7 @@ use { RenderError, }, }, + ahash::AHashMap, std::rc::Rc, }; @@ -25,6 +26,7 @@ pub struct EglContext { pub dpy: Rc, pub ext: GlExt, pub ctx: EGLContext, + pub formats: Rc>, } impl Drop for EglContext { diff --git a/src/gfx_apis/gl/egl/display.rs b/src/gfx_apis/gl/egl/display.rs index 95681b63..d6e7e6ba 100644 --- a/src/gfx_apis/gl/egl/display.rs +++ b/src/gfx_apis/gl/egl/display.rs @@ -1,7 +1,7 @@ use { crate::{ format::{formats, Format}, - gfx_api::{GfxFormat, GfxModifier}, + gfx_api::GfxFormat, gfx_apis::gl::{ egl::{ context::EglContext, @@ -30,16 +30,30 @@ use { }, RenderError, }, - video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice, INVALID_MODIFIER}, + video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice, Modifier, INVALID_MODIFIER}, }, ahash::AHashMap, + indexmap::{IndexMap, IndexSet}, std::{ptr, rc::Rc}, }; +#[derive(Debug)] +pub struct EglFormat { + pub format: &'static Format, + pub implicit_external_only: bool, + pub modifiers: IndexMap, +} + +#[derive(Debug)] +pub struct EglModifier { + pub modifier: u64, + pub external_only: bool, +} + #[derive(Debug)] pub struct EglDisplay { pub exts: DisplayExt, - pub formats: Rc>, + pub formats: AHashMap, pub gbm: Rc, pub dpy: EGLDisplay, } @@ -61,7 +75,7 @@ impl EglDisplay { } let mut dpy = EglDisplay { exts: DisplayExt::empty(), - formats: Rc::new(AHashMap::new()), + formats: AHashMap::new(), gbm: Rc::new(gbm), dpy, }; @@ -89,7 +103,7 @@ impl EglDisplay { if !dpy.exts.intersects(DisplayExt::KHR_SURFACELESS_CONTEXT) { return Err(RenderError::SurfacelessContext); } - dpy.formats = Rc::new(query_formats(dpy.dpy)?); + dpy.formats = query_formats(dpy.dpy)?; Ok(Rc::new(dpy)) } @@ -109,27 +123,62 @@ impl EglDisplay { log::warn!("EGL display does not support gpu reset notifications"); } attrib.push(EGL_NONE); - unsafe { - let ctx = eglCreateContext( + let ctx = unsafe { + eglCreateContext( self.dpy, EGLConfig::none(), EGLContext::none(), attrib.as_ptr(), - ); - if ctx.is_none() { - return Err(RenderError::CreateContext); - } - let mut ctx = EglContext { - dpy: self.clone(), - ext: GlExt::empty(), - ctx, - }; - ctx.ext = ctx.with_current(|| Ok(get_gl_ext()))?; - if !ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE) { - return Err(RenderError::OesEglImage); + ) + }; + if ctx.is_none() { + return Err(RenderError::CreateContext); + } + let mut ctx = EglContext { + dpy: self.clone(), + ext: GlExt::empty(), + ctx, + formats: Default::default(), + }; + ctx.ext = ctx.with_current(|| Ok(get_gl_ext()))?; + if !ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE) { + return Err(RenderError::OesEglImage); + } + ctx.formats = { + let mut formats = AHashMap::new(); + let supports_external_only = ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE_EXTERNAL); + for (&drm, format) in &self.formats { + if format.implicit_external_only && !supports_external_only { + continue; + } + let mut read_modifiers = IndexSet::new(); + let mut write_modifiers = IndexSet::new(); + for modifier in format.modifiers.values() { + if modifier.external_only && !supports_external_only { + continue; + } + if !modifier.external_only { + write_modifiers.insert(modifier.modifier); + } + read_modifiers.insert(modifier.modifier); + } + if !read_modifiers.is_empty() || !write_modifiers.is_empty() { + formats.insert( + drm, + GfxFormat { + format: format.format, + read_modifiers, + write_modifiers, + }, + ); + } } - Ok(Rc::new(ctx)) + Rc::new(formats) + }; + if ctx.formats.is_empty() { + return Err(RenderError::NoSupportedFormats); } + Ok(Rc::new(ctx)) } pub(in crate::gfx_apis::gl) fn import_dmabuf( @@ -214,6 +263,7 @@ impl EglDisplay { width: buf.width, height: buf.height, external_only: format.external_only, + format: buf.format, })) } } @@ -228,7 +278,7 @@ impl Drop for EglDisplay { } } -unsafe fn query_formats(dpy: EGLDisplay) -> Result, RenderError> { +unsafe fn query_formats(dpy: EGLDisplay) -> Result, RenderError> { let mut vec = vec![]; let mut num = 0; let res = PROCS.eglQueryDmaBufFormatsEXT(dpy, num, ptr::null_mut(), &mut num); @@ -248,7 +298,7 @@ unsafe fn query_formats(dpy: EGLDisplay) -> Result, Ren let (modifiers, external_only) = query_modifiers(dpy, fmt, format)?; res.insert( format.drm, - GfxFormat { + EglFormat { format, implicit_external_only: external_only, modifiers, @@ -263,7 +313,7 @@ unsafe fn query_modifiers( dpy: EGLDisplay, gl_format: EGLint, format: &'static Format, -) -> Result<(AHashMap, bool), RenderError> { +) -> Result<(IndexMap, bool), RenderError> { let mut mods = vec![]; let mut ext_only = vec![]; let mut num = 0; @@ -293,11 +343,11 @@ unsafe fn query_modifiers( } mods.set_len(num as usize); ext_only.set_len(num as usize); - let mut res = AHashMap::new(); + let mut res = IndexMap::new(); for (modifier, ext_only) in mods.iter().copied().zip(ext_only.iter().copied()) { res.insert( modifier as _, - GfxModifier { + EglModifier { modifier: modifier as _, external_only: ext_only == EGL_TRUE, }, @@ -309,7 +359,7 @@ unsafe fn query_modifiers( } res.insert( INVALID_MODIFIER, - GfxModifier { + EglModifier { modifier: INVALID_MODIFIER, external_only, }, diff --git a/src/gfx_apis/gl/egl/image.rs b/src/gfx_apis/gl/egl/image.rs index ff7a1c0d..ab2412b5 100644 --- a/src/gfx_apis/gl/egl/image.rs +++ b/src/gfx_apis/gl/egl/image.rs @@ -1,8 +1,11 @@ use { - crate::gfx_apis::gl::egl::{ - display::EglDisplay, - sys::{EGLImageKHR, EGL_FALSE}, - PROCS, + crate::{ + format::Format, + gfx_apis::gl::egl::{ + display::EglDisplay, + sys::{EGLImageKHR, EGL_FALSE}, + PROCS, + }, }, std::rc::Rc, }; @@ -13,6 +16,7 @@ pub struct EglImage { pub width: i32, pub height: i32, pub external_only: bool, + pub format: &'static Format, } impl Drop for EglImage { diff --git a/src/gfx_apis/gl/gl/frame_buffer.rs b/src/gfx_apis/gl/gl/frame_buffer.rs index 70d289a0..d9ef828e 100644 --- a/src/gfx_apis/gl/gl/frame_buffer.rs +++ b/src/gfx_apis/gl/gl/frame_buffer.rs @@ -11,7 +11,7 @@ use { }; pub struct GlFrameBuffer { - pub _rb: Option>, + pub rb: Rc, pub _tex: Option>, pub ctx: Rc, pub width: i32, diff --git a/src/gfx_apis/gl/gl/render_buffer.rs b/src/gfx_apis/gl/gl/render_buffer.rs index a1cee485..7e582b9e 100644 --- a/src/gfx_apis/gl/gl/render_buffer.rs +++ b/src/gfx_apis/gl/gl/render_buffer.rs @@ -56,7 +56,7 @@ impl GlRenderBuffer { let status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); let fb = GlFrameBuffer { - _rb: Some(self.clone()), + rb: self.clone(), _tex: None, ctx: self.ctx.clone(), fbo, diff --git a/src/gfx_apis/gl/gl/texture.rs b/src/gfx_apis/gl/gl/texture.rs index 58db32d6..f083eaa6 100644 --- a/src/gfx_apis/gl/gl/texture.rs +++ b/src/gfx_apis/gl/gl/texture.rs @@ -23,6 +23,7 @@ pub struct GlTexture { pub width: i32, pub height: i32, pub external_only: bool, + pub format: &'static Format, } pub fn image_target(external_only: bool) -> GLenum { @@ -58,6 +59,7 @@ impl GlTexture { width: img.width, height: img.height, external_only: img.external_only, + format: img.format, }) } @@ -101,6 +103,7 @@ impl GlTexture { width, height, external_only: false, + format, }) } } diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index 55674b08..b9680151 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -14,18 +14,14 @@ use { renderer::{framebuffer::Framebuffer, image::Image}, GfxGlState, RenderError, Texture, }, - video::{ - dmabuf::DmaBuf, - drm::{Drm, NodeType}, - gbm::GbmDevice, - }, + video::{dmabuf::DmaBuf, drm::Drm, gbm::GbmDevice}, }, ahash::AHashMap, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ffi::CString, fmt::{Debug, Formatter}, - mem, rc::Rc, }, uapi::ustr, @@ -82,19 +78,11 @@ impl GlRenderContext { self.ctx.reset_status() } - pub fn supports_external_texture(&self) -> bool { - self.ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE_EXTERNAL) - } - pub(in crate::gfx_apis::gl) fn from_drm_device(drm: &Drm) -> Result { - let nodes = drm.get_nodes()?; - let node = match nodes - .get(&NodeType::Render) - .or_else(|| nodes.get(&NodeType::Primary)) - { - None => return Err(RenderError::NoRenderNode), - Some(path) => Rc::new(path.to_owned()), - }; + let node = drm + .get_render_node()? + .ok_or(RenderError::NoRenderNode) + .map(Rc::new)?; let dpy = EglDisplay::create(drm)?; if !dpy.formats.contains_key(&XRGB8888.drm) { return Err(RenderError::XRGB888); @@ -161,7 +149,7 @@ impl GlRenderContext { } pub fn formats(&self) -> Rc> { - self.ctx.dpy.formats.clone() + self.ctx.formats.clone() } fn dmabuf_fb(self: &Rc, buf: &DmaBuf) -> Result, RenderError> { @@ -203,18 +191,10 @@ impl GlRenderContext { } impl GfxContext for GlRenderContext { - fn take_render_ops(&self) -> Vec { - mem::take(&mut self.gfx_ops.borrow_mut()) - } - fn reset_status(&self) -> Option { self.reset_status() } - fn supports_external_texture(&self) -> bool { - self.supports_external_texture() - } - fn render_node(&self) -> Rc { self.render_node() } @@ -239,6 +219,7 @@ impl GfxContext for GlRenderContext { fn shmem_texture( self: Rc, + _old: Option>, data: &[Cell], format: &'static Format, width: i32, @@ -254,4 +235,8 @@ impl GfxContext for GlRenderContext { fn gbm(&self) -> &GbmDevice { &self.gbm } + + fn gfx_api(&self) -> GfxApi { + GfxApi::OpenGl + } } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 1d73d475..99dc5e5b 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -1,9 +1,7 @@ use { crate::{ - cursor::Cursor, - fixed::Fixed, - format::{Format, ARGB8888, XRGB8888}, - gfx_api::{GfxFramebuffer, GfxTexture}, + format::Format, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer}, gfx_apis::gl::{ gl::{ frame_buffer::GlFrameBuffer, @@ -16,16 +14,13 @@ use { run_ops, sys::{glBlendFunc, glFlush, glReadnPixels, GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, }, - rect::Rect, - renderer::{renderer_base::RendererBase, RenderResult, Renderer}, - scale::Scale, - state::State, - tree::Node, + theme::Color, }, std::{ any::Any, cell::Cell, fmt::{Debug, Formatter}, + mem, rc::Rc, }, }; @@ -42,72 +37,6 @@ impl Debug for Framebuffer { } impl Framebuffer { - pub fn clear(&self) { - self.clear_with(0.0, 0.0, 0.0, 0.0); - } - - pub fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) { - let _ = self.ctx.ctx.with_current(|| { - unsafe { - glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); - glViewport(0, 0, self.gl.width, self.gl.height); - glClearColor(r, g, b, a); - glClear(GL_COLOR_BUFFER_BIT); - } - Ok(()) - }); - } - - pub fn copy_texture( - &self, - state: &State, - texture: &Rc, - x: i32, - y: i32, - alpha: bool, - ) { - let mut ops = self.ctx.gfx_ops.borrow_mut(); - ops.clear(); - let scale = Scale::from_int(1); - let extents = Rect::new_sized(0, 0, self.gl.width, self.gl.height).unwrap(); - let mut renderer = Renderer { - base: RendererBase { - ops: &mut ops, - scaled: false, - scale, - scalef: 1.0, - }, - state, - on_output: false, - result: &mut RenderResult::default(), - logical_extents: extents, - physical_extents: extents, - }; - let format = match alpha { - true => ARGB8888, - false => XRGB8888, - }; - renderer - .base - .render_texture(texture, x, y, format, None, None, scale, i32::MAX, i32::MAX); - let _ = self.ctx.ctx.with_current(|| { - unsafe { - glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); - glViewport(0, 0, self.gl.width, self.gl.height); - if alpha { - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); - } - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - } - run_ops(self, &ops); - unsafe { - glFlush(); - } - Ok(()) - }); - } - pub fn copy_to_shm( &self, x: i32, @@ -137,121 +66,15 @@ impl Framebuffer { }); } - pub fn render_custom(&self, scale: Scale, f: &mut dyn FnMut(&mut RendererBase)) { - let mut ops = self.ctx.gfx_ops.borrow_mut(); - ops.clear(); - let mut renderer = RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }; - f(&mut renderer); + pub fn render(&self, ops: Vec, clear: Option<&Color>) { let _ = self.ctx.ctx.with_current(|| { unsafe { glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); glViewport(0, 0, self.gl.width, self.gl.height); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - } - run_ops(self, &ops); - unsafe { - glFlush(); - } - Ok(()) - }); - } - - pub fn render( - &self, - node: &dyn Node, - state: &State, - cursor_rect: Option, - on_output: bool, - result: &mut RenderResult, - scale: Scale, - render_hardware_cursor: bool, - ) { - let mut ops = self.ctx.gfx_ops.borrow_mut(); - ops.clear(); - let mut renderer = Renderer { - base: RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }, - state, - on_output, - result, - logical_extents: node.node_absolute_position().at_point(0, 0), - physical_extents: Rect::new(0, 0, self.gl.width, self.gl.height).unwrap(), - }; - node.node_render(&mut renderer, 0, 0, i32::MAX, i32::MAX); - if let Some(rect) = cursor_rect { - let seats = state.globals.lock_seats(); - for seat in seats.values() { - if let Some(cursor) = seat.get_cursor() { - let (mut x, mut y) = seat.get_position(); - if let Some(dnd_icon) = seat.dnd_icon() { - let extents = dnd_icon.extents.get().move_( - x.round_down() + dnd_icon.buf_x.get(), - y.round_down() + dnd_icon.buf_y.get(), - ); - if extents.intersects(&rect) { - let (x, y) = rect.translate(extents.x1(), extents.y1()); - renderer.render_surface(&dnd_icon, x, y, i32::MAX, i32::MAX); - } - } - if render_hardware_cursor || !seat.hardware_cursor() { - cursor.tick(); - x -= Fixed::from_int(rect.x1()); - y -= Fixed::from_int(rect.y1()); - cursor.render(&mut renderer, x, y); - } + if let Some(c) = clear { + glClearColor(c.r, c.g, c.b, c.a); + glClear(GL_COLOR_BUFFER_BIT); } - } - } - let _ = self.ctx.ctx.with_current(|| { - let c = state.theme.colors.background.get(); - unsafe { - glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); - glViewport(0, 0, self.gl.width, self.gl.height); - glClearColor(c.r, c.g, c.b, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - } - run_ops(self, &ops); - unsafe { - glFlush(); - } - Ok(()) - }); - } - - pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) { - let mut ops = self.ctx.gfx_ops.borrow_mut(); - ops.clear(); - let mut res = RenderResult::default(); - let mut renderer = Renderer { - base: RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }, - state, - on_output: false, - result: &mut res, - logical_extents: Rect::new_empty(0, 0), - physical_extents: Rect::new(0, 0, self.gl.width, self.gl.height).unwrap(), - }; - cursor.render_hardware_cursor(&mut renderer); - let _ = self.ctx.ctx.with_current(|| { - unsafe { - glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); - glViewport(0, 0, self.gl.width, self.gl.height); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } run_ops(self, &ops); @@ -260,6 +83,7 @@ impl Framebuffer { } Ok(()) }); + *self.ctx.gfx_ops.borrow_mut() = ops; } } @@ -268,23 +92,18 @@ impl GfxFramebuffer for Framebuffer { self } - fn clear(&self) { - self.clear() + fn take_render_ops(&self) -> Vec { + let mut ops = mem::take(&mut *self.ctx.gfx_ops.borrow_mut()); + ops.clear(); + ops } - fn clear_with(&self, r: f32, g: f32, b: f32, a: f32) { - self.clear_with(r, g, b, a) + fn size(&self) -> (i32, i32) { + (self.gl.width, self.gl.height) } - fn copy_texture( - &self, - state: &State, - texture: &Rc, - x: i32, - y: i32, - alpha: bool, - ) { - self.copy_texture(state, texture, x, y, alpha) + fn render(&self, ops: Vec, clear: Option<&Color>) { + self.render(ops, clear); } fn copy_to_shm( @@ -295,36 +114,12 @@ impl GfxFramebuffer for Framebuffer { height: i32, format: &Format, shm: &[Cell], - ) { - self.copy_to_shm(x, y, width, height, format, shm) - } - - fn render_custom(&self, scale: Scale, f: &mut dyn FnMut(&mut RendererBase)) { - self.render_custom(scale, f) - } - - fn render( - &self, - node: &dyn Node, - state: &State, - cursor_rect: Option, - on_output: bool, - result: &mut RenderResult, - scale: Scale, - render_hardware_cursor: bool, - ) { - self.render( - node, - state, - cursor_rect, - on_output, - result, - scale, - render_hardware_cursor, - ) + ) -> Result<(), GfxError> { + self.copy_to_shm(x, y, width, height, format, shm); + Ok(()) } - fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) { - self.render_hardware_cursor(cursor, state, scale) + fn format(&self) -> &'static Format { + self.gl.rb.img.format } } diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index 0eb73dd8..e6ac3480 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -1,10 +1,12 @@ use { crate::{ - gfx_api::GfxTexture, - gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext}, + format::Format, + gfx_api::{GfxError, GfxTexture}, + gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext, RenderError}, }, std::{ any::Any, + cell::Cell, fmt::{Debug, Formatter}, rc::Rc, }, @@ -32,15 +34,28 @@ impl Texture { } impl GfxTexture for Texture { - fn width(&self) -> i32 { - self.width() + fn size(&self) -> (i32, i32) { + (self.width(), self.height()) } - fn height(&self) -> i32 { - self.height() + fn as_any(&self) -> &dyn Any { + self } - fn as_any(&self) -> &dyn Any { + fn into_any(self: Rc) -> Rc { self } + + fn read_pixels( + self: Rc, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + _stride: i32, + _format: &Format, + _shm: &[Cell], + ) -> Result<(), GfxError> { + Err(RenderError::UnsupportedOperation.into()) + } } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs new file mode 100644 index 00000000..5dfc8e91 --- /dev/null +++ b/src/gfx_apis/vulkan.rs @@ -0,0 +1,266 @@ +mod allocator; +mod command; +mod descriptor; +mod device; +mod fence; +mod format; +mod image; +mod instance; +mod pipeline; +mod renderer; +mod sampler; +mod semaphore; +mod shaders; +mod staging; +mod util; + +use { + crate::{ + async_engine::AsyncEngine, + format::Format, + gfx_api::{GfxContext, GfxError, GfxFormat, GfxImage, GfxTexture, ResetStatus}, + gfx_apis::vulkan::{ + image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, + }, + io_uring::IoUring, + utils::oserror::OsError, + video::{ + dmabuf::DmaBuf, + drm::{Drm, DrmError}, + gbm::{GbmDevice, GbmError}, + }, + }, + ahash::AHashMap, + ash::{vk, LoadingError}, + gpu_alloc::{AllocationError, MapError}, + jay_config::video::GfxApi, + once_cell::sync::Lazy, + std::{ + cell::Cell, + ffi::{CStr, CString}, + rc::Rc, + sync::Arc, + }, + thiserror::Error, + uapi::c::dev_t, +}; + +#[derive(Debug, Error)] +pub enum VulkanError { + #[error("Could not create a GBM device")] + Gbm(#[source] GbmError), + #[error("Could not load libvulkan.so")] + Load(#[source] Arc), + #[error("Could not list instance extensions")] + InstanceExtensions(#[source] vk::Result), + #[error("Could not list instance layers")] + InstanceLayers(#[source] vk::Result), + #[error("Could not list device extensions")] + DeviceExtensions(#[source] vk::Result), + #[error("Could not create the device")] + CreateDevice(#[source] vk::Result), + #[error("Could not create a semaphore")] + CreateSemaphore(#[source] vk::Result), + #[error("Could not create a fence")] + CreateFence(#[source] vk::Result), + #[error("Could not create the buffer")] + CreateBuffer(#[source] vk::Result), + #[error("Could not create a shader module")] + CreateShaderModule(#[source] vk::Result), + #[error("Missing required instance extension {0:?}")] + MissingInstanceExtension(&'static CStr), + #[error("Could not allocate a command pool")] + AllocateCommandPool(#[source] vk::Result), + #[error("Could not allocate a command buffer")] + AllocateCommandBuffer(#[source] vk::Result), + #[error("Device does not have a graphics queue")] + NoGraphicsQueue, + #[error("Missing required device extension {0:?}")] + MissingDeviceExtension(&'static CStr), + #[error("Could not create an instance")] + CreateInstance(#[source] vk::Result), + #[error("Could not create a debug-utils messenger")] + Messenger(#[source] vk::Result), + #[error("Could not fstat the DRM FD")] + Fstat(#[source] OsError), + #[error("Could not enumerate the physical devices")] + EnumeratePhysicalDevices(#[source] vk::Result), + #[error("Could not find a vulkan device that matches dev_t {0}")] + NoDeviceFound(dev_t), + #[error("Could not load image properties")] + LoadImageProperties(#[source] vk::Result), + #[error("Device does not support rending and texturing from the XRGB8888 format")] + XRGB8888, + #[error("Device does not support syncobj import")] + SyncobjImport, + #[error("Could not start a command buffer")] + BeginCommandBuffer(vk::Result), + #[error("Could not end a command buffer")] + EndCommandBuffer(vk::Result), + #[error("Could not submit a command buffer")] + Submit(vk::Result), + #[error("Could not create a sampler")] + CreateSampler(#[source] vk::Result), + #[error("Could not create a pipeline layout")] + CreatePipelineLayout(#[source] vk::Result), + #[error("Could not create a descriptor set layout")] + CreateDescriptorSetLayout(#[source] vk::Result), + #[error("Could not create a pipeline")] + CreatePipeline(#[source] vk::Result), + #[error("The format is not supported")] + FormatNotSupported, + #[error("The modifier is not supported")] + ModifierNotSupported, + #[error("The modifier does not support this use-case")] + ModifierUseNotSupported, + #[error("The image has a non-positive size")] + NonPositiveImageSize, + #[error("The image is too large")] + ImageTooLarge, + #[error("Could not retrieve device properties")] + GetDeviceProperties(#[source] vk::Result), + #[error("The dmabuf has an incorrect number of planes")] + BadPlaneCount, + #[error("The dmabuf is disjoint but the modifier does not support disjoint buffers")] + DisjointNotSupported, + #[error("Could not create the image")] + CreateImage(#[source] vk::Result), + #[error("Could not create an image view")] + CreateImageView(#[source] vk::Result), + #[error("Could not query the memory fd properties")] + MemoryFdProperties(#[source] vk::Result), + #[error("There is no matching memory type")] + MemoryType, + #[error("Could not duplicate the DRM fd")] + Dupfd(#[source] OsError), + #[error("Could not allocate memory")] + AllocateMemory(#[source] vk::Result), + #[error("Could not allocate memory")] + AllocateMemory2(#[source] AllocationError), + #[error("Could not bind memory to the image")] + BindImageMemory(#[source] vk::Result), + #[error("The format does not support shared memory images")] + ShmNotSupported, + #[error("Could not bind memory to the buffer")] + BindBufferMemory(#[source] vk::Result), + #[error("Could not map the memory")] + MapMemory(#[source] MapError), + #[error("Could not flush modified memory")] + FlushMemory(#[source] vk::Result), + #[error("Could not export a sync file from a dma-buf")] + IoctlExportSyncFile(#[source] OsError), + #[error("Could not import a sync file into a semaphore")] + ImportSyncFile(#[source] vk::Result), + #[error("Could not import a sync file into a dma-buf")] + IoctlImportSyncFile(#[source] OsError), + #[error("Could not export a sync file from a semaphore")] + ExportSyncFile(#[source] vk::Result), + #[error("Could not fetch the render node of the device")] + FetchRenderNode(#[source] DrmError), + #[error("Device has no render node")] + NoRenderNode, + #[error("Overflow while calculating shm buffer size")] + ShmOverflow, + #[error("Shm stride does not match format or width")] + InvalidStride, + #[error("Shm stride and height do not match buffer size")] + InvalidBufferSize, + #[error("The shm parameters are invalid x={x}, y={y}, width={width}, height={height}, stride={stride}")] + InvalidShmParameters { + x: i32, + y: i32, + width: i32, + height: i32, + stride: i32, + }, + #[error("Unsupported operation")] + UnsupportedOperation, +} + +impl From for GfxError { + fn from(value: VulkanError) -> Self { + Self(Box::new(value)) + } +} + +pub static VULKAN_VALIDATION: Lazy = + Lazy::new(|| std::env::var("JAY_VULKAN_VALIDATION").ok().as_deref() == Some("1")); + +pub fn create_graphics_context( + eng: &Rc, + ring: &Rc, + drm: &Drm, +) -> Result, GfxError> { + let instance = VulkanInstance::new(eng, ring, *VULKAN_VALIDATION)?; + let device = instance.create_device(drm)?; + let renderer = device.create_renderer()?; + Ok(Rc::new(Context(renderer))) +} + +#[derive(Debug)] +struct Context(Rc); + +impl GfxContext for Context { + fn reset_status(&self) -> Option { + None + } + + fn render_node(&self) -> Rc { + self.0.device.render_node.clone() + } + + fn formats(&self) -> Rc> { + self.0.formats.clone() + } + + fn dmabuf_img(self: Rc, buf: &DmaBuf) -> Result, GfxError> { + self.0 + .import_dmabuf(buf) + .map(|v| v as _) + .map_err(|e| e.into()) + } + + fn shmem_texture( + self: Rc, + old: Option>, + data: &[Cell], + format: &'static Format, + width: i32, + height: i32, + stride: i32, + ) -> Result, GfxError> { + if let Some(old) = old { + let old = old.into_vk(&self.0.device.device); + let shm = match &old.ty { + VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Internal(shm) => shm, + }; + if old.width as i32 == width + && old.height as i32 == height + && shm.stride as i32 == stride + && old.format.vk_format == format.vk_format + { + shm.upload(data)?; + return Ok(old); + } + } + let tex = self + .0 + .create_shm_texture(format, width, height, stride, data, false)?; + Ok(tex as _) + } + + fn gbm(&self) -> &GbmDevice { + &self.0.device.gbm + } + + fn gfx_api(&self) -> GfxApi { + GfxApi::Vulkan + } +} + +impl Drop for Context { + fn drop(&mut self) { + self.0.on_drop(); + } +} diff --git a/src/gfx_apis/vulkan/allocator.rs b/src/gfx_apis/vulkan/allocator.rs new file mode 100644 index 00000000..83f661ba --- /dev/null +++ b/src/gfx_apis/vulkan/allocator.rs @@ -0,0 +1,128 @@ +use { + crate::{ + gfx_apis::vulkan::{device::VulkanDevice, instance::API_VERSION, VulkanError}, + utils::{numcell::NumCell, ptr_ext::MutPtrExt}, + }, + ash::vk::{DeviceMemory, DeviceSize, MemoryRequirements}, + gpu_alloc::{Config, GpuAllocator, MemoryBlock, Request, UsageFlags}, + gpu_alloc_ash::AshMemoryDevice, + std::{ + cell::{Cell, UnsafeCell}, + rc::Rc, + }, +}; + +pub struct VulkanAllocator { + pub(super) device: Rc, + pub(super) non_coherent_atom_mask: u64, + allocator: UnsafeCell>, + total: NumCell, +} + +pub struct VulkanAllocation { + pub(super) allocator: Rc, + pub(super) memory: DeviceMemory, + pub(super) offset: DeviceSize, + pub(super) mem: Option<*mut u8>, + pub(super) size: DeviceSize, + block: Cell>>, +} + +impl Drop for VulkanAllocation { + fn drop(&mut self) { + unsafe { + self.allocator.total.fetch_sub(self.size); + let mut block = self.block.take().unwrap(); + if let Some(_ptr) = self.mem { + // log::info!("free = {:?} - {:?} ({})", ptr, ptr.add(block.size() as usize), block.size()); + block.unmap(AshMemoryDevice::wrap(&self.allocator.device.device)); + } + self.allocator + .allocator + .get() + .deref_mut() + .dealloc(AshMemoryDevice::wrap(&self.allocator.device.device), block); + } + } +} + +impl VulkanDevice { + pub fn create_allocator(self: &Rc) -> Result, VulkanError> { + let config = Config::i_am_prototyping(); + let props = unsafe { + gpu_alloc_ash::device_properties( + &self.instance.instance, + API_VERSION, + self.physical_device, + ) + }; + let mut props = props.map_err(VulkanError::GetDeviceProperties)?; + props.buffer_device_address = false; + let non_coherent_atom_size = props.non_coherent_atom_size; + let allocator = GpuAllocator::new(config, props); + Ok(Rc::new(VulkanAllocator { + device: self.clone(), + non_coherent_atom_mask: non_coherent_atom_size - 1, + allocator: UnsafeCell::new(allocator), + total: Default::default(), + })) + } +} + +impl VulkanAllocator { + fn allocator(&self) -> &mut GpuAllocator { + unsafe { self.allocator.get().deref_mut() } + } + + pub fn alloc( + self: &Rc, + req: &MemoryRequirements, + usage: UsageFlags, + map: bool, + ) -> Result { + let request = Request { + size: req.size, + align_mask: req.alignment - 1, + usage, + memory_types: req.memory_type_bits, + }; + let block = unsafe { + self.allocator() + .alloc(AshMemoryDevice::wrap(&self.device.device), request) + }; + let mut block = block.map_err(VulkanError::AllocateMemory2)?; + let ptr = match map { + true => { + let ptr = unsafe { + block.map( + AshMemoryDevice::wrap(&self.device.device), + 0, + block.size() as usize, + ) + }; + Some(ptr.map_err(VulkanError::MapMemory)?.as_ptr()) + } + false => None, + }; + self.total.fetch_add(block.size()); + Ok(VulkanAllocation { + allocator: self.clone(), + memory: *block.memory(), + offset: block.offset(), + mem: ptr, + size: block.size(), + block: Cell::new(Some(block)), + }) + } +} + +impl Drop for VulkanAllocator { + fn drop(&mut self) { + unsafe { + self.allocator + .get() + .deref_mut() + .cleanup(AshMemoryDevice::wrap(&self.device.device)); + } + } +} diff --git a/src/gfx_apis/vulkan/command.rs b/src/gfx_apis/vulkan/command.rs new file mode 100644 index 00000000..e45a2374 --- /dev/null +++ b/src/gfx_apis/vulkan/command.rs @@ -0,0 +1,69 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + CommandBuffer, CommandBufferAllocateInfo, CommandBufferLevel, CommandPool, + CommandPoolCreateFlags, CommandPoolCreateInfo, + }, + std::rc::Rc, +}; + +pub struct VulkanCommandPool { + pub(super) device: Rc, + pub(super) pool: CommandPool, +} + +pub struct VulkanCommandBuffer { + pub(super) pool: Rc, + pub(super) buffer: CommandBuffer, +} + +impl Drop for VulkanCommandPool { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_command_pool(self.pool, None); + } + } +} + +impl Drop for VulkanCommandBuffer { + fn drop(&mut self) { + unsafe { + self.pool + .device + .device + .free_command_buffers(self.pool.pool, &[self.buffer]); + } + } +} + +impl VulkanCommandPool { + pub fn allocate_buffer(self: &Rc) -> Result, VulkanError> { + let create_info = CommandBufferAllocateInfo::builder() + .command_pool(self.pool) + .command_buffer_count(1) + .level(CommandBufferLevel::PRIMARY); + let buffer = unsafe { self.device.device.allocate_command_buffers(&create_info) }; + let mut buffer = buffer.map_err(VulkanError::AllocateCommandBuffer)?; + assert_eq!(buffer.len(), 1); + Ok(Rc::new(VulkanCommandBuffer { + pool: self.clone(), + buffer: buffer.pop().unwrap(), + })) + } +} + +impl VulkanDevice { + pub fn create_command_pool(self: &Rc) -> Result, VulkanError> { + let info = CommandPoolCreateInfo::builder() + .queue_family_index(self.graphics_queue_idx) + .flags( + CommandPoolCreateFlags::TRANSIENT | CommandPoolCreateFlags::RESET_COMMAND_BUFFER, + ); + let pool = unsafe { self.device.create_command_pool(&info, None) }; + let pool = pool.map_err(VulkanError::AllocateCommandPool)?; + Ok(Rc::new(VulkanCommandPool { + device: self.clone(), + pool, + })) + } +} diff --git a/src/gfx_apis/vulkan/descriptor.rs b/src/gfx_apis/vulkan/descriptor.rs new file mode 100644 index 00000000..006efa7c --- /dev/null +++ b/src/gfx_apis/vulkan/descriptor.rs @@ -0,0 +1,49 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, sampler::VulkanSampler, VulkanError}, + ash::vk::{ + DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags, + DescriptorSetLayoutCreateInfo, DescriptorType, ShaderStageFlags, + }, + std::{rc::Rc, slice}, +}; + +pub(super) struct VulkanDescriptorSetLayout { + pub(super) device: Rc, + pub(super) layout: DescriptorSetLayout, + pub(super) _sampler: Rc, +} + +impl Drop for VulkanDescriptorSetLayout { + fn drop(&mut self) { + unsafe { + self.device + .device + .destroy_descriptor_set_layout(self.layout, None); + } + } +} + +impl VulkanDevice { + pub(super) fn create_descriptor_set_layout( + &self, + sampler: &Rc, + ) -> Result, VulkanError> { + let immutable_sampler = [sampler.sampler]; + let binding = DescriptorSetLayoutBinding::builder() + .stage_flags(ShaderStageFlags::FRAGMENT) + .immutable_samplers(&immutable_sampler) + .descriptor_count(1) + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .build(); + let create_info = DescriptorSetLayoutCreateInfo::builder() + .bindings(slice::from_ref(&binding)) + .flags(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR); + let layout = unsafe { self.device.create_descriptor_set_layout(&create_info, None) }; + let layout = layout.map_err(VulkanError::CreateDescriptorSetLayout)?; + Ok(Rc::new(VulkanDescriptorSetLayout { + device: sampler.device.clone(), + layout, + _sampler: sampler.clone(), + })) + } +} diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs new file mode 100644 index 00000000..0d0a6886 --- /dev/null +++ b/src/gfx_apis/vulkan/device.rs @@ -0,0 +1,343 @@ +use { + crate::{ + format::XRGB8888, + gfx_apis::vulkan::{ + format::VulkanFormat, + instance::{ + map_extension_properties, ApiVersionDisplay, Extensions, VulkanInstance, + API_VERSION, + }, + util::OnDrop, + VulkanError, + }, + video::{drm::Drm, gbm::GbmDevice}, + }, + ahash::AHashMap, + arrayvec::ArrayVec, + ash::{ + extensions::khr::{ExternalFenceFd, ExternalMemoryFd, ExternalSemaphoreFd, PushDescriptor}, + vk::{ + DeviceCreateInfo, DeviceMemory, DeviceQueueCreateInfo, ExtExternalMemoryDmaBufFn, + ExtImageDrmFormatModifierFn, ExtPhysicalDeviceDrmFn, ExtQueueFamilyForeignFn, + ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags, + ExternalSemaphoreProperties, KhrDriverPropertiesFn, KhrExternalFenceFdFn, + KhrExternalMemoryFdFn, KhrExternalSemaphoreFdFn, KhrPushDescriptorFn, + MemoryPropertyFlags, MemoryType, PhysicalDevice, PhysicalDeviceDriverProperties, + PhysicalDeviceDriverPropertiesKHR, PhysicalDeviceDrmPropertiesEXT, + PhysicalDeviceDynamicRenderingFeatures, PhysicalDeviceExternalSemaphoreInfo, + PhysicalDeviceProperties, PhysicalDeviceProperties2, + PhysicalDeviceSynchronization2Features, PhysicalDeviceTimelineSemaphoreFeatures, Queue, + QueueFlags, MAX_MEMORY_TYPES, + }, + Device, + }, + isnt::std_1::collections::IsntHashMap2Ext, + std::{ + ffi::{CStr, CString}, + rc::Rc, + }, + uapi::Ustr, +}; + +pub struct VulkanDevice { + pub(super) physical_device: PhysicalDevice, + pub(super) render_node: Rc, + pub(super) gbm: GbmDevice, + pub(super) instance: Rc, + pub(super) device: Device, + pub(super) external_memory_fd: ExternalMemoryFd, + pub(super) external_semaphore_fd: ExternalSemaphoreFd, + pub(super) external_fence_fd: ExternalFenceFd, + pub(super) push_descriptor: PushDescriptor, + pub(super) formats: AHashMap, + pub(super) memory_types: ArrayVec, + pub(super) graphics_queue: Queue, + pub(super) graphics_queue_idx: u32, +} + +impl Drop for VulkanDevice { + fn drop(&mut self) { + unsafe { + self.device.destroy_device(None); + } + } +} + +impl VulkanDevice { + pub(super) fn find_memory_type( + &self, + flags: MemoryPropertyFlags, + memory_type_bits: u32, + ) -> Option { + for (idx, ty) in self.memory_types.iter().enumerate() { + if memory_type_bits & (1 << idx as u32) != 0 { + if ty.property_flags.contains(flags) { + return Some(idx as _); + } + } + } + None + } +} + +struct FreeMem<'a>(&'a Device, DeviceMemory); + +impl<'a> Drop for FreeMem<'a> { + fn drop(&mut self) { + unsafe { + self.0.free_memory(self.1, None); + } + } +} + +impl VulkanInstance { + fn get_device_extensions(&self, phy_dev: PhysicalDevice) -> Result { + unsafe { + self.instance + .enumerate_device_extension_properties(phy_dev) + .map(map_extension_properties) + .map_err(VulkanError::DeviceExtensions) + } + } + + fn find_dev(&self, drm: &Drm) -> Result { + let stat = match uapi::fstat(drm.raw()) { + Ok(s) => s, + Err(e) => return Err(VulkanError::Fstat(e.into())), + }; + let dev = stat.st_rdev; + log::info!( + "Searching for vulkan device with devnum {}:{}", + uapi::major(dev), + uapi::minor(dev) + ); + let phy_devs = unsafe { self.instance.enumerate_physical_devices() }; + let phy_devs = match phy_devs { + Ok(d) => d, + Err(e) => return Err(VulkanError::EnumeratePhysicalDevices(e)), + }; + let mut devices = vec![]; + for phy_dev in phy_devs { + let props = unsafe { self.instance.get_physical_device_properties(phy_dev) }; + if props.api_version < API_VERSION { + devices.push((props, None, None)); + continue; + } + let extensions = match self.get_device_extensions(phy_dev) { + Ok(e) => e, + Err(e) => { + log::error!( + "Could not enumerate extensions of device with id {}: {:#}", + props.device_id, + e + ); + devices.push((props, None, None)); + continue; + } + }; + if !extensions.contains_key(ExtPhysicalDeviceDrmFn::name()) { + devices.push((props, Some(extensions), None)); + continue; + } + let has_driver_props = extensions.contains_key(KhrDriverPropertiesFn::name()); + let mut drm_props = PhysicalDeviceDrmPropertiesEXT::builder().build(); + let mut driver_props = PhysicalDeviceDriverPropertiesKHR::builder().build(); + let mut props2 = PhysicalDeviceProperties2::builder().push_next(&mut drm_props); + if has_driver_props { + props2 = props2.push_next(&mut driver_props); + } + unsafe { + self.instance + .get_physical_device_properties2(phy_dev, &mut props2); + } + let primary_dev = + uapi::makedev(drm_props.primary_major as _, drm_props.primary_minor as _); + let render_dev = + uapi::makedev(drm_props.render_major as _, drm_props.render_minor as _); + if primary_dev == dev || render_dev == dev { + log::info!("Device with id {} matches", props.device_id); + log_device(&props, Some(&extensions), Some(&driver_props)); + return Ok(phy_dev); + } + devices.push((props, Some(extensions), Some(driver_props))); + } + if devices.is_empty() { + log::warn!("Found no devices"); + } else { + log::warn!("Found the following devices but none matches:"); + for (props, extensions, driver_props) in devices.iter() { + log::warn!("Found the following devices but none matches:"); + log::warn!("-----"); + log_device(props, extensions.as_ref(), driver_props.as_ref()); + } + } + Err(VulkanError::NoDeviceFound(dev)) + } + + fn find_graphics_queue(&self, phy_dev: PhysicalDevice) -> Result { + let props = unsafe { + self.instance + .get_physical_device_queue_family_properties(phy_dev) + }; + props + .iter() + .position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS)) + .map(|v| v as _) + .ok_or(VulkanError::NoGraphicsQueue) + } + + fn supports_semaphore_import(&self, phy_dev: PhysicalDevice) -> bool { + let mut props = ExternalSemaphoreProperties::builder().build(); + let info = PhysicalDeviceExternalSemaphoreInfo::builder() + .handle_type(ExternalSemaphoreHandleTypeFlags::OPAQUE_FD) + .build(); + unsafe { + self.instance + .get_physical_device_external_semaphore_properties(phy_dev, &info, &mut props); + } + props + .external_semaphore_features + .contains(ExternalSemaphoreFeatureFlags::IMPORTABLE) + } + + pub fn create_device(self: &Rc, drm: &Drm) -> Result, VulkanError> { + let render_node = drm + .get_render_node() + .map_err(VulkanError::FetchRenderNode)? + .ok_or(VulkanError::NoRenderNode) + .map(Rc::new)?; + let gbm = GbmDevice::new(drm).map_err(VulkanError::Gbm)?; + let phy_dev = self.find_dev(drm)?; + let extensions = self.get_device_extensions(phy_dev)?; + for &ext in REQUIRED_DEVICE_EXTENSIONS { + if extensions.not_contains_key(ext) { + return Err(VulkanError::MissingDeviceExtension(ext)); + } + } + let graphics_queue_idx = self.find_graphics_queue(phy_dev)?; + if !self.supports_semaphore_import(phy_dev) { + return Err(VulkanError::SyncobjImport); + } + let enabled_extensions: Vec<_> = REQUIRED_DEVICE_EXTENSIONS + .iter() + .map(|n| n.as_ptr()) + .collect(); + let mut semaphore_features = + PhysicalDeviceTimelineSemaphoreFeatures::builder().timeline_semaphore(true); + let mut synchronization2_features = + PhysicalDeviceSynchronization2Features::builder().synchronization2(true); + let mut dynamic_rendering_features = + PhysicalDeviceDynamicRenderingFeatures::builder().dynamic_rendering(true); + let queue_create_info = DeviceQueueCreateInfo::builder() + .queue_family_index(graphics_queue_idx) + .queue_priorities(&[1.0]) + .build(); + let device_create_info = DeviceCreateInfo::builder() + .push_next(&mut semaphore_features) + .push_next(&mut synchronization2_features) + .push_next(&mut dynamic_rendering_features) + .queue_create_infos(std::slice::from_ref(&queue_create_info)) + .enabled_extension_names(&enabled_extensions); + let device = unsafe { + self.instance + .create_device(phy_dev, &device_create_info, None) + }; + let device = match device { + Ok(d) => d, + Err(e) => return Err(VulkanError::CreateDevice(e)), + }; + let destroy_device = OnDrop(|| unsafe { device.destroy_device(None) }); + let formats = self.load_formats(phy_dev)?; + let supports_xrgb8888 = formats + .get(&XRGB8888.drm) + .map(|f| { + let mut supports_rendering = false; + let mut supports_texturing = false; + f.modifiers.values().for_each(|v| { + supports_rendering |= v.render_max_extents.is_some(); + supports_texturing |= v.texture_max_extents.is_some(); + }); + supports_rendering && supports_texturing + }) + .unwrap_or(false); + if !supports_xrgb8888 { + return Err(VulkanError::XRGB8888); + } + destroy_device.forget(); + let external_memory_fd = ExternalMemoryFd::new(&self.instance, &device); + let external_semaphore_fd = ExternalSemaphoreFd::new(&self.instance, &device); + let external_fence_fd = ExternalFenceFd::new(&self.instance, &device); + let push_descriptor = PushDescriptor::new(&self.instance, &device); + let memory_properties = + unsafe { self.instance.get_physical_device_memory_properties(phy_dev) }; + let memory_types = memory_properties.memory_types + [..memory_properties.memory_type_count as _] + .iter() + .copied() + .collect(); + let graphics_queue = unsafe { device.get_device_queue(graphics_queue_idx, 0) }; + Ok(Rc::new(VulkanDevice { + physical_device: phy_dev, + render_node, + gbm, + instance: self.clone(), + device, + external_memory_fd, + external_semaphore_fd, + external_fence_fd, + push_descriptor, + formats, + memory_types, + graphics_queue, + graphics_queue_idx, + })) + } +} + +const REQUIRED_DEVICE_EXTENSIONS: &[&CStr] = &[ + KhrExternalMemoryFdFn::name(), + KhrExternalSemaphoreFdFn::name(), + KhrExternalFenceFdFn::name(), + ExtExternalMemoryDmaBufFn::name(), + ExtQueueFamilyForeignFn::name(), + ExtImageDrmFormatModifierFn::name(), + KhrPushDescriptorFn::name(), +]; + +fn log_device( + props: &PhysicalDeviceProperties, + extensions: Option<&Extensions>, + driver_props: Option<&PhysicalDeviceDriverProperties>, +) { + log::info!(" api version: {}", ApiVersionDisplay(props.api_version)); + log::info!( + " driver version: {}", + ApiVersionDisplay(props.driver_version) + ); + log::info!(" vendor id: {}", props.vendor_id); + log::info!(" device id: {}", props.device_id); + log::info!(" device type: {:?}", props.device_type); + unsafe { + log::info!( + " device name: {}", + Ustr::from_ptr(props.device_name.as_ptr()).display() + ); + } + if props.api_version < API_VERSION { + log::warn!(" device does not support vulkan 1.3"); + } + if let Some(extensions) = extensions { + if !extensions.contains_key(ExtPhysicalDeviceDrmFn::name()) { + log::warn!(" device does support not the VK_EXT_physical_device_drm extension"); + } + } + if let Some(driver_props) = driver_props { + unsafe { + log::info!( + " driver: {} ({})", + Ustr::from_ptr(driver_props.driver_name.as_ptr()).display(), + Ustr::from_ptr(driver_props.driver_info.as_ptr()).display() + ); + } + } +} diff --git a/src/gfx_apis/vulkan/fence.rs b/src/gfx_apis/vulkan/fence.rs new file mode 100644 index 00000000..defa0923 --- /dev/null +++ b/src/gfx_apis/vulkan/fence.rs @@ -0,0 +1,49 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + ExportFenceCreateInfo, ExternalFenceHandleTypeFlags, Fence, FenceCreateInfo, + FenceGetFdInfoKHR, + }, + std::rc::Rc, + uapi::OwnedFd, +}; + +pub struct VulkanFence { + pub(super) device: Rc, + pub(super) fence: Fence, +} + +impl Drop for VulkanFence { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_fence(self.fence, None); + } + } +} + +impl VulkanDevice { + pub fn create_fence(self: &Rc) -> Result, VulkanError> { + let fence = { + let mut export_info = ExportFenceCreateInfo::builder() + .handle_types(ExternalFenceHandleTypeFlags::SYNC_FD); + let create_info = FenceCreateInfo::builder().push_next(&mut export_info); + let fence = unsafe { self.device.create_fence(&create_info, None) }; + fence.map_err(VulkanError::CreateFence)? + }; + Ok(Rc::new(VulkanFence { + device: self.clone(), + fence, + })) + } +} + +impl VulkanFence { + pub fn export_syncfile(&self) -> Result, VulkanError> { + let info = FenceGetFdInfoKHR::builder() + .fence(self.fence) + .handle_type(ExternalFenceHandleTypeFlags::SYNC_FD); + let res = unsafe { self.device.external_fence_fd.get_fence_fd(&info) }; + res.map_err(VulkanError::ExportSyncFile) + .map(|fd| Rc::new(OwnedFd::new(fd))) + } +} diff --git a/src/gfx_apis/vulkan/format.rs b/src/gfx_apis/vulkan/format.rs new file mode 100644 index 00000000..cef95cef --- /dev/null +++ b/src/gfx_apis/vulkan/format.rs @@ -0,0 +1,277 @@ +use { + crate::{ + format::{Format, FORMATS}, + gfx_apis::vulkan::{instance::VulkanInstance, VulkanError}, + video::Modifier, + }, + ahash::AHashMap, + ash::{ + vk, + vk::{ + DrmFormatModifierPropertiesEXT, DrmFormatModifierPropertiesListEXT, + ExternalImageFormatProperties, ExternalMemoryFeatureFlags, + ExternalMemoryHandleTypeFlags, FormatFeatureFlags, FormatProperties2, + ImageFormatProperties2, ImageTiling, ImageType, ImageUsageFlags, PhysicalDevice, + PhysicalDeviceExternalImageFormatInfo, PhysicalDeviceImageDrmFormatModifierInfoEXT, + PhysicalDeviceImageFormatInfo2, SharingMode, + }, + }, + isnt::std_1::collections::IsntHashMapExt, +}; + +#[derive(Debug)] +pub struct VulkanFormat { + pub format: &'static Format, + pub modifiers: AHashMap, + pub shm: Option, + pub features: FormatFeatureFlags, +} + +#[derive(Debug)] +pub struct VulkanFormatFeatures { + pub linear_sampling: bool, +} + +#[derive(Debug)] +pub struct VulkanModifier { + pub modifier: Modifier, + pub planes: usize, + pub features: FormatFeatureFlags, + pub render_max_extents: Option, + pub texture_max_extents: Option, +} + +#[derive(Copy, Clone, Debug)] +pub struct VulkanMaxExtents { + pub width: u32, + pub height: u32, +} + +#[derive(Debug)] +pub struct VulkanShmFormat { + pub max_extents: VulkanMaxExtents, +} + +const FRAMEBUFFER_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::COLOR_ATTACHMENT.as_raw() + | FormatFeatureFlags::COLOR_ATTACHMENT_BLEND.as_raw(), +); +const TEX_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::SAMPLED_IMAGE.as_raw() + | FormatFeatureFlags::TRANSFER_SRC.as_raw() + | FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR.as_raw(), +); +const SHM_FEATURES: FormatFeatureFlags = FormatFeatureFlags::from_raw( + 0 | FormatFeatureFlags::TRANSFER_SRC.as_raw() + | FormatFeatureFlags::TRANSFER_DST.as_raw() + | TEX_FEATURES.as_raw(), +); + +const FRAMEBUFFER_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + 0 | ImageUsageFlags::COLOR_ATTACHMENT.as_raw() | ImageUsageFlags::TRANSFER_SRC.as_raw(), +); +const TEX_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + 0 | ImageUsageFlags::SAMPLED.as_raw() | ImageUsageFlags::TRANSFER_SRC.as_raw(), +); +const SHM_USAGE: ImageUsageFlags = ImageUsageFlags::from_raw( + 0 | ImageUsageFlags::TRANSFER_SRC.as_raw() + | ImageUsageFlags::TRANSFER_DST.as_raw() + | TEX_USAGE.as_raw(), +); + +impl VulkanInstance { + pub(super) fn load_formats( + &self, + phy_dev: PhysicalDevice, + ) -> Result, VulkanError> { + let mut res = AHashMap::new(); + for format in FORMATS { + self.load_format(phy_dev, format, &mut res)?; + } + Ok(res) + } + + fn load_format( + &self, + phy_dev: PhysicalDevice, + format: &'static Format, + dst: &mut AHashMap, + ) -> Result<(), VulkanError> { + let mut modifier_props = DrmFormatModifierPropertiesListEXT::builder().build(); + let mut format_properties = FormatProperties2::builder() + .push_next(&mut modifier_props) + .build(); + unsafe { + self.instance.get_physical_device_format_properties2( + phy_dev, + format.vk_format, + &mut format_properties, + ); + } + let shm = self.load_shm_format(phy_dev, format, &format_properties)?; + let modifiers = self.load_drm_format(phy_dev, format, &modifier_props)?; + if shm.is_some() || modifiers.is_not_empty() { + dst.insert( + format.drm, + VulkanFormat { + format, + modifiers, + shm, + features: format_properties.format_properties.optimal_tiling_features, + }, + ); + } + Ok(()) + } + + fn load_shm_format( + &self, + phy_dev: PhysicalDevice, + format: &Format, + props: &FormatProperties2, + ) -> Result, VulkanError> { + if !props + .format_properties + .optimal_tiling_features + .contains(SHM_FEATURES) + { + return Ok(None); + } + let format_info = PhysicalDeviceImageFormatInfo2::builder() + .ty(ImageType::TYPE_2D) + .format(format.vk_format) + .tiling(ImageTiling::OPTIMAL) + .usage(SHM_USAGE); + let mut format_properties = ImageFormatProperties2::builder(); + let res = unsafe { + self.instance.get_physical_device_image_format_properties2( + phy_dev, + &format_info, + &mut format_properties, + ) + }; + if let Err(e) = res { + return match e { + vk::Result::ERROR_FORMAT_NOT_SUPPORTED => Ok(None), + _ => Err(VulkanError::LoadImageProperties(e)), + }; + } + Ok(Some(VulkanShmFormat { + max_extents: VulkanMaxExtents { + width: format_properties.image_format_properties.max_extent.width, + height: format_properties.image_format_properties.max_extent.height, + }, + })) + } + + fn load_drm_format( + &self, + phy_dev: PhysicalDevice, + format: &Format, + props: &DrmFormatModifierPropertiesListEXT, + ) -> Result, VulkanError> { + if props.drm_format_modifier_count == 0 { + return Ok(AHashMap::new()); + } + let mut drm_mods = vec![ + DrmFormatModifierPropertiesEXT::default(); + props.drm_format_modifier_count as usize + ]; + let mut modifier_props = DrmFormatModifierPropertiesListEXT::builder() + .drm_format_modifier_properties(&mut drm_mods) + .build(); + let mut format_properties = FormatProperties2::builder() + .push_next(&mut modifier_props) + .build(); + unsafe { + self.instance.get_physical_device_format_properties2( + phy_dev, + format.vk_format, + &mut format_properties, + ); + }; + let mut mods = AHashMap::new(); + for modifier in drm_mods { + let render_max_extents = self.get_max_extents( + phy_dev, + format, + FRAMEBUFFER_FEATURES, + FRAMEBUFFER_USAGE, + &modifier, + )?; + let texture_max_extents = + self.get_max_extents(phy_dev, format, TEX_FEATURES, TEX_USAGE, &modifier)?; + mods.insert( + modifier.drm_format_modifier, + VulkanModifier { + modifier: modifier.drm_format_modifier, + planes: modifier.drm_format_modifier_plane_count as _, + features: modifier.drm_format_modifier_tiling_features, + render_max_extents, + texture_max_extents, + }, + ); + } + Ok(mods) + } + + fn get_max_extents( + &self, + phy_dev: PhysicalDevice, + format: &Format, + features: FormatFeatureFlags, + usage: ImageUsageFlags, + props: &DrmFormatModifierPropertiesEXT, + ) -> Result, VulkanError> { + if !props.drm_format_modifier_tiling_features.contains(features) { + return Ok(None); + } + let mut modifier_info = PhysicalDeviceImageDrmFormatModifierInfoEXT::builder() + .drm_format_modifier(props.drm_format_modifier) + .sharing_mode(SharingMode::EXCLUSIVE) + .build(); + let mut external_image_format_info = PhysicalDeviceExternalImageFormatInfo::builder() + .handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .build(); + let image_format_info = PhysicalDeviceImageFormatInfo2::builder() + .ty(ImageType::TYPE_2D) + .format(format.vk_format) + .usage(usage) + .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .push_next(&mut external_image_format_info) + .push_next(&mut modifier_info) + .build(); + + let mut external_image_format_props = ExternalImageFormatProperties::builder().build(); + let mut image_format_props = ImageFormatProperties2::builder() + .push_next(&mut external_image_format_props) + .build(); + + let res = unsafe { + self.instance.get_physical_device_image_format_properties2( + phy_dev, + &image_format_info, + &mut image_format_props, + ) + }; + + if let Err(e) = res { + return match e { + vk::Result::ERROR_FORMAT_NOT_SUPPORTED => Ok(None), + _ => Err(VulkanError::LoadImageProperties(e)), + }; + } + let importable = external_image_format_props + .external_memory_properties + .external_memory_features + .contains(ExternalMemoryFeatureFlags::IMPORTABLE); + if !importable { + return Ok(None); + } + + Ok(Some(VulkanMaxExtents { + width: image_format_props.image_format_properties.max_extent.width, + height: image_format_props.image_format_properties.max_extent.height, + })) + } +} diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs new file mode 100644 index 00000000..2176ddc6 --- /dev/null +++ b/src/gfx_apis/vulkan/image.rs @@ -0,0 +1,582 @@ +use { + crate::{ + format::Format, + gfx_api::{GfxApiOpt, GfxError, GfxFramebuffer, GfxImage, GfxTexture}, + gfx_apis::vulkan::{ + allocator::VulkanAllocation, device::VulkanDevice, format::VulkanMaxExtents, + renderer::VulkanRenderer, util::OnDrop, VulkanError, + }, + theme::Color, + utils::clonecell::CloneCell, + video::{ + dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, + Modifier, + }, + }, + ash::vk::{ + BindImageMemoryInfo, BindImagePlaneMemoryInfo, ComponentMapping, ComponentSwizzle, + DeviceMemory, DeviceSize, Extent3D, ExternalMemoryHandleTypeFlags, + ExternalMemoryImageCreateInfo, FormatFeatureFlags, Image, ImageAspectFlags, + ImageCreateFlags, ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, + ImageLayout, ImageMemoryRequirementsInfo2, ImagePlaneMemoryRequirementsInfo, + ImageSubresourceRange, ImageTiling, ImageType, ImageUsageFlags, ImageView, + ImageViewCreateInfo, ImageViewType, ImportMemoryFdInfoKHR, MemoryAllocateInfo, + MemoryDedicatedAllocateInfo, MemoryPropertyFlags, MemoryRequirements2, SampleCountFlags, + SharingMode, SubresourceLayout, + }, + gpu_alloc::UsageFlags, + std::{ + any::Any, + cell::{Cell, RefCell}, + fmt::{Debug, Formatter}, + mem, + rc::Rc, + }, +}; + +pub struct VulkanDmaBufImageTemplate { + pub(super) renderer: Rc, + pub(super) format: &'static Format, + pub(super) width: u32, + pub(super) height: u32, + pub(super) modifier: Modifier, + pub(super) disjoint: bool, + pub(super) planes: PlaneVec, + pub(super) render_max_extents: Option, + pub(super) texture_max_extents: Option, +} + +pub struct VulkanImage { + pub(super) renderer: Rc, + pub(super) format: &'static Format, + pub(super) width: u32, + pub(super) height: u32, + pub(super) stride: u32, + pub(super) texture_view: ImageView, + pub(super) render_view: Option, + pub(super) image: Image, + pub(super) is_undefined: Cell, + pub(super) ty: VulkanImageMemory, + pub(super) render_ops: CloneCell>, +} + +pub enum VulkanImageMemory { + DmaBuf(VulkanDmaBufImage), + Internal(VulkanShmImage), +} + +pub struct VulkanDmaBufImage { + pub(super) template: Rc, + pub(super) mems: PlaneVec, +} + +pub struct VulkanShmImage { + pub(super) to_flush: RefCell>>, + pub(super) size: DeviceSize, + pub(super) stride: u32, + pub(super) _allocation: VulkanAllocation, +} + +impl Drop for VulkanDmaBufImage { + fn drop(&mut self) { + unsafe { + for &mem in &self.mems { + self.template.renderer.device.device.free_memory(mem, None); + } + } + } +} + +impl Drop for VulkanImage { + fn drop(&mut self) { + unsafe { + self.renderer + .device + .device + .destroy_image_view(self.texture_view, None); + if let Some(render_view) = self.render_view { + self.renderer + .device + .device + .destroy_image_view(render_view, None); + } + self.renderer.device.device.destroy_image(self.image, None); + } + } +} + +impl VulkanShmImage { + pub fn upload(&self, buffer: &[Cell]) -> Result<(), VulkanError> { + let buffer = unsafe { + std::slice::from_raw_parts(buffer.as_ptr() as *const u8, buffer.len()).to_vec() + }; + *self.to_flush.borrow_mut() = Some(buffer); + Ok(()) + } +} + +impl VulkanRenderer { + pub fn create_shm_texture( + self: &Rc, + format: &'static Format, + width: i32, + height: i32, + stride: i32, + data: &[Cell], + for_download: bool, + ) -> Result, VulkanError> { + if width <= 0 || height <= 0 || stride <= 0 { + return Err(VulkanError::NonPositiveImageSize); + } + let width = width as u32; + let height = height as u32; + let stride = stride as u32; + if stride % format.bpp != 0 || stride / format.bpp < width { + return Err(VulkanError::InvalidStride); + } + let vk_format = self + .device + .formats + .get(&format.drm) + .ok_or(VulkanError::FormatNotSupported)?; + let shm = vk_format.shm.as_ref().ok_or(VulkanError::ShmNotSupported)?; + if width > shm.max_extents.width || height > shm.max_extents.height { + return Err(VulkanError::ImageTooLarge); + } + let size = stride.checked_mul(height).ok_or(VulkanError::ShmOverflow)?; + let usage = ImageUsageFlags::TRANSFER_SRC + | match for_download { + true => ImageUsageFlags::COLOR_ATTACHMENT, + false => ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, + }; + let create_info = ImageCreateInfo::builder() + .image_type(ImageType::TYPE_2D) + .format(format.vk_format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::OPTIMAL) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width, + height, + depth: 1, + }) + .usage(usage) + .build(); + let image = unsafe { self.device.device.create_image(&create_info, None) }; + let image = image.map_err(VulkanError::CreateImage)?; + let destroy_image = OnDrop(|| unsafe { self.device.device.destroy_image(image, None) }); + let memory_requirements = + unsafe { self.device.device.get_image_memory_requirements(image) }; + let allocation = + self.allocator + .alloc(&memory_requirements, UsageFlags::FAST_DEVICE_ACCESS, false)?; + let res = unsafe { + self.device + .device + .bind_image_memory(image, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindImageMemory)?; + let image_view_create_info = ImageViewCreateInfo::builder() + .image(image) + .format(format.vk_format) + .view_type(ImageViewType::TYPE_2D) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { + self.device + .device + .create_image_view(&image_view_create_info, None) + }; + let view = view.map_err(VulkanError::CreateImageView)?; + let shm = VulkanShmImage { + to_flush: Default::default(), + size: size as u64, + stride, + _allocation: allocation, + }; + shm.upload(data)?; + destroy_image.forget(); + Ok(Rc::new(VulkanImage { + renderer: self.clone(), + format, + width, + height, + stride, + texture_view: view, + render_view: None, + image, + is_undefined: Cell::new(true), + ty: VulkanImageMemory::Internal(shm), + render_ops: Default::default(), + })) + } + + pub fn import_dmabuf( + self: &Rc, + dmabuf: &DmaBuf, + ) -> Result, VulkanError> { + let format = self + .device + .formats + .get(&dmabuf.format.drm) + .ok_or(VulkanError::FormatNotSupported)?; + let modifier = format + .modifiers + .get(&dmabuf.modifier) + .ok_or(VulkanError::ModifierNotSupported)?; + if dmabuf.width <= 0 || dmabuf.height <= 0 { + return Err(VulkanError::NonPositiveImageSize); + } + let width = dmabuf.width as u32; + let height = dmabuf.height as u32; + let can_render = match &modifier.render_max_extents { + None => false, + Some(t) => width <= t.width && height <= t.height, + }; + let can_texture = match &modifier.texture_max_extents { + None => false, + Some(t) => width <= t.width && height <= t.height, + }; + if !can_render && !can_texture { + if modifier.render_max_extents.is_none() && modifier.texture_max_extents.is_none() { + return Err(VulkanError::ModifierUseNotSupported); + } + return Err(VulkanError::ImageTooLarge); + } + if modifier.planes != dmabuf.planes.len() { + return Err(VulkanError::BadPlaneCount); + } + let disjoint = dmabuf.is_disjoint(); + if disjoint && !modifier.features.contains(FormatFeatureFlags::DISJOINT) { + return Err(VulkanError::DisjointNotSupported); + } + Ok(Rc::new(VulkanDmaBufImageTemplate { + renderer: self.clone(), + format: dmabuf.format, + width, + height, + modifier: dmabuf.modifier, + disjoint, + planes: dmabuf.planes.clone(), + render_max_extents: modifier.render_max_extents, + texture_max_extents: modifier.texture_max_extents, + })) + } +} + +impl VulkanDevice { + pub fn create_image_view( + &self, + image: Image, + format: &'static Format, + for_rendering: bool, + ) -> Result { + let create_info = ImageViewCreateInfo::builder() + .image(image) + .view_type(ImageViewType::TYPE_2D) + .format(format.vk_format) + .components(ComponentMapping { + r: ComponentSwizzle::IDENTITY, + g: ComponentSwizzle::IDENTITY, + b: ComponentSwizzle::IDENTITY, + a: match format.has_alpha || for_rendering { + true => ComponentSwizzle::IDENTITY, + false => ComponentSwizzle::ONE, + }, + }) + .subresource_range(ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }); + let view = unsafe { self.device.create_image_view(&create_info, None) }; + view.map_err(VulkanError::CreateImageView) + } +} + +impl VulkanDmaBufImageTemplate { + pub fn create_framebuffer(self: &Rc) -> Result, VulkanError> { + self.create_image(true, None) + } + + pub fn create_texture( + self: &Rc, + shm: Option, + ) -> Result, VulkanError> { + self.create_image(false, shm) + } + + fn create_image( + self: &Rc, + for_rendering: bool, + shm: Option, + ) -> Result, VulkanError> { + let device = &self.renderer.device; + let max_extents = match for_rendering { + true => self.render_max_extents, + false => self.texture_max_extents, + }; + let max_extents = max_extents.ok_or(VulkanError::ModifierUseNotSupported)?; + if self.width > max_extents.width || self.height > max_extents.height { + return Err(VulkanError::ImageTooLarge); + } + let image = { + let plane_layouts: PlaneVec<_> = self + .planes + .iter() + .map(|p| SubresourceLayout { + offset: p.offset as _, + row_pitch: p.stride as _, + size: 0, + array_pitch: 0, + depth_pitch: 0, + }) + .collect(); + let mut mod_info = ImageDrmFormatModifierExplicitCreateInfoEXT::builder() + .drm_format_modifier(self.modifier) + .plane_layouts(&plane_layouts) + .build(); + let mut memory_image_create_info = ExternalMemoryImageCreateInfo::builder() + .handle_types(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .build(); + let flags = match self.disjoint { + true => ImageCreateFlags::DISJOINT, + false => ImageCreateFlags::empty(), + }; + let usage = ImageUsageFlags::TRANSFER_SRC + | match (for_rendering, shm.is_some()) { + (true, _) => ImageUsageFlags::COLOR_ATTACHMENT, + (false, false) => ImageUsageFlags::SAMPLED, + (false, true) => ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER_DST, + }; + let create_info = ImageCreateInfo::builder() + .image_type(ImageType::TYPE_2D) + .format(self.format.vk_format) + .mip_levels(1) + .array_layers(1) + .tiling(ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .samples(SampleCountFlags::TYPE_1) + .sharing_mode(SharingMode::EXCLUSIVE) + .initial_layout(ImageLayout::UNDEFINED) + .extent(Extent3D { + width: self.width, + height: self.height, + depth: 1, + }) + .usage(usage) + .flags(flags) + .push_next(&mut memory_image_create_info) + .push_next(&mut mod_info) + .build(); + let image = unsafe { device.device.create_image(&create_info, None) }; + image.map_err(VulkanError::CreateImage)? + }; + let destroy_image = OnDrop(|| unsafe { device.device.destroy_image(image, None) }); + let num_device_memories = match self.disjoint { + true => self.planes.len(), + false => 1, + }; + let mut device_memories = PlaneVec::new(); + let mut free_device_memories = PlaneVec::new(); + let mut bind_image_plane_memory_infos = PlaneVec::new(); + for plane_idx in 0..num_device_memories { + let dma_buf_plane = &self.planes[plane_idx]; + let memory_fd_properties = unsafe { + device.external_memory_fd.get_memory_fd_properties( + ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, + dma_buf_plane.fd.raw(), + ) + }; + let memory_fd_properties = + memory_fd_properties.map_err(VulkanError::MemoryFdProperties)?; + let mut image_memory_requirements_info = + ImageMemoryRequirementsInfo2::builder().image(image); + let mut image_plane_memory_requirements_info; + if self.disjoint { + let plane_aspect = match plane_idx { + 0 => ImageAspectFlags::MEMORY_PLANE_0_EXT, + 1 => ImageAspectFlags::MEMORY_PLANE_1_EXT, + 2 => ImageAspectFlags::MEMORY_PLANE_2_EXT, + 3 => ImageAspectFlags::MEMORY_PLANE_3_EXT, + _ => unreachable!(), + }; + image_plane_memory_requirements_info = + ImagePlaneMemoryRequirementsInfo::builder().plane_aspect(plane_aspect); + image_memory_requirements_info = image_memory_requirements_info + .push_next(&mut image_plane_memory_requirements_info); + bind_image_plane_memory_infos.push( + BindImagePlaneMemoryInfo::builder() + .plane_aspect(plane_aspect) + .build(), + ); + } + let mut memory_requirements = MemoryRequirements2::default(); + unsafe { + device.device.get_image_memory_requirements2( + &image_memory_requirements_info, + &mut memory_requirements, + ); + } + let memory_type_bits = memory_requirements.memory_requirements.memory_type_bits + & memory_fd_properties.memory_type_bits; + let memory_type_index = self + .renderer + .device + .find_memory_type(MemoryPropertyFlags::empty(), memory_type_bits) + .ok_or(VulkanError::MemoryType)?; + let fd = uapi::fcntl_dupfd_cloexec(dma_buf_plane.fd.raw(), 0) + .map_err(|e| VulkanError::Dupfd(e.into()))?; + let mut memory_dedicated_allocate_info = + MemoryDedicatedAllocateInfo::builder().image(image).build(); + let mut import_memory_fd_info = ImportMemoryFdInfoKHR::builder() + .fd(fd.raw()) + .handle_type(ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .build(); + let memory_allocate_info = MemoryAllocateInfo::builder() + .allocation_size(memory_requirements.memory_requirements.size) + .memory_type_index(memory_type_index) + .push_next(&mut import_memory_fd_info) + .push_next(&mut memory_dedicated_allocate_info) + .build(); + let device_memory = + unsafe { device.device.allocate_memory(&memory_allocate_info, None) }; + let device_memory = device_memory.map_err(VulkanError::AllocateMemory)?; + fd.unwrap(); + device_memories.push(device_memory); + free_device_memories.push(OnDrop(move || unsafe { + device.device.free_memory(device_memory, None) + })); + } + let mut bind_image_memory_infos = Vec::with_capacity(num_device_memories); + for (i, mem) in device_memories.iter().copied().enumerate() { + let mut info = BindImageMemoryInfo::builder().image(image).memory(mem); + if self.disjoint { + info = info.push_next(&mut bind_image_plane_memory_infos[i]); + } + bind_image_memory_infos.push(info.build()); + } + let res = unsafe { device.device.bind_image_memory2(&bind_image_memory_infos) }; + res.map_err(VulkanError::BindImageMemory)?; + let texture_view = device.create_image_view(image, self.format, false)?; + let render_view = device.create_image_view(image, self.format, true)?; + free_device_memories.drain(..).for_each(mem::forget); + mem::forget(destroy_image); + Ok(Rc::new(VulkanImage { + renderer: self.renderer.clone(), + texture_view, + render_view: Some(render_view), + image, + width: self.width, + height: self.height, + stride: 0, + render_ops: Default::default(), + ty: VulkanImageMemory::DmaBuf(VulkanDmaBufImage { + template: self.clone(), + mems: device_memories, + }), + format: self.format, + is_undefined: Cell::new(true), + })) + } +} + +impl GfxImage for VulkanDmaBufImageTemplate { + fn to_framebuffer(self: Rc) -> Result, GfxError> { + self.create_framebuffer() + .map(|v| v as _) + .map_err(|e| e.into()) + } + + fn to_texture(self: Rc) -> Result, GfxError> { + self.create_texture(None) + .map(|v| v as _) + .map_err(|e| e.into()) + } + + fn width(&self) -> i32 { + self.width as i32 + } + + fn height(&self) -> i32 { + self.height as i32 + } +} + +impl Debug for VulkanImage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VulkanDmaBufImage").finish_non_exhaustive() + } +} + +impl GfxFramebuffer for VulkanImage { + fn as_any(&self) -> &dyn Any { + self + } + + fn take_render_ops(&self) -> Vec { + self.render_ops.take() + } + + fn size(&self) -> (i32, i32) { + (self.width as _, self.height as _) + } + + fn render(&self, ops: Vec, clear: Option<&Color>) { + self.renderer.execute(self, &ops, clear).unwrap(); + } + + fn copy_to_shm( + &self, + _x: i32, + _y: i32, + _width: i32, + _height: i32, + _format: &Format, + _shm: &[Cell], + ) -> Result<(), GfxError> { + return Err(VulkanError::UnsupportedOperation.into()); + } + + fn format(&self) -> &'static Format { + self.format + } +} + +impl GfxTexture for VulkanImage { + fn size(&self) -> (i32, i32) { + (self.width as _, self.height as _) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn into_any(self: Rc) -> Rc { + self + } + + fn read_pixels( + self: Rc, + x: i32, + y: i32, + width: i32, + height: i32, + stride: i32, + format: &'static Format, + shm: &[Cell], + ) -> Result<(), GfxError> { + self.renderer + .read_pixels(&self, x, y, width, height, stride, format, shm) + .map_err(|e| e.into()) + } +} diff --git a/src/gfx_apis/vulkan/instance.rs b/src/gfx_apis/vulkan/instance.rs new file mode 100644 index 00000000..56985968 --- /dev/null +++ b/src/gfx_apis/vulkan/instance.rs @@ -0,0 +1,228 @@ +use { + crate::{ + async_engine::AsyncEngine, + gfx_apis::vulkan::{util::OnDrop, VulkanError, VULKAN_VALIDATION}, + io_uring::IoUring, + }, + ahash::{AHashMap, AHashSet}, + ash::{ + extensions::ext::DebugUtils, + vk::{ + api_version_major, api_version_minor, api_version_patch, api_version_variant, + ApplicationInfo, Bool32, DebugUtilsMessageSeverityFlagsEXT, + DebugUtilsMessageTypeFlagsEXT, DebugUtilsMessengerCallbackDataEXT, + DebugUtilsMessengerCreateInfoEXT, DebugUtilsMessengerEXT, ExtDebugUtilsFn, + ExtValidationFeaturesFn, ExtensionProperties, InstanceCreateInfo, LayerProperties, + ValidationFeaturesEXT, API_VERSION_1_3, FALSE, + }, + Entry, Instance, LoadingError, + }, + isnt::std_1::collections::IsntHashMap2Ext, + log::Level, + once_cell::sync::Lazy, + std::{ + ffi::{c_void, CStr, CString}, + fmt::{Display, Formatter}, + iter::IntoIterator, + rc::Rc, + slice, + sync::Arc, + }, + uapi::{ustr, Ustr}, +}; + +pub struct VulkanInstance { + pub(super) _entry: &'static Entry, + pub(super) instance: Instance, + pub(super) debug_utils: DebugUtils, + pub(super) messenger: DebugUtilsMessengerEXT, + pub(super) eng: Rc, + pub(super) ring: Rc, +} + +impl VulkanInstance { + pub fn new( + eng: &Rc, + ring: &Rc, + validation: bool, + ) -> Result, VulkanError> { + static ENTRY: Lazy>> = + Lazy::new(|| unsafe { Entry::load() }.map_err(Arc::new)); + let entry = match &*ENTRY { + Ok(e) => e, + Err(e) => return Err(VulkanError::Load(e.clone())), + }; + let extensions = get_instance_extensions(entry, None)?; + for &ext in REQUIRED_INSTANCE_EXTENSIONS { + if extensions.not_contains_key(ext) { + return Err(VulkanError::MissingInstanceExtension(ext)); + } + } + let mut enabled_extensions: Vec<_> = REQUIRED_INSTANCE_EXTENSIONS + .iter() + .map(|c| c.as_ptr()) + .collect(); + let app_info = ApplicationInfo::builder() + .api_version(API_VERSION) + .application_name(ustr!("jay").as_c_str().unwrap()) + .application_version(1); + let mut severity = DebugUtilsMessageSeverityFlagsEXT::empty() + | DebugUtilsMessageSeverityFlagsEXT::ERROR + | DebugUtilsMessageSeverityFlagsEXT::WARNING; + if *VULKAN_VALIDATION { + severity |= DebugUtilsMessageSeverityFlagsEXT::INFO + | DebugUtilsMessageSeverityFlagsEXT::VERBOSE; + } + let types = DebugUtilsMessageTypeFlagsEXT::empty() + | DebugUtilsMessageTypeFlagsEXT::VALIDATION + | DebugUtilsMessageTypeFlagsEXT::PERFORMANCE + | DebugUtilsMessageTypeFlagsEXT::GENERAL; + let mut debug_info = DebugUtilsMessengerCreateInfoEXT::builder() + .message_severity(severity) + .message_type(types) + .pfn_user_callback(Some(debug_callback)); + let validation_features = [ + // ash::vk::ValidationFeatureEnableEXT::DEBUG_PRINTF, + // ash::vk::ValidationFeatureEnableEXT::BEST_PRACTICES, + // ash::vk::ValidationFeatureEnableEXT::SYNCHRONIZATION_VALIDATION, + // ash::vk::ValidationFeatureEnableEXT::GPU_ASSISTED, + ]; + let mut validation_info = + ValidationFeaturesEXT::builder().enabled_validation_features(&validation_features); + let mut create_info = InstanceCreateInfo::builder() + .application_info(&app_info) + .push_next(&mut debug_info); + let validation_layer_name = VALIDATION_LAYER.as_ptr(); + if validation { + if get_available_layers(entry)?.contains(VALIDATION_LAYER) { + create_info = + create_info.enabled_layer_names(slice::from_ref(&validation_layer_name)); + let extensions = get_instance_extensions(entry, Some(VALIDATION_LAYER))?; + if extensions.contains_key(ExtValidationFeaturesFn::name()) { + enabled_extensions.push(ExtValidationFeaturesFn::name().as_ptr()); + create_info = create_info.push_next(&mut validation_info); + } else { + log::warn!("{:?} is not available", ExtValidationFeaturesFn::name(),); + } + } else { + log::warn!( + "Vulkan validation was requested but validation layers are not available" + ); + } + } + create_info = create_info.enabled_extension_names(&enabled_extensions); + let instance = match unsafe { entry.create_instance(&create_info, None) } { + Ok(i) => i, + Err(e) => return Err(VulkanError::CreateInstance(e)), + }; + let destroy_instance = OnDrop(|| unsafe { instance.destroy_instance(None) }); + let debug_utils = DebugUtils::new(entry, &instance); + let messenger = unsafe { debug_utils.create_debug_utils_messenger(&debug_info, None) }; + let messenger = match messenger { + Ok(m) => m, + Err(e) => return Err(VulkanError::Messenger(e)), + }; + destroy_instance.forget(); + Ok(Rc::new(Self { + _entry: entry, + instance, + debug_utils, + messenger, + eng: eng.clone(), + ring: ring.clone(), + })) + } +} + +impl Drop for VulkanInstance { + fn drop(&mut self) { + unsafe { + self.debug_utils + .destroy_debug_utils_messenger(self.messenger, None); + self.instance.destroy_instance(None); + } + } +} + +const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ExtDebugUtilsFn::name()]; + +const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation"; + +pub type Extensions = AHashMap; + +fn get_instance_extensions(entry: &Entry, layer: Option<&CStr>) -> Result { + entry + .enumerate_instance_extension_properties(layer) + .map_err(VulkanError::InstanceExtensions) + .map(map_extension_properties) +} + +fn get_available_layers(entry: &Entry) -> Result, VulkanError> { + entry + .enumerate_instance_layer_properties() + .map_err(VulkanError::InstanceLayers) + .map(map_layer_properties) +} + +fn map_layer_properties(props: Vec) -> AHashSet { + props + .into_iter() + .map(|e| unsafe { CStr::from_ptr(e.layer_name.as_ptr()).to_owned() }) + .collect() +} + +pub fn map_extension_properties(props: Vec) -> Extensions { + props + .into_iter() + .map(|e| { + let s = unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }; + (s.to_owned(), e.spec_version) + }) + .collect() +} + +unsafe extern "system" fn debug_callback( + message_severity: DebugUtilsMessageSeverityFlagsEXT, + _message_types: DebugUtilsMessageTypeFlagsEXT, + p_callback_data: *const DebugUtilsMessengerCallbackDataEXT, + _p_user_data: *mut c_void, +) -> Bool32 { + let _level = match message_severity { + DebugUtilsMessageSeverityFlagsEXT::ERROR => Level::Error, + DebugUtilsMessageSeverityFlagsEXT::WARNING => Level::Warn, + DebugUtilsMessageSeverityFlagsEXT::INFO => Level::Info, + DebugUtilsMessageSeverityFlagsEXT::VERBOSE => Level::Trace, + _ => Level::Warn, + }; + let data = &*p_callback_data; + let message = Ustr::from_ptr(data.p_message); + let message_id_name = if data.p_message_id_name.is_null() { + ustr!("") + } else { + Ustr::from_ptr(data.p_message_id_name) + }; + log::log!( + Level::Info, + "VULKAN: {} ({})", + message.display(), + message_id_name.display() + ); + FALSE +} + +pub struct ApiVersionDisplay(pub u32); + +impl Display for ApiVersionDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}.{}.{}.{}", + api_version_variant(self.0), + api_version_major(self.0), + api_version_minor(self.0), + api_version_patch(self.0), + ) + } +} + +pub const API_VERSION: u32 = API_VERSION_1_3; diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs new file mode 100644 index 00000000..cd30ad92 --- /dev/null +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -0,0 +1,182 @@ +use { + crate::{ + format::ARGB8888, + gfx_apis::vulkan::{ + descriptor::VulkanDescriptorSetLayout, device::VulkanDevice, shaders::VulkanShader, + util::OnDrop, VulkanError, + }, + }, + arrayvec::ArrayVec, + ash::vk::{ + BlendFactor, BlendOp, ColorComponentFlags, CullModeFlags, DynamicState, FrontFace, + GraphicsPipelineCreateInfo, Pipeline, PipelineCache, PipelineColorBlendAttachmentState, + PipelineColorBlendStateCreateInfo, PipelineDynamicStateCreateInfo, + PipelineInputAssemblyStateCreateInfo, PipelineLayout, PipelineLayoutCreateInfo, + PipelineMultisampleStateCreateInfo, PipelineRasterizationStateCreateInfo, + PipelineRenderingCreateInfo, PipelineShaderStageCreateInfo, + PipelineVertexInputStateCreateInfo, PipelineViewportStateCreateInfo, PolygonMode, + PrimitiveTopology, PushConstantRange, SampleCountFlags, ShaderStageFlags, + }, + std::{mem, rc::Rc, slice}, + uapi::ustr, +}; + +pub(super) struct VulkanPipeline { + pub(super) vert: Rc, + pub(super) _frag: Rc, + pub(super) frag_push_offset: u32, + pub(super) pipeline_layout: PipelineLayout, + pub(super) pipeline: Pipeline, + pub(super) _frag_descriptor_set_layout: Option>, +} + +pub(super) struct PipelineCreateInfo { + pub(super) vert: Rc, + pub(super) frag: Rc, + pub(super) alpha: bool, + pub(super) frag_descriptor_set_layout: Option>, +} + +impl VulkanDevice { + pub(super) fn create_pipeline( + &self, + info: PipelineCreateInfo, + ) -> Result, VulkanError> { + self.create_pipeline_(info, mem::size_of::() as _, mem::size_of::() as _) + } + + fn create_pipeline_( + &self, + info: PipelineCreateInfo, + vert_push_size: u32, + frag_push_size: u32, + ) -> Result, VulkanError> { + let pipeline_layout = { + let mut push_constant_ranges = ArrayVec::<_, 2>::new(); + let mut push_constant_offset = 0; + if vert_push_size > 0 { + push_constant_ranges.push( + PushConstantRange::builder() + .stage_flags(ShaderStageFlags::VERTEX) + .offset(0) + .size(vert_push_size) + .build(), + ); + push_constant_offset += vert_push_size; + } + if frag_push_size > 0 { + push_constant_ranges.push( + PushConstantRange::builder() + .stage_flags(ShaderStageFlags::FRAGMENT) + .offset(push_constant_offset) + .size(frag_push_size) + .build(), + ); + #[allow(unused_assignments)] + { + push_constant_offset += frag_push_size; + } + } + let mut descriptor_set_layouts = ArrayVec::<_, 1>::new(); + descriptor_set_layouts + .extend(info.frag_descriptor_set_layout.as_ref().map(|l| l.layout)); + let create_info = PipelineLayoutCreateInfo::builder() + .push_constant_ranges(&push_constant_ranges) + .set_layouts(&descriptor_set_layouts); + let layout = unsafe { self.device.create_pipeline_layout(&create_info, None) }; + layout.map_err(VulkanError::CreatePipelineLayout)? + }; + let destroy_layout = + OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) }); + let pipeline = { + let main = ustr!("main").as_c_str().unwrap(); + let stages = [ + PipelineShaderStageCreateInfo::builder() + .stage(ShaderStageFlags::VERTEX) + .module(info.vert.module) + .name(main) + .build(), + PipelineShaderStageCreateInfo::builder() + .stage(ShaderStageFlags::FRAGMENT) + .module(info.frag.module) + .name(main) + .build(), + ]; + let input_assembly_state = PipelineInputAssemblyStateCreateInfo::builder() + .topology(PrimitiveTopology::TRIANGLE_STRIP); + let vertex_input_state = PipelineVertexInputStateCreateInfo::builder(); + let rasterization_state = PipelineRasterizationStateCreateInfo::builder() + .polygon_mode(PolygonMode::FILL) + .cull_mode(CullModeFlags::BACK) + .line_width(1.0) + .front_face(FrontFace::COUNTER_CLOCKWISE); + let multisampling_state = PipelineMultisampleStateCreateInfo::builder() + .sample_shading_enable(false) + .rasterization_samples(SampleCountFlags::TYPE_1); + let mut blending = PipelineColorBlendAttachmentState::builder() + .color_write_mask(ColorComponentFlags::RGBA); + if info.alpha { + blending = blending + .blend_enable(true) + .src_color_blend_factor(BlendFactor::ONE) + .dst_color_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .color_blend_op(BlendOp::ADD) + .src_alpha_blend_factor(BlendFactor::ONE) + .dst_alpha_blend_factor(BlendFactor::ONE_MINUS_SRC_ALPHA) + .alpha_blend_op(BlendOp::ADD); + } + let color_blend_state = PipelineColorBlendStateCreateInfo::builder() + .attachments(slice::from_ref(&blending)); + let dynamic_states = [DynamicState::VIEWPORT, DynamicState::SCISSOR]; + let dynamic_state = + PipelineDynamicStateCreateInfo::builder().dynamic_states(&dynamic_states); + let viewport_state = PipelineViewportStateCreateInfo::builder() + .viewport_count(1) + .scissor_count(1); + let mut pipeline_rendering_create_info = PipelineRenderingCreateInfo::builder() + .color_attachment_formats(slice::from_ref(&ARGB8888.vk_format)); + let create_info = GraphicsPipelineCreateInfo::builder() + .push_next(&mut pipeline_rendering_create_info) + .stages(&stages) + .input_assembly_state(&input_assembly_state) + .vertex_input_state(&vertex_input_state) + .rasterization_state(&rasterization_state) + .multisample_state(&multisampling_state) + .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state) + .viewport_state(&viewport_state) + .layout(pipeline_layout); + let pipelines = unsafe { + self.device.create_graphics_pipelines( + PipelineCache::null(), + slice::from_ref(&create_info), + None, + ) + }; + let mut pipelines = pipelines + .map_err(|e| e.1) + .map_err(VulkanError::CreatePipeline)?; + assert_eq!(pipelines.len(), 1); + pipelines.pop().unwrap() + }; + destroy_layout.forget(); + Ok(Rc::new(VulkanPipeline { + vert: info.vert, + _frag: info.frag, + frag_push_offset: vert_push_size, + pipeline_layout, + pipeline, + _frag_descriptor_set_layout: info.frag_descriptor_set_layout, + })) + } +} + +impl Drop for VulkanPipeline { + fn drop(&mut self) { + unsafe { + let device = &self.vert.device.device; + device.destroy_pipeline(self.pipeline, None); + device.destroy_pipeline_layout(self.pipeline_layout, None); + } + } +} diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs new file mode 100644 index 00000000..f3d4e9e5 --- /dev/null +++ b/src/gfx_apis/vulkan/renderer.rs @@ -0,0 +1,1019 @@ +use { + crate::{ + async_engine::SpawnedFuture, + format::Format, + gfx_api::{ + AbsoluteRect, BufferPoint, BufferPoints, GfxApiOpt, GfxFormat, GfxFramebuffer, + GfxTexture, + }, + gfx_apis::vulkan::{ + allocator::VulkanAllocator, + command::{VulkanCommandBuffer, VulkanCommandPool}, + device::VulkanDevice, + fence::VulkanFence, + image::{VulkanImage, VulkanImageMemory}, + pipeline::{PipelineCreateInfo, VulkanPipeline}, + semaphore::VulkanSemaphore, + shaders::{ + FillFragPushConstants, FillVertPushConstants, TexVertPushConstants, FILL_FRAG, + FILL_VERT, TEX_FRAG, TEX_VERT, + }, + staging::VulkanStagingBuffer, + VulkanError, + }, + io_uring::IoUring, + theme::Color, + utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, + video::dmabuf::{ + dma_buf_export_sync_file, dma_buf_import_sync_file, DMA_BUF_SYNC_READ, + DMA_BUF_SYNC_WRITE, + }, + }, + ahash::AHashMap, + ash::{ + vk::{ + AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferImageCopy, BufferImageCopy2, + BufferMemoryBarrier2, ClearColorValue, ClearValue, CommandBuffer, + CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, + CopyBufferToImageInfo2, DependencyInfo, DependencyInfoKHR, DescriptorImageInfo, + DescriptorType, Extent2D, Extent3D, Fence, ImageAspectFlags, ImageLayout, + ImageMemoryBarrier2, ImageMemoryBarrier2Builder, ImageSubresourceLayers, + ImageSubresourceRange, PipelineBindPoint, PipelineStageFlags2, Rect2D, + RenderingAttachmentInfo, RenderingInfo, SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, + ShaderStageFlags, SubmitInfo2, Viewport, WriteDescriptorSet, QUEUE_FAMILY_FOREIGN_EXT, + }, + Device, + }, + isnt::std_1::collections::IsntHashMapExt, + std::{ + cell::{Cell, RefCell}, + fmt::{Debug, Formatter}, + mem, ptr, + rc::Rc, + slice, + }, + uapi::OwnedFd, +}; + +pub struct VulkanRenderer { + pub(super) formats: Rc>, + pub(super) device: Rc, + pub(super) fill_pipeline: Rc, + pub(super) tex_pipeline: Rc, + pub(super) command_pool: Rc, + pub(super) command_buffers: Stack>, + pub(super) wait_semaphores: Stack>, + pub(super) total_buffers: NumCell, + pub(super) memory: RefCell, + pub(super) pending_frames: CopyHashMap>, + pub(super) allocator: Rc, + pub(super) last_point: NumCell, +} + +#[derive(Default)] +pub(super) struct Memory { + sample: Vec>, + flush: Vec>, + flush_staging: Vec<(Rc, VulkanStagingBuffer)>, + textures: Vec>, + image_barriers: Vec, + shm_barriers: Vec, + wait_semaphores: Vec>, + wait_semaphore_infos: Vec, + release_fence: Option>, + release_syncfile: Option>, +} + +pub(super) struct PendingFrame { + point: u64, + renderer: Rc, + cmd: Cell>>, + _textures: Vec>, + _staging: Vec<(Rc, VulkanStagingBuffer)>, + wait_semaphores: Cell>>, + waiter: Cell>>, + _release_fence: Option>, +} + +impl VulkanDevice { + pub fn create_renderer(self: &Rc) -> Result, VulkanError> { + let fill_pipeline = self.create_pipeline::( + PipelineCreateInfo { + vert: self.create_shader(FILL_VERT)?, + frag: self.create_shader(FILL_FRAG)?, + alpha: true, + frag_descriptor_set_layout: None, + }, + )?; + let sampler = self.create_sampler()?; + let tex_descriptor_set_layout = self.create_descriptor_set_layout(&sampler)?; + let tex_pipeline = + self.create_pipeline::(PipelineCreateInfo { + vert: self.create_shader(TEX_VERT)?, + frag: self.create_shader(TEX_FRAG)?, + alpha: true, + frag_descriptor_set_layout: Some(tex_descriptor_set_layout.clone()), + })?; + let command_pool = self.create_command_pool()?; + let formats: AHashMap = self + .formats + .iter() + .map(|(drm, vk)| { + ( + *drm, + GfxFormat { + format: vk.format, + read_modifiers: vk + .modifiers + .values() + .filter(|m| m.texture_max_extents.is_some()) + .map(|m| m.modifier) + .collect(), + write_modifiers: vk + .modifiers + .values() + .filter(|m| m.render_max_extents.is_some()) + .map(|m| m.modifier) + .collect(), + }, + ) + }) + .collect(); + let allocator = self.create_allocator()?; + Ok(Rc::new(VulkanRenderer { + formats: Rc::new(formats), + device: self.clone(), + fill_pipeline, + tex_pipeline, + command_pool, + command_buffers: Default::default(), + wait_semaphores: Default::default(), + total_buffers: Default::default(), + memory: Default::default(), + pending_frames: Default::default(), + allocator, + last_point: Default::default(), + })) + } +} + +impl VulkanRenderer { + fn collect_memory(&self, opts: &[GfxApiOpt]) { + let mut memory = self.memory.borrow_mut(); + memory.sample.clear(); + memory.flush.clear(); + for cmd in opts { + if let GfxApiOpt::CopyTexture(c) = cmd { + let tex = c.tex.clone().into_vk(&self.device.device); + match &tex.ty { + VulkanImageMemory::DmaBuf(_) => memory.sample.push(tex.clone()), + VulkanImageMemory::Internal(shm) => { + if shm.to_flush.borrow_mut().is_some() { + memory.flush.push(tex.clone()); + } + } + } + memory.textures.push(tex); + } + } + } + + fn begin_command_buffer(&self, buf: CommandBuffer) -> Result<(), VulkanError> { + let begin_info = + CommandBufferBeginInfo::builder().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); + unsafe { + self.device + .device + .begin_command_buffer(buf, &begin_info) + .map_err(VulkanError::BeginCommandBuffer) + } + } + + fn write_shm_staging_buffers(self: &Rc) -> Result<(), VulkanError> { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.flush_staging.clear(); + for img in &memory.flush { + let shm = match &img.ty { + VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Internal(s) => s, + }; + let staging = self.create_staging_buffer(shm.size, true, false, true)?; + let to_flush = shm.to_flush.borrow_mut(); + let to_flush = to_flush.as_ref().unwrap(); + staging.upload(|mem, size| unsafe { + let size = size.min(to_flush.len()); + ptr::copy_nonoverlapping(to_flush.as_ptr(), mem, size); + })?; + memory.flush_staging.push((img.clone(), staging)); + } + Ok(()) + } + + fn initial_barriers(&self, buf: CommandBuffer, fb: &VulkanImage) { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.image_barriers.clear(); + memory.shm_barriers.clear(); + let fb_image_memory_barrier = image_barrier() + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(self.device.graphics_queue_idx) + .image(fb.image) + .old_layout(if fb.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::GENERAL + }) + .new_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .dst_access_mask(AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .build(); + memory.image_barriers.push(fb_image_memory_barrier); + for img in &memory.sample { + let image_memory_barrier = image_barrier() + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(self.device.graphics_queue_idx) + .image(img.image) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .build(); + memory.image_barriers.push(image_memory_barrier); + } + for (img, staging) in &memory.flush_staging { + let image_memory_barrier = image_barrier() + .image(img.image) + .old_layout(if img.is_undefined.get() { + ImageLayout::UNDEFINED + } else { + ImageLayout::SHADER_READ_ONLY_OPTIMAL + }) + .new_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .dst_access_mask(AccessFlags2::TRANSFER_WRITE) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .build(); + memory.image_barriers.push(image_memory_barrier); + let buffer_memory_barrier = BufferMemoryBarrier2::builder() + .buffer(staging.buffer) + .offset(0) + .size(staging.size) + .src_access_mask(AccessFlags2::HOST_WRITE) + .src_stage_mask(PipelineStageFlags2::HOST) + .dst_access_mask(AccessFlags2::TRANSFER_READ) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .build(); + memory.shm_barriers.push(buffer_memory_barrier); + } + let dep_info = DependencyInfoKHR::builder() + .buffer_memory_barriers(&memory.shm_barriers) + .image_memory_barriers(&memory.image_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn copy_shm_to_image(&self, cmd: CommandBuffer) { + let memory = self.memory.borrow_mut(); + for (img, staging) in &memory.flush_staging { + let cpy = BufferImageCopy2::builder() + .buffer_image_height(img.height) + .buffer_row_length(img.stride / img.format.bpp) + .image_extent(Extent3D { + width: img.width, + height: img.height, + depth: 1, + }) + .image_subresource(ImageSubresourceLayers { + aspect_mask: ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }) + .build(); + let info = CopyBufferToImageInfo2::builder() + .src_buffer(staging.buffer) + .dst_image(img.image) + .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .regions(slice::from_ref(&cpy)); + unsafe { + self.device.device.cmd_copy_buffer_to_image2(cmd, &info); + } + } + } + + fn secondary_barriers(&self, buf: CommandBuffer) { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + if memory.flush.is_empty() { + return; + } + memory.image_barriers.clear(); + for img in &memory.flush { + let image_memory_barrier = image_barrier() + .image(img.image) + .old_layout(ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(AccessFlags2::TRANSFER_WRITE) + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .dst_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .build(); + memory.image_barriers.push(image_memory_barrier); + } + let dep_info = DependencyInfoKHR::builder().image_memory_barriers(&memory.image_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn begin_rendering(&self, buf: CommandBuffer, fb: &VulkanImage, clear: Option<&Color>) { + let rendering_attachment_info = { + let mut rai = RenderingAttachmentInfo::builder() + .image_view(fb.render_view.unwrap_or(fb.texture_view)) + .image_layout(ImageLayout::GENERAL) + .load_op(AttachmentLoadOp::LOAD) + .store_op(AttachmentStoreOp::STORE); + if let Some(clear) = clear { + rai = rai + .clear_value(ClearValue { + color: ClearColorValue { + float32: clear.to_array_linear(), + }, + }) + .load_op(AttachmentLoadOp::CLEAR); + } + rai + }; + let rendering_info = RenderingInfo::builder() + .render_area(Rect2D { + offset: Default::default(), + extent: Extent2D { + width: fb.width, + height: fb.height, + }, + }) + .layer_count(1) + .color_attachments(slice::from_ref(&rendering_attachment_info)); + unsafe { + self.device.device.cmd_begin_rendering(buf, &rendering_info); + } + } + + fn set_viewport(&self, buf: CommandBuffer, fb: &VulkanImage) { + let viewport = Viewport { + x: 0.0, + y: 0.0, + width: fb.width as _, + height: fb.height as _, + min_depth: 0.0, + max_depth: 1.0, + }; + let scissor = Rect2D { + offset: Default::default(), + extent: Extent2D { + width: fb.width, + height: fb.height, + }, + }; + unsafe { + self.device + .device + .cmd_set_viewport(buf, 0, slice::from_ref(&viewport)); + self.device + .device + .cmd_set_scissor(buf, 0, slice::from_ref(&scissor)); + } + } + + fn record_draws( + &self, + buf: CommandBuffer, + fb: &VulkanImage, + opts: &[GfxApiOpt], + ) -> Result<(), VulkanError> { + let dev = &self.device.device; + let mut current_pipeline = None; + let mut bind = |pipeline: &VulkanPipeline| { + if current_pipeline != Some(pipeline.pipeline) { + current_pipeline = Some(pipeline.pipeline); + unsafe { + dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline); + } + } + }; + let width = fb.width as f32; + let height = fb.height as f32; + for opt in opts { + match opt { + GfxApiOpt::Sync => {} + GfxApiOpt::FillRect(r) => { + bind(&self.fill_pipeline); + let vert = FillVertPushConstants { + pos: r.rect.to_vk(width, height), + }; + let frag = FillFragPushConstants { + color: r.color.to_array_linear(), + }; + unsafe { + dev.cmd_push_constants( + buf, + self.fill_pipeline.pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + uapi::as_bytes(&vert), + ); + dev.cmd_push_constants( + buf, + self.fill_pipeline.pipeline_layout, + ShaderStageFlags::FRAGMENT, + self.fill_pipeline.frag_push_offset, + uapi::as_bytes(&frag), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + GfxApiOpt::CopyTexture(c) => { + let tex = c.tex.as_vk(&self.device.device); + bind(&self.tex_pipeline); + let vert = TexVertPushConstants { + pos: c.target.to_vk(width, height), + tex_pos: c.source.to_vk(), + }; + let image_info = DescriptorImageInfo::builder() + .image_view(tex.texture_view) + .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); + let write_descriptor_set = WriteDescriptorSet::builder() + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&image_info)) + .build(); + unsafe { + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + self.tex_pipeline.pipeline_layout, + 0, + slice::from_ref(&write_descriptor_set), + ); + dev.cmd_push_constants( + buf, + self.tex_pipeline.pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + uapi::as_bytes(&vert), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } + } + } + } + Ok(()) + } + + fn end_rendering(&self, buf: CommandBuffer) { + unsafe { + self.device.device.cmd_end_rendering(buf); + } + } + + fn final_barriers(&self, buf: CommandBuffer, fb: &VulkanImage) { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.image_barriers.clear(); + memory.shm_barriers.clear(); + let fb_image_memory_barrier = image_barrier() + .src_queue_family_index(self.device.graphics_queue_idx) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .image(fb.image) + .old_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(ImageLayout::GENERAL) + .src_access_mask( + AccessFlags2::COLOR_ATTACHMENT_WRITE | AccessFlags2::COLOR_ATTACHMENT_READ, + ) + .src_stage_mask(PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .build(); + memory.image_barriers.push(fb_image_memory_barrier); + for img in &memory.sample { + let image_memory_barrier = image_barrier() + .src_queue_family_index(self.device.graphics_queue_idx) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .old_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .new_layout(ImageLayout::GENERAL) + .image(img.image) + .src_access_mask(AccessFlags2::SHADER_SAMPLED_READ) + .src_stage_mask(PipelineStageFlags2::FRAGMENT_SHADER) + .build(); + memory.image_barriers.push(image_memory_barrier); + } + let dep_info = DependencyInfoKHR::builder() + .image_memory_barriers(&memory.image_barriers) + .buffer_memory_barriers(&memory.shm_barriers); + unsafe { + self.device.device.cmd_pipeline_barrier2(buf, &dep_info); + } + } + + fn end_command_buffer(&self, buf: CommandBuffer) -> Result<(), VulkanError> { + unsafe { + self.device + .device + .end_command_buffer(buf) + .map_err(VulkanError::EndCommandBuffer) + } + } + + fn create_wait_semaphores(&self, fb: &VulkanImage) -> Result<(), VulkanError> { + let mut memory = self.memory.borrow_mut(); + let memory = &mut *memory; + memory.wait_semaphore_infos.clear(); + let import = |infos: &mut Vec, + semaphores: &mut Vec>, + img: &VulkanImage, + flag: u32| + -> Result<(), VulkanError> { + if let VulkanImageMemory::DmaBuf(buf) = &img.ty { + for plane in &buf.template.planes { + let fd = dma_buf_export_sync_file(&plane.fd, flag) + .map_err(VulkanError::IoctlExportSyncFile)?; + let semaphore = self.allocate_semaphore()?; + semaphore.import_syncfile(fd)?; + infos.push( + SemaphoreSubmitInfo::builder() + .semaphore(semaphore.semaphore) + .stage_mask(PipelineStageFlags2::TOP_OF_PIPE) + .build(), + ); + semaphores.push(semaphore); + } + } + Ok(()) + }; + for texture in &memory.textures { + import( + &mut memory.wait_semaphore_infos, + &mut memory.wait_semaphores, + texture, + DMA_BUF_SYNC_READ, + )?; + } + import( + &mut memory.wait_semaphore_infos, + &mut memory.wait_semaphores, + fb, + DMA_BUF_SYNC_WRITE, + )?; + Ok(()) + } + + fn import_release_semaphore(&self, fb: &VulkanImage) { + let memory = self.memory.borrow(); + let syncfile = match memory.release_syncfile.as_ref() { + Some(syncfile) => syncfile, + _ => return, + }; + let import = |img: &VulkanImage, flag: u32| { + if let VulkanImageMemory::DmaBuf(buf) = &img.ty { + for plane in &buf.template.planes { + let res = dma_buf_import_sync_file(&plane.fd, flag, &syncfile) + .map_err(VulkanError::IoctlImportSyncFile); + if let Err(e) = res { + log::error!("Could not import syncfile into dmabuf: {}", ErrorFmt(e)); + log::warn!("Relying on implicit sync"); + } + } + } + }; + for texture in &memory.textures { + import(texture, DMA_BUF_SYNC_WRITE); + } + import(fb, DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE); + } + + fn submit(&self, buf: CommandBuffer) -> Result<(), VulkanError> { + let mut memory = self.memory.borrow_mut(); + let release_fence = self.device.create_fence()?; + let command_buffer_info = CommandBufferSubmitInfo::builder() + .command_buffer(buf) + .build(); + let submit_info = SubmitInfo2::builder() + .wait_semaphore_infos(&memory.wait_semaphore_infos) + .command_buffer_infos(slice::from_ref(&command_buffer_info)) + .build(); + unsafe { + self.device + .device + .queue_submit2( + self.device.graphics_queue, + slice::from_ref(&submit_info), + release_fence.fence, + ) + .map_err(VulkanError::Submit)?; + } + let release_syncfile = match release_fence.export_syncfile() { + Ok(s) => Some(s), + Err(e) => { + log::error!("Could not export syncfile from fence: {}", ErrorFmt(e)); + None + } + }; + memory.release_fence = Some(release_fence); + memory.release_syncfile = release_syncfile; + Ok(()) + } + + fn store_layouts(&self, fb: &VulkanImage) { + fb.is_undefined.set(false); + let memory = self.memory.borrow_mut(); + for img in &memory.flush { + img.is_undefined.set(false); + let shm = match &img.ty { + VulkanImageMemory::DmaBuf(_) => unreachable!(), + VulkanImageMemory::Internal(s) => s, + }; + shm.to_flush.take(); + } + } + + fn create_pending_frame(self: &Rc, buf: Rc) { + let point = self.last_point.fetch_add(1) + 1; + let mut memory = self.memory.borrow_mut(); + let frame = Rc::new(PendingFrame { + point, + renderer: self.clone(), + cmd: Cell::new(Some(buf)), + _textures: mem::take(&mut memory.textures), + _staging: mem::take(&mut memory.flush_staging), + wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)), + waiter: Cell::new(None), + _release_fence: memory.release_fence.take(), + }); + self.pending_frames.set(frame.point, frame.clone()); + let future = self.device.instance.eng.spawn(await_release( + memory.release_syncfile.take(), + self.device.instance.ring.clone(), + frame.clone(), + self.clone(), + )); + frame.waiter.set(Some(future)); + } + + pub fn read_pixels( + self: &Rc, + tex: &Rc, + x: i32, + y: i32, + width: i32, + height: i32, + stride: i32, + format: &'static Format, + dst: &[Cell], + ) -> Result<(), VulkanError> { + if x < 0 || y < 0 || width <= 0 || height <= 0 || stride <= 0 { + return Err(VulkanError::InvalidShmParameters { + x, + y, + width, + height, + stride, + }); + } + let width = width as u32; + let height = height as u32; + let stride = stride as u32; + if x == 0 && y == 0 && width == tex.width && height == tex.height && format == tex.format { + return self.read_all_pixels(tex, stride, dst); + } + let tmp_tex = self.create_shm_texture( + format, + width as i32, + height as i32, + stride as i32, + &[], + true, + )?; + (&*tmp_tex as &dyn GfxFramebuffer).copy_texture(&(tex.clone() as _), x, y); + self.read_all_pixels(&tmp_tex, stride, dst) + } + + fn read_all_pixels( + self: &Rc, + tex: &VulkanImage, + stride: u32, + dst: &[Cell], + ) -> Result<(), VulkanError> { + if stride < tex.width * tex.format.bpp || stride % tex.format.bpp != 0 { + return Err(VulkanError::InvalidStride); + } + let size = stride as u64 * tex.height as u64; + if size != dst.len() as u64 { + return Err(VulkanError::InvalidBufferSize); + } + let region = BufferImageCopy::builder() + .buffer_row_length(stride / tex.format.bpp) + .buffer_image_height(tex.height) + .image_subresource(ImageSubresourceLayers { + aspect_mask: ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }) + .image_extent(Extent3D { + width: tex.width, + height: tex.height, + depth: 1, + }) + .build(); + let staging = self.create_staging_buffer(size, false, true, true)?; + let initial_tex_barrier = image_barrier() + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(self.device.graphics_queue_idx) + .image(tex.image) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .dst_access_mask(AccessFlags2::TRANSFER_READ) + .dst_stage_mask(PipelineStageFlags2::TRANSFER); + let initial_buffer_barrier = BufferMemoryBarrier2::builder() + .buffer(staging.buffer) + .offset(0) + .size(staging.size) + .dst_access_mask(AccessFlags2::TRANSFER_WRITE) + .dst_stage_mask(PipelineStageFlags2::TRANSFER); + let initial_barriers = DependencyInfo::builder() + .buffer_memory_barriers(slice::from_ref(&initial_buffer_barrier)) + .image_memory_barriers(slice::from_ref(&initial_tex_barrier)); + let final_tex_barrier = image_barrier() + .src_queue_family_index(self.device.graphics_queue_idx) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .image(tex.image) + .old_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) + .new_layout(ImageLayout::GENERAL) + .src_access_mask(AccessFlags2::TRANSFER_READ) + .src_stage_mask(PipelineStageFlags2::TRANSFER); + let final_buffer_barrier = BufferMemoryBarrier2::builder() + .buffer(staging.buffer) + .offset(0) + .size(staging.size) + .src_access_mask(AccessFlags2::TRANSFER_WRITE) + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask(AccessFlags2::HOST_READ) + .dst_stage_mask(PipelineStageFlags2::HOST); + let final_barriers = DependencyInfo::builder() + .buffer_memory_barriers(slice::from_ref(&final_buffer_barrier)) + .image_memory_barriers(slice::from_ref(&final_tex_barrier)); + let buf = self.allocate_command_buffer()?; + let mut semaphores = vec![]; + let mut semaphore_infos = vec![]; + if let VulkanImageMemory::DmaBuf(buf) = &tex.ty { + for plane in &buf.template.planes { + let fd = dma_buf_export_sync_file(&plane.fd, DMA_BUF_SYNC_READ) + .map_err(VulkanError::IoctlExportSyncFile)?; + let semaphore = self.allocate_semaphore()?; + semaphore.import_syncfile(fd)?; + let semaphore_info = SemaphoreSubmitInfo::builder() + .semaphore(semaphore.semaphore) + .stage_mask(PipelineStageFlags2::TOP_OF_PIPE) + .build(); + semaphores.push(semaphore); + semaphore_infos.push(semaphore_info); + } + } + let command_buffer_info = CommandBufferSubmitInfo::builder().command_buffer(buf.buffer); + let submit_info = SubmitInfo2::builder() + .wait_semaphore_infos(&semaphore_infos) + .command_buffer_infos(slice::from_ref(&command_buffer_info)); + let begin_info = + CommandBufferBeginInfo::builder().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); + unsafe { + self.device + .device + .begin_command_buffer(buf.buffer, &begin_info) + .map_err(VulkanError::BeginCommandBuffer)?; + self.device + .device + .cmd_pipeline_barrier2(buf.buffer, &initial_barriers); + self.device.device.cmd_copy_image_to_buffer( + buf.buffer, + tex.image, + ImageLayout::TRANSFER_SRC_OPTIMAL, + staging.buffer, + &[region], + ); + self.device + .device + .cmd_pipeline_barrier2(buf.buffer, &final_barriers); + self.device + .device + .end_command_buffer(buf.buffer) + .map_err(VulkanError::EndCommandBuffer)?; + self.device + .device + .queue_submit2( + self.device.graphics_queue, + slice::from_ref(&submit_info), + Fence::null(), + ) + .map_err(VulkanError::Submit)?; + } + self.block(); + self.command_buffers.push(buf); + for semaphore in semaphores { + self.wait_semaphores.push(semaphore); + } + staging.download(|mem, size| unsafe { + ptr::copy_nonoverlapping(mem, dst.as_ptr() as _, size); + })?; + Ok(()) + } + + pub fn execute( + self: &Rc, + fb: &VulkanImage, + opts: &[GfxApiOpt], + clear: Option<&Color>, + ) -> Result<(), VulkanError> { + let res = self.try_execute(fb, opts, clear); + { + let mut memory = self.memory.borrow_mut(); + memory.flush.clear(); + memory.textures.clear(); + memory.flush_staging.clear(); + memory.sample.clear(); + memory.wait_semaphores.clear(); + memory.release_fence.take(); + memory.release_syncfile.take(); + } + res + } + + fn allocate_command_buffer(&self) -> Result, VulkanError> { + let buf = match self.command_buffers.pop() { + Some(b) => b, + _ => { + self.total_buffers.fetch_add(1); + self.command_pool.allocate_buffer()? + } + }; + Ok(buf) + } + + fn allocate_semaphore(&self) -> Result, VulkanError> { + let semaphore = match self.wait_semaphores.pop() { + Some(s) => s, + _ => self.device.create_semaphore()?, + }; + Ok(semaphore) + } + + fn try_execute( + self: &Rc, + fb: &VulkanImage, + opts: &[GfxApiOpt], + clear: Option<&Color>, + ) -> Result<(), VulkanError> { + let buf = self.allocate_command_buffer()?; + self.collect_memory(opts); + self.begin_command_buffer(buf.buffer)?; + self.write_shm_staging_buffers()?; + self.initial_barriers(buf.buffer, fb); + self.copy_shm_to_image(buf.buffer); + self.secondary_barriers(buf.buffer); + self.begin_rendering(buf.buffer, fb, clear); + self.set_viewport(buf.buffer, fb); + self.record_draws(buf.buffer, fb, opts)?; + self.end_rendering(buf.buffer); + self.final_barriers(buf.buffer, fb); + self.end_command_buffer(buf.buffer)?; + self.create_wait_semaphores(fb)?; + self.submit(buf.buffer)?; + self.import_release_semaphore(fb); + self.store_layouts(fb); + self.create_pending_frame(buf); + Ok(()) + } + + fn block(&self) { + log::warn!("Blocking."); + unsafe { + if let Err(e) = self.device.device.device_wait_idle() { + log::error!("Could not wait for device idle: {}", ErrorFmt(e)); + } + } + } + + pub fn on_drop(&self) { + let mut pending_frames = self.pending_frames.lock(); + if pending_frames.is_not_empty() { + log::warn!("Context dropped with pending frames."); + self.block(); + } + pending_frames.values().for_each(|f| { + f.waiter.take(); + }); + pending_frames.clear(); + } +} + +impl Debug for VulkanRenderer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VulkanRenderer").finish_non_exhaustive() + } +} + +#[derive(Debug)] +pub struct TmpShmTexture(pub i32, pub i32); + +impl VulkanImage { + fn assert_device(&self, device: &Device) { + assert_eq!( + self.renderer.device.device.handle(), + device.handle(), + "Mixed vulkan device use" + ); + } +} + +impl dyn GfxTexture { + fn as_vk(&self, device: &Device) -> &VulkanImage { + let img: &VulkanImage = self + .as_any() + .downcast_ref() + .expect("Non-vulkan texture passed into vulkan"); + img.assert_device(device); + img + } + + pub(super) fn into_vk(self: Rc, device: &Device) -> Rc { + let img: Rc = self + .into_any() + .downcast() + .expect("Non-vulkan texture passed into vulkan"); + img.assert_device(device); + img + } +} + +impl AbsoluteRect { + fn to_vk(&self, width: f32, height: f32) -> [[f32; 2]; 4] { + let x1 = 2.0 * self.x1 / width - 1.0; + let x2 = 2.0 * self.x2 / width - 1.0; + let y1 = 2.0 * self.y1 / height - 1.0; + let y2 = 2.0 * self.y2 / height - 1.0; + [[x2, y1], [x1, y1], [x2, y2], [x1, y2]] + } +} + +impl BufferPoint { + fn to_vk(&self) -> [f32; 2] { + [self.x, self.y] + } +} + +impl BufferPoints { + fn to_vk(&self) -> [[f32; 2]; 4] { + [ + self.top_right.to_vk(), + self.top_left.to_vk(), + self.bottom_right.to_vk(), + self.bottom_left.to_vk(), + ] + } +} + +fn image_barrier() -> ImageMemoryBarrier2Builder<'static> { + ImageMemoryBarrier2::builder().subresource_range( + ImageSubresourceRange::builder() + .aspect_mask(ImageAspectFlags::COLOR) + .layer_count(1) + .level_count(1) + .build(), + ) +} + +async fn await_release( + syncfile: Option>, + ring: Rc, + frame: Rc, + renderer: Rc, +) { + let mut is_released = false; + if let Some(syncfile) = syncfile { + if let Err(e) = ring.readable(&syncfile).await { + log::error!( + "Could not wait for release semaphore to be signaled: {}", + ErrorFmt(e) + ); + } else { + is_released = true; + } + } + if !is_released { + frame.renderer.block(); + } + if let Some(buf) = frame.cmd.take() { + frame.renderer.command_buffers.push(buf); + } + for wait_semaphore in frame.wait_semaphores.take() { + frame.renderer.wait_semaphores.push(wait_semaphore); + } + renderer.pending_frames.remove(&frame.point); +} diff --git a/src/gfx_apis/vulkan/sampler.rs b/src/gfx_apis/vulkan/sampler.rs new file mode 100644 index 00000000..88852825 --- /dev/null +++ b/src/gfx_apis/vulkan/sampler.rs @@ -0,0 +1,42 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + BorderColor, Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode, + }, + std::rc::Rc, +}; + +pub struct VulkanSampler { + pub(super) device: Rc, + pub(super) sampler: Sampler, +} + +impl VulkanDevice { + pub(super) fn create_sampler(self: &Rc) -> Result, VulkanError> { + let create_info = SamplerCreateInfo::builder() + .mag_filter(Filter::LINEAR) + .min_filter(Filter::LINEAR) + .mipmap_mode(SamplerMipmapMode::NEAREST) + .address_mode_u(SamplerAddressMode::REPEAT) + .address_mode_v(SamplerAddressMode::REPEAT) + .address_mode_w(SamplerAddressMode::REPEAT) + .max_anisotropy(1.0) + .min_lod(0.0) + .max_lod(0.25) + .border_color(BorderColor::FLOAT_TRANSPARENT_BLACK); + let sampler = unsafe { self.device.create_sampler(&create_info, None) }; + let sampler = sampler.map_err(VulkanError::CreateSampler)?; + Ok(Rc::new(VulkanSampler { + device: self.clone(), + sampler, + })) + } +} + +impl Drop for VulkanSampler { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_sampler(self.sampler, None); + } + } +} diff --git a/src/gfx_apis/vulkan/semaphore.rs b/src/gfx_apis/vulkan/semaphore.rs new file mode 100644 index 00000000..1d7b3f3c --- /dev/null +++ b/src/gfx_apis/vulkan/semaphore.rs @@ -0,0 +1,54 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ + ExternalSemaphoreHandleTypeFlags, ImportSemaphoreFdInfoKHR, Semaphore, SemaphoreCreateInfo, + SemaphoreImportFlags, + }, + std::{mem, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct VulkanSemaphore { + pub(super) device: Rc, + pub(super) semaphore: Semaphore, +} + +impl Drop for VulkanSemaphore { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_semaphore(self.semaphore, None); + } + } +} + +impl VulkanDevice { + pub fn create_semaphore(self: &Rc) -> Result, VulkanError> { + let sem = { + let create_info = SemaphoreCreateInfo::builder(); + let sem = unsafe { self.device.create_semaphore(&create_info, None) }; + sem.map_err(VulkanError::CreateSemaphore)? + }; + Ok(Rc::new(VulkanSemaphore { + device: self.clone(), + semaphore: sem, + })) + } +} + +impl VulkanSemaphore { + pub fn import_syncfile(&self, syncfile: OwnedFd) -> Result<(), VulkanError> { + let fd_info = ImportSemaphoreFdInfoKHR::builder() + .fd(syncfile.raw()) + .flags(SemaphoreImportFlags::TEMPORARY) + .handle_type(ExternalSemaphoreHandleTypeFlags::SYNC_FD) + .semaphore(self.semaphore); + let res = unsafe { + self.device + .external_semaphore_fd + .import_semaphore_fd(&fd_info) + }; + mem::forget(syncfile); + res.map_err(VulkanError::ImportSyncFile)?; + Ok(()) + } +} diff --git a/src/gfx_apis/vulkan/shaders.rs b/src/gfx_apis/vulkan/shaders.rs new file mode 100644 index 00000000..511895ac --- /dev/null +++ b/src/gfx_apis/vulkan/shaders.rs @@ -0,0 +1,67 @@ +use { + crate::gfx_apis::vulkan::{device::VulkanDevice, VulkanError}, + ash::vk::{ShaderModule, ShaderModuleCreateInfo}, + std::rc::Rc, + uapi::Packed, +}; + +pub const FILL_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.vert.spv")); +pub const FILL_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fill.frag.spv")); +pub const TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.vert.spv")); +pub const TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.frag.spv")); + +pub struct VulkanShader { + pub(super) device: Rc, + pub(super) module: ShaderModule, +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct FillVertPushConstants { + pub pos: [[f32; 2]; 4], +} + +unsafe impl Packed for FillVertPushConstants {} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct FillFragPushConstants { + pub color: [f32; 4], +} + +unsafe impl Packed for FillFragPushConstants {} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct TexVertPushConstants { + pub pos: [[f32; 2]; 4], + pub tex_pos: [[f32; 2]; 4], +} + +unsafe impl Packed for TexVertPushConstants {} + +impl VulkanDevice { + pub(super) fn create_shader( + self: &Rc, + src: &[u8], + ) -> Result, VulkanError> { + let src: Vec = uapi::pod_iter(src).unwrap().collect(); + let create_info = ShaderModuleCreateInfo::builder().code(&src); + let module = unsafe { self.device.create_shader_module(&create_info, None) }; + module + .map_err(VulkanError::CreateShaderModule) + .map(|m| VulkanShader { + device: self.clone(), + module: m, + }) + .map(Rc::new) + } +} + +impl Drop for VulkanShader { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_shader_module(self.module, None); + } + } +} diff --git a/src/gfx_apis/vulkan/shaders/fill.frag b/src/gfx_apis/vulkan/shaders/fill.frag new file mode 100644 index 00000000..04d99c83 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/fill.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(push_constant, std430) uniform Data { + layout(offset = 32) vec4 color; +} data; + +layout(location = 0) out vec4 out_color; + +void main() { + out_color = data.color; +} diff --git a/src/gfx_apis/vulkan/shaders/fill.vert b/src/gfx_apis/vulkan/shaders/fill.vert new file mode 100644 index 00000000..62abce65 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/fill.vert @@ -0,0 +1,18 @@ +#version 450 +//#extension GL_EXT_debug_printf : enable + +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; +} data; + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; break; + case 1: pos = data.pos[1]; break; + case 2: pos = data.pos[2]; break; + case 3: pos = data.pos[3]; break; + } + gl_Position = vec4(pos, 0.0, 1.0); +// debugPrintfEXT("gl_Position = %v4f", gl_Position); +} diff --git a/src/gfx_apis/vulkan/shaders/tex.frag b/src/gfx_apis/vulkan/shaders/tex.frag new file mode 100644 index 00000000..5d3b52e2 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/tex.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D tex; +layout(location = 0) in vec2 tex_pos; +layout(location = 0) out vec4 out_color; + +void main() { + out_color = textureLod(tex, tex_pos, 0); +} diff --git a/src/gfx_apis/vulkan/shaders/tex.vert b/src/gfx_apis/vulkan/shaders/tex.vert new file mode 100644 index 00000000..5a013dd5 --- /dev/null +++ b/src/gfx_apis/vulkan/shaders/tex.vert @@ -0,0 +1,21 @@ +#version 450 +//#extension GL_EXT_debug_printf : enable + +layout(push_constant, std430) uniform Data { + layout(offset = 0) vec2 pos[4]; + layout(offset = 32) vec2 tex_pos[4]; +} data; + +layout(location = 0) out vec2 tex_pos; + +void main() { + vec2 pos; + switch (gl_VertexIndex) { + case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break; + case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; break; + case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; break; + case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; break; + } + gl_Position = vec4(pos, 0.0, 1.0); +// debugPrintfEXT("gl_Position = %v4f, tex_pos = %v2f", gl_Position, tex_pos); +} diff --git a/src/gfx_apis/vulkan/staging.rs b/src/gfx_apis/vulkan/staging.rs new file mode 100644 index 00000000..cdf4a2c6 --- /dev/null +++ b/src/gfx_apis/vulkan/staging.rs @@ -0,0 +1,103 @@ +use { + crate::gfx_apis::vulkan::{ + allocator::VulkanAllocation, device::VulkanDevice, renderer::VulkanRenderer, util::OnDrop, + VulkanError, + }, + ash::vk::{Buffer, BufferCreateInfo, BufferUsageFlags, MappedMemoryRange}, + gpu_alloc::UsageFlags, + std::rc::Rc, +}; + +pub struct VulkanStagingBuffer { + pub(super) device: Rc, + pub(super) allocation: VulkanAllocation, + pub(super) buffer: Buffer, + pub(super) size: u64, +} + +impl VulkanRenderer { + pub(super) fn create_staging_buffer( + self: &Rc, + size: u64, + upload: bool, + download: bool, + transient: bool, + ) -> Result { + let mut vk_usage = BufferUsageFlags::empty(); + let mut usage = UsageFlags::empty(); + if upload { + vk_usage |= BufferUsageFlags::TRANSFER_SRC; + usage |= UsageFlags::UPLOAD; + } + if download { + vk_usage |= BufferUsageFlags::TRANSFER_DST; + usage |= UsageFlags::DOWNLOAD; + } + if transient { + usage |= UsageFlags::TRANSIENT; + } + let buffer = { + let create_info = BufferCreateInfo::builder().size(size).usage(vk_usage); + let buffer = unsafe { self.device.device.create_buffer(&create_info, None) }; + buffer.map_err(VulkanError::CreateBuffer)? + }; + let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) }); + let memory_requirements = + unsafe { self.device.device.get_buffer_memory_requirements(buffer) }; + let allocation = self.allocator.alloc(&memory_requirements, usage, true)?; + { + let res = unsafe { + self.device + .device + .bind_buffer_memory(buffer, allocation.memory, allocation.offset) + }; + res.map_err(VulkanError::BindBufferMemory)?; + } + destroy_buffer.forget(); + Ok(VulkanStagingBuffer { + device: self.device.clone(), + allocation, + buffer, + size, + }) + } +} + +impl VulkanStagingBuffer { + pub fn upload(&self, f: F) -> Result + where + F: FnOnce(*mut u8, usize) -> T, + { + let t = f(self.allocation.mem.unwrap(), self.size as usize); + let range = self.range(); + let res = unsafe { self.device.device.flush_mapped_memory_ranges(&[range]) }; + res.map_err(VulkanError::FlushMemory).map(|_| t) + } + + pub fn download(&self, f: F) -> Result + where + F: FnOnce(*const u8, usize) -> T, + { + let range = self.range(); + let res = unsafe { self.device.device.invalidate_mapped_memory_ranges(&[range]) }; + res.map_err(VulkanError::FlushMemory)?; + Ok(f(self.allocation.mem.unwrap(), self.size as usize)) + } + + fn range(&self) -> MappedMemoryRange { + let atom_mask = self.allocation.allocator.non_coherent_atom_mask; + MappedMemoryRange::builder() + .memory(self.allocation.memory) + .offset(self.allocation.offset & !atom_mask) + .size((self.allocation.size + atom_mask) & !atom_mask) + .build() + } +} + +impl Drop for VulkanStagingBuffer { + fn drop(&mut self) { + unsafe { + self.device.device.destroy_buffer(self.buffer, None); + } + } +} diff --git a/src/gfx_apis/vulkan/util.rs b/src/gfx_apis/vulkan/util.rs new file mode 100644 index 00000000..a7a12d79 --- /dev/null +++ b/src/gfx_apis/vulkan/util.rs @@ -0,0 +1,17 @@ +use std::mem; + +pub struct OnDrop(pub F) +where + F: FnMut() + Copy; + +impl OnDrop { + pub fn forget(self) { + mem::forget(self); + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + (self.0)(); + } +} diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 47da44bf..321bbecf 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -143,6 +143,7 @@ impl JayCompositor { dmabuf.height, plane.offset, plane.stride, + dmabuf.modifier, ); } Err(e) => { diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index 6097ac67..9149c335 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -17,11 +17,13 @@ use { video::{ dmabuf::DmaBuf, gbm::{GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, - ModifiedFormat, INVALID_MODIFIER, + Modifier, INVALID_MODIFIER, LINEAR_MODIFIER, }, wire::{jay_screencast::*, JayScreencastId}, }, ahash::AHashSet, + indexmap::{indexset, IndexSet}, + once_cell::sync::Lazy, std::{ cell::{Cell, RefCell}, ops::{Deref, DerefMut}, @@ -163,9 +165,7 @@ impl JayScreencast { let mut buffer = self.buffers.borrow_mut(); for (idx, buffer) in buffer.deref_mut().iter_mut().enumerate() { if buffer.free { - buffer - .fb - .copy_texture(&self.client.state, texture, 0, 0, false); + buffer.fb.copy_texture(texture, 0, 0); self.client.event(Ready { self_id: self.id, idx: idx as _, @@ -195,21 +195,37 @@ impl JayScreencast { pub fn realloc(&self, ctx: &Rc) -> Result<(), JayScreencastError> { let mut buffers = vec![]; + let formats = ctx.formats(); + let format = match formats.get(&XRGB8888.drm) { + Some(f) => f, + _ => return Err(JayScreencastError::XRGB8888), + }; if let Some(output) = self.output.get() { let mode = output.global.mode.get(); let num = 3; for _ in 0..num { - let format = ModifiedFormat { - format: XRGB8888, - modifier: INVALID_MODIFIER, + let mut usage = GBM_BO_USE_RENDERING; + let modifiers = match self.linear.get() { + true if format.write_modifiers.contains(&LINEAR_MODIFIER) => { + static MODS: Lazy> = + Lazy::new(|| indexset![LINEAR_MODIFIER]); + &MODS + } + true if format.write_modifiers.contains(&INVALID_MODIFIER) => { + usage |= GBM_BO_USE_LINEAR; + static MODS: Lazy> = + Lazy::new(|| indexset![INVALID_MODIFIER]); + &MODS + } + true => return Err(JayScreencastError::Modifier), + false if format.write_modifiers.is_empty() => { + return Err(JayScreencastError::XRGB8888Writing) + } + false => &format.write_modifiers, }; - let mut flags = GBM_BO_USE_RENDERING; - if self.linear.get() { - flags |= GBM_BO_USE_LINEAR; - } - let buffer = ctx - .gbm() - .create_bo(mode.width, mode.height, &format, flags)?; + let buffer = + ctx.gbm() + .create_bo(mode.width, mode.height, XRGB8888, modifiers, usage)?; let fb = ctx.clone().dmabuf_img(buffer.dmabuf())?.to_framebuffer()?; buffers.push(ScreencastBuffer { dmabuf: buffer.dmabuf().clone(), @@ -443,6 +459,12 @@ pub enum JayScreencastError { GbmError(#[from] GbmError), #[error(transparent)] GfxError(#[from] GfxError), + #[error("Render context does not support XRGB8888 format")] + XRGB8888, + #[error("Render context does not support XRGB8888 format for rendering")] + XRGB8888Writing, + #[error("Render context supports neither linear or invalid modifier")] + Modifier, } efrom!(JayScreencastError, MsgParserError); efrom!(JayScreencastError, ClientError); diff --git a/src/ifs/jay_screenshot.rs b/src/ifs/jay_screenshot.rs index 6ef3c7fc..aff61452 100644 --- a/src/ifs/jay_screenshot.rs +++ b/src/ifs/jay_screenshot.rs @@ -24,6 +24,7 @@ impl JayScreenshot { height: i32, offset: u32, stride: u32, + modifier: u64, ) { self.client.event(Dmabuf { self_id: self.id, @@ -33,6 +34,8 @@ impl JayScreenshot { height: height as _, offset, stride, + modifier_lo: modifier as u32, + modifier_hi: (modifier >> 32) as u32, }); } diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index e38dffb5..1547a65a 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -149,8 +149,15 @@ impl WlBuffer { if self.render_ctx_version.replace(ctx_version) == ctx_version { return; } - self.texture.set(None); + let had_texture = self.texture.set(None).is_some(); self.famebuffer.set(None); + self.reset_storage_after_gfx_context_change(); + if had_texture { + self.update_texture_or_log(); + } + } + + fn reset_storage_after_gfx_context_change(&self) { let mut storage = self.storage.borrow_mut(); if let Some(storage) = &mut *storage { if let WlBufferStorage::Shm { .. } = storage { @@ -167,7 +174,7 @@ impl WlBuffer { Ok(image) => image, Err(e) => { log::error!( - "Cannot re-import wl_buffer after graphics context reset: {}", + "Cannot re-import wl_buffer after graphics context change: {}", ErrorFmt(e) ); return; @@ -177,7 +184,13 @@ impl WlBuffer { } } - pub fn update_texture(&self) -> Result<(), WlBufferError> { + pub fn update_texture_or_log(&self) { + if let Err(e) = self.update_texture() { + log::warn!("Could not update texture: {}", ErrorFmt(e)); + } + } + + fn update_texture(&self) -> Result<(), WlBufferError> { let storage = self.storage.borrow_mut(); let storage = match storage.deref() { Some(s) => s, @@ -185,10 +198,10 @@ impl WlBuffer { }; match storage { WlBufferStorage::Shm { mem, stride } => { - self.texture.set(None); + let old = self.texture.take(); if let Some(ctx) = self.client.state.render_ctx.get() { let tex = mem.access(|mem| { - ctx.shmem_texture(mem, self.format, self.width, self.height, *stride) + ctx.shmem_texture(old, mem, self.format, self.width, self.height, *stride) })??; self.texture.set(Some(tex)); } diff --git a/src/ifs/wl_drm.rs b/src/ifs/wl_drm.rs index f38b4e55..4bf5a01c 100644 --- a/src/ifs/wl_drm.rs +++ b/src/ifs/wl_drm.rs @@ -8,7 +8,7 @@ use { object::Object, utils::buffd::{MsgParser, MsgParserError}, video::{ - dmabuf::{DmaBuf, DmaBufPlane}, + dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, INVALID_MODIFIER, }, wire::{wl_drm::*, WlDrmId}, @@ -123,7 +123,7 @@ impl WlDrm { height: req.height, format, modifier: INVALID_MODIFIER, - planes: vec![], + planes: PlaneVec::new(), }; if req.stride0 > 0 { dmabuf.planes.push(DmaBufPlane { diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 45947675..0d69f0c5 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -19,6 +19,7 @@ use { buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, linkedlist::LinkedList, }, wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id}, @@ -220,8 +221,10 @@ impl WlOutputGlobal { continue; } let rect = capture.rect; - if let Some(WlBufferStorage::Shm { mem, .. }) = wl_buffer.storage.borrow_mut().deref() { - let res = mem.access(|mem| { + if let Some(WlBufferStorage::Shm { mem, stride }) = + wl_buffer.storage.borrow_mut().deref() + { + let acc = mem.access(|mem| { fb.copy_to_shm( rect.x1(), rect.y1(), @@ -229,10 +232,39 @@ impl WlOutputGlobal { rect.height(), XRGB8888, mem, - ); + ) }); + let mut res = match acc { + Ok(res) => res, + Err(e) => { + capture.client.error(e); + continue; + } + }; + if res.is_err() { + let acc = mem.access(|mem| { + tex.clone().read_pixels( + capture.rect.x1(), + capture.rect.y1(), + capture.rect.width(), + capture.rect.height(), + *stride, + wl_buffer.format, + mem, + ) + }); + res = match acc { + Ok(res) => res, + Err(e) => { + capture.client.error(e); + continue; + } + }; + } if let Err(e) = res { - capture.client.error(e); + log::warn!("Could not read texture to memory: {}", ErrorFmt(e)); + capture.send_failed(); + continue; } // capture.send_flags(FLAGS_Y_INVERT); } else { @@ -244,13 +276,7 @@ impl WlOutputGlobal { continue; } }; - fb.copy_texture( - &self.state, - tex, - -capture.rect.x1(), - -capture.rect.y1(), - false, - ); + fb.copy_texture(tex, -capture.rect.x1(), -capture.rect.y1()); } if capture.with_damage.get() { capture.send_damage(); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 3488d6ef..d1a76069 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -766,7 +766,7 @@ impl WlSurface { } } if let Some(buffer) = buffer_change { - let _ = buffer.update_texture(); + buffer.update_texture_or_log(); self.buffer.set(Some(buffer)); self.buf_x.fetch_add(dx); self.buf_y.fetch_add(dy); diff --git a/src/ifs/zwp_linux_buffer_params_v1.rs b/src/ifs/zwp_linux_buffer_params_v1.rs index 67fcbff3..fbfb38e5 100644 --- a/src/ifs/zwp_linux_buffer_params_v1.rs +++ b/src/ifs/zwp_linux_buffer_params_v1.rs @@ -9,7 +9,7 @@ use { buffd::{MsgParser, MsgParserError}, errorfmt::ErrorFmt, }, - video::dmabuf::{DmaBuf, DmaBufPlane}, + video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec, MAX_PLANES}, wire::{zwp_linux_buffer_params_v1::*, WlBufferId, ZwpLinuxBufferParamsV1Id}, }, ahash::AHashMap, @@ -27,7 +27,7 @@ const INTERLACED: u32 = 2; #[allow(dead_code)] const BOTTOM_FIRST: u32 = 4; -const MAX_PLANE: u32 = 3; +const MAX_PLANE: u32 = MAX_PLANES as u32 - 1; pub struct ZwpLinuxBufferParamsV1 { pub id: ZwpLinuxBufferParamsV1Id, @@ -110,7 +110,7 @@ impl ZwpLinuxBufferParamsV1 { Some(m) => m, _ => return Err(ZwpLinuxBufferParamsV1Error::NoPlanes), }; - if !format.modifiers.contains_key(&modifier) { + if !format.read_modifiers.contains(&modifier) { return Err(ZwpLinuxBufferParamsV1Error::InvalidModifier(modifier)); } let mut dmabuf = DmaBuf { @@ -118,7 +118,7 @@ impl ZwpLinuxBufferParamsV1 { height, format: format.format, modifier, - planes: vec![], + planes: PlaneVec::new(), }; let mut planes: Vec<_> = self.planes.borrow_mut().drain().map(|v| v.1).collect(); planes.sort_by_key(|a| a.plane_idx); diff --git a/src/ifs/zwp_linux_dmabuf_v1.rs b/src/ifs/zwp_linux_dmabuf_v1.rs index 3b506cf0..5c8370a4 100644 --- a/src/ifs/zwp_linux_dmabuf_v1.rs +++ b/src/ifs/zwp_linux_dmabuf_v1.rs @@ -42,16 +42,10 @@ impl ZwpLinuxDmabufV1Global { if let Some(ctx) = client.state.render_ctx.get() { let formats = ctx.formats(); for format in formats.values() { - if format.implicit_external_only && !ctx.supports_external_texture() { - continue; - } obj.send_format(format.format.drm); if version >= MODIFIERS_SINCE_VERSION { - for modifier in format.modifiers.values() { - if modifier.external_only && !ctx.supports_external_texture() { - continue; - } - obj.send_modifier(format.format.drm, modifier.modifier); + for &modifier in &format.read_modifiers { + obj.send_modifier(format.format.drm, modifier); } } } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index d1c57d41..d6f99a69 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -10,7 +10,6 @@ use { compositor::TestFuture, fixed::Fixed, gfx_api::GfxError, - gfx_apis::create_gfx_context, it::test_error::TestResult, state::State, time::now_usec, @@ -178,7 +177,7 @@ impl TestBackend { } }; let drm = Drm::open_existing(file); - let ctx = match create_gfx_context(&drm) { + let ctx = match self.state.create_gfx_context(&drm, None) { Ok(ctx) => ctx, Err(e) => return Err(TestBackendError::RenderContext(e)), }; diff --git a/src/main.rs b/src/main.rs index b1748ec7..e2133382 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ c_variadic, // https://github.com/rust-lang/rust/issues/44930 thread_local, // https://github.com/rust-lang/rust/issues/29594 extern_types, // https://github.com/rust-lang/rust/issues/43467 + c_str_literals, // https://github.com/rust-lang/rust/issues/105723 )] #![allow( clippy::len_zero, diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index d1cd8861..17946f26 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -35,6 +35,7 @@ use { }, }, ahash::AHashMap, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ops::Deref, @@ -169,16 +170,17 @@ impl UsrJayRenderCtxOwner for PortalDisplay { } if self.render_ctx.get().is_none() { let drm = Drm::open_existing(fd); - let ctx = match create_gfx_context(&drm) { - Ok(c) => c, - Err(e) => { - log::error!( - "Could not create render context from drm device: {}", - ErrorFmt(e) - ); - return; - } - }; + let ctx = + match create_gfx_context(&self.state.eng, &self.state.ring, &drm, GfxApi::OpenGl) { + Ok(c) => c, + Err(e) => { + log::error!( + "Could not create render context from drm device: {}", + ErrorFmt(e) + ); + return; + } + }; let ctx = Rc::new(PortalRenderCtx { dev_id, ctx }); self.render_ctx.set(Some(ctx.clone())); self.state.render_ctxs.set(dev_id, Rc::downgrade(&ctx)); diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index be779c64..60502f52 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -22,7 +22,7 @@ use { clonecell::{CloneCell, UnsafeCellCloneSafe}, copyhashmap::CopyHashMap, }, - video::dmabuf::DmaBuf, + video::dmabuf::{DmaBuf, PlaneVec}, wire::jay_screencast::Ready, wire_dbus::{ org, @@ -86,7 +86,7 @@ pub struct StartedScreencast { session: Rc, node: Rc, port: Rc, - buffers: RefCell>, + buffers: RefCell>, dpy: Rc, jay_screencast: Rc, } @@ -276,7 +276,7 @@ impl ScreencastSession { } impl UsrJayScreencastOwner for StartedScreencast { - fn buffers(&self, buffers: Vec) { + fn buffers(&self, buffers: PlaneVec) { if buffers.len() == 0 { return; } diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index b97cdd77..eb99eac9 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -4,18 +4,18 @@ use { cursor::KnownCursor, fixed::Fixed, format::ARGB8888, - gfx_api::{GfxContext, GfxFramebuffer, GfxTexture}, + gfx_api::{GfxContext, GfxFramebuffer}, ifs::zwlr_layer_shell_v1::OVERLAY, portal::ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, renderer::renderer_base::RendererBase, scale::Scale, - text::{self, TextMeasurement}, + text::{self, TextMeasurement, TextTexture}, theme::Color, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, rc_eq::rc_eq, }, - video::{gbm::GBM_BO_USE_RENDERING, ModifiedFormat, INVALID_MODIFIER}, + video::gbm::GBM_BO_USE_RENDERING, wire::{ wp_fractional_scale_v1::PreferredScale, zwlr_layer_surface_v1::Configure, ZwpLinuxBufferParamsV1Id, @@ -118,7 +118,7 @@ pub struct Button { pub bg_hover_color: Cell, pub text: RefCell, pub font: RefCell>, - pub tex: CloneCell>>, + pub tex: CloneCell>, pub owner: CloneCell>>, } @@ -162,10 +162,12 @@ impl GuiElement for Button { _max_width: f32, _max_height: f32, ) -> (f32, f32) { + let old_tex = self.tex.take(); let font = self.font.borrow_mut(); let text = self.text.borrow_mut(); let tex = text::render_fitting2( ctx, + old_tex, None, &font, &text, @@ -213,10 +215,9 @@ impl GuiElement for Button { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x1 + self.tex_off_x.get(), y1 + self.tex_off_y.get()); r.render_texture( - &tex, + &tex.texture, tx.round() as _, ty.round() as _, - ARGB8888, None, None, r.scale(), @@ -260,7 +261,7 @@ pub struct Label { pub data: GuiElementData, pub font: RefCell>, pub text: RefCell, - pub tex: CloneCell>>, + pub tex: CloneCell>, } impl Default for Label { @@ -286,10 +287,12 @@ impl GuiElement for Label { _max_width: f32, _max_height: f32, ) -> (f32, f32) { + let old_tex = self.tex.take(); let text = self.text.borrow_mut(); let font = self.font.borrow_mut(); let tex = text::render_fitting2( ctx, + old_tex, None, &font, &text, @@ -300,7 +303,10 @@ impl GuiElement for Label { ) .ok(); let (tex, width, height) = match tex { - Some((t, _)) => (Some(t.clone()), t.width(), t.height()), + Some((t, _)) => { + let (width, height) = t.texture.size(); + (Some(t.clone()), width, height) + } _ => (None, 0, 0), }; self.tex.set(tex); @@ -311,10 +317,9 @@ impl GuiElement for Label { if let Some(tex) = self.tex.get() { let (tx, ty) = r.scale_point_f(x, y); r.render_texture( - &tex, + &tex.texture, tx.round() as _, ty.round() as _, - ARGB8888, None, None, r.scale(), @@ -638,12 +643,12 @@ impl WindowData { self.have_frame.set(false); buf.free.set(false); - buf.fb.render_custom(self.scale.get(), &mut |r| { - r.clear(&Color::from_gray(0)); - if let Some(content) = self.content.get() { - content.render_at(r, 0.0, 0.0) - } - }); + buf.fb + .render_custom(self.scale.get(), Some(&Color::from_gray(0)), &mut |r| { + if let Some(content) = self.content.get() { + content.render_at(r, 0.0, 0.0) + } + }); self.surface.attach(&buf.wl); self.surface.commit(); @@ -693,16 +698,26 @@ impl WindowData { self.frame_missed.set(true); let width = (self.width.get() as f64 * self.scale.get().to_f64()).round() as i32; let height = (self.height.get() as f64 * self.scale.get().to_f64()).round() as i32; + let formats = ctx.ctx.formats(); + let format = match formats.get(&ARGB8888.drm) { + None => { + log::error!("Render context does not support ARGB8888 format"); + return; + } + Some(f) => f, + }; + if format.write_modifiers.is_empty() { + log::error!("Render context cannot render to ARGB8888 format"); + return; + } for _ in 0..NUM_BUFFERS { - let format = ModifiedFormat { - format: ARGB8888, - modifier: INVALID_MODIFIER, - }; - let bo = match ctx - .ctx - .gbm() - .create_bo(width, height, &format, GBM_BO_USE_RENDERING) - { + let bo = match ctx.ctx.gbm().create_bo( + width, + height, + ARGB8888, + &format.write_modifiers, + GBM_BO_USE_RENDERING, + ) { Ok(b) => b, Err(e) => { log::error!("Could not allocate dmabuf: {}", ErrorFmt(e)); diff --git a/src/renderer.rs b/src/renderer.rs index 8d1768d1..e548c66d 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,6 +1,5 @@ use { crate::{ - format::ARGB8888, gfx_api::{BufferPoints, GfxApiOpt}, ifs::{ wl_buffer::WlBuffer, @@ -45,8 +44,7 @@ impl Debug for RenderResult { pub struct Renderer<'a> { pub base: RendererBase<'a>, pub state: &'a State, - pub on_output: bool, - pub result: &'a mut RenderResult, + pub result: Option<&'a mut RenderResult>, pub logical_extents: Rect, pub physical_extents: Rect, } @@ -141,25 +139,15 @@ impl Renderer<'_> { let scale = output.preferred_scale.get(); for title in &rd.titles { let (x, y) = self.base.scale_point(x + title.tex_x, y + title.tex_y); - self.base.render_texture( - &title.tex, - x, - y, - ARGB8888, - None, - None, - scale, - i32::MAX, - i32::MAX, - ); + self.base + .render_texture(&title.tex, x, y, None, None, scale, i32::MAX, i32::MAX); } if let Some(status) = &rd.status { let (x, y) = self.base.scale_point(x + status.tex_x, y + status.tex_y); self.base.render_texture( - &status.tex, + &status.tex.texture, x, y, - ARGB8888, None, None, scale, @@ -198,13 +186,13 @@ impl Renderer<'_> { &Color::from_rgba_straight(20, 20, 20, 255), ); if let Some(tex) = placeholder.textures.get(&self.base.scale) { - let x = x + (pos.width() - tex.width()) / 2; - let y = y + (pos.height() - tex.height()) / 2; + let (tex_width, tex_height) = tex.texture.size(); + let x = x + (pos.width() - tex_width) / 2; + let y = y + (pos.height() - tex_height) / 2; self.base.render_texture( - &tex, + &tex.texture, x, y, - ARGB8888, None, None, self.base.scale, @@ -238,10 +226,9 @@ impl Renderer<'_> { for title in titles { let (x, y) = self.base.scale_point(x + title.x, y + title.y); self.base.render_texture( - &title.tex, + &title.tex.texture, x, y, - ARGB8888, None, None, self.base.scale, @@ -367,14 +354,14 @@ impl Renderer<'_> { } else { self.render_buffer(&buffer, x, y, *tpoints, size, max_width, max_height); } - if self.on_output { + if let Some(result) = self.result.as_deref_mut() { { let mut fr = surface.frame_requests.borrow_mut(); - self.result.frame_requests.extend(fr.drain(..)); + result.frame_requests.extend(fr.drain(..)); } { let mut fbs = surface.presentation_feedback.borrow_mut(); - self.result.presentation_feedbacks.extend(fbs.drain(..)); + result.presentation_feedbacks.extend(fbs.drain(..)); } } } @@ -394,7 +381,6 @@ impl Renderer<'_> { &tex, x, y, - buffer.format, Some(tpoints), Some(tsize), self.base.scale, @@ -407,6 +393,8 @@ impl Renderer<'_> { { self.base.fill_boxes(&[rect], color); } + } else { + log::info!("live buffer has neither a texture nor is a single-pixel buffer"); } } @@ -440,10 +428,9 @@ impl Renderer<'_> { if let Some(title) = floating.title_textures.get(&self.base.scale) { let (x, y) = self.base.scale_point(x + bw, y + bw); self.base.render_texture( - &title, + &title.texture, x, y, - ARGB8888, None, None, self.base.scale, diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index 6d022d30..5ee35e8d 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -1,9 +1,7 @@ use { crate::{ - format::Format, gfx_api::{ - AbsoluteRect, BufferPoint, BufferPoints, Clear, CopyTexture, FillRect, GfxApiOpt, - GfxTexture, + AbsoluteRect, BufferPoint, BufferPoints, CopyTexture, FillRect, GfxApiOpt, GfxTexture, }, rect::Rect, scale::Scale, @@ -62,10 +60,6 @@ impl RendererBase<'_> { rect } - pub fn clear(&mut self, c: &Color) { - self.ops.push(GfxApiOpt::Clear(Clear { color: *c })) - } - pub fn fill_boxes(&mut self, boxes: &[Rect], color: &Color) { self.fill_boxes2(boxes, color, 0, 0); } @@ -123,7 +117,6 @@ impl RendererBase<'_> { texture: &Rc, x: i32, y: i32, - format: &'static Format, tpoints: Option, tsize: Option<(i32, i32)>, tscale: Scale, @@ -140,7 +133,7 @@ impl RendererBase<'_> { let (twidth, theight) = if let Some(size) = tsize { size } else { - let (mut w, mut h) = (texture.width(), texture.height()); + let (mut w, mut h) = texture.size(); if tscale != self.scale { let tscale = tscale.to_f64(); w = (w as f64 * self.scalef / tscale).round() as _; @@ -186,7 +179,6 @@ impl RendererBase<'_> { self.ops.push(GfxApiOpt::CopyTexture(CopyTexture { tex: texture.clone(), - format, source: texcoord, target: AbsoluteRect { x1: x, diff --git a/src/screenshoter.rs b/src/screenshoter.rs index 2fea0a97..4732abfe 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -7,7 +7,7 @@ use { video::{ drm::DrmError, gbm::{GbmBo, GbmError, GBM_BO_USE_LINEAR, GBM_BO_USE_RENDERING}, - ModifiedFormat, INVALID_MODIFIER, + INVALID_MODIFIER, LINEAR_MODIFIER, }, }, std::{ops::Deref, rc::Rc}, @@ -27,6 +27,10 @@ pub enum ScreenshooterError { RenderError(#[from] GfxError), #[error(transparent)] DrmError(#[from] DrmError), + #[error("Render context does not support XRGB8888")] + XRGB8888, + #[error("Render context supports neither linear nor invalid modifier for XRGB8888 rendering")] + Linear, } pub struct Screenshot { @@ -43,24 +47,31 @@ pub fn take_screenshot(state: &State) -> Result if extents.is_empty() { return Err(ScreenshooterError::EmptyDisplay); } - let format = ModifiedFormat { - format: XRGB8888, - modifier: INVALID_MODIFIER, + let formats = ctx.formats(); + let mut usage = GBM_BO_USE_RENDERING; + let modifiers = match formats.get(&XRGB8888.drm) { + None => return Err(ScreenshooterError::XRGB8888), + Some(f) if f.write_modifiers.contains(&LINEAR_MODIFIER) => &[LINEAR_MODIFIER], + Some(f) if f.write_modifiers.contains(&INVALID_MODIFIER) => { + usage |= GBM_BO_USE_LINEAR; + &[INVALID_MODIFIER] + } + Some(_) => return Err(ScreenshooterError::Linear), }; let gbm = ctx.gbm(); let bo = gbm.create_bo( extents.width(), extents.height(), - &format, - GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR, + XRGB8888, + modifiers, + usage, )?; let fb = ctx.clone().dmabuf_fb(bo.dmabuf())?; - fb.render( + fb.render_node( state.root.deref(), state, Some(state.root.extents.get()), - false, - &mut Default::default(), + None, Scale::from_int(1), true, ); diff --git a/src/state.rs b/src/state.rs index 3d164de2..8677fce2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,7 +14,8 @@ use { dbus::Dbus, drm_feedback::DrmFeedback, forker::ForkerProxy, - gfx_api::GfxContext, + gfx_api::{GfxContext, GfxError}, + gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ ext_session_lock_v1::ExtSessionLockV1, @@ -45,6 +46,7 @@ use { errorfmt::ErrorFmt, fdcloser::FdCloser, linkedlist::LinkedList, numcell::NumCell, queue::AsyncQueue, refcounted::RefCounted, run_toplevel::RunToplevel, }, + video::drm::Drm, wheel::Wheel, wire::{ JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwpLinuxDmabufFeedbackV1Id, @@ -54,7 +56,7 @@ use { }, ahash::AHashMap, bstr::ByteSlice, - jay_config::PciId, + jay_config::{video::GfxApi, PciId}, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, @@ -131,6 +133,7 @@ pub struct State { pub render_ctx_watchers: CopyHashMap<(ClientId, JayRenderCtxId), Rc>, pub workspace_watchers: CopyHashMap<(ClientId, JayWorkspaceWatcherId), Rc>, pub default_workspace_capture: Cell, + pub default_gfx_api: Cell, } // impl Drop for State { @@ -237,6 +240,7 @@ impl DrmDevData { struct UpdateTextTexturesVisitor; impl NodeVisitorBase for UpdateTextTexturesVisitor { fn visit_container(&mut self, node: &Rc) { + node.children.iter().for_each(|c| c.title_tex.clear()); node.schedule_compute_render_data(); node.node_visit_children(self); } @@ -245,16 +249,31 @@ impl NodeVisitorBase for UpdateTextTexturesVisitor { node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { + node.title_textures.clear(); node.schedule_render_titles(); node.node_visit_children(self); } fn visit_placeholder(&mut self, node: &Rc) { + node.textures.clear(); node.update_texture(); node.node_visit_children(self); } } impl State { + pub fn create_gfx_context( + &self, + drm: &Drm, + api: Option, + ) -> Result, GfxError> { + create_gfx_context( + &self.eng, + &self.ring, + drm, + api.unwrap_or(self.default_gfx_api.get()), + ) + } + pub fn add_output_scale(&self, scale: Scale) { if self.scales.add(scale) { self.output_scales_changed(); @@ -339,11 +358,17 @@ impl State { impl NodeVisitorBase for Walker { fn visit_container(&mut self, node: &Rc) { node.render_data.borrow_mut().titles.clear(); + node.children.iter().for_each(|c| c.title_tex.clear()); + node.node_visit_children(self); + } + fn visit_workspace(&mut self, node: &Rc) { + node.title_texture.set(None); node.node_visit_children(self); } fn visit_output(&mut self, node: &Rc) { node.render_data.borrow_mut().titles.clear(); node.render_data.borrow_mut().status.take(); + node.hardware_cursor.set(None); node.node_visit_children(self); } fn visit_float(&mut self, node: &Rc) { diff --git a/src/tasks/drmdev.rs b/src/tasks/drmdev.rs index c4b413d5..f5c58ff3 100644 --- a/src/tasks/drmdev.rs +++ b/src/tasks/drmdev.rs @@ -103,11 +103,13 @@ impl DrvDevHandler { if let Some(config) = self.state.config.get() { config.new_drm_dev(self.id); } + self.log_gfx_api(); 'outer: loop { #[allow(clippy::never_loop)] while let Some(event) = self.data.dev.event() { match event { DrmEvent::Removed => break 'outer, + DrmEvent::GfxApiChanged => self.log_gfx_api(), } } ae.triggered().await; @@ -121,4 +123,13 @@ impl DrvDevHandler { self.data.handler.set(None); self.state.drm_devs.remove(&self.id); } + + fn log_gfx_api(&self) { + let api = self.data.dev.gtx_api(); + log::info!( + "Using {:?} for device {}", + api, + self.data.devnode.as_deref().unwrap_or(""), + ) + } } diff --git a/src/text.rs b/src/text.rs index 9913641e..c4cb221e 100644 --- a/src/text.rs +++ b/src/text.rs @@ -11,8 +11,13 @@ use { }, rect::Rect, theme::Color, + utils::clonecell::UnsafeCellCloneSafe, + }, + std::{ + borrow::Cow, + ops::{Deref, Neg}, + rc::Rc, }, - std::{ops::Neg, rc::Rc}, thiserror::Error, }; @@ -32,6 +37,47 @@ pub enum TextError { ImageData(#[source] PangoError), } +#[derive(PartialEq)] +struct Config<'a> { + x: i32, + y: Option, + width: i32, + height: i32, + padding: i32, + font: Cow<'a, str>, + text: Cow<'a, str>, + color: Color, + ellipsize: bool, + markup: bool, + scale: Option, +} + +impl<'a> Config<'a> { + fn to_static(self) -> Config<'static> { + Config { + x: self.x, + y: self.y, + width: self.width, + height: self.height, + padding: self.padding, + font: Cow::Owned(self.font.into_owned()), + text: Cow::Owned(self.text.into_owned()), + color: self.color, + ellipsize: self.ellipsize, + markup: self.markup, + scale: self.scale, + } + } +} + +#[derive(Clone)] +pub struct TextTexture { + config: Rc>, + pub texture: Rc, +} + +unsafe impl UnsafeCellCloneSafe for TextTexture {} + struct Data { image: Rc, cctx: Rc, @@ -95,20 +141,22 @@ pub fn measure( pub fn render( ctx: &Rc, + old: Option, width: i32, height: i32, font: &str, text: &str, color: Color, scale: Option, -) -> Result, TextError> { +) -> Result { render2( - ctx, 1, None, width, height, 1, font, text, color, true, false, scale, + ctx, old, 1, None, width, height, 1, font, text, color, true, false, scale, ) } fn render2( ctx: &Rc, + old: Option, x: i32, y: Option, width: i32, @@ -120,7 +168,25 @@ fn render2( ellipsize: bool, markup: bool, scale: Option, -) -> Result, TextError> { +) -> Result { + let config = Config { + x, + y, + width, + height, + padding, + font: Cow::Borrowed(font), + text: Cow::Borrowed(text), + color, + ellipsize, + markup, + scale, + }; + if let Some(old2) = &old { + if old2.config.deref() == &config { + return Ok(old.unwrap()); + } + } let data = create_data(font, width, height, scale)?; if ellipsize { data.layout @@ -144,25 +210,30 @@ fn render2( Ok(d) => d, Err(e) => return Err(TextError::ImageData(e)), }; + let old = old.map(|o| o.texture); match ctx .clone() - .shmem_texture(bytes, ARGB8888, width, height, data.image.stride()) + .shmem_texture(old, bytes, ARGB8888, width, height, data.image.stride()) { - Ok(t) => Ok(t), + Ok(t) => Ok(TextTexture { + config: Rc::new(config.to_static()), + texture: t, + }), Err(e) => Err(TextError::RenderError(e)), } } pub fn render_fitting( ctx: &Rc, + old: Option, height: Option, font: &str, text: &str, color: Color, markup: bool, scale: Option, -) -> Result, TextError> { - render_fitting2(ctx, height, font, text, color, markup, scale, false).map(|(a, _)| a) +) -> Result { + render_fitting2(ctx, old, height, font, text, color, markup, scale, false).map(|(a, _)| a) } #[derive(Debug, Copy, Clone, Default)] @@ -174,6 +245,7 @@ pub struct TextMeasurement { pub fn render_fitting2( ctx: &Rc, + old: Option, height: Option, font: &str, text: &str, @@ -181,7 +253,7 @@ pub fn render_fitting2( markup: bool, scale: Option, include_measurements: bool, -) -> Result<(Rc, TextMeasurement), TextError> { +) -> Result<(TextTexture, TextMeasurement), TextError> { let measurement = measure(font, text, markup, scale, include_measurements)?; let y = match height { Some(_) => None, @@ -189,6 +261,7 @@ pub fn render_fitting2( }; let res = render2( ctx, + old, measurement.ink_rect.x1().neg(), y, measurement.ink_rect.width(), diff --git a/src/theme.rs b/src/theme.rs index 575be67b..65d63a9e 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -38,6 +38,13 @@ fn to_u8(c: f32) -> u8 { } impl Color { + pub const TRANSPARENT: Self = Self { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }; + pub fn from_gray(g: u8) -> Self { Self::from_rgb(g, g, g) } @@ -77,6 +84,27 @@ impl Color { pub fn to_rgba_premultiplied(self) -> [u8; 4] { [to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)] } + + #[allow(dead_code)] + pub fn to_array_srgb(self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } + + pub fn to_array_linear(self) -> [f32; 4] { + fn to_linear(srgb: f32) -> f32 { + if srgb <= 0.04045 { + srgb / 12.92 + } else { + (srgb + 0.055 / 1.055).powf(2.4) + } + } + [ + to_linear(self.r), + to_linear(self.g), + to_linear(self.b), + self.a, + ] + } } impl From for Color { diff --git a/src/tree/container.rs b/src/tree/container.rs index 41d517f2..aebfdd59 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -3,7 +3,6 @@ use { backend::KeyState, cursor::KnownCursor, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{ collect_kb_foci, collect_kb_foci2, wl_pointer::PendingScroll, NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, @@ -12,7 +11,7 @@ use { renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node, NodeId, ToplevelData, ToplevelNode, WorkspaceNode, @@ -24,7 +23,7 @@ use { numcell::NumCell, rc_eq::rc_eq, scroller::Scroller, - smallmap::SmallMapMut, + smallmap::{SmallMap, SmallMapMut}, }, }, ahash::AHashMap, @@ -77,7 +76,7 @@ tree_id!(ContainerNodeId); pub struct ContainerTitle { pub x: i32, pub y: i32, - pub tex: Rc, + pub tex: TextTexture, } #[derive(Default)] @@ -128,6 +127,7 @@ pub struct ContainerChild { pub node: Rc, pub active: Cell, title: RefCell, + pub title_tex: SmallMap, pub title_rect: Cell, focus_history: Cell>>>, @@ -182,6 +182,7 @@ impl ContainerNode { content: Default::default(), factor: Cell::new(1.0), title: Default::default(), + title_tex: Default::default(), title_rect: Default::default(), focus_history: Default::default(), }), @@ -289,6 +290,7 @@ impl ContainerNode { content: Default::default(), factor: Default::default(), title: Default::default(), + title_tex: Default::default(), title_rect: Default::default(), focus_history: Default::default(), }); @@ -678,6 +680,7 @@ impl ContainerNode { } let title = child.title.borrow_mut(); for (scale, _) in scales.iter() { + let old_tex = child.title_tex.remove(scale); let titles = rd.titles.get_or_default_mut(*scale); 'render_title: { let mut th = th; @@ -693,12 +696,24 @@ impl ContainerNode { break 'render_title; } if let Some(ctx) = &ctx { - match text::render(ctx, width, th, &font, title.deref(), color, scalef) { - Ok(t) => titles.push(ContainerTitle { - x: rect.x1(), - y: rect.y1(), - tex: t, - }), + match text::render( + ctx, + old_tex, + width, + th, + &font, + title.deref(), + color, + scalef, + ) { + Ok(t) => { + child.title_tex.insert(*scale, t.clone()); + titles.push(ContainerTitle { + x: rect.x1(), + y: rect.y1(), + tex: t, + }) + } Err(e) => { log::error!("Could not render title {}: {}", title, ErrorFmt(e)); } @@ -1268,6 +1283,7 @@ impl ContainingNode for ContainerNode { content: Default::default(), factor: Cell::new(node.factor.get()), title: Default::default(), + title_tex: Default::default(), title_rect: Cell::new(node.title_rect.get()), focus_history: Cell::new(None), }); diff --git a/src/tree/float.rs b/src/tree/float.rs index 1f0e5b60..268cfe83 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -3,13 +3,12 @@ use { backend::KeyState, cursor::KnownCursor, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT}, rect::Rect, renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ walker::NodeVisitor, ContainingNode, FindTreeResult, FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, @@ -44,7 +43,7 @@ pub struct FloatNode { pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, pub title: RefCell, - pub title_textures: CopyHashMap>, + pub title_textures: CopyHashMap, seats: RefCell>, } @@ -179,7 +178,6 @@ impl FloatNode { let bw = theme.sizes.border_width.get(); let font = theme.font.borrow_mut(); let title = self.title.borrow_mut(); - self.title_textures.clear(); let pos = self.position.get(); if pos.width() <= 2 * bw || title.is_empty() { return; @@ -190,6 +188,7 @@ impl FloatNode { }; let scales = self.state.scales.lock(); for (scale, _) in scales.iter() { + let old_tex = self.title_textures.remove(scale); let mut th = th; let mut scalef = None; let mut width = pos.width() - 2 * bw; @@ -202,7 +201,7 @@ impl FloatNode { if th == 0 || width == 0 { continue; } - let texture = match text::render(&ctx, width, th, &font, &title, tc, scalef) { + let texture = match text::render(&ctx, old_tex, width, th, &font, &title, tc, scalef) { Ok(t) => t, Err(e) => { log::error!("Could not render title {}: {}", title, ErrorFmt(e)); diff --git a/src/tree/output.rs b/src/tree/output.rs index 933188c4..da518f4b 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -23,7 +23,7 @@ use { renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode, }, @@ -165,6 +165,7 @@ impl OutputNode { let output_width = self.global.pos.get().width(); rd.underline = Rect::new_sized(0, th, output_width, 1).unwrap(); for ws in self.workspaces.iter() { + let old_tex = ws.title_texture.take(); let mut title_width = th; 'create_texture: { if let Some(ctx) = self.state.render_ctx.get() { @@ -177,6 +178,7 @@ impl OutputNode { }; let title = match text::render_fitting( &ctx, + old_tex, Some(texture_height), &font, &ws.name, @@ -190,8 +192,9 @@ impl OutputNode { break 'create_texture; } }; + ws.title_texture.set(Some(title.clone())); let mut x = pos + 1; - let mut width = title.width(); + let (mut width, _) = title.texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } @@ -205,7 +208,7 @@ impl OutputNode { x2: pos + title_width, tex_x: x, tex_y: 0, - tex: title, + tex: title.texture, ws: ws.deref().clone(), }); } @@ -224,6 +227,7 @@ impl OutputNode { pos += title_width; } 'set_status: { + let old_tex = rd.status.take().map(|s| s.tex); let ctx = match self.state.render_ctx.get() { Some(ctx) => ctx, _ => break 'set_status, @@ -235,6 +239,7 @@ impl OutputNode { let tc = self.state.theme.colors.bar_text.get(); let title = match text::render_fitting( &ctx, + old_tex, Some(texture_height), &font, &status, @@ -248,7 +253,7 @@ impl OutputNode { break 'set_status; } }; - let mut width = title.width(); + let (mut width, _) = title.texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } @@ -324,6 +329,7 @@ impl OutputNode { desired_output: CloneCell::new(self.global.output_id.clone()), jay_workspaces: Default::default(), capture: self.state.default_workspace_capture.clone(), + title_texture: Default::default(), }); ws.output_link .set(Some(self.workspaces.add_last(ws.clone()))); @@ -473,7 +479,7 @@ pub struct OutputTitle { pub struct OutputStatus { pub tex_x: i32, pub tex_y: i32, - pub tex: Rc, + pub tex: TextTexture, } #[derive(Copy, Clone)] diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index 72c3635b..e259d550 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -3,13 +3,12 @@ use { client::Client, cursor::KnownCursor, fixed::Fixed, - gfx_api::GfxTexture, ifs::wl_seat::{NodeSeatState, WlSeatGlobal}, rect::Rect, renderer::Renderer, scale::Scale, state::State, - text, + text::{self, TextTexture}, tree::{ Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, ToplevelNode, @@ -25,7 +24,7 @@ pub struct PlaceholderNode { id: PlaceholderNodeId, toplevel: ToplevelData, destroyed: Cell, - pub textures: SmallMap, 2>, + pub textures: SmallMap, } impl PlaceholderNode { @@ -47,11 +46,11 @@ impl PlaceholderNode { } pub fn update_texture(&self) { - self.textures.clear(); if let Some(ctx) = self.toplevel.state.render_ctx.get() { let scales = self.toplevel.state.scales.lock(); let rect = self.toplevel.pos.get(); for (scale, _) in scales.iter() { + let old_tex = self.textures.remove(scale); let mut width = rect.width(); let mut height = rect.height(); if *scale != 1 { @@ -63,6 +62,7 @@ impl PlaceholderNode { let font = format!("monospace {}", width / 10); match text::render_fitting( &ctx, + old_tex, Some(height), &font, "Fullscreen", diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 00c11353..6bb8de77 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -10,6 +10,7 @@ use { }, rect::Rect, renderer::Renderer, + text::TextTexture, tree::{ container::ContainerNode, walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitorBase, OutputNode, StackedNode, @@ -43,6 +44,7 @@ pub struct WorkspaceNode { pub desired_output: CloneCell>, pub jay_workspaces: CopyHashMap<(ClientId, JayWorkspaceId), Rc>, pub capture: Cell, + pub title_texture: Cell>, } impl WorkspaceNode { diff --git a/src/utils/clonecell.rs b/src/utils/clonecell.rs index 5d2c31ab..200bc9e2 100644 --- a/src/utils/clonecell.rs +++ b/src/utils/clonecell.rs @@ -59,7 +59,7 @@ impl CloneCell { } } -impl Default for CloneCell { +impl Default for CloneCell { fn default() -> Self { Self::new(Default::default()) } @@ -77,6 +77,8 @@ unsafe impl UnsafeCellCloneSafe for NodeRef {} unsafe impl UnsafeCellCloneSafe for () {} unsafe impl UnsafeCellCloneSafe for u64 {} unsafe impl UnsafeCellCloneSafe for i32 {} +unsafe impl UnsafeCellCloneSafe for u32 {} +unsafe impl UnsafeCellCloneSafe for usize {} unsafe impl UnsafeCellCloneSafe for (A, B) {} diff --git a/src/utils/stack.rs b/src/utils/stack.rs index 15a36d95..f415a8c8 100644 --- a/src/utils/stack.rs +++ b/src/utils/stack.rs @@ -1,5 +1,8 @@ use { - crate::utils::ptr_ext::{MutPtrExt, PtrExt}, + crate::utils::{ + clonecell::UnsafeCellCloneSafe, + ptr_ext::{MutPtrExt, PtrExt}, + }, std::{cell::UnsafeCell, mem}, }; @@ -28,7 +31,7 @@ impl Stack { pub fn to_vec(&self) -> Vec where - T: Clone, + T: UnsafeCellCloneSafe, { unsafe { let v = self.vec.get().deref(); diff --git a/src/video.rs b/src/video.rs index 68f27e97..3c4d318e 100644 --- a/src/video.rs +++ b/src/video.rs @@ -1,5 +1,3 @@ -use crate::format::Format; - pub mod dmabuf; pub mod drm; pub mod gbm; @@ -9,9 +7,3 @@ pub type Modifier = u64; pub const INVALID_MODIFIER: Modifier = 0x00ff_ffff_ffff_ffff; #[allow(dead_code)] pub const LINEAR_MODIFIER: Modifier = 0; - -#[derive(Copy, Clone)] -pub struct ModifiedFormat { - pub format: &'static Format, - pub modifier: Modifier, -} diff --git a/src/video/dmabuf.rs b/src/video/dmabuf.rs index e0684bef..1f8fe7ad 100644 --- a/src/video/dmabuf.rs +++ b/src/video/dmabuf.rs @@ -1,4 +1,9 @@ -use {crate::format::Format, std::rc::Rc, uapi::OwnedFd}; +use { + crate::{format::Format, utils::oserror::OsError, video::Modifier}, + arrayvec::ArrayVec, + std::rc::Rc, + uapi::{c::ioctl, OwnedFd, _IOW, _IOWR}, +}; #[derive(Clone)] pub struct DmaBufPlane { @@ -12,6 +17,81 @@ pub struct DmaBuf { pub width: i32, pub height: i32, pub format: &'static Format, - pub modifier: u64, - pub planes: Vec, + pub modifier: Modifier, + pub planes: PlaneVec, +} + +pub const MAX_PLANES: usize = 4; + +pub type PlaneVec = ArrayVec; + +impl DmaBuf { + pub fn is_disjoint(&self) -> bool { + if self.planes.len() <= 1 { + return false; + } + let stat = match uapi::fstat(self.planes[0].fd.raw()) { + Ok(s) => s, + _ => return true, + }; + for plane in &self.planes[1..] { + let stat2 = match uapi::fstat(plane.fd.raw()) { + Ok(s) => s, + _ => return true, + }; + if stat2.st_ino != stat.st_ino { + return true; + } + } + false + } +} + +const DMA_BUF_BASE: u64 = b'b' as _; + +#[allow(non_camel_case_types)] +#[repr(C)] +struct dma_buf_export_sync_file { + flags: u32, + fd: i32, +} + +#[allow(non_camel_case_types)] +#[repr(C)] +struct dma_buf_import_sync_file { + flags: u32, + fd: i32, +} + +pub const DMA_BUF_SYNC_READ: u32 = 1 << 0; +pub const DMA_BUF_SYNC_WRITE: u32 = 1 << 1; + +const DMA_BUF_IOCTL_EXPORT_SYNC_FILE: u64 = _IOWR::(DMA_BUF_BASE, 2); +const DMA_BUF_IOCTL_IMPORT_SYNC_FILE: u64 = _IOW::(DMA_BUF_BASE, 3); + +pub fn dma_buf_export_sync_file(dmabuf: &OwnedFd, flags: u32) -> Result { + let mut data = dma_buf_export_sync_file { flags, fd: -1 }; + let res = unsafe { ioctl(dmabuf.raw(), DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &mut data) }; + if res != 0 { + Err(OsError::default()) + } else { + Ok(OwnedFd::new(data.fd)) + } +} + +pub fn dma_buf_import_sync_file( + dmabuf: &OwnedFd, + flags: u32, + sync_file: &OwnedFd, +) -> Result<(), OsError> { + let mut data = dma_buf_import_sync_file { + flags, + fd: sync_file.raw(), + }; + let res = unsafe { ioctl(dmabuf.raw(), DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &mut data) }; + if res != 0 { + Err(OsError::default()) + } else { + Ok(()) + } } diff --git a/src/video/drm.rs b/src/video/drm.rs index 24d55df2..e1e09f68 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -17,6 +17,7 @@ use { }, ahash::AHashMap, bstr::{BString, ByteSlice}, + indexmap::IndexSet, std::{ cell::RefCell, ffi::CString, @@ -35,8 +36,11 @@ use crate::{ utils::{buf::Buf, errorfmt::ErrorFmt, stack::Stack, syncqueue::SyncQueue, vec_ext::VecExt}, video::{ dmabuf::DmaBuf, - drm::sys::{get_version, DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH}, - INVALID_MODIFIER, + drm::sys::{ + drm_format_modifier, drm_format_modifier_blob, get_version, DRM_CAP_CURSOR_HEIGHT, + DRM_CAP_CURSOR_WIDTH, FORMAT_BLOB_CURRENT, + }, + Modifier, INVALID_MODIFIER, }, }; pub use sys::{ @@ -106,6 +110,8 @@ pub enum DrmError { InvalidRead, #[error("Could not determine the drm version")] Version(#[source] OsError), + #[error("Format of IN_FORMATS property is invalid")] + InFormats, } fn render_node_name(fd: c::c_int) -> Result { @@ -169,11 +175,24 @@ impl Drm { get_nodes(self.fd.raw()).map_err(DrmError::GetNodes) } + pub fn get_render_node(&self) -> Result, DrmError> { + let nodes = self.get_nodes()?; + Ok(nodes + .get(&NodeType::Render) + .or_else(|| nodes.get(&NodeType::Primary)) + .map(|c| c.to_owned())) + } + pub fn version(&self) -> Result { get_version(self.fd.raw()).map_err(DrmError::Version) } } +pub struct InFormat { + pub format: u32, + pub modifiers: IndexSet, +} + pub struct DrmMaster { drm: Drm, u32_bufs: Stack>, @@ -373,6 +392,64 @@ impl DrmMaster { } } + pub fn get_in_formats(&self, property: u32) -> Result, DrmError> { + let blob = self.getblob_vec::(DrmBlob(property))?; + let header: drm_format_modifier_blob = match uapi::pod_read_init(blob.as_bytes()) { + Ok(h) => h, + Err(_) => { + log::error!("Header of IN_FORMATS blob doesn't fit in the blob"); + return Err(DrmError::InFormats); + } + }; + if header.version != FORMAT_BLOB_CURRENT { + log::error!( + "Header of IN_FORMATS has an invalid version: {}", + header.version + ); + return Err(DrmError::InFormats); + } + let formats_start = header.formats_offset as usize; + let formats_end = formats_start + .wrapping_add((header.count_formats as usize).wrapping_mul(mem::size_of::())); + let modifiers_start = header.modifiers_offset as usize; + let modifiers_end = modifiers_start.wrapping_add( + (header.count_modifiers as usize).wrapping_mul(mem::size_of::()), + ); + if blob.len() < formats_end || formats_end < formats_start { + log::error!("Formats of IN_FORMATS blob don't fit in the blob"); + return Err(DrmError::InFormats); + } + if blob.len() < modifiers_end || modifiers_end < modifiers_start { + log::error!("Formats of IN_FORMATS blob don't fit in the blob"); + return Err(DrmError::InFormats); + } + let mut formats: Vec<_> = uapi::pod_iter::(&blob[formats_start..formats_end]) + .unwrap() + .map(|f| InFormat { + format: f, + modifiers: IndexSet::new(), + }) + .collect(); + let modifiers = + uapi::pod_iter::(&blob[modifiers_start..modifiers_end]) + .unwrap(); + for modifier in modifiers { + let offset = modifier.offset as usize; + let mut indices = modifier.formats; + while indices != 0 { + let idx = indices.trailing_zeros(); + indices &= !(1 << idx); + let idx = idx as usize + offset; + if idx >= formats.len() { + log::error!("Modifier offset is out of bounds"); + return Err(DrmError::InFormats); + } + formats[idx].modifiers.insert(modifier.modifier); + } + } + Ok(formats) + } + #[allow(clippy::await_holding_refcell_ref)] pub async fn event(&self) -> Result, DrmError> { if self.events.is_empty() { diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 3c792857..e33dd6c2 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -1132,3 +1132,27 @@ pub fn get_version(fd: c::c_int) -> Result { desc: desc.into(), }) } + +pub const FORMAT_BLOB_CURRENT: u32 = 1; + +#[repr(C)] +pub struct drm_format_modifier_blob { + pub version: u32, + pub flags: u32, + pub count_formats: u32, + pub formats_offset: u32, + pub count_modifiers: u32, + pub modifiers_offset: u32, +} + +unsafe impl Pod for drm_format_modifier_blob {} + +#[repr(C)] +pub struct drm_format_modifier { + pub formats: u64, + pub offset: u32, + pub pad: u32, + pub modifier: u64, +} + +unsafe impl Pod for drm_format_modifier {} diff --git a/src/video/gbm.rs b/src/video/gbm.rs index c9df1cff..904f9330 100644 --- a/src/video/gbm.rs +++ b/src/video/gbm.rs @@ -2,12 +2,12 @@ use { crate::{ - format::formats, + format::{formats, Format}, utils::oserror::OsError, video::{ - dmabuf::{DmaBuf, DmaBufPlane}, + dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, drm::{Drm, DrmError}, - ModifiedFormat, INVALID_MODIFIER, + Modifier, INVALID_MODIFIER, }, }, std::{ @@ -27,13 +27,15 @@ pub enum GbmError { #[error("Cloud not create a gbm device")] CreateDevice, #[error("Cloud not create a gbm buffer")] - CreateBo, + CreateBo(#[source] OsError), #[error("gbm buffer has an unknown format")] UnknownFormat, #[error("Could not retrieve a drm-buf fd")] DrmFd, #[error("Could not map bo")] MapBo(#[source] OsError), + #[error("Tried to allocate a buffer with no modifier")] + NoModifier, } pub type Device = u8; @@ -161,7 +163,7 @@ unsafe fn export_bo(bo: *mut Bo) -> Result { } }, planes: { - let mut planes = vec![]; + let mut planes = PlaneVec::new(); for plane in 0..gbm_bo_get_plane_count(bo) { let offset = gbm_bo_get_offset(bo, plane); let stride = gbm_bo_get_stride_for_plane(bo, plane); @@ -195,30 +197,36 @@ impl GbmDevice { self.dev } - pub fn create_bo( + pub fn create_bo<'a>( &self, width: i32, height: i32, - format: &ModifiedFormat, - usage: u32, + format: &Format, + modifiers: impl IntoIterator, + mut usage: u32, ) -> Result { unsafe { - let (modifiers, n_modifiers) = if format.modifier == INVALID_MODIFIER { + let modifiers: Vec = modifiers.into_iter().copied().collect(); + if modifiers.is_empty() { + return Err(GbmError::NoModifier); + } + let (modifiers, n_modifiers) = if modifiers == [INVALID_MODIFIER] { (ptr::null(), 0) } else { - (&format.modifier as _, 1) + usage &= !GBM_BO_USE_LINEAR; + (modifiers.as_ptr() as _, modifiers.len() as _) }; let bo = gbm_bo_create_with_modifiers2( self.dev, width as _, height as _, - format.format.drm, + format.drm, modifiers, n_modifiers, usage, ); if bo.is_null() { - return Err(GbmError::CreateBo); + return Err(GbmError::CreateBo(OsError::default())); } let bo = BoHolder { bo }; let dma = export_bo(bo.bo)?; @@ -250,7 +258,7 @@ impl GbmDevice { usage, ); if bo.is_null() { - return Err(GbmError::CreateBo); + return Err(GbmError::CreateBo(OsError::default())); } let bo = BoHolder { bo }; Ok(GbmBo { diff --git a/src/wl_usr/usr_ifs/usr_jay_screencast.rs b/src/wl_usr/usr_ifs/usr_jay_screencast.rs index 4d277ec9..87917e43 100644 --- a/src/wl_usr/usr_ifs/usr_jay_screencast.rs +++ b/src/wl_usr/usr_ifs/usr_jay_screencast.rs @@ -6,7 +6,7 @@ use { buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, }, - video::dmabuf::{DmaBuf, DmaBufPlane}, + video::dmabuf::{DmaBuf, DmaBufPlane, PlaneVec}, wire::{jay_screencast::*, JayScreencastId}, wl_usr::{usr_ifs::usr_jay_output::UsrJayOutput, usr_object::UsrObject, UsrCon}, }, @@ -19,8 +19,8 @@ pub struct UsrJayScreencast { pub con: Rc, pub owner: CloneCell>>, - pub pending_buffers: RefCell>, - pub pending_planes: RefCell>, + pub pending_buffers: RefCell>, + pub pending_planes: RefCell>, pub pending_config: RefCell, } @@ -35,7 +35,7 @@ pub struct UsrJayScreencastServerConfig { } pub trait UsrJayScreencastOwner { - fn buffers(&self, buffers: Vec) { + fn buffers(&self, buffers: PlaneVec) { let _ = buffers; } diff --git a/wire/jay_screenshot.txt b/wire/jay_screenshot.txt index 726e9d53..41cc34b8 100644 --- a/wire/jay_screenshot.txt +++ b/wire/jay_screenshot.txt @@ -7,6 +7,8 @@ msg dmabuf = 0 { height: u32, offset: u32, stride: u32, + modifier_lo: u32, + modifier_hi: u32, } msg error = 1 {