QNX

QNX Screen screen_post_window() and Qualcomm OpenWF Display

A short summary

Posted by half cup coffee on January 12, 2026

QNX Screen screen_post_window() and Qualcomm OpenWF Display Integration

Overview

This article explains how QNX Screen Graphics Subsystem integrates with hardware vendor implementations, specifically focusing on Qualcomm’s OpenWF Display (WFD) architecture. Understanding this flow is crucial for embedded graphics development on QNX platforms.

Architecture Overview

System Stack

┌─────────────────────────────────────┐
│ Application (SCREEN_POST_WINDOW) │
├─────────────────────────────────────┤
│ QNX Screen Graphics Subsystem │
│ (libscreen.so) │
├─────────────────────────────────────┤
│ Screen Composition Manager │
│ (screen compositor) │
├─────────────────────────────────────┤
│ OpenWF Display (WFD) Layer │ ← Hardware abstraction
├─────────────────────────────────────┤
│ Vendor-Specific WFD Driver │ ← Vendor implements
│ (libwfdqcom.so for Qualcomm) │
├─────────────────────────────────────┤
│ DRM/KMS (Kernel Mode Setting) │
├─────────────────────────────────────┤
│ Kernel Driver (msm_drm) │
├─────────────────────────────────────┤
│ Hardware (MDP + Display HW) │
└─────────────────────────────────────┘

Key Components

Component Purpose
Screen Library Application-facing API for graphics operations
Screen Compositor Manages window composition and layer ordering
OpenWF Display Hardware-independent display abstraction (Khronos standard)
Vendor WFD Driver Hardware-specific implementation
DRM/KMS Linux kernel display framework

The SCREEN_POST_WINDOW Journey

Function Signature

int screen_post_window(screen_window_t win, 
                    screen_buffer_t buf, 
                    int count, 
                    const int *dirty_rects, 
                    int flags);

What Happens Step-by-Step

Application renders to buffer

screen_get_window_property_pv(win, SCREEN_PROPERTY_RENDER_BUFFERS, &bufs);
// ... render content to bufs[0] ...

// Post the buffer to make it visible
screen_post_window(win, bufs[0], 1, NULL, 0);

Screen Library Processing

  • Validates window and buffer handles
  • Queues buffer for composition
  • Sends message to Screen compositor process

Screen Compositor

  • Receives posted buffers from multiple windows
  • Performs composition based on:
    • Z-order (window stacking)
    • Transparency/alpha blending
    • Clipping regions
  • Assigns windows to hardware pipelines

Dirty Rectangle Optimization

// Only update changed regions
int dirty_rects[] = {x, y, width, height};
screen_post_window(win, buf, 1, dirty_rects, 0);
Flag Behavior
SCREEN_WAIT_IDLE Blocks until buffer is displayed
SCREEN_POST_ASYNC Returns immediately without waiting
0 (default) Standard vsync-synchronized posting

OpenWF Display Abstraction Layer

Why OpenWF Display?

OpenWF Display provides a vendor-neutral API for display management, allowing the same application code to run on different hardware platforms.

Core Concepts

Devices: Physical display hardware units

WFDDevice device = wfdCreateDevice(WFD_DEFAULT_DEVICE_ID, NULL);

Ports: Physical display outputs (HDMI, LVDS, DSI, DisplayPort)

WFDPort port = wfdCreatePort(device, port_id, NULL);
WFDPortMode mode = wfdGetPortMode(device, port);

Pipelines: Hardware composition layers (overlays)

WFDPipeline pipeline = wfdCreatePipeline(device, pipeline_id, NULL);

Sources: Image/buffer sources for pipelines

WFDSource source = wfdCreateSourceFromStream(device, pipeline, stream, NULL);

Pipeline Architecture

Source Buffer          Pipelines              Display
┌──────────┐          ┌──────────┐
│ Video    │ ────────→│Pipeline 0│─┐
└──────────┘          │ (Video)  │ │
                   └──────────┘ │
┌──────────┐          ┌──────────┐ │
│ UI       │ ────────→│Pipeline 1│─┼─→ Composition → Output
└──────────┘          │(Graphics)│ │
                   └──────────┘ │
┌──────────┐          ┌──────────┐ │
│ Cursor   │ ────────→│Pipeline 2│─┘
└──────────┘          │ (Cursor) │
                   └──────────┘

Standard WFD API Flow

// 1. Create device
WFDDevice device = wfdCreateDevice(WFD_DEFAULT_DEVICE_ID, NULL);

// 2. Enumerate and configure port
WFDint port_ids[10];
wfdEnumeratePorts(device, port_ids, 10, NULL);

// 3. Create pipeline (hardware layer)
WFDPipeline pipeline = wfdCreatePipeline(device, pipeline_id, NULL);

// 4. Create source from buffer
WFDSource source = wfdCreateSourceFromStream(device, pipeline, stream, NULL);

// 5. Bind source to pipeline
wfdBindSourceToPipeline(device, pipeline, source, 
                     WFD_TRANSITION_AT_VSYNC, NULL);

// 6. Commit changes to hardware
wfdDeviceCommit(device, WFD_COMMIT_ENTIRE_DEVICE, WFD_INVALID_HANDLE);

Qualcomm Implementation Details in QNX

Qualcomm Display Architecture

┌──────────────────────────────────────────────────┐
│  libwfdqcom.so (Qualcomm WFD for QNX)            │
├──────────────────────────────────────────────────┤
│  QNX Graphics Framework                          │
├──────────────────────────────────────────────────┤
│  Screen Driver Interface                         │
├──────────────────────────────────────────────────┤
│  MDP (Mobile Display Processor) Hardware         │
│  ┌────────────────────────────────────────┐      │
│  │ SSPP (Source Surface Processor Planes) │      │
│  │  ├─ VIG Pipes (Video + Graphics)       │      │
│  │  ├─ RGB Pipes (Graphics only)          │      │
│  │  └─ DMA Pipes (Simple overlays)        │      │
│  ├────────────────────────────────────────┤      │
│  │ Layer Mixer (Blending engine)          │      │
│  ├────────────────────────────────────────┤      │
│  │ DSPP (Display Post-Processor)          │      │
│  │  ├─ Color correction                   │      │
│  │  ├─ Dithering                          │      │
│  │  └─ Sharpening                         │      │
│  ├────────────────────────────────────────┤      │
│  │ Interface (DSI/HDMI/DP)                │      │
│  └────────────────────────────────────────┘      │
└──────────────────────────────────────────────────┘

MDP Pipeline Types

Pipeline Type Purpose Capabilities
VIG (Video) Video playback Scaling, YUV→RGB, rotation, deinterlacing
RGB (Graphics) UI rendering Alpha blending, basic transforms
DMA (Direct) Cursors, simple overlays Minimal processing, low latency

Qualcomm-Specific WFD Implementation

// In libwfdqcom.so for QNX

WFDDevice wfdCreateDevice(WFDint deviceId, const WFDint *attribList) {
 DEVICE *dev = calloc(1, sizeof(DEVICE));
 
 // Open QNX graphics device
 dev->gfx_fd = open("/dev/screen", O_RDWR);
 
 // Initialize MDP resources through QNX interface
 screen_get_display_property_iv(display, SCREEN_PROPERTY_PIPELINE_COUNT, 
                                &dev->num_pipelines);
 
 // Setup memory allocator
 dev->mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
 
 // Initialize hardware pipelines
 init_mdp_pipelines(dev);
 
 return (WFDDevice)dev;
}

Qualcomm on QNX uses shared memory and physical memory allocation:

// Allocate physically contiguous buffer
void *virt_addr;
off64_t phys_addr;
size_t buffer_size = width * height * 4;  // RGBA

// Map physical memory
virt_addr = mmap64(NULL, buffer_size, 
                PROT_READ | PROT_WRITE | PROT_NOCACHE,
                MAP_SHARED | MAP_PHYS,
                NOFD, 
                phys_addr);

// Or use QNX Screen allocator
screen_create_buffer(&buf);
screen_set_buffer_property_iv(buf, SCREEN_PROPERTY_PHYSICALLY_CONTIGUOUS, 
                           &physically_contiguous);

QNX-Specific Integration

// Qualcomm WFD on QNX uses direct hardware access

int qcom_wfd_commit(DEVICE *dev) {
 // For each active pipeline
 for (int i = 0; i < dev->num_pipelines; i++) {
     PIPELINE *pipe = &dev->pipelines[i];
     
     if (!pipe->enabled) continue;
     
     SOURCE *src = pipe->staged_source;
     
     // Get physical address from Screen buffer
     off64_t phys_addr;
     screen_get_buffer_property_llv(src->screen_buf,
                                    SCREEN_PROPERTY_PHYSICAL_ADDRESS,
                                    &phys_addr);
     
     // Program MDP registers directly
     configure_mdp_sspp(pipe->hw_pipe_id, phys_addr, 
                       src->format, src->width, src->height);
     
     configure_layer_mixer(pipe->mixer_id, pipe->zorder, 
                          pipe->alpha);
 }
 
 // Trigger hardware update at next vsync
 trigger_mdp_flush();
 
 return 0;
}

Deep Dive: wfdBindSourceToPipeline

WFD_API_CALL void WFD_APIENTRY
wfdBindSourceToPipeline(WFDDevice device, 
                     WFDPipeline pipeline, 
                     WFDSource source, 
                     WFDTransition transition,
                     const WFDRect *region) WFD_APIEXIT

WFDDevice device

Handle to the display device (typically represents one display controller)

WFDPipeline pipeline

Hardware layer/overlay to use

typedef struct {
 uint32_t hw_pipe_id;     // MDP_SSPP_VIG0, RGB0, etc.
 uint32_t z_order;        // Stacking order (0=bottom)
 WFDRect src_rect;        // Source crop region
 WFDRect dst_rect;        // Destination position/size
 uint32_t alpha;          // Global alpha (0-255)
 bool needs_commit;       // Dirty flag
} PIPELINE;

WFDSource source:

typedef struct {
 void *buffer_handle;     // ION/dmabuf handle
 uint32_t phys_addr;      // Physical memory address
 uint32_t format;         // WFD_FORMAT_RGBA8888, NV12, etc.
 uint32_t width;          // Buffer width
 uint32_t height;         // Buffer height
 uint32_t stride;         // Bytes per row
 WFDEGLImage egl_image;   // Optional EGL integration
} SOURCE;

WFDTransition transition

Transition Behavior
WFD_TRANSITION_IMMEDIATE Apply immediately (may tear)
WFD_TRANSITION_AT_VSYNC Wait for vertical blank
Vendor extensions Fade, wipe, or other effects

WFDRect region: Source crop rectangle (NULL = entire source)

typedef struct {
 WFDint x;         // Left offset
 WFDint y;         // Top offset
 WFDint width;     // Crop width
 WFDint height;    // Crop height
} WFDRect;

Implementation Flow

void wfdBindSourceToPipeline(WFDDevice device, 
                          WFDPipeline pipeline, 
                          WFDSource source,
                          WFDTransition transition,
                          const WFDRect *region) {
 
 // 1. Validate handles
 DEVICE *dev = (DEVICE*)device;
 PIPELINE *pipe = (PIPELINE*)pipeline;
 SOURCE *src = (SOURCE*)source;
 
 if (!dev || !pipe || !src) {
     wfdSetError(device, WFD_ERROR_BAD_HANDLE);
     return;
 }
 
 // 2. Extract buffer information
 uint32_t phys_addr = src->phys_addr;
 uint32_t format = src->format;
 uint32_t stride = src->stride;
 
 // 3. Set source crop region
 if (region) {
     pipe->src_x = region->x;
     pipe->src_y = region->y;
     pipe->src_w = region->width;
     pipe->src_h = region->height;
 } else {
     // Use entire source
     pipe->src_x = 0;
     pipe->src_y = 0;
     pipe->src_w = src->width;
     pipe->src_h = src->height;
 }
 
 // 4. Configure MDP SSPP (hardware layer)
 struct mdp_sspp_config config = {
     .base_addr = phys_addr,
     .format = convert_wfd_to_mdp_format(format),
     .width = src->width,
     .height = src->height,
     .stride = stride,
     .src_rect = {pipe->src_x, pipe->src_y, 
                  pipe->src_w, pipe->src_h},
 };
 
 // 5. Apply hardware-specific optimizations
 if (src->format & UBWC_FLAG) {
     // Enable Qualcomm's compression
     config.compression = MDP_COMPRESSION_UBWC;
 }
 
 if (is_yuv_format(format)) {
     // Setup color space conversion
     config.csc_matrix = CSC_BT709_TO_RGB;
 }
 
 // 6. Stage configuration (doesn't apply to hardware yet)
 pipe->staged_config = config;
 pipe->staged_source = src;
 pipe->transition = transition;
 
 // 7. Mark as dirty for commit
 pipe->needs_commit = true;
 dev->pending_changes = true;
 
 // Note: Changes won't be visible until wfdDeviceCommit() is called!
}

What Happens in Hardware

// For Qualcomm MDP hardware

// 1. Configure SSPP (Source Surface Processing Pipe)
MDP_SSPP_SRC_SIZE = (height << 16) | width;
MDP_SSPP_SRC_IMG_SIZE = (height << 16) | width;
MDP_SSPP_SRC_XY = (y_offset << 16) | x_offset;
MDP_SSPP_OUT_SIZE = (dst_height << 16) | dst_width;
MDP_SSPP_OUT_XY = (dst_y << 16) | dst_x;

// 2. Configure fetch from memory
MDP_SSPP_SRC0_ADDR = phys_addr;
MDP_SSPP_SRC_YSTRIDE0 = stride;
MDP_SSPP_SRC_FORMAT = format_config;

// 3. Configure layer mixer input
MDP_LM_BLEND_CFG = alpha_blend_mode;
MDP_LM_BLEND_ALPHA = global_alpha;

// 4. Flush to make changes take effect at next vsync
MDP_CTL_FLUSH = (1 << pipe_id) | (1 << mixer_id);

Visual Example

Source Buffer (1920x1080 RGBA)
┌─────────────────────────────┐
│                             │
│  ┌──────────────┐          │ ← region = {100, 100, 800, 600}
│  │ Cropped Area │          │   (Only this part sent to display)
│  │              │          │
│  └──────────────┘          │
│                             │
└─────────────────────────────┘
      ↓ wfdBindSourceToPipeline
      ↓
MDP VIG0 Pipeline (Hardware Layer)
      ↓ Scaling/Blending/CSC
      ↓
Display (scaled to fit dst_rect)
┌─────────────────────────────┐
│                             │
│   ┌──────────────────┐      │
│   │   Scaled Image   │      │
│   │                  │      │
│   └──────────────────┘      │
│                             │
└─────────────────────────────┘

End-to-End Example on QNX

// ========================================
// 1. APPLICATION LEVEL
// ========================================
screen_context_t screen_ctx;
screen_window_t win;
screen_buffer_t bufs[2];

// Initialize Screen
screen_create_context(&screen_ctx, SCREEN_APPLICATION_CONTEXT);
screen_create_window(&win, screen_ctx);

// Create double buffers
screen_set_window_property_iv(win, SCREEN_PROPERTY_BUFFER_COUNT, 2);
screen_create_window_buffers(win, 2);
screen_get_window_property_pv(win, SCREEN_PROPERTY_RENDER_BUFFERS, 
                            (void**)&bufs);

// Render frame to buffer
void *pixels;
screen_get_buffer_property_pv(bufs[0], SCREEN_PROPERTY_POINTER, 
                            (void**)&pixels);
// ... draw to pixels ...

// POST THE FRAME
screen_post_window(win, bufs[0], 1, NULL, 0);

// ========================================
// 2. SCREEN COMPOSITOR (QNX Process)
// ========================================
// Receives post notification via QNX message passing
compositor_handle_post(window, buffer) {
 // Get buffer physical address
 off64_t phys_addr;
 screen_get_buffer_property_llv(buffer,
                                SCREEN_PROPERTY_PHYSICAL_ADDRESS,
                                &phys_addr);
 
 // Get buffer format
 int format;
 screen_get_buffer_property_iv(buffer,
                               SCREEN_PROPERTY_FORMAT,
                               &format);
 
 // Assign to WFD pipeline based on window properties
 WFDPipeline pipeline = assign_pipeline(window);
 
 // Create WFD source from buffer
 WFDSource source = wfdCreateSourceFromBuffer(
     device, buffer, phys_addr, format);
 
 // ========================================
 // 3. BIND SOURCE TO PIPELINE
 // ========================================
 wfdBindSourceToPipeline(
     device,
     pipeline,
     source,
     WFD_TRANSITION_AT_VSYNC,
     NULL  // Use entire buffer
 );
 // At this point: STAGED but not visible yet!
 
 // ========================================
 // 4. COMMIT TO HARDWARE
 // ========================================
 wfdDeviceCommit(device, WFD_COMMIT_ENTIRE_DEVICE, 
                 WFD_INVALID_HANDLE);
}

// ========================================
// 5. QUALCOMM WFD DRIVER (libwfdqcom.so)
// ========================================
WFDErrorCode wfdDeviceCommit(WFDDevice device, ...) {
 DEVICE *dev = (DEVICE*)device;
 
 // Map MDP hardware registers
 uintptr_t mdp_base = mmap_device_io(MDP_REG_SIZE, MDP_BASE_ADDR);
 
 for (int i = 0; i < dev->num_pipelines; i++) {
     PIPELINE *pipe = &dev->pipelines[i];
     
     if (!pipe->needs_commit) continue;
     
     SOURCE *src = pipe->staged_source;
     
     // ========================================
     // 6. PROGRAM HARDWARE REGISTERS
     // ========================================
     
     // Configure source surface processor
     configure_sspp_registers(mdp_base, pipe->hw_pipe_id,
                             src->phys_addr,
                             src->format,
                             src->width, src->height,
                             pipe->src_rect);
     
     // Configure layer mixer
     configure_mixer_registers(mdp_base, pipe->mixer_id,
                              pipe->zorder,
                              pipe->alpha,
                              pipe->dst_rect);
     
     // Mark as committed
     pipe->needs_commit = false;
 }
 
 // ========================================
 // 7. TRIGGER HARDWARE UPDATE
 // ========================================
 
 // Flush configuration (applies at next vsync)
 uint32_t flush_mask = calculate_flush_mask(dev);
 out32(mdp_base + MDP_CTL_FLUSH, flush_mask);
 
 // Wait for vsync interrupt (optional)
 if (wait_for_vsync) {
     struct _pulse pulse;
     MsgReceivePulse(vsync_chid, &pulse, sizeof(pulse), NULL);
 }
 
 return WFD_ERROR_NONE;
}

// ========================================
// 8. HARDWARE (MDP)
// ========================================
// At next VSYNC interrupt:
// - MDP fetches pixels from physical memory
// - Applies scaling, color conversion
// - Blends with other layers in layer mixer
// - Sends to display interface (DSI/HDMI)

// ========================================
// 9. DISPLAY SHOWS FRAME
// ========================================

Timing Diagram

Application Thread          Compositor Thread         Hardware
  |                            |                        |
  | screen_post_window()       |                        |
  |--------------------------->|                        |
  |                            | wfdBindSourceToPipeline|
  |                            |----------------------->|
  |                            | (staged, not visible)  |
  |                            |                        |
  | (can render next frame)    | wfdDeviceCommit()      |
  |                            |----------------------->|
  |                            |                        | Configure registers
  |                            |                        |
  | screen_post_window()       |                        |
  | (next frame)               |                        |
  |--------------------------->| (queued)               |
  |                            |                        |
  |                            |                        | VSYNC!
  |                            |                        |-----> Frame 1 visible
  |                            | wfdBindSourceToPipeline|
  |                            | (frame 2)              |
  |                            |----------------------->|
  |                            | wfdDeviceCommit()      |
  |                            |----------------------->|
  |                            |                        | VSYNC!
  |                            |                        |-----> Frame 2 visible

Performance Optimizations

Zero-Copy Video Playback

// Video decoder writes directly to physically contiguous buffer
// MDP scans out directly - no CPU copies!

Physical Buffer ──────> MDP VIG Pipe ──────> Display
 (YUV)                 (YUV→RGB)          (RGB pixels)
                       Hardware CSC

UBWC (Universal Bandwidth Compression)

// Allocate UBWC-compressed buffer on QNX
int format = SCREEN_FORMAT_RGBA8888 | SCREEN_FORMAT_UBWC;
screen_set_buffer_property_iv(buf, SCREEN_PROPERTY_FORMAT, &format);

// MDP automatically decompresses during scanout
// Transparent to application

Hardware Composition

// Instead of CPU composition:
// ❌ CPU blends video + UI → single buffer → display

// Use hardware layers on QNX:
// ✅ Video buffer → VIG0 pipe ──┐
// ✅ UI buffer    → RGB0 pipe ──┼─→ Layer Mixer → Display
// ✅ Cursor       → DMA0 pipe ──┘
//
// No CPU involvement, much lower power!

Asynchronous Commits

// Non-blocking commit
wfdDeviceCommit(device, WFD_COMMIT_ENTIRE_DEVICE, WFD_INVALID_HANDLE);
// Returns immediately, flip happens at next vsync

// Application can start rendering next frame right away
screen_get_window_property_pv(win, SCREEN_PROPERTY_RENDER_BUFFERS, &bufs);
// Render to bufs[1] while bufs[0] is being displayed

Dirty Rectangle Tracking

// Only update changed regions
int dirty[] = {x, y, width, height};
screen_post_window(win, buf, 1, dirty, 0);

// Compositor only re-blends affected areas
// Saves composition bandwidth

Format Optimization

Use Case Recommended Format Why
UI/Text SCREEN_FORMAT_RGBA8888 Alpha channel for blending
Photos SCREEN_FORMAT_RGB888 No alpha needed, saves memory
Video SCREEN_FORMAT_NV12 Native camera/decoder format
Low-power UI SCREEN_FORMAT_RGB565 Half the bandwidth