【WPF】渲染失败甚至程序崩溃,D3DImage.Lock卡死,报COMException异常和UCEERR_RENDERTHREADFAILURE消息
最近遇到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)。
原因分析
看了好多篇文章,多数都指向微软的这篇文章,并且按照这篇文章排查问题:
概述一下这篇文章的内容,主要说了这几个点:
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设备丢失这个最核心的原因。微软的论坛也有人反馈这个问题
我没有仔细去看为什么D3D设备会丢失,估计是因为Win+P选择了“仅第二屏幕”,Windows的显示机制重置了很多东西,主屏幕临时变成了第二屏,注意如果改回其他模式,主屏幕还是会变回第一屏,也就是说Windows对“仅第二屏幕”这个选项做了特殊处理,可能正是因为这个特殊处理,才导致D3D设备都失效了。
解决方法
知道是D3D设备失效就好办了,如果能让代码知道是什么时候失效的,重建D3D的对象就可以了。D3DImage类有IsFrontBufferAvailableChanged事件,注册这个事件,在事件触发时重新创建D3D的相关对象即可。
已经改好的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;
}
}
}
}
更多推荐
所有评论(0)