dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.1k stars 1.17k forks source link

WPF Rendering issue on any machine #4276

Open Erapchu opened 3 years ago

Erapchu commented 3 years ago

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 commented 3 years ago

Duplicate of #2158 ?

Erapchu commented 3 years ago

@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 commented 3 years ago

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

Erapchu commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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 commented 2 years ago

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 commented 2 years ago

@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 commented 1 year ago

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 commented 10 months ago

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 commented 10 months ago

@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 commented 10 months ago

@HyksosSowo try to disable hardware acceleration for that window

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

Valkirie commented 10 months ago

@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 commented 10 months ago

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 commented 8 months ago

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 :

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