Skip to content

WPF Rendering issue on any machine #4276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Erapchu opened this issue Mar 12, 2021 · 16 comments
Open

WPF Rendering issue on any machine #4276

Erapchu opened this issue Mar 12, 2021 · 16 comments
Labels
Bug Product bug (most likely) .NET Framework
Milestone

Comments

@Erapchu
Copy link

Erapchu commented Mar 12, 2021

  • .NET Version: 5
  • Windows version: Windows 10 20H2 (OS Build 19042.867)
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes, also .NET Framework 4.5.2, and all .NET Core versions.

Problem description:
WPF windows stop rendering at all when use BitmapCache on any window's control or root control and first shown WPF window was hidden by set Visibility to Hidden or call Hide() method.

Actual behavior:
There's no exceptions or fatal crashes. All WPF windows that use BitmapCache just stop rendering at all! While FIRST hidden window will not be shown.

Expected behavior:
WPF Windows should render normally.

Minimal repro:
Please follow this steps to catch problem on your machine or build any project below. Please, help me resolve it.

https://github.com/Erapchu/GhostWindows
https://github.com/Erapchu/GhostWindowsCore

  1. Create empty WPF project.
  2. Use property CacheMode="BitmapCache" in MainWindow's Grid;
  3. Show MainWindow on start application, then hide it. Show new MainWindow. You can use a code below. Additionally remove StartupUri in App.xaml
  4. Press Ctrl + Alt + Del or Win + L and try to interact with showed window (resize, minimize) it should be broken now.

In App.xaml.cs OnStartup overrided method:
var window1 = new MainWindow(); window1.Show(); await Task.Delay(1000); window1.Hide(); var window2 = new MainWindow(); window2.Show();

@lindexi
Copy link
Member

lindexi commented Mar 12, 2021

Duplicate of #2158 ?

@Erapchu
Copy link
Author

Erapchu commented Mar 12, 2021

@lindexi Looks like yes. Disable HW acceleration or don't use BitmapCache at all - is not a good idea. Issue #2158 is quite old. I faced it again and temporary solution is disable BitmapCache but performance is bad.

@ryalanms ryalanms added this to the Future milestone Mar 15, 2021
@ryalanms ryalanms added the Bug Product bug (most likely) label Mar 15, 2021
@ryalanms
Copy link
Member

@Erapchu: Thank you for the report and investigation. Are you okay with us closing this as a duplicate? Thanks.

@Erapchu
Copy link
Author

Erapchu commented Mar 15, 2021

Of course. But I want to know why it's happen and how to resolve for now. Future - update will be applied to new .NET version? Not for old like .NET framework?

@predavid
Copy link
Contributor

@Erapchu - it will be applied to the new .NET version. I will be serviced down to .NET Framework based on community intertest/upvoting. And we would like to track these community responses via a single bug/Issue. Let us use this new one and close #2158

@Erapchu
Copy link
Author

Erapchu commented Mar 26, 2021

I did some investigations.
If OS bring up any full-screen window (like UAC prompt when application required administrator or Ctrl + Alt + Del or sign-out), I faced that issue again.
Why BitmapCache broke all windows and how to detect it? Can I subscribe WPF window to WndProc and listen for messages to catch when need to restart rendering (one more question how to do it?).
We just using BitmapCache to improve rendering animations. Can we use something different, because this element invokes problems.

@Erapchu
Copy link
Author

Erapchu commented Jul 7, 2021

Setting up BitmapCache to root Grid of the window and Ctrl + Alt + Del invokes problem even if first window was showed, but not focused.
When rendering suspended, WM_PAINT received by window in infinite loop. When remove BitmapCache, problem is vanished, until set it again. When hover mouse over very first window, ALL OTHER windows with BitmapCache rendering normally.
Can anyone deep dive in that problem? @predavid @ryalanms
develop branch was updated here - https://github.com/Erapchu/GhostWindows

Ghost windows video, behavior

@HyksosSowo
Copy link

We are also stuck with this problem, before in Windows 7 it was much less problematic, we only had to force an invalidate visual and it seemed to fix the issue. But in Windows 10 now it is much worst, I can't believe Microsoft does not fix such a huge bug.

We can't disable the bitmap cache and can't disable HW acceleration, because we have a very big complex application, we can't simply close and re-open the window.

And knowing that the original bug since windows 7 was never work on ... I have very low expectation from Microsoft, so we had to find a work around.

So, if anyone is looking for an other workaround... it's a bit nasty but I think it is the only way to recover while keeping your current WPF window open, if you can run with admin privilege, you have to kill dwm.exe everything will flicker and recover.

If you can't run as admin, you have to create a GPU virus and hang it out in an infinite loop, that trigger the hang device watch dog of Windows that unload and reload this device, here is how to do it programmatically, last answer here: https://gamedev.stackexchange.com/questions/108141/how-can-i-test-dxgi-error-device-removed-error-handling

This work surprising well, after all the screen flicker for couple of seconds everything is back working.

Now our only last issue is to detect when we have to run that virus, we can detect the session unlock but not the UAC security prompt (only work with admin right)

@Erapchu
Copy link
Author

Erapchu commented Jul 8, 2022

@HyksosSowo the only one solution is don't hide windows, but close and re-open them. Close it completely when bitmapcache is in visual tree
Anyway we need to sacrifice something

@EZ64cool
Copy link

EZ64cool commented Aug 9, 2023

Hey, we've been running into this issue for a while as well and have finally come to a stable (but not ideal) solution to this issue.
When creating a window make sure to set AllowsTransparency to true (This will also require setting WindowStyle to None meaning a custom style and Window class will be needed to add back the default windows functionality).

I couldn't tell you why this works unfortunately (but hopefully it can help narrow down the issue).
I've tried setting AllowsTransparency on only windows using BitmapCache but it will still cause the problem unless all windows use it.

It's also worth noting that if you're creating WS_CHILD window's you'll also need to set the minimum supported OS to windows 8 or higher inside the app.manifest file

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 8 -->
    <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
  </application>
</compatibility>

I hope this helps someone out there, as it was a huge pain to find!

@Valkirie
Copy link

Valkirie commented Jan 2, 2024

Can still reproduce in .NET8. I'm trying to mitigate the issue by invalidating all UIElement.CacheMode within a window. But as soon as you restore any CacheMode, WM_PAINT frenzy start all over again. And if you don't restore CacheMode you're still getting half frozen DropDown elements.

Hook WndProc

hwndSource = PresentationSource.FromVisual(this) as HwndSource;
hwndSource.AddHook(WndProc);

On WM_PAINT, store non-null CacheMode, and set to null

const int WM_PAINT = 0x000F;
private Timer WM_PAINT_TIMER;
private Dictionary<UIElement, CacheMode> cacheModes = new();

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case WM_PAINT:
            {
                // Loop through all visual elements in the window
                foreach (var element in WPFUtils.FindVisualChildren<UIElement>(this))
                {
                    if (element.CacheMode is null)
                        continue;

                    // Store the previous CacheMode value
                    cacheModes[element] = element.CacheMode.Clone();

                    // Set the CacheMode to null
                    element.CacheMode = null;
                }

                WM_PAINT_TIMER.Stop();
                WM_PAINT_TIMER.Start();
            }
            break;
    }

    return IntPtr.Zero;
};

Restore non-null CacheMode

private void WM_PAINT_TIMER_Tick(object? sender, EventArgs e)
{
    // UI thread
    Application.Current.Dispatcher.Invoke(() =>
    {
        // Set the CacheMode back to the previous value
        foreach (UIElement element in cacheModes.Keys)
            element.CacheMode = cacheModes[element];
    });
}

FindVisualChildren

// Helper method to find all visual children of a given type
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject
{
    if (parent != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);

            if (child is T)
            {
                yield return (T)child;
            }

            foreach (var childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

@HyksosSowo
Copy link

@HyksosSowo the only one solution is don't hide windows, but close and re-open them. Close it completely when bitmapcache is in visual tree Anyway we need to sacrifice something

@Erapchu we can't close the window. We also can't use the transparency trick as our WPF window is inside a HwndSource.

The GPU virus is still the only solution that reliably work.

@Erapchu
Copy link
Author

Erapchu commented Jan 6, 2024

@HyksosSowo try to disable hardware acceleration for that window

https://stackoverflow.com/questions/2169600/how-hardware-acceleration-can-be-disabled-in-wpf

@Valkirie
Copy link

Valkirie commented Jan 6, 2024

@HyksosSowo try to disable hardware acceleration for that window

https://stackoverflow.com/questions/2169600/how-hardware-acceleration-can-be-disabled-in-wpf

Obviously it works but it's like turning off a few cylinder of your engine because it misbehaves. It suxx...

@HyksosSowo
Copy link

HyksosSowo commented Jan 6, 2024

We are not using WPF for simple dialog box, we are using it to load huge animated graphics, we can't use software rendering... and the funny thing is to get good performance we need HW acceleration + bitmap cache or else it suxx.

@CycloneRing
Copy link

CycloneRing commented Mar 6, 2024

I also faced same issue, I am using many HLSL shaders and D3DImage in my WPF UI.

Only solution I came up with is to create a empty D3DImage and assign IsFrontBufferAvailableChanged event.

internal void IsFrontBufferAvailableChanged_Event (object sender, DependencyPropertyChangedEventArgs e)
{
    if (D3DImageSource.IsFrontBufferAvailable)
    {
        // Recover from software rendering and switch back to HW acceleration 
        System.Windows.Media.RenderOptions.ProcessRenderMode = RenderMode.Default;
    }
    else
    {
        // Will stop WM_PAINT flood
        System.Windows.Media.RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
    }
}

Keep this D3DImage somewhere hidden in your window, This works fine and will invalidate BitmapCache objects but the side effect is your UI will flick for ~2 seconds before recovering from WM_PAINT flood.

Some finds I faced during working on this issue using injectors and reverse engineering :

  • Find 1 : Here's a new find, If you apply bitmap cache to something in second window it will work fine.
  • Find 2 : If you just have a button on another window and hover it with mouse frozen window gets fixed again. It doesn't need to have a bitmap cache!

NEW UPDATE
I started digging more and deep dived in wpfgfx and native core. I came up with a very good solution, You can check it out at :
https://github.com/CycloneRing/WpfBitmapCacheIssue

Here's a solid solution and it also smooth out animations :
https://github.com/CycloneRing/WpfBitmapCacheIssue/tree/mc-hook

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Product bug (most likely) .NET Framework
Projects
None yet
Development

No branches or pull requests

8 participants