最近遇到WPF程序抛出ystem.Runtime.InteropServices.COMException异常,异常消息UCEERR_RENDERTHREADFAILURE (Exception from HRESULT: 0x88980406),或者先在D3DImage.Lock卡死,然后再出异常的问题。

现象和复现方法

出现了标题中的现象,你很可能有至少两个屏幕,并且可能在你的程序中直接或间接用了如下的库或技术:

1、WpfD3D这个开源库

2、自行使用了WPF中的D3DImage类,实现D3D渲染

3、以任意方式在WPF中使用了D3D

复现的的方法:按Win+P,在出现的投影菜单中选择“仅第二屏幕”,之后渲染就会停止,通常整个程序也会卡死,多重复几次Win+P选择其他选项,或者随便点击程序的UI,就会抛出ystem.Runtime.InteropServices.COMException异常,异常的描述是UCEERR_RENDERTHREADFAILURE (Exception from HRESULT: 0x88980406)。

原因分析

看了好多篇文章,多数都指向微软的这篇文章,并且按照这篇文章排查问题:

WPF render thread failures

概述一下这篇文章的内容,主要说了这几个点:

1、渲染不是在主线程实现的,而是独立的渲染线程完成的,主线程告诉渲染线程该绘制什么

2、你可能遇到一些什么渲染异常,包括System.Runtime.InteropServices.COMException、System.InvalidOperationException和System.OutOfMemoryException

3、你通常都会发现在出现异常的时候,调用堆栈是固定的一些位置,然而调用堆栈并没什么卵用(原文就是这样说的:Render thread failures will generate one of the calls stacks shown above (or a minor variation thereof) regardless of the root cause)

4、建议你检查有没有内存泄漏、升级.Net FrameWork、升级系统等等

最终的结论是,渲染线程崩溃是很难查的问题,因为在主线程收到异常的时候,渲染线程已经已经已经已经挂掉了……

 

 

直到我看到了这一篇文章:

wpf D3DImage 偶现性无法渲染图像,D3D设备丢失的解决办法

标题的【D3D设备丢失】很重要。文章的作者是安装了向日葵,但是还不是本质的原因,他描述了D3D设备丢失这个最核心的原因。微软的论坛也有人反馈这个问题

D3DImage losing its D3DDevice

我没有仔细去看为什么D3D设备会丢失,估计是因为Win+P选择了“仅第二屏幕”,Windows的显示机制重置了很多东西,主屏幕临时变成了第二屏,注意如果改回其他模式,主屏幕还是会变回第一屏,也就是说Windows对“仅第二屏幕”这个选项做了特殊处理,可能正是因为这个特殊处理,才导致D3D设备都失效了。

解决方法

知道是D3D设备失效就好办了,如果能让代码知道是什么时候失效的,重建D3D的对象就可以了。D3DImage类有IsFrontBufferAvailableChanged事件,注册这个事件,在事件触发时重新创建D3D的相关对象即可。

已经改好的WpfD3D库

WpfD3D修复多个缺陷(项目实际在用)

这个是我一直在用的WpfD3D库,源自开源的版本,但是也经过我改造了很多,包括修复了一些缺陷,增加了接口可以做D3DRenderSource和WriteableRenderSource的动态加载和动态切换等。对于这次的问题,核心的修改就是在D3DRender\D3DImageSource.cs文件的749-791行:

        private void OnIsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            // 判断硬件渲染支持
            int level = RenderCapability.Tier >> 16;
            bool useSoftRender = level == 0;

            // 软件渲染判断IsFrontBufferAvailable没意义
            if (!useSoftRender)
            {
                if(!imageSource.IsFrontBufferAvailable)
                {
                    return;
                }
            }

            if (textureSurface != null)
            {
                lock(renderLock)
                {
                    // 重新创建D3D的对象,修复多屏切换(Win+P选择仅第二屏)时出现
                    // System.Runtime.InteropServices.COMException异常,异常信息
                    // UCEERR_RENDERTHREADFAILURE (异常来自 HRESULT:0x88980406)
                    ReleaseResource();

                    // 增加try-catch预防D3D内部发出的无效调用异常
                    try
                    {
                        dummyWindow?.Close();
                        dummyWindow = new Form();
                        hwnd = dummyWindow.Handle;

                        InitD3D();
                        Format d3dFormat = ConvertToD3D(frameFormat);
                        CreateResource(d3dFormat, yWidth, yHeight);
                    }
                    catch(Exception ex)
                    {
                        ErrorOccur = true;
                        LastException = ex;
                    }
                }
            }
        }

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐