mirror of
https://github.com/CloudNebulaProject/wayray.git
synced 2026-04-10 13:10:41 +00:00
Add headless backend with PixmanRenderer, refactor into backend modules
Restructure wrsrvd to support two backends: a headless PixmanRenderer (default) for running without a display server, and the existing Winit backend (via --backend winit). The render logic is split into per-backend modules, and the old render.rs is removed.
This commit is contained in:
parent
f394d8cd7d
commit
8a3d14ff19
7 changed files with 633 additions and 296 deletions
139
Cargo.lock
generated
139
Cargo.lock
generated
|
|
@ -169,7 +169,16 @@ version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block2"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -368,6 +377,17 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctrlc"
|
||||||
|
version = "3.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
|
||||||
|
dependencies = [
|
||||||
|
"dispatch2",
|
||||||
|
"nix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-icon"
|
name = "cursor-icon"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -390,6 +410,18 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dispatch2"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"block2 0.6.2",
|
||||||
|
"libc",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dlib"
|
name = "dlib"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -902,6 +934,18 @@ dependencies = [
|
||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.31.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
|
|
@ -958,6 +1002,15 @@ dependencies = [
|
||||||
"objc2-encode",
|
"objc2-encode",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||||
|
dependencies = [
|
||||||
|
"objc2-encode",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-app-kit"
|
name = "objc2-app-kit"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -965,9 +1018,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
"objc2-core-image",
|
"objc2-core-image",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
|
|
@ -981,8 +1034,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
@ -993,8 +1046,8 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1005,8 +1058,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1016,8 +1069,8 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-metal",
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
@ -1028,8 +1081,8 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-contacts",
|
"objc2-contacts",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
@ -1047,10 +1100,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1059,8 +1112,8 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
@ -1072,8 +1125,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1084,8 +1137,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-metal",
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
@ -1096,7 +1149,7 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1107,8 +1160,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-cloud-kit",
|
"objc2-cloud-kit",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
"objc2-core-image",
|
"objc2-core-image",
|
||||||
|
|
@ -1127,8 +1180,8 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1139,8 +1192,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
@ -1176,6 +1229,12 @@ version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -1208,6 +1267,24 @@ version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pixman"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cea217d496c19ac0a8e502b37078e1f683d16344adee9eb247a5d57c165e1edf"
|
||||||
|
dependencies = [
|
||||||
|
"drm-fourcc",
|
||||||
|
"paste",
|
||||||
|
"pixman-sys",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pixman-sys"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1a0483e89e81d7915defe83c51f23f6800594d64f6f4a21253ce87fd8444ada"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
|
|
@ -1593,6 +1670,7 @@ dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading",
|
"libloading",
|
||||||
|
"pixman",
|
||||||
"profiling",
|
"profiling",
|
||||||
"rand",
|
"rand",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
|
|
@ -2331,7 +2409,7 @@ dependencies = [
|
||||||
"android-activity",
|
"android-activity",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2 0.5.1",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"calloop 0.13.0",
|
"calloop 0.13.0",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
|
|
@ -2344,7 +2422,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"ndk",
|
"ndk",
|
||||||
"objc2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
|
|
@ -2491,6 +2569,7 @@ dependencies = [
|
||||||
name = "wrsrvd"
|
name = "wrsrvd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ctrlc",
|
||||||
"miette",
|
"miette",
|
||||||
"smithay",
|
"smithay",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,7 @@ smithay = { version = "0.7", default-features = false, features = [
|
||||||
"wayland_frontend",
|
"wayland_frontend",
|
||||||
"desktop",
|
"desktop",
|
||||||
"renderer_gl",
|
"renderer_gl",
|
||||||
|
"renderer_pixman",
|
||||||
"backend_winit",
|
"backend_winit",
|
||||||
] }
|
] }
|
||||||
|
ctrlc = "3"
|
||||||
|
|
|
||||||
240
crates/wrsrvd/src/backend/headless.rs
Normal file
240
crates/wrsrvd/src/backend/headless.rs
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use miette::Result;
|
||||||
|
use smithay::{
|
||||||
|
backend::{
|
||||||
|
allocator::Fourcc,
|
||||||
|
renderer::{
|
||||||
|
Bind, ExportMem, Offscreen,
|
||||||
|
damage::OutputDamageTracker,
|
||||||
|
element::texture::TextureRenderElement,
|
||||||
|
pixman::{PixmanRenderer, PixmanTexture},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
desktop::{Window, space::render_output},
|
||||||
|
output::Output,
|
||||||
|
reexports::pixman as pixman_lib,
|
||||||
|
reexports::{
|
||||||
|
calloop::{self, EventLoop},
|
||||||
|
wayland_server::Display,
|
||||||
|
},
|
||||||
|
utils::{Buffer as BufferCoord, Rectangle, Size},
|
||||||
|
wayland::{compositor::CompositorClientState, socket::ListeningSocketSource},
|
||||||
|
};
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use crate::errors::WayRayError;
|
||||||
|
use crate::handlers::ClientState;
|
||||||
|
use crate::state::WayRay;
|
||||||
|
|
||||||
|
/// Dark grey clear color for the compositor background.
|
||||||
|
const CLEAR_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.0];
|
||||||
|
|
||||||
|
/// Data accessible from calloop event callbacks in the headless backend.
|
||||||
|
struct CalloopData {
|
||||||
|
state: WayRay,
|
||||||
|
display: Display<WayRay>,
|
||||||
|
renderer: PixmanRenderer,
|
||||||
|
render_buffer: pixman_lib::Image<'static, 'static>,
|
||||||
|
damage_tracker: OutputDamageTracker,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the compositor with the headless PixmanRenderer backend.
|
||||||
|
///
|
||||||
|
/// This creates a CPU-only software renderer suitable for headless servers
|
||||||
|
/// with no display hardware.
|
||||||
|
pub fn run(display: Display<WayRay>, mut state: WayRay, output: Output) -> Result<()> {
|
||||||
|
// Create the PixmanRenderer (CPU software renderer).
|
||||||
|
let mut renderer = PixmanRenderer::new().map_err(|e| {
|
||||||
|
WayRayError::BackendInit(Box::<dyn std::error::Error + Send + Sync>::from(
|
||||||
|
e.to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
info!("pixman headless backend initialized");
|
||||||
|
|
||||||
|
// Create an in-memory render buffer matching the output size.
|
||||||
|
let output_size = output.current_mode().unwrap().size;
|
||||||
|
let buffer_size: Size<i32, BufferCoord> = Size::from((output_size.w, output_size.h));
|
||||||
|
let render_buffer: pixman_lib::Image<'static, 'static> = renderer
|
||||||
|
.create_buffer(Fourcc::Argb8888, buffer_size)
|
||||||
|
.map_err(|e| {
|
||||||
|
WayRayError::BackendInit(Box::<dyn std::error::Error + Send + Sync>::from(
|
||||||
|
e.to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
info!(
|
||||||
|
width = output_size.w,
|
||||||
|
height = output_size.h,
|
||||||
|
"headless render buffer created"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a Wayland listening socket for clients.
|
||||||
|
let listening_socket =
|
||||||
|
ListeningSocketSource::new_auto().map_err(|e| WayRayError::DisplayInit(Box::new(e)))?;
|
||||||
|
let socket_name = listening_socket.socket_name().to_os_string();
|
||||||
|
info!(?socket_name, "wayland socket created");
|
||||||
|
|
||||||
|
// Set WAYLAND_DISPLAY so child processes can find us.
|
||||||
|
// SAFETY: This is called early in main before any other threads are spawned,
|
||||||
|
// so there are no concurrent readers of the environment.
|
||||||
|
unsafe { std::env::set_var("WAYLAND_DISPLAY", &socket_name) };
|
||||||
|
|
||||||
|
// Create the calloop event loop.
|
||||||
|
let mut event_loop: EventLoop<CalloopData> =
|
||||||
|
EventLoop::try_new().map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
|
||||||
|
let loop_handle = event_loop.handle();
|
||||||
|
|
||||||
|
// Insert the Wayland listening socket as a calloop source.
|
||||||
|
loop_handle
|
||||||
|
.insert_source(listening_socket, |client_stream, _, data| {
|
||||||
|
data.display
|
||||||
|
.handle()
|
||||||
|
.insert_client(
|
||||||
|
client_stream,
|
||||||
|
Arc::new(ClientState {
|
||||||
|
compositor_state: CompositorClientState::default(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e.error)))?;
|
||||||
|
|
||||||
|
// Set up a timer to drive the render loop at ~60fps.
|
||||||
|
let timer = calloop::timer::Timer::from_duration(Duration::from_millis(16));
|
||||||
|
loop_handle
|
||||||
|
.insert_source(timer, |_, _, data| {
|
||||||
|
render_headless_frame(data);
|
||||||
|
calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(16))
|
||||||
|
})
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e.error)))?;
|
||||||
|
|
||||||
|
// Create a damage tracker for efficient rendering.
|
||||||
|
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||||
|
|
||||||
|
// Map the output into the compositor space.
|
||||||
|
state.space.map_output(&output, (0, 0));
|
||||||
|
|
||||||
|
let mut calloop_data = CalloopData {
|
||||||
|
state,
|
||||||
|
display,
|
||||||
|
renderer,
|
||||||
|
render_buffer,
|
||||||
|
damage_tracker,
|
||||||
|
};
|
||||||
|
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
|
||||||
|
// Handle SIGINT/SIGTERM for graceful shutdown.
|
||||||
|
let running_clone = running.clone();
|
||||||
|
ctrlc_handler(&running_clone);
|
||||||
|
|
||||||
|
info!("entering headless main event loop");
|
||||||
|
|
||||||
|
while running.load(Ordering::SeqCst) {
|
||||||
|
// Dispatch Wayland clients.
|
||||||
|
calloop_data
|
||||||
|
.display
|
||||||
|
.dispatch_clients(&mut calloop_data.state)
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
|
||||||
|
calloop_data
|
||||||
|
.display
|
||||||
|
.flush_clients()
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
|
||||||
|
// Dispatch calloop sources (timer + Wayland socket) with ~16ms timeout.
|
||||||
|
event_loop
|
||||||
|
.dispatch(Duration::from_millis(16), &mut calloop_data)
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("headless backend shutting down");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render a single frame using the headless PixmanRenderer.
|
||||||
|
fn render_headless_frame(data: &mut CalloopData) {
|
||||||
|
let output = data.state.output.clone();
|
||||||
|
|
||||||
|
// Bind the in-memory buffer as the render target.
|
||||||
|
let mut target = match data.renderer.bind(&mut data.render_buffer) {
|
||||||
|
Ok(target) => target,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "failed to bind headless render buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let custom_elements: &[TextureRenderElement<PixmanTexture>] = &[];
|
||||||
|
|
||||||
|
let render_result = render_output::<_, _, Window, _>(
|
||||||
|
&output,
|
||||||
|
&mut data.renderer,
|
||||||
|
&mut target,
|
||||||
|
1.0,
|
||||||
|
0, // buffer age: 0 means full redraw (no swap chain in headless)
|
||||||
|
[&data.state.space],
|
||||||
|
custom_elements,
|
||||||
|
&mut data.damage_tracker,
|
||||||
|
CLEAR_COLOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
match render_result {
|
||||||
|
Ok(result) => {
|
||||||
|
let damage = result.damage.cloned();
|
||||||
|
|
||||||
|
// Read pixels from the CPU buffer for network transport.
|
||||||
|
let output_size = data.state.output.current_mode().unwrap().size;
|
||||||
|
let region: Rectangle<i32, BufferCoord> =
|
||||||
|
Rectangle::from_size(Size::from((output_size.w, output_size.h)));
|
||||||
|
|
||||||
|
match data
|
||||||
|
.renderer
|
||||||
|
.copy_framebuffer(&target, region, Fourcc::Argb8888)
|
||||||
|
{
|
||||||
|
Ok(mapping) => match data.renderer.map_texture(&mapping) {
|
||||||
|
Ok(pixels) => {
|
||||||
|
let damage_rects = damage.as_ref().map(|d| d.len()).unwrap_or(0);
|
||||||
|
tracing::debug!(
|
||||||
|
width = output_size.w,
|
||||||
|
height = output_size.h,
|
||||||
|
bytes = pixels.len(),
|
||||||
|
damage_rects,
|
||||||
|
"headless framebuffer captured"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(?err, "failed to map headless framebuffer");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(?err, "failed to copy headless framebuffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send frame callbacks to all mapped surfaces.
|
||||||
|
let time = data.state.clock.now();
|
||||||
|
for window in data.state.space.elements() {
|
||||||
|
window.send_frame(&output, time, Some(Duration::ZERO), |_, _| {
|
||||||
|
Some(output.clone())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "headless damage tracker render failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install a Ctrl-C handler that sets the running flag to false.
|
||||||
|
fn ctrlc_handler(running: &Arc<AtomicBool>) {
|
||||||
|
let r = running.clone();
|
||||||
|
// Ignore error if handler can't be set (e.g., in tests).
|
||||||
|
let _ = ctrlc::set_handler(move || {
|
||||||
|
info!("received shutdown signal");
|
||||||
|
r.store(false, Ordering::SeqCst);
|
||||||
|
});
|
||||||
|
}
|
||||||
2
crates/wrsrvd/src/backend/mod.rs
Normal file
2
crates/wrsrvd/src/backend/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod headless;
|
||||||
|
pub mod winit;
|
||||||
259
crates/wrsrvd/src/backend/winit.rs
Normal file
259
crates/wrsrvd/src/backend/winit.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use miette::Result;
|
||||||
|
use smithay::{
|
||||||
|
backend::{
|
||||||
|
allocator::Fourcc,
|
||||||
|
renderer::{
|
||||||
|
ExportMem,
|
||||||
|
damage::OutputDamageTracker,
|
||||||
|
element::texture::TextureRenderElement,
|
||||||
|
gles::{GlesRenderer, GlesTexture},
|
||||||
|
},
|
||||||
|
winit::{self, WinitEvent, WinitGraphicsBackend},
|
||||||
|
},
|
||||||
|
desktop::{Window, space::render_output},
|
||||||
|
output::Output,
|
||||||
|
reexports::wayland_server::Display,
|
||||||
|
utils::{Buffer as BufferCoord, Rectangle, Size},
|
||||||
|
wayland::{compositor::CompositorClientState, socket::ListeningSocketSource},
|
||||||
|
};
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use crate::errors::WayRayError;
|
||||||
|
use crate::handlers::ClientState;
|
||||||
|
use crate::state::WayRay;
|
||||||
|
|
||||||
|
/// Dark grey clear color for the compositor background.
|
||||||
|
const CLEAR_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.0];
|
||||||
|
|
||||||
|
/// Data accessible from calloop event callbacks in the Winit backend.
|
||||||
|
struct CalloopData {
|
||||||
|
state: WayRay,
|
||||||
|
display: Display<WayRay>,
|
||||||
|
backend: WinitGraphicsBackend<GlesRenderer>,
|
||||||
|
damage_tracker: OutputDamageTracker,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the compositor with the Winit backend (opens a window for development).
|
||||||
|
pub fn run(display: Display<WayRay>, mut state: WayRay, output: Output) -> Result<()> {
|
||||||
|
// Initialize the Winit backend (opens a window, creates a GlesRenderer).
|
||||||
|
let (backend, winit_event_loop) = winit::init::<GlesRenderer>().map_err(|e| {
|
||||||
|
WayRayError::BackendInit(Box::<dyn std::error::Error + Send + Sync>::from(
|
||||||
|
e.to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
info!("winit backend initialized");
|
||||||
|
|
||||||
|
// Update the output mode to match the actual Winit window size.
|
||||||
|
let window_size = backend.window_size();
|
||||||
|
let mode = smithay::output::Mode {
|
||||||
|
size: window_size,
|
||||||
|
refresh: 60_000,
|
||||||
|
};
|
||||||
|
output.change_current_state(Some(mode), None, None, None);
|
||||||
|
output.set_preferred(mode);
|
||||||
|
info!(?window_size, "output mode updated to match winit window");
|
||||||
|
|
||||||
|
// Create a Wayland listening socket for clients.
|
||||||
|
let listening_socket =
|
||||||
|
ListeningSocketSource::new_auto().map_err(|e| WayRayError::DisplayInit(Box::new(e)))?;
|
||||||
|
let socket_name = listening_socket.socket_name().to_os_string();
|
||||||
|
info!(?socket_name, "wayland socket created");
|
||||||
|
|
||||||
|
// Set WAYLAND_DISPLAY so child processes can find us.
|
||||||
|
// SAFETY: This is called early in main before any other threads are spawned,
|
||||||
|
// so there are no concurrent readers of the environment.
|
||||||
|
unsafe { std::env::set_var("WAYLAND_DISPLAY", &socket_name) };
|
||||||
|
|
||||||
|
// Create the calloop event loop.
|
||||||
|
let mut event_loop: smithay::reexports::calloop::EventLoop<CalloopData> =
|
||||||
|
smithay::reexports::calloop::EventLoop::try_new()
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
|
||||||
|
let loop_handle = event_loop.handle();
|
||||||
|
|
||||||
|
// Insert the Wayland listening socket as a calloop source.
|
||||||
|
loop_handle
|
||||||
|
.insert_source(listening_socket, |client_stream, _, data| {
|
||||||
|
data.display
|
||||||
|
.handle()
|
||||||
|
.insert_client(
|
||||||
|
client_stream,
|
||||||
|
Arc::new(ClientState {
|
||||||
|
compositor_state: CompositorClientState::default(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e.error)))?;
|
||||||
|
|
||||||
|
// Shared flag to signal the main loop to exit.
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
let running_clone = running.clone();
|
||||||
|
|
||||||
|
// Insert the Winit event loop as a calloop source.
|
||||||
|
loop_handle
|
||||||
|
.insert_source(winit_event_loop, move |event, _, data| match event {
|
||||||
|
WinitEvent::Resized { size, scale_factor } => {
|
||||||
|
info!(?size, scale_factor, "window resized");
|
||||||
|
}
|
||||||
|
WinitEvent::Focus(focused) => {
|
||||||
|
info!(focused, "window focus changed");
|
||||||
|
}
|
||||||
|
WinitEvent::Input(event) => {
|
||||||
|
data.state.process_input_event(event);
|
||||||
|
}
|
||||||
|
WinitEvent::Redraw => {
|
||||||
|
render_winit_frame(&mut data.state, &mut data.backend, &mut data.damage_tracker);
|
||||||
|
}
|
||||||
|
WinitEvent::CloseRequested => {
|
||||||
|
info!("close requested, shutting down");
|
||||||
|
running_clone.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e.error)))?;
|
||||||
|
|
||||||
|
// Create a damage tracker for efficient rendering.
|
||||||
|
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||||
|
|
||||||
|
// Map the output into the compositor space.
|
||||||
|
state.space.map_output(&output, (0, 0));
|
||||||
|
|
||||||
|
let mut calloop_data = CalloopData {
|
||||||
|
state,
|
||||||
|
display,
|
||||||
|
backend,
|
||||||
|
damage_tracker,
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("entering winit main event loop");
|
||||||
|
|
||||||
|
while running.load(Ordering::SeqCst) {
|
||||||
|
// Dispatch Wayland clients.
|
||||||
|
calloop_data
|
||||||
|
.display
|
||||||
|
.dispatch_clients(&mut calloop_data.state)
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
|
||||||
|
calloop_data
|
||||||
|
.display
|
||||||
|
.flush_clients()
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
|
||||||
|
// Dispatch calloop sources (Winit events + Wayland socket) with ~16ms timeout.
|
||||||
|
event_loop
|
||||||
|
.dispatch(Duration::from_millis(16), &mut calloop_data)
|
||||||
|
.map_err(|e| WayRayError::EventLoop(Box::new(e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("winit backend shutting down");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the compositor space to the Winit backend window.
|
||||||
|
///
|
||||||
|
/// Uses `OutputDamageTracker` for efficient re-rendering: only
|
||||||
|
/// damaged regions are redrawn each frame.
|
||||||
|
fn render_winit_frame(
|
||||||
|
state: &mut WayRay,
|
||||||
|
backend: &mut WinitGraphicsBackend<GlesRenderer>,
|
||||||
|
damage_tracker: &mut OutputDamageTracker,
|
||||||
|
) {
|
||||||
|
let output = state.output.clone();
|
||||||
|
|
||||||
|
// Get buffer age before bind (avoids borrow conflict).
|
||||||
|
let age = backend.buffer_age().unwrap_or(0);
|
||||||
|
|
||||||
|
// Render within a block so framebuffer is dropped before submit.
|
||||||
|
let render_result = {
|
||||||
|
let (renderer, mut framebuffer) = match backend.bind() {
|
||||||
|
Ok(pair) => pair,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "failed to bind winit backend for rendering");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let custom_elements: &[TextureRenderElement<GlesTexture>] = &[];
|
||||||
|
|
||||||
|
let render_result = render_output::<_, _, Window, _>(
|
||||||
|
&output,
|
||||||
|
renderer,
|
||||||
|
&mut framebuffer,
|
||||||
|
1.0,
|
||||||
|
age,
|
||||||
|
[&state.space],
|
||||||
|
custom_elements,
|
||||||
|
damage_tracker,
|
||||||
|
CLEAR_COLOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
match render_result {
|
||||||
|
Ok(result) => {
|
||||||
|
let damage = result.damage.cloned();
|
||||||
|
|
||||||
|
// Verify framebuffer capture path works (will be consumed
|
||||||
|
// by network transport in Phase 1).
|
||||||
|
let output_size = state.output.current_mode().unwrap().size;
|
||||||
|
let region: Rectangle<i32, BufferCoord> =
|
||||||
|
Rectangle::from_size(Size::from((output_size.w, output_size.h)));
|
||||||
|
|
||||||
|
match renderer.copy_framebuffer(&framebuffer, region, Fourcc::Argb8888) {
|
||||||
|
Ok(mapping) => match renderer.map_texture(&mapping) {
|
||||||
|
Ok(pixels) => {
|
||||||
|
let damage_rects = damage.as_ref().map(|d| d.len()).unwrap_or(0);
|
||||||
|
tracing::debug!(
|
||||||
|
width = output_size.w,
|
||||||
|
height = output_size.h,
|
||||||
|
bytes = pixels.len(),
|
||||||
|
damage_rects,
|
||||||
|
"framebuffer captured"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(?err, "failed to map framebuffer");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(?err, "failed to copy framebuffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(damage)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "damage tracker render failed");
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(damage) = render_result {
|
||||||
|
let has_damage = damage.is_some();
|
||||||
|
|
||||||
|
let submit_result = if let Some(ref rects) = damage {
|
||||||
|
backend.submit(Some(rects))
|
||||||
|
} else {
|
||||||
|
backend.submit(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = submit_result {
|
||||||
|
warn!(?err, "failed to submit frame");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send frame callbacks to all mapped surfaces so clients
|
||||||
|
// know they can draw the next frame.
|
||||||
|
if has_damage {
|
||||||
|
let time = state.clock.now();
|
||||||
|
for window in state.space.elements() {
|
||||||
|
window.send_frame(&output, time, Some(Duration::ZERO), |_, _| {
|
||||||
|
Some(output.clone())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,35 +1,17 @@
|
||||||
|
mod backend;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod render;
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use crate::handlers::ClientState;
|
|
||||||
use crate::state::WayRay;
|
use crate::state::WayRay;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
|
||||||
renderer::{damage::OutputDamageTracker, gles::GlesRenderer},
|
|
||||||
winit::{self, WinitEvent, WinitGraphicsBackend},
|
|
||||||
},
|
|
||||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||||
reexports::wayland_server::Display,
|
reexports::wayland_server::Display,
|
||||||
utils::Transform,
|
utils::Transform,
|
||||||
wayland::{compositor::CompositorClientState, socket::ListeningSocketSource},
|
|
||||||
};
|
};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
/// Data accessible from calloop event callbacks.
|
|
||||||
struct CalloopData {
|
|
||||||
state: WayRay,
|
|
||||||
display: Display<WayRay>,
|
|
||||||
backend: WinitGraphicsBackend<GlesRenderer>,
|
|
||||||
damage_tracker: OutputDamageTracker,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter(
|
.with_env_filter(
|
||||||
|
|
@ -40,20 +22,17 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
info!("wrsrvd starting");
|
info!("wrsrvd starting");
|
||||||
|
|
||||||
|
// Parse backend selection from CLI args.
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let use_winit = args
|
||||||
|
.windows(2)
|
||||||
|
.any(|w| w[0] == "--backend" && w[1] == "winit");
|
||||||
|
|
||||||
// Create the Wayland display.
|
// Create the Wayland display.
|
||||||
let mut display =
|
let mut display =
|
||||||
Display::<WayRay>::new().map_err(|e| errors::WayRayError::DisplayInit(Box::new(e)))?;
|
Display::<WayRay>::new().map_err(|e| errors::WayRayError::DisplayInit(Box::new(e)))?;
|
||||||
|
|
||||||
// Initialize the Winit backend (opens a window, creates a GlesRenderer).
|
// Create a virtual output.
|
||||||
let (backend, winit_event_loop) = winit::init::<GlesRenderer>().map_err(|e| {
|
|
||||||
errors::WayRayError::BackendInit(Box::<dyn std::error::Error + Send + Sync>::from(
|
|
||||||
e.to_string(),
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
info!("winit backend initialized");
|
|
||||||
|
|
||||||
// Create a virtual output matching the window size.
|
|
||||||
let window_size = backend.window_size();
|
|
||||||
let output = Output::new(
|
let output = Output::new(
|
||||||
"wayray-0".to_string(),
|
"wayray-0".to_string(),
|
||||||
PhysicalProperties {
|
PhysicalProperties {
|
||||||
|
|
@ -64,9 +43,11 @@ fn main() -> Result<()> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Default to 1280x720 for headless; Winit will use its window size
|
||||||
|
// but we still need an initial mode for state setup.
|
||||||
let mode = Mode {
|
let mode = Mode {
|
||||||
size: window_size,
|
size: (1280, 720).into(),
|
||||||
refresh: 60_000, // 60 Hz in millihertz
|
refresh: 60_000,
|
||||||
};
|
};
|
||||||
output.change_current_state(Some(mode), Some(Transform::Normal), None, None);
|
output.change_current_state(Some(mode), Some(Transform::Normal), None, None);
|
||||||
output.set_preferred(mode);
|
output.set_preferred(mode);
|
||||||
|
|
@ -75,110 +56,16 @@ fn main() -> Result<()> {
|
||||||
output.create_global::<WayRay>(&display.handle());
|
output.create_global::<WayRay>(&display.handle());
|
||||||
|
|
||||||
// Create compositor state.
|
// Create compositor state.
|
||||||
let mut state = WayRay::new(&mut display, output.clone());
|
let state = WayRay::new(&mut display, output.clone());
|
||||||
|
|
||||||
// Map the output into the compositor space.
|
info!(
|
||||||
state.space.map_output(&output, (0, 0));
|
backend = if use_winit { "winit" } else { "headless" },
|
||||||
info!("output mapped: {:?} @ {:?}", mode.size, mode.refresh);
|
"dispatching to backend"
|
||||||
|
|
||||||
// Create a Wayland listening socket for clients.
|
|
||||||
let listening_socket = ListeningSocketSource::new_auto()
|
|
||||||
.map_err(|e| errors::WayRayError::DisplayInit(Box::new(e)))?;
|
|
||||||
let socket_name = listening_socket.socket_name().to_os_string();
|
|
||||||
info!(?socket_name, "wayland socket created");
|
|
||||||
|
|
||||||
// Set WAYLAND_DISPLAY so child processes can find us.
|
|
||||||
// SAFETY: This is called early in main before any other threads are spawned,
|
|
||||||
// so there are no concurrent readers of the environment.
|
|
||||||
unsafe { std::env::set_var("WAYLAND_DISPLAY", &socket_name) };
|
|
||||||
|
|
||||||
// Create the calloop event loop.
|
|
||||||
let mut event_loop: smithay::reexports::calloop::EventLoop<CalloopData> =
|
|
||||||
smithay::reexports::calloop::EventLoop::try_new()
|
|
||||||
.map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?;
|
|
||||||
|
|
||||||
let loop_handle = event_loop.handle();
|
|
||||||
|
|
||||||
// Insert the Wayland listening socket as a calloop source.
|
|
||||||
// When a new client connects, insert it into the display.
|
|
||||||
loop_handle
|
|
||||||
.insert_source(listening_socket, |client_stream, _, data| {
|
|
||||||
data.display
|
|
||||||
.handle()
|
|
||||||
.insert_client(
|
|
||||||
client_stream,
|
|
||||||
Arc::new(ClientState {
|
|
||||||
compositor_state: CompositorClientState::default(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.map_err(|e| errors::WayRayError::EventLoop(Box::new(e.error)))?;
|
|
||||||
|
|
||||||
// Shared flag to signal the main loop to exit.
|
|
||||||
let running = Arc::new(AtomicBool::new(true));
|
|
||||||
let running_clone = running.clone();
|
|
||||||
|
|
||||||
// Insert the Winit event loop as a calloop source.
|
|
||||||
loop_handle
|
|
||||||
.insert_source(winit_event_loop, move |event, _, data| match event {
|
|
||||||
WinitEvent::Resized { size, scale_factor } => {
|
|
||||||
// TODO: Update output mode, damage tracker, and space mapping
|
|
||||||
// to reflect the new window size. Currently the compositor
|
|
||||||
// continues rendering at the original size after resize.
|
|
||||||
info!(?size, scale_factor, "window resized");
|
|
||||||
}
|
|
||||||
WinitEvent::Focus(focused) => {
|
|
||||||
info!(focused, "window focus changed");
|
|
||||||
}
|
|
||||||
WinitEvent::Input(event) => {
|
|
||||||
data.state.process_input_event(event);
|
|
||||||
}
|
|
||||||
WinitEvent::Redraw => {
|
|
||||||
render::render_output_frame(
|
|
||||||
&mut data.state,
|
|
||||||
&mut data.backend,
|
|
||||||
&mut data.damage_tracker,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if use_winit {
|
||||||
|
backend::winit::run(display, state, output)
|
||||||
|
} else {
|
||||||
|
backend::headless::run(display, state, output)
|
||||||
}
|
}
|
||||||
WinitEvent::CloseRequested => {
|
|
||||||
info!("close requested, shutting down");
|
|
||||||
running_clone.store(false, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|e| errors::WayRayError::EventLoop(Box::new(e.error)))?;
|
|
||||||
|
|
||||||
// Create a damage tracker for efficient rendering.
|
|
||||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
|
||||||
|
|
||||||
let mut calloop_data = CalloopData {
|
|
||||||
state,
|
|
||||||
display,
|
|
||||||
backend,
|
|
||||||
damage_tracker,
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("entering main event loop");
|
|
||||||
|
|
||||||
// Main event loop.
|
|
||||||
while running.load(Ordering::SeqCst) {
|
|
||||||
// Dispatch Wayland clients.
|
|
||||||
calloop_data
|
|
||||||
.display
|
|
||||||
.dispatch_clients(&mut calloop_data.state)
|
|
||||||
.map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?;
|
|
||||||
|
|
||||||
calloop_data
|
|
||||||
.display
|
|
||||||
.flush_clients()
|
|
||||||
.map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?;
|
|
||||||
|
|
||||||
// Dispatch calloop sources (Winit events + Wayland socket) with ~16ms timeout.
|
|
||||||
event_loop
|
|
||||||
.dispatch(Duration::from_millis(16), &mut calloop_data)
|
|
||||||
.map_err(|e| errors::WayRayError::EventLoop(Box::new(e)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("wrsrvd shutting down");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
use smithay::{
|
|
||||||
backend::{
|
|
||||||
allocator::Fourcc,
|
|
||||||
renderer::{
|
|
||||||
ExportMem,
|
|
||||||
damage::OutputDamageTracker,
|
|
||||||
element::texture::TextureRenderElement,
|
|
||||||
gles::{GlesRenderer, GlesTexture},
|
|
||||||
},
|
|
||||||
winit::WinitGraphicsBackend,
|
|
||||||
},
|
|
||||||
desktop::{Window, space::render_output},
|
|
||||||
utils::{Buffer as BufferCoord, Rectangle, Size},
|
|
||||||
};
|
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
use crate::state::WayRay;
|
|
||||||
|
|
||||||
/// Dark grey clear color for the compositor background.
|
|
||||||
const CLEAR_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.0];
|
|
||||||
|
|
||||||
/// Render the compositor space to the Winit backend window.
|
|
||||||
///
|
|
||||||
/// Uses `OutputDamageTracker` for efficient re-rendering: only
|
|
||||||
/// damaged regions are redrawn each frame.
|
|
||||||
///
|
|
||||||
/// Returns `true` if any damage was present and submitted.
|
|
||||||
pub fn render_output_frame(
|
|
||||||
state: &mut WayRay,
|
|
||||||
backend: &mut WinitGraphicsBackend<GlesRenderer>,
|
|
||||||
damage_tracker: &mut OutputDamageTracker,
|
|
||||||
) -> bool {
|
|
||||||
let output = state.output.clone();
|
|
||||||
|
|
||||||
// Get buffer age before bind (avoids borrow conflict).
|
|
||||||
let age = backend.buffer_age().unwrap_or(0);
|
|
||||||
|
|
||||||
// Render within a block so framebuffer is dropped before submit.
|
|
||||||
let render_result = {
|
|
||||||
let (renderer, mut framebuffer) = match backend.bind() {
|
|
||||||
Ok(pair) => pair,
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "failed to bind winit backend for rendering");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// The empty custom elements slice needs a concrete type.
|
|
||||||
let custom_elements: &[TextureRenderElement<GlesTexture>] = &[];
|
|
||||||
|
|
||||||
let render_result = render_output::<_, _, Window, _>(
|
|
||||||
&output,
|
|
||||||
renderer,
|
|
||||||
&mut framebuffer,
|
|
||||||
1.0, // alpha
|
|
||||||
age,
|
|
||||||
[&state.space],
|
|
||||||
custom_elements,
|
|
||||||
damage_tracker,
|
|
||||||
CLEAR_COLOR,
|
|
||||||
);
|
|
||||||
|
|
||||||
match render_result {
|
|
||||||
Ok(result) => {
|
|
||||||
let damage = result.damage.cloned();
|
|
||||||
|
|
||||||
// Verify framebuffer capture path works (will be consumed
|
|
||||||
// by network transport in Phase 1).
|
|
||||||
let output_size = state.output.current_mode().unwrap().size;
|
|
||||||
let region: Rectangle<i32, BufferCoord> =
|
|
||||||
Rectangle::from_size(Size::from((output_size.w, output_size.h)));
|
|
||||||
|
|
||||||
match renderer.copy_framebuffer(&framebuffer, region, Fourcc::Argb8888) {
|
|
||||||
Ok(mapping) => match renderer.map_texture(&mapping) {
|
|
||||||
Ok(pixels) => {
|
|
||||||
let damage_rects = damage.as_ref().map(|d| d.len()).unwrap_or(0);
|
|
||||||
tracing::debug!(
|
|
||||||
width = output_size.w,
|
|
||||||
height = output_size.h,
|
|
||||||
bytes = pixels.len(),
|
|
||||||
damage_rects,
|
|
||||||
"framebuffer captured"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::warn!(?err, "failed to map framebuffer");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
tracing::warn!(?err, "failed to copy framebuffer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(damage)
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "damage tracker render failed");
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// framebuffer is now dropped, backend is no longer borrowed.
|
|
||||||
|
|
||||||
match render_result {
|
|
||||||
Ok(damage) => {
|
|
||||||
let has_damage = damage.is_some();
|
|
||||||
|
|
||||||
let submit_result = if let Some(ref rects) = damage {
|
|
||||||
backend.submit(Some(rects))
|
|
||||||
} else {
|
|
||||||
backend.submit(None)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = submit_result {
|
|
||||||
warn!(?err, "failed to submit frame");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send frame callbacks to all mapped surfaces so clients
|
|
||||||
// know they can draw the next frame.
|
|
||||||
let time = state.clock.now();
|
|
||||||
for window in state.space.elements() {
|
|
||||||
window.send_frame(&output, time, Some(std::time::Duration::ZERO), |_, _| {
|
|
||||||
Some(output.clone())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
has_damage
|
|
||||||
}
|
|
||||||
Err(()) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue