D3D12Viewport::Resize 导致 Pure virtual function called崩溃

原因分析:

资产加载的警告是以Async的方式放在主线程执行​(下图是SkeletalMesh):

​[Image Removed]FD3D12Viewport::Resize​函数中,释放BackBuffer之前会BlockUntilIdle等待GPU,而FD3D12Device::BlockUntilIdle里面会等待当前线程的Async Task执行完毕,这些Task包含了“弹出MessageLog”,而弹出MessageLog会关闭Viewport所在的下拉建议框Window。

查看源码发现,关闭Slate Window​时,将FD3D12Viewport放到渲染线程释放,BackBuffer会随之隐式释放:​

[Image Removed]​

然而调整Slate Window尺寸时,BackBuffer直接在游戏线程的FD3D12Viewport::Resize中释放:

​[Image Removed]​

在这个案例中,由于提前关闭了搜索建议框这个Window,所以BackBuffer跟随Viewport跑到了渲染线程释放,然后在RHI线程执行析构,但同一时刻游戏线程,仍在访问BackBuffer​,导致了Pure virtual function called崩溃。

查看了Vulkan源码,没有这个问题。​

问题:

FD3D12Device​::BlockUntilIdle 必须等待CPU的Task,是否有必要?

FD3D12Viewport的BackBuffer​在游戏线程创建,但可能会在游戏线程或渲染线程释放,是否符合规范?

Steps to Reproduce

Content Browser 的搜索框输入字符过程中会弹出下拉建议框:

[Image Removed]

同时,筛选出来的资产,在加载过程中如果有问题,会弹出Message Out窗口显示警告:

​[Image Removed]

Message Out窗口出现时会强制关闭下拉建议框,此时出现Pure virtual function called崩溃。

[Image Removed]

您好,

感谢您报告这个问题,我们正在尝试复现。

您好,

感谢您的询问。

在 FSlateRHIRenderer::OnWindowDestroyed 里,BeginReleaseResource(ViewportInfo) 的效果只是减少 FRHIViewport 上的引用计数。这不一定代表这个 Viewport 就立即被释放了。您的调用栈上的 FD3D12Viewport 应该是一个不同的 Viewport 对象,因为在 Game Thread 上至少还有一个它的引用。

Vulkan 的对象没有内置引用计数,所以相应的逻辑也是不一样的。此外,VkSwapchainKHR 也不支持 Resize 操作,如果要调整尺寸的话需要重新创建整个对象。

请问您可以提供一个简单测试工程以及能够复现这个崩溃的资源文件吗?

[Content removed] 。

关于您的其他问题:

  • FD3D12Device::BlockUntilIdle 的作用仅限于等待所有 GPU 队列完成操作。请问您这里提到的 CPU Task 是指哪一部分代码?
  • D3D12 API 设计上是允许多线程调用的,在不同的线程上创建和释放对象应该不会有问题。

感谢回复!

FD3D12Device::BlockUntilIdle内部等待GraphEvent的代码:

[Image Removed]​

我们注意到FD3D12Viewport::Resize中,销毁BackBuffer之前,会调用BlockUntilIdle,这里有可能运行之前创建的"Open MessageOut"任务。

我们项目的同学在搜索资产时,错误资产的提示框几乎都会走这个路径,从而导致崩溃。

[Image Removed]

但是很抱歉,我创建了测试工程和资产,用同样的步骤,却无法复现这个崩溃。调试发现:测试工程中,"Open MessageOut"任务是在FEngineLoop::Tick()中的FFrameEndSync::Sync()中运行的。

[Image Removed]

我猜测和项目复杂度有关系。我会尝试在官方实例工程上修改和复现,并告知详细步骤。

您好,

这可能是一个竞态条件问题。FD3D12Device::BlockUntilIdle 期间执行其他任务应该是正常行为,因为“Open MessageOut”和“销毁 BackBuffer”之间的顺序需要由其他代码来保证。一般来讲,引用计数也能帮助减少这类问题。

您可以尝试在复现这个问题的时候使用“-onethread”,“-forcerhibypass”,以及“-norhithread”等命令行参数吗?这些参数会影响多线程事件发生的先后顺序,也许能暴露更多的同步问题。