mirror of
https://github.com/CloudNebulaProject/wayray.git
synced 2026-04-10 13:10:41 +00:00
Wire end-to-end frame encoding and network into headless backend
Start QUIC server in wrsrvd main.rs before dispatching to backend, passing NetworkHandle to the headless backend. The headless render loop now encodes each frame as XOR diff against the previous frame, compresses damage regions with zstd, and sends FrameUpdate messages to connected clients via the network channel. Network input from remote clients is drained each iteration of the main event loop and injected into the compositor via inject_network_input(). Connection state (client connected/disconnected) is tracked to avoid encoding frames when no client is listening.
This commit is contained in:
parent
eb8394d247
commit
564c473ab4
2 changed files with 158 additions and 11 deletions
|
|
@ -24,9 +24,12 @@ use smithay::{
|
||||||
wayland::{compositor::CompositorClientState, socket::ListeningSocketSource},
|
wayland::{compositor::CompositorClientState, socket::ListeningSocketSource},
|
||||||
};
|
};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use wayray_protocol::encoding;
|
||||||
|
use wayray_protocol::messages::FrameUpdate;
|
||||||
|
|
||||||
use crate::errors::WayRayError;
|
use crate::errors::WayRayError;
|
||||||
use crate::handlers::ClientState;
|
use crate::handlers::ClientState;
|
||||||
|
use crate::network::{CompositorToNet, NetToCompositor, NetworkHandle};
|
||||||
use crate::state::WayRay;
|
use crate::state::WayRay;
|
||||||
|
|
||||||
/// Dark grey clear color for the compositor background.
|
/// Dark grey clear color for the compositor background.
|
||||||
|
|
@ -39,13 +42,26 @@ struct CalloopData {
|
||||||
renderer: PixmanRenderer,
|
renderer: PixmanRenderer,
|
||||||
render_buffer: pixman_lib::Image<'static, 'static>,
|
render_buffer: pixman_lib::Image<'static, 'static>,
|
||||||
damage_tracker: OutputDamageTracker,
|
damage_tracker: OutputDamageTracker,
|
||||||
|
/// Network handle for sending frames and receiving input.
|
||||||
|
net_handle: NetworkHandle,
|
||||||
|
/// Previous frame's pixel data for XOR diff encoding.
|
||||||
|
previous_frame: Vec<u8>,
|
||||||
|
/// Frame sequence counter.
|
||||||
|
frame_sequence: u64,
|
||||||
|
/// Whether a remote client is currently connected.
|
||||||
|
client_connected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the compositor with the headless PixmanRenderer backend.
|
/// Run the compositor with the headless PixmanRenderer backend.
|
||||||
///
|
///
|
||||||
/// This creates a CPU-only software renderer suitable for headless servers
|
/// This creates a CPU-only software renderer suitable for headless servers
|
||||||
/// with no display hardware.
|
/// with no display hardware.
|
||||||
pub fn run(display: Display<WayRay>, mut state: WayRay, output: Output) -> Result<()> {
|
pub fn run(
|
||||||
|
display: Display<WayRay>,
|
||||||
|
mut state: WayRay,
|
||||||
|
output: Output,
|
||||||
|
net_handle: NetworkHandle,
|
||||||
|
) -> Result<()> {
|
||||||
// Create the PixmanRenderer (CPU software renderer).
|
// Create the PixmanRenderer (CPU software renderer).
|
||||||
let mut renderer = PixmanRenderer::new().map_err(|e| {
|
let mut renderer = PixmanRenderer::new().map_err(|e| {
|
||||||
WayRayError::BackendInit(Box::<dyn std::error::Error + Send + Sync>::from(
|
WayRayError::BackendInit(Box::<dyn std::error::Error + Send + Sync>::from(
|
||||||
|
|
@ -117,12 +133,20 @@ pub fn run(display: Display<WayRay>, mut state: WayRay, output: Output) -> Resul
|
||||||
// Map the output into the compositor space.
|
// Map the output into the compositor space.
|
||||||
state.space.map_output(&output, (0, 0));
|
state.space.map_output(&output, (0, 0));
|
||||||
|
|
||||||
|
// Initialize previous frame buffer (all zeros = black).
|
||||||
|
let frame_size = (output_size.w * output_size.h * 4) as usize;
|
||||||
|
let previous_frame = vec![0u8; frame_size];
|
||||||
|
|
||||||
let mut calloop_data = CalloopData {
|
let mut calloop_data = CalloopData {
|
||||||
state,
|
state,
|
||||||
display,
|
display,
|
||||||
renderer,
|
renderer,
|
||||||
render_buffer,
|
render_buffer,
|
||||||
damage_tracker,
|
damage_tracker,
|
||||||
|
net_handle,
|
||||||
|
previous_frame,
|
||||||
|
frame_sequence: 0,
|
||||||
|
client_connected: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let running = Arc::new(AtomicBool::new(true));
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
|
@ -134,6 +158,9 @@ pub fn run(display: Display<WayRay>, mut state: WayRay, output: Output) -> Resul
|
||||||
info!("entering headless main event loop");
|
info!("entering headless main event loop");
|
||||||
|
|
||||||
while running.load(Ordering::SeqCst) {
|
while running.load(Ordering::SeqCst) {
|
||||||
|
// Drain network events (input from remote clients, connection state).
|
||||||
|
drain_network_events(&mut calloop_data);
|
||||||
|
|
||||||
// Dispatch Wayland clients.
|
// Dispatch Wayland clients.
|
||||||
calloop_data
|
calloop_data
|
||||||
.display
|
.display
|
||||||
|
|
@ -152,9 +179,44 @@ pub fn run(display: Display<WayRay>, mut state: WayRay, output: Output) -> Resul
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("headless backend shutting down");
|
info!("headless backend shutting down");
|
||||||
|
calloop_data.net_handle.shutdown();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drain all pending network events (input, connection changes).
|
||||||
|
fn drain_network_events(data: &mut CalloopData) {
|
||||||
|
use std::sync::mpsc::TryRecvError;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match data.net_handle.rx.try_recv() {
|
||||||
|
Ok(NetToCompositor::Input(input_msg)) => {
|
||||||
|
data.state.inject_network_input(input_msg);
|
||||||
|
}
|
||||||
|
Ok(NetToCompositor::ClientConnected(hello)) => {
|
||||||
|
info!(
|
||||||
|
version = hello.version,
|
||||||
|
capabilities = ?hello.capabilities,
|
||||||
|
"remote client connected"
|
||||||
|
);
|
||||||
|
data.client_connected = true;
|
||||||
|
}
|
||||||
|
Ok(NetToCompositor::ClientDisconnected) => {
|
||||||
|
info!("remote client disconnected");
|
||||||
|
data.client_connected = false;
|
||||||
|
}
|
||||||
|
Ok(NetToCompositor::Control(ctrl)) => {
|
||||||
|
tracing::debug!(?ctrl, "received control message");
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => break,
|
||||||
|
Err(TryRecvError::Disconnected) => {
|
||||||
|
warn!("network channel disconnected");
|
||||||
|
data.client_connected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Render a single frame using the headless PixmanRenderer.
|
/// Render a single frame using the headless PixmanRenderer.
|
||||||
fn render_headless_frame(data: &mut CalloopData) {
|
fn render_headless_frame(data: &mut CalloopData) {
|
||||||
let output = data.state.output.clone();
|
let output = data.state.output.clone();
|
||||||
|
|
@ -197,15 +259,17 @@ fn render_headless_frame(data: &mut CalloopData) {
|
||||||
{
|
{
|
||||||
Ok(mapping) => match data.renderer.map_texture(&mapping) {
|
Ok(mapping) => match data.renderer.map_texture(&mapping) {
|
||||||
Ok(pixels) => {
|
Ok(pixels) => {
|
||||||
let damage_rects = damage.as_ref().map(|d| d.len()).unwrap_or(0);
|
// Send frame over network if a client is connected.
|
||||||
tracing::debug!(
|
if data.client_connected {
|
||||||
width = output_size.w,
|
send_frame_to_network(
|
||||||
height = output_size.h,
|
data,
|
||||||
bytes = pixels.len(),
|
pixels,
|
||||||
damage_rects,
|
&damage,
|
||||||
"headless framebuffer captured"
|
output_size.w,
|
||||||
|
output_size.h,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!(?err, "failed to map headless framebuffer");
|
tracing::warn!(?err, "failed to map headless framebuffer");
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +293,77 @@ fn render_headless_frame(data: &mut CalloopData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encode the current frame as XOR diff against the previous frame and
|
||||||
|
/// send it to the connected client via the network channel.
|
||||||
|
fn send_frame_to_network(
|
||||||
|
data: &mut CalloopData,
|
||||||
|
current_pixels: &[u8],
|
||||||
|
damage: &Option<Vec<Rectangle<i32, smithay::utils::Physical>>>,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
) {
|
||||||
|
let stride = width as usize * 4;
|
||||||
|
|
||||||
|
// Compute XOR diff.
|
||||||
|
let diff = encoding::xor_diff(current_pixels, &data.previous_frame);
|
||||||
|
|
||||||
|
// Encode damaged regions. If damage tracking reports specific rects, use those;
|
||||||
|
// otherwise encode the full frame as a single region.
|
||||||
|
let regions: Vec<_> = match damage {
|
||||||
|
Some(rects) if !rects.is_empty() => rects
|
||||||
|
.iter()
|
||||||
|
.map(|rect| {
|
||||||
|
encoding::encode_region(
|
||||||
|
&diff,
|
||||||
|
stride,
|
||||||
|
rect.loc.x.max(0) as u32,
|
||||||
|
rect.loc.y.max(0) as u32,
|
||||||
|
(rect.size.w.min(width - rect.loc.x.max(0))).max(0) as u32,
|
||||||
|
(rect.size.h.min(height - rect.loc.y.max(0))).max(0) as u32,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(|r| r.width > 0 && r.height > 0)
|
||||||
|
.collect(),
|
||||||
|
_ => {
|
||||||
|
// Full-frame update.
|
||||||
|
vec![encoding::encode_region(
|
||||||
|
&diff,
|
||||||
|
stride,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
width as u32,
|
||||||
|
height as u32,
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
data.frame_sequence += 1;
|
||||||
|
let frame_update = FrameUpdate {
|
||||||
|
sequence: data.frame_sequence,
|
||||||
|
regions,
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
sequence = data.frame_sequence,
|
||||||
|
regions = frame_update.regions.len(),
|
||||||
|
"sending frame to client"
|
||||||
|
);
|
||||||
|
|
||||||
|
if data
|
||||||
|
.net_handle
|
||||||
|
.tx
|
||||||
|
.send(CompositorToNet::SendFrame(frame_update))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
warn!("failed to send frame to network thread");
|
||||||
|
data.client_connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store current frame as previous for next diff.
|
||||||
|
data.previous_frame.clear();
|
||||||
|
data.previous_frame.extend_from_slice(current_pixels);
|
||||||
|
}
|
||||||
|
|
||||||
/// Install a Ctrl-C handler that sets the running flag to false.
|
/// Install a Ctrl-C handler that sets the running flag to false.
|
||||||
fn ctrlc_handler(running: &Arc<AtomicBool>) {
|
fn ctrlc_handler(running: &Arc<AtomicBool>) {
|
||||||
let r = running.clone();
|
let r = running.clone();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod handlers;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
use crate::network::{ServerConfig, start_server};
|
||||||
use crate::state::WayRay;
|
use crate::state::WayRay;
|
||||||
use miette::Result;
|
use miette::Result;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
|
@ -59,14 +60,25 @@ fn main() -> Result<()> {
|
||||||
// Create compositor state.
|
// Create compositor state.
|
||||||
let state = WayRay::new(&mut display, output.clone());
|
let state = WayRay::new(&mut display, output.clone());
|
||||||
|
|
||||||
|
// Start the QUIC network server for remote client connections.
|
||||||
|
let output_size = mode.size;
|
||||||
|
let net_handle = start_server(ServerConfig {
|
||||||
|
output_width: output_size.w as u32,
|
||||||
|
output_height: output_size.h as u32,
|
||||||
|
..ServerConfig::default()
|
||||||
|
});
|
||||||
|
info!("QUIC network server started");
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
backend = if use_winit { "winit" } else { "headless" },
|
backend = if use_winit { "winit" } else { "headless" },
|
||||||
"dispatching to backend"
|
"dispatching to backend"
|
||||||
);
|
);
|
||||||
|
|
||||||
if use_winit {
|
if use_winit {
|
||||||
backend::winit::run(display, state, output)
|
let result = backend::winit::run(display, state, output);
|
||||||
|
net_handle.shutdown();
|
||||||
|
result
|
||||||
} else {
|
} else {
|
||||||
backend::headless::run(display, state, output)
|
backend::headless::run(display, state, output, net_handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue