perf: fix RDP window drag blockiness — full window drag + remove throttle
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m44s
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m44s
Root cause: PerformanceFlags::default() included DISABLE_FULLWINDOWDRAG which told the RDP server to skip window contents during drag operations. Additionally, the frame_gen % 2 backend throttle dropped 50% of frame notifications during rapid updates. Fix: - PerformanceFlags: removed DISABLE_FULLWINDOWDRAG, added DISABLE_WALLPAPER, DISABLE_CURSOR_SHADOW, ENABLE_DESKTOP_COMPOSITION for optimal rendering - Removed backend frame throttle — frontend rAF coalescing handles rate limiting - Simplified buffer architecture: eliminated back_buffer and TokioMutex, RDP thread writes directly to front_buffer via RwLock write lock - Removed unused tokio::sync::Mutex import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d657b3742f
commit
6acd674905
@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::Mutex as TokioMutex;
|
|
||||||
|
|
||||||
use ironrdp::connector::{self, ClientConnector, ConnectionResult, Credentials, DesktopSize};
|
use ironrdp::connector::{self, ClientConnector, ConnectionResult, Credentials, DesktopSize};
|
||||||
use ironrdp::graphics::image_processing::PixelFormat;
|
use ironrdp::graphics::image_processing::PixelFormat;
|
||||||
@ -71,15 +71,10 @@ struct RdpSessionHandle {
|
|||||||
hostname: String,
|
hostname: String,
|
||||||
width: u16,
|
width: u16,
|
||||||
height: u16,
|
height: u16,
|
||||||
/// Double-buffered: back_buffer is written by the RDP thread,
|
/// Frame buffer: RDP thread writes via RwLock write, IPC reads via RwLock read.
|
||||||
/// front_buffer is read by the IPC command. Swap on GraphicsUpdate
|
/// Brief write-lock per GraphicsUpdate, concurrent reads for get_frame.
|
||||||
/// so reads never block writes. Arcs kept alive via struct ownership.
|
|
||||||
front_buffer: Arc<std::sync::RwLock<Vec<u8>>>,
|
front_buffer: Arc<std::sync::RwLock<Vec<u8>>>,
|
||||||
#[allow(dead_code)]
|
|
||||||
back_buffer: Arc<TokioMutex<Vec<u8>>>,
|
|
||||||
frame_dirty: Arc<AtomicBool>,
|
frame_dirty: Arc<AtomicBool>,
|
||||||
#[allow(dead_code)]
|
|
||||||
frame_generation: Arc<std::sync::atomic::AtomicU64>,
|
|
||||||
input_tx: mpsc::UnboundedSender<InputEvent>,
|
input_tx: mpsc::UnboundedSender<InputEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,10 +101,8 @@ impl RdpService {
|
|||||||
for pixel in initial_buf.chunks_exact_mut(4) {
|
for pixel in initial_buf.chunks_exact_mut(4) {
|
||||||
pixel[3] = 255;
|
pixel[3] = 255;
|
||||||
}
|
}
|
||||||
let front_buffer = Arc::new(std::sync::RwLock::new(initial_buf.clone()));
|
let front_buffer = Arc::new(std::sync::RwLock::new(initial_buf));
|
||||||
let back_buffer = Arc::new(TokioMutex::new(initial_buf));
|
|
||||||
let frame_dirty = Arc::new(AtomicBool::new(false));
|
let frame_dirty = Arc::new(AtomicBool::new(false));
|
||||||
let frame_generation = Arc::new(std::sync::atomic::AtomicU64::new(0));
|
|
||||||
|
|
||||||
let (input_tx, input_rx) = mpsc::unbounded_channel();
|
let (input_tx, input_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
@ -119,9 +112,7 @@ impl RdpService {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
front_buffer: front_buffer.clone(),
|
front_buffer: front_buffer.clone(),
|
||||||
back_buffer: back_buffer.clone(),
|
|
||||||
frame_dirty: frame_dirty.clone(),
|
frame_dirty: frame_dirty.clone(),
|
||||||
frame_generation: frame_generation.clone(),
|
|
||||||
input_tx,
|
input_tx,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -167,10 +158,8 @@ impl RdpService {
|
|||||||
if let Err(e) = run_active_session(
|
if let Err(e) = run_active_session(
|
||||||
connection_result,
|
connection_result,
|
||||||
framed,
|
framed,
|
||||||
back_buffer,
|
|
||||||
front_buffer,
|
front_buffer,
|
||||||
frame_dirty,
|
frame_dirty,
|
||||||
frame_generation,
|
|
||||||
input_rx,
|
input_rx,
|
||||||
width as u16,
|
width as u16,
|
||||||
height as u16,
|
height as u16,
|
||||||
@ -319,7 +308,11 @@ fn build_connector_config(config: &RdpConfig) -> Result<connector::Config, Strin
|
|||||||
request_data: None,
|
request_data: None,
|
||||||
autologon: false,
|
autologon: false,
|
||||||
enable_audio_playback: false,
|
enable_audio_playback: false,
|
||||||
performance_flags: PerformanceFlags::default(),
|
performance_flags: PerformanceFlags::DISABLE_WALLPAPER
|
||||||
|
| PerformanceFlags::DISABLE_MENUANIMATIONS
|
||||||
|
| PerformanceFlags::DISABLE_CURSOR_SHADOW
|
||||||
|
| PerformanceFlags::ENABLE_FONT_SMOOTHING
|
||||||
|
| PerformanceFlags::ENABLE_DESKTOP_COMPOSITION,
|
||||||
desktop_scale_factor: 0,
|
desktop_scale_factor: 0,
|
||||||
hardware_id: None,
|
hardware_id: None,
|
||||||
license_cache: None,
|
license_cache: None,
|
||||||
@ -349,7 +342,7 @@ async fn establish_connection(config: connector::Config, hostname: &str, port: u
|
|||||||
Ok((connection_result, upgraded_framed))
|
Ok((connection_result, upgraded_framed))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_active_session(connection_result: ConnectionResult, framed: UpgradedFramed, back_buffer: Arc<TokioMutex<Vec<u8>>>, front_buffer: Arc<std::sync::RwLock<Vec<u8>>>, frame_dirty: Arc<AtomicBool>, frame_generation: Arc<std::sync::atomic::AtomicU64>, mut input_rx: mpsc::UnboundedReceiver<InputEvent>, width: u16, height: u16, app_handle: tauri::AppHandle, session_id: String) -> Result<(), String> {
|
async fn run_active_session(connection_result: ConnectionResult, framed: UpgradedFramed, front_buffer: Arc<std::sync::RwLock<Vec<u8>>>, frame_dirty: Arc<AtomicBool>, mut input_rx: mpsc::UnboundedReceiver<InputEvent>, width: u16, height: u16, app_handle: tauri::AppHandle, session_id: String) -> Result<(), String> {
|
||||||
let (mut reader, mut writer) = split_tokio_framed(framed);
|
let (mut reader, mut writer) = split_tokio_framed(framed);
|
||||||
let mut image = DecodedImage::new(PixelFormat::RgbA32, width, height);
|
let mut image = DecodedImage::new(PixelFormat::RgbA32, width, height);
|
||||||
let mut active_stage = ActiveStage::new(connection_result);
|
let mut active_stage = ActiveStage::new(connection_result);
|
||||||
@ -408,24 +401,16 @@ async fn run_active_session(connection_result: ConnectionResult, framed: Upgrade
|
|||||||
match out {
|
match out {
|
||||||
ActiveStageOutput::ResponseFrame(frame) => { writer.write_all(&frame).await.map_err(|e| format!("Failed to write RDP response frame: {}", e))?; }
|
ActiveStageOutput::ResponseFrame(frame) => { writer.write_all(&frame).await.map_err(|e| format!("Failed to write RDP response frame: {}", e))?; }
|
||||||
ActiveStageOutput::GraphicsUpdate(_region) => {
|
ActiveStageOutput::GraphicsUpdate(_region) => {
|
||||||
// Write to back buffer (async mutex, no contention with reads)
|
// Write decoded image directly to front buffer.
|
||||||
|
// Single RwLock write — readers use read lock, no contention.
|
||||||
{
|
{
|
||||||
let mut buf = back_buffer.lock().await;
|
|
||||||
let src = image.data();
|
let src = image.data();
|
||||||
if src.len() == buf.len() { buf.copy_from_slice(src); } else { *buf = src.to_vec(); }
|
|
||||||
}
|
|
||||||
// Swap into front buffer (RwLock write — brief, readers use read lock)
|
|
||||||
{
|
|
||||||
let back = back_buffer.lock().await;
|
|
||||||
let mut front = front_buffer.write().unwrap_or_else(|e| e.into_inner());
|
let mut front = front_buffer.write().unwrap_or_else(|e| e.into_inner());
|
||||||
front.copy_from_slice(&back);
|
if src.len() == front.len() { front.copy_from_slice(src); } else { *front = src.to_vec(); }
|
||||||
}
|
}
|
||||||
let frame_gen = frame_generation.fetch_add(1, Ordering::Release);
|
|
||||||
frame_dirty.store(true, Ordering::Release);
|
frame_dirty.store(true, Ordering::Release);
|
||||||
// Throttle: only emit event every other frame to avoid flooding IPC
|
// Signal frontend — rAF coalescing prevents flood
|
||||||
if frame_gen % 2 == 0 {
|
let _ = app_handle.emit(&format!("rdp:frame:{}", session_id), ());
|
||||||
let _ = app_handle.emit(&format!("rdp:frame:{}", session_id), ());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ActiveStageOutput::Terminate(reason) => { info!("RDP session terminated: {:?}", reason); return Ok(()); }
|
ActiveStageOutput::Terminate(reason) => { info!("RDP session terminated: {:?}", reason); return Ok(()); }
|
||||||
ActiveStageOutput::DeactivateAll(_) => { warn!("RDP server sent DeactivateAll — reconnection not yet implemented"); return Ok(()); }
|
ActiveStageOutput::DeactivateAll(_) => { warn!("RDP server sent DeactivateAll — reconnection not yet implemented"); return Ok(()); }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user