Regarding WPF BitmapCache Issue + R&D + Solutions
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
Hi,
I've been tackling the well-known `BitmapCache` issue involving multiple windows in WPF. I've delved deeply into the problem, examining WPF internals down to the native core. I'm actively working on and addressing the issue, You can find the code at [this repository](https://github.com/CycloneRing/WpfBitmapCacheIssue).
### What is happening, How it's happening
To reproduce this bug, you need to create at least two windows. Let's designate the first window as the **"Prime Window"** and subsequent windows as **"Alternative Windows"**. Adding a `BitmapCache` to any Alternative Window created after the Prime Window leads to a visual freeze on those Alternative Windows after pressing Alt + Ctrl + Del, locking the screen, or prompting UAC. Let's refer to this event as the **"Display Reset"**.
Following a Display Reset, hovering over a reactive control (such as a Button or Checkbox) on the Prime Window triggers a `WM_PAINT` message. However, on the Alternative Windows, the `WM_PAINT` fails, causing the operating system's graphic manager to continually send (spam) `WM_PAINT` messages to the Alternative Windows. This incessant messaging is the root cause of the lock and lag experienced on Alternative Windows. It's suspected that the presence of a `BitmapCache` element causes the painting to fail and return false.
### Previous Solutions Provided by Community :
1. Terminating DWM.exe resolves the lock issue, It's a very ugly and dirty way to fix it, But it works.
2. Switching to software rendering and disabling Hardware Acceleration has been suggested. However, this is deemed undesirable due to the necessity of GPU usage in 2024.
### Current Findings and Proposed Solutions :
1. Applying a `BitmapCache` to an element in the Prime Window prevents lock on Alternative Windows after a Display Reset. [[Branch](https://github.com/CycloneRing/WpfBitmapCacheIssue/tree/fix-cache-all)]
2. Hovering over a single reactive control (such as a Button or Checkbox) on the Prime Window with the mouse rectifies the lock on Alternative Windows, without necessitating any `BitmapCache` on the Prime Window. [[Branch](https://github.com/CycloneRing/WpfBitmapCacheIssue/tree/master)]
3. Closing the Prime Window promotes the second window created after it to the status of Prime Window, resolving any lock situation it may be in.
4. To detect Display Reset, utilize a `D3DImage` and listen for the `IsFrontBufferAvailableChanged` event. Upon Display Reset, switch `ProcessRenderMode` to software and back to hardware, It will effectively resolve the problem. However, this approach may introduce visual glitches that I personally do not like. [[Details](https://github.com/dotnet/wpf/issues/4276#issuecomment-1982032660)]
### MediaControl Solution (Cross-Version) :
**MediaControl** is an internal component of **Milcore** (WPF Backend Render Engine) which controls rendering options in a running WPF application. Although primarily designed for debugging and profiling, it offers a potential solution to this issue.
My current solution involves hooking MediaControl within your WPF application, manipulating rendering options, and setting the `DisableDirtyRegionSupport` option to `true`. By disabling dirty region support, the bitmap cache problem disappears. However, this solution may lead to increased resource usage and impact performance since the [entire WPF window will be rendered](https://github.com/dotnet/wpf/blob/2ac5235aeac17c014cef129d6e2740e32848bf84/src/Microsoft.DotNet.Wpf/src/WpfGfx/core/uce/drawingcontext.cpp#L5975) instead of only the changed areas. [[Implementation](https://github.com/CycloneRing/WpfBitmapCacheIssue/tree/mc-hook)]
In this solution, we connect to a shared memory path created per process at `wpfgfx_v0400-`, then modify the structure to change the `DisableDirtyRegionSupport` flag. ~~I'm actively working on a fully managed approach to eliminate the dependency on `milctrl_v0300_x64.dll`.~~
> **Note:** To make this solution work you need to change `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\EnableDebugControl` to 1, If it doesn't be set wpfgfx doesn't create MediaControl. You can also simply hook `RegQueryValueEx` and return 1 when wpfgfx ask for the registry key.
### Real-Time Patcher Solution (Specific-Version) :
In this solution, Instead of using MediaControl to disable Dirty Region Support we directly patch it in wpfgfx. It's stored as `g_fDirtyRegion_Enabled` inside wpfgfx dll file, To find the offset for installed version you need to download pdb of wpfgfx and use [OffsetExtractor](https://github.com/CycloneRing/WpfBitmapCacheIssue/blob/patcher/WpfBitmapCacheIssue/Native/main.cpp) to find the static offset of the value.
By using this method we can directly disable Dirty Region Support, Unlock, Restore it back to enabled. [[Implementation](https://github.com/CycloneRing/WpfBitmapCacheIssue/tree/patcher)]
> **Note:** I will update this section with any further discoveries or refinements in solution.
Contributor guide
Assessment
This issue has not been assessed yet.