Skip to content

fix(rdp6): use actual bitmap dimensions instead of rectangle dimensions#1398

Open
ArmConSol (armconsol) wants to merge 4 commits into
Devolutions:masterfrom
armconsol:fix/rdp6-bitmap-stride-mismatch
Open

fix(rdp6): use actual bitmap dimensions instead of rectangle dimensions#1398
ArmConSol (armconsol) wants to merge 4 commits into
Devolutions:masterfrom
armconsol:fix/rdp6-bitmap-stride-mismatch

Conversation

@armconsol

Copy link
Copy Markdown

Problem

When connecting to servers that send RDP6 compressed bitmaps with tiled updates (e.g., NVIDIA DGX), the rendered display shows horizontal striping and blur artifacts.

Root Cause

RDP6 BitmapUpdate PDUs can have different dimensions than their placement rectangles:

  • update.width × update.height = actual decoded bitmap size
  • update.rectangle = where to place the bitmap on screen

The current implementation of apply_rgb24() chunks the decoded RGB24 data using rectangle.width(), but the decoder produces data with update.width pixels per row. When these values differ, it causes stride mismatches and pixel data misalignment.

Example from Real Traffic

Connecting to NVIDIA DGX server at 1920x1080:

32 bpp compressed RDP6_BITMAP_STREAM: source=348x11, rectangle=345x11
  update.width = 348
  Rectangle (left:788, right:1132) width = 1132 - 788 + 1 = 345

The decoder produces 348 pixels/row, but apply_rgb24() chunks at 345 pixels/row → pixel misalignment → horizontal stripes.

Changes

  1. Add apply_rgb24_with_dimensions() that accepts explicit source_width parameter
  2. Update RDP6 handler in fast_path.rs to use update.width (not rectangle.width()) for chunking
  3. Keep apply_rgb24() for backward compatibility (delegates to new function)

Testing

  • ✅ Verified clean rendering at 1920x1080 against NVIDIA DGX server
  • ✅ No horizontal striping or blur artifacts
  • ✅ No regressions on other RDP servers

@armconsol

Copy link
Copy Markdown
Author

Fixes #1399

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes rendering artifacts with RDP6-compressed (32bpp) bitmap updates where the decoded bitmap’s source dimensions (update.width/update.height) differ from the placement rectangle dimensions (update.rectangle). It updates the session image application logic so RGB24 row chunking can be based on the true decoded stride, improving correctness for tiled updates (e.g., NVIDIA DGX).

Changes:

  • Added an RGB24 application API that accepts an explicit source width (stride) and updated the RDP6 fast-path bitmap handler to use update.width.
  • Refactored BGR24 application to support configurable row order (bottom-up vs top-down).
  • Expanded debug logging around RDP6 bitmap decode/placement to surface dimension mismatches.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
crates/ironrdp-session/src/image.rs Adds stride-aware RGB24 application and introduces configurable row ordering for BGR24 application.
crates/ironrdp-session/src/fast_path.rs Uses update.width/update.height for RDP6 decode/apply instead of rectangle dimensions and improves debug visibility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ironrdp-session/src/image.rs Outdated
Comment thread crates/ironrdp-session/src/image.rs Outdated
Comment thread crates/ironrdp-session/src/fast_path.rs
@armconsol

Copy link
Copy Markdown
Author

Copilot Review Addressed

Thank you for the review! All three issues have been fixed:

✅ Issue #1: Box lifetime and performance

Status: Fixed in commit 07a0a89

Removed the Box<dyn Iterator> pattern in apply_bgr24_bitmap_with_order(). Now uses direct if/else branches to keep everything monomorphized, eliminating:

  • Heap allocation overhead
  • Dynamic dispatch in hot pixel-copy path
  • Lifetime issues with borrowed data

✅ Issue #2: Bounds checking for source_width > rectangle.width()

Status: Fixed in commit 07a0a89

Added comprehensive bounds checking in apply_rgb24_with_dimensions():

  • Clamp copy_width = source_width.min(rect_width)
  • Clamp copy_height = (source_height).min(rect_height)
  • Guard against source_width == 0 to prevent chunks_exact(0) panic
  • Only copy the overlapping region: row[..copy_width * 3]

This prevents out-of-bounds writes when the bitmap extends beyond the rectangle.

✅ Issue #3: Structured logging fields

Status: Fixed in commit 07a0a89

Converted debug logs to use structured tracing fields per STYLE.md:

debug!(
    source_width,
    source_height,
    rect_width,
    rect_height,
    rect = ?update.rectangle,
    data_len = update.bitmap_data.len(),
    expected_len = source_width * source_height * 3,
    "Compressed RDP6 bitmap stream (32 bpp)"
);

This improves downstream filtering/aggregation and keeps messages concise.


All changes verified:

  • cargo check -p ironrdp-session passes
  • ✅ TRCAA builds successfully with updated patch
  • ✅ DGX rendering still works correctly (no regressions)

@armconsol

Copy link
Copy Markdown
Author

@copilot

ArmConSol (armconsol) pushed a commit to armconsol/tftsr-devops_investigation that referenced this pull request Jul 1, 2026
sd-arman and others added 4 commits July 1, 2026 09:12
The RDP6 bitmap stream decoder outputs RGB24 data in top-to-bottom order,
but apply_rgb24 was flipping rows (flip=true) causing horizontal striping
artifacts. Changed to flip=false for RDP6 decoded bitmaps.
RDP6 compressed bitmaps can have different dimensions than the update
rectangle. The decoder produces source_width × source_height pixels,
but the rectangle specifies the placement coordinates.

Previously, apply_rgb24() was chunking the decoded data using rectangle.width(),
which caused pixel data to be read at the wrong stride when
update.width != rectangle.width(). This manifested as horizontal striping
and blur artifacts.

Fix: Add apply_rgb24_with_dimensions() that accepts explicit source_width
parameter, and use update.width (not rectangle.width()) for chunking.
1. Remove Box<dyn Iterator> in apply_bgr24_bitmap_with_order
   - Eliminates unnecessary heap allocation and dynamic dispatch
   - Fixes lifetime issues by using direct if/else branches
   - Keeps hot pixel-copy path monomorphized for performance

2. Add bounds checking in apply_rgb24_with_dimensions
   - Clamp copy dimensions to min(source, rectangle) to prevent OOB writes
   - Guard against source_width == 0 to avoid chunks_exact(0) panic
   - Only copy the overlapping region when source > rectangle

3. Use structured tracing fields in debug logs
   - Convert formatted strings to structured fields per STYLE.md
   - Improves downstream filtering and aggregation
   - Keeps log messages concise

All issues identified by Copilot review now resolved.
@armconsol
ArmConSol (armconsol) force-pushed the fix/rdp6-bitmap-stride-mismatch branch from 07a0a89 to b0cac43 Compare July 1, 2026 14:12
ArmConSol (armconsol) pushed a commit to armconsol/tftsr-devops_investigation that referenced this pull request Jul 1, 2026
Resolves password-prompt-loop and striping artifacts on DGX connections.

Changes:
- Updated Cargo.toml to use patched IronRDP (path dependencies)
- Added CI step to clone IronRDP patch before builds
- Documented patch in IRONRDP_PATCH.md
- Contributed fix upstream: Devolutions/IronRDP#1398
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants