WPF Rendering Explained: From OnRender to Screen

This article walks developers through the complete WPF rendering pipeline, detailing the three-layer architecture, message loop handling, Dispatcher processing, DirectX integration, and how drawing commands travel from OnRender to the screen, while providing code examples and references to deepen understanding.

Seewo Tech Circle
Seewo Tech Circle
Seewo Tech Circle
WPF Rendering Explained: From OnRender to Screen

WPF Rendering Overview

WPF (Windows Presentation Foundation) is a UI framework whose core concern for developers is rendering. The rendering process is complex and involves multiple layers, a message loop, and a Dispatcher that ultimately sends drawing commands to DirectX for screen display.

WPF Composition Layers

WPF consists of three main modules: PresentationFramework , PresentationCore , and the underlying layer. The topmost layer exposes elements to developers; rendering instructions are issued via DrawingContext in overridden OnRender methods.

The architecture can be visualized as three layers:

Managed layer – contains managed assemblies such as PresentationFramework.dll, PresentationCore.dll, and WindowsBase.dll.

Unmanaged layer – includes native libraries like milCore.dll (Media Integration Layer) and WindowsCodecs.dll that provide high‑performance DirectX rendering and image support.

Core system layer – comprises system components such as User32, DirectX, GDI, the CLR, and device drivers.

Message Loop

WPF uses the Windows message loop to receive user interaction and system messages. The class HwndSubclass attaches to a window via Attach and CriticalAttach methods:

internal IntPtr Attach(IntPtr hwnd)
{
  // 忽略代码
  return this.CriticalAttach(hwnd);
}
internal IntPtr CriticalAttach(IntPtr hwnd)
{
  // 忽略代码
  NativeMethods.WndProc newWndProc = new NativeMethods.WndProc(this.SubclassWndProc); // 创建处理消息
  IntPtr windowLongPtr = UnsafeNativeMethods.GetWindowLongPtr(new HandleRef((object) this, hwnd), -4);
  this.HookWindowProc(hwnd, newWndProc, windowLongPtr);
  return (IntPtr) this._gcHandle;
}
private void HookWindowProc(IntPtr hwnd, NativeMethods.WndProc newWndProc, IntPtr oldWndProc)
{
  _hwndAttached = hwnd;
  _hwndHandleRef = new HandleRef(null, _hwndAttached);
  _bond = Bond.Attached;

  _attachedWndProc = newWndProc;
  _oldWndProc = oldWndProc;

  // 这有下面的代码才是获得消息
  IntPtr oldWndProc2 = (IntPtr)UnsafeNativeMethods.CriticalSetWindowLong(_hwndHandleRef, NativeMethods.GWL_WNDPROC, _attachedWndProc);

  // 跟踪这个窗口,可以在 CLR 关闭,撤掉管理的窗口代码
  ManagedWndProcTracker.TrackHwndSubclass(this, _hwndAttached);
}

The native SetWindowLongPtr call is performed via:

internal static IntPtr CriticalSetWindowLong(HandleRef hWnd, int nIndex, NativeMethods.WndProc dwNewLong)
{
  int errorCode;
  IntPtr retVal;

  retVal = NativeMethodsSetLastError.SetWindowLongPtrWndProc(hWnd, nIndex, dwNewLong);
  errorCode = Marshal.GetLastWin32Error();

  return retVal;
}

Dispatcher Scheduling

The Dispatcher receives the custom message DispatcherProcessQueue and processes the rendering queue. The core hook is WndProcHook:

private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
   if(message == _msgProcessQueue)
   {
     ProcessQueue();
   }
}

The static constructor registers the message:

static Dispatcher()
{
  _msgProcessQueue = UnsafeNativeMethods.RegisterWindowMessage("DispatcherProcessQueue");
  // 下面还有很多代码
}

When the Dispatcher processes the queue, it invokes the callback that ultimately calls the rendering thread.

RenderContent and DUCE Interaction

After drawing primitives are collected, UIElement.RenderContent sends them to the composition system:

internal override void RenderContent(RenderContext ctx, bool isOnChannel)
{
  DUCE.Channel channel = ctx.Channel;
  DUCE.IResource drawingContent = (DUCE.IResource) this._drawingContent;
  drawingContent.AddRefOnChannel(channel);
  DUCE.CompositionNode.SetContent(this._proxy.GetHandle(channel), drawingContent.GetHandle(channel), channel);
  this.SetFlags(channel, true, VisualProxyFlags.IsContentConnected);
}

The low‑level command is issued via:

internal static unsafe void SetContent(DUCE.ResourceHandle hCompositionNode, DUCE.ResourceHandle hContent, DUCE.Channel channel)
{
  DUCE.MILCMD_VISUAL_SETCONTENT visualSetcontent;
  visualSetcontent.Type = MILCMD.MilCmdVisualSetContent;
  visualSetcontent.Handle = hCompositionNode;
  visualSetcontent.hContent = hContent;
  channel.SendCommand((byte*) &visualSetcontent, sizeof(DUCE.MILCMD_VISUAL_SETCONTENT));
}

Desktop Window Manager (DWM)

DWM composes the final window bitmap, handling transparency, shadows, and other visual effects. It receives a WM_PAINT message from the system (not from the application) to know when to present the window.

From Control to Screen

The overall flow is:

Developer overrides OnRender and issues drawing commands via DrawingContext.

The commands are collected into a visual tree and sent to the Dispatcher.

The Dispatcher processes a custom message, invoking the rendering pipeline.

Rendering commands travel through the unmanaged milCore layer to DirectX.

DirectX draws the content to a window surface.

DWM composites the window bitmap and presents it on the screen.

Understanding each step helps diagnose performance issues and customize rendering behavior.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RenderingWindowsDispatcherDirectXWPF
Seewo Tech Circle
Written by

Seewo Tech Circle

Seewo Tech Circle

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.