Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions crates/ironrdp-egfx/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,58 @@ impl GraphicsPipelineServer {
Some(frame_id)
}

/// Queue an uncompressed bitmap frame for transmission via EGFX
///
/// Sends raw pixel data through `WireToSurface1` with `Codec1Type::Uncompressed`.
/// Used for V8 clients that support EGFX but not H.264 (AVC420/AVC444).
///
/// The `bitmap_data` should be in the surface's pixel format (typically XRGB).
///
/// Returns `Some(frame_id)` if queued, `None` if not ready or backpressured.
pub fn send_uncompressed_frame(
&mut self,
surface_id: u16,
bitmap_data: &[u8],
dest_width: u16,
dest_height: u16,
timestamp_ms: u32,
) -> Option<u32> {
Comment thread
glamberson marked this conversation as resolved.
if !self.is_ready() {
return None;
}
if self.should_backpressure() {
Comment thread
glamberson marked this conversation as resolved.
self.qoe.record_backpressure();
return None;
}

let surface = self.surfaces.get(surface_id)?;

let timestamp = Self::make_timestamp(timestamp_ms);
let frame_id = self.frames.begin_frame(timestamp);

let dest_rect = InclusiveRectangle {
left: 0,
top: 0,
right: dest_width.saturating_sub(1),
bottom: dest_height.saturating_sub(1),
};

self.output_queue
.push_back(GfxPdu::StartFrame(StartFramePdu { timestamp, frame_id }));

self.output_queue.push_back(GfxPdu::WireToSurface1(WireToSurface1Pdu {
surface_id,
codec_id: Codec1Type::Uncompressed,
pixel_format: surface.pixel_format,
destination_rectangle: dest_rect,
bitmap_data: bitmap_data.to_vec(),
}));

self.output_queue.push_back(GfxPdu::EndFrame(EndFramePdu { frame_id }));

Some(frame_id)
}

// ========================================================================
// Output Management
// ========================================================================
Expand Down
56 changes: 56 additions & 0 deletions crates/ironrdp-testsuite-core/tests/egfx/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,59 @@ fn test_qoe_reset() {
server.reset_qoe();
assert!(server.qoe_snapshot().is_none());
}

// ============================================================================
// Uncompressed Frame Tests
// ============================================================================

#[test]
fn test_send_uncompressed_frame_queues_correctly() {
let handler = Box::new(TestHandler::new());
let mut server = GraphicsPipelineServer::new(handler);

// V8 client: EGFX but no H.264
let client_caps_pdu = GfxPdu::CapabilitiesAdvertise(CapabilitiesAdvertisePdu(vec![CapabilitySet::V8 {
flags: CapabilitiesV8Flags::SMALL_CACHE,
}]));
let payload = encode_pdu(&client_caps_pdu);
let _output = server.process(0, &payload).expect("process failed");

let surface_id = server.create_surface(64, 64).unwrap();
server.map_surface_to_output(surface_id, 0, 0);
server.drain_output(); // Clear setup PDUs

// 64x64 XRGB = 16384 bytes
let pixel_data = vec![0xFFu8; 64 * 64 * 4];
let frame_id = server.send_uncompressed_frame(surface_id, &pixel_data, 64, 64, 0);
assert!(frame_id.is_some());

// Output: StartFrame + WireToSurface1 + EndFrame
let output = server.drain_output();
assert_eq!(output.len(), 3);
}

#[test]
fn test_send_uncompressed_frame_backpressure() {
let handler = Box::new(TestHandler::new());
let mut server = GraphicsPipelineServer::new(handler);
server.set_max_frames_in_flight(1);

let client_caps_pdu = GfxPdu::CapabilitiesAdvertise(CapabilitiesAdvertisePdu(vec![CapabilitySet::V8 {
flags: CapabilitiesV8Flags::SMALL_CACHE,
}]));
let payload = encode_pdu(&client_caps_pdu);
let _output = server.process(0, &payload).expect("process failed");

let surface_id = server.create_surface(64, 64).unwrap();
server.drain_output();

let pixel_data = vec![0xFFu8; 64 * 64 * 4];

// First frame succeeds
let frame1 = server.send_uncompressed_frame(surface_id, &pixel_data, 64, 64, 0);
assert!(frame1.is_some());

// Second frame blocked by backpressure
let frame2 = server.send_uncompressed_frame(surface_id, &pixel_data, 64, 64, 16);
assert!(frame2.is_none());
}
Loading