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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
