Using ReloadPackages to reload texture from new pak files at runtime leads an occasional wild pointer crash in FD3D11UniformBuffer

Problem

I use ReloadPackages to reload texture A from new pak files at runtime. If a material is using texture A, then an occasional problem may happen:


FRHIResource::Release() RHIResources.h:54

TArray::~TArray<…>() Array.h:621

FD3D11UniformBuffer::~FD3D11UniformBuffer() D3D11UniformBuffer.cpp:381

<lambda_84e30748...>::operator()(TArray<…> &) RHI.cpp:599

FRHIResource::FlushPendingDeletes(bool) RHI.cpp:629

FlushPendingDeleteRHIResources_RenderThread() RenderingThread.cpp:1303

<lambda_83aa676a...>::operator()(FRHICommandListImmediate &) SceneRendering.cpp:4008

TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`43'::FDrawSceneCommandName,<lambda_83aa676af25f62e876425d334fdfc6e4> >::DoTask(Type,const TRefCountPtr<FGraphEvent> &) RenderingThread.h:183

TGraphTask<TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`43'::FDrawSceneCommandName,<lambda_83aa676af25f62e876425d334fdfc6e4> > >::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32> > &,Type) TaskGraphInterfaces.h:886

FNamedTaskThread::ProcessTasksNamedThread(int, bool) TaskGraph.cpp:710

FNamedTaskThread::ProcessTasksUntilQuit(int) TaskGraph.cpp:601

FTaskGraphImplementation::ProcessThreadUntilRequestReturn(Type) TaskGraph.cpp:1480

RenderingThreadMain(FEvent *) RenderingThread.cpp:372

FRenderingThread::Run() RenderingThread.cpp:526

FRunnableThreadWin::Run() WindowsRunnableThread.cpp:84

FRunnableThreadWin::GuardedRun() WindowsRunnableThread.cpp:27

FRunnableThreadWin::_ThreadProc(void *) WindowsRunnableThread.h:37

The reason for this error is that in FD3D11UniformBuffer, there is a table that stores resources.


/** Resource table containing RHI references. */

TArray<TRefCountPtr<FRHIResource> > ResourceTable;

When FD3D11UniformBuffer is destroyed, this TArray is also destroyed

When TArray is destroyed, the destructor is called for each member first

Here, a TRefCountPtr<FRHIResource> is already a wild pointer

This array view:

Then an error is reported at 0x417014e3e0000000

This is the call to the destructor of FD3D11UniformBuffer in the delete queue, so it still depends on who initiated the destructor of FD3D11UniformBuffer

Because the release method is to put the RHI resource into the delete queue, and class FD3D11UniformBuffer : public FRHIUniformBuffer, class FRHIUniformBuffer : public FRHIResource, so someone should have called it Release() of FRHIUniformBuffer

My try

I added Log to FRHIUniformBuffer::Release and disabled optimization


PRAGMA_DISABLE_OPTIMIZATION

FORCEINLINE_DEBUGGABLE uint32 Release() const

{

const FRHIUniformBufferLayout* LocalLayout = Layout;

#if VALIDATE_UNIFORM_BUFFER_LIFETIME

int32 LocalNumMeshCommandReferencesForDebugging = NumMeshCommandReferencesForDebugging;

#endif

UE_LOG(LogTemp, Warning, TEXT("Someone call FRHIUniformBuffer::Release()!"));

After recompiling, I found that FRHIUniformBuffer::Release() is called very frequently

This is from the command FRHICommandSetShaderUniformBuffer


<lambda_fd12fd3e...>::operator()<…>(const FLogCategoryLogTemp &, const wchar_t (&)[43]) RHIResources.h:777

FRHIUniformBuffer::Release() RHIResources.h:777

TRefCountPtr::operator=(FRHIUniformBuffer *) RefCounting.h:205

FD3D11DynamicRHI::RHISetShaderUniformBuffer(FRHIComputeShader *, unsigned int, FRHIUniformBuffer *) D3D11Commands.cpp:701

FRHICommandSetShaderUniformBuffer::Execute(FRHICommandListBase &) RHICommandListCommandExecutes.inl:142

FRHICommand::ExecuteAndDestruct(FRHICommandListBase &, FRHICommandListDebugContext &) RHICommandList.h:765

FRHICommandListExecutor::ExecuteInner_DoExecute(FRHICommandListBase &) RHICommandList.cpp:372

FRHICommandListExecutor::ExecuteInner(FRHICommandListBase &) RHICommandList.cpp:657

FRHICommandListExecutor::ExecuteList(FRHICommandListImmediate &) RHICommandList.cpp:708

FRHICommandListImmediate::ImmediateFlush(Type) RHICommandList.inl:98

FRHICommandList::EndScene() RHICommandList.cpp:1574

TRDGLambdaPass::ExecuteImpl(FRHIComputeCommandList &) RenderGraphPass.h:424

FRDGPass::Execute(FRHIComputeCommandList &) RenderGraphPass.cpp:305

FRDGBuilder::ExecutePass(FRDGPass *) RenderGraphBuilder.cpp:1638

FRDGBuilder::Execute() RenderGraphBuilder.cpp:1308

FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate &) DeferredShadingRenderer.cpp:2709

RenderViewFamily_RenderThread(FRHICommandListImmediate &, FSceneRenderer *) SceneRendering.cpp:3739

<lambda_83aa676a...>::operator()(FRHICommandListImmediate &) SceneRendering.cpp:4007

TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`43'::FDrawSceneCommandName,<lambda_83aa676af25f62e876425d334fdfc6e4> >::DoTask(Type,const TRefCountPtr<FGraphEvent> &) RenderingThread.h:183

TGraphTask<TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`43'::FDrawSceneCommandName,<lambda_83aa676af25f62e876425d334fdfc6e4> > >::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32> > &,Type) TaskGraphInterfaces.h:886

FNamedTaskThread::ProcessTasksNamedThread(int, bool) TaskGraph.cpp:710

FNamedTaskThread::ProcessTasksUntilQuit(int) TaskGraph.cpp:601

FTaskGraphImplementation::ProcessThreadUntilRequestReturn(Type) TaskGraph.cpp:1480

RenderingThreadMain(FEvent *) RenderingThread.cpp:372

FRenderingThread::Run() RenderingThread.cpp:526

FRunnableThreadWin::Run() WindowsRunnableThread.cpp:84

FRunnableThreadWin::GuardedRun() WindowsRunnableThread.cpp:27

FRunnableThreadWin::_ThreadProc(void *) WindowsRunnableThread.h:37

This is from the command FRHICommandSetGraphicsPipelineState


FRHIUniformBuffer::Release() RHIResources.h:777

TRefCountPtr::operator=(FRHIUniformBuffer *) RefCounting.h:205

FD3D11DynamicRHI::RHISetBoundShaderState(FRHIBoundShaderState *) D3D11Commands.cpp:361

IRHICommandContextPSOFallback::RHISetGraphicsPipelineState(FRHIGraphicsPipelineState *, bool) RHIContext.h:789

FD3D11DynamicRHI::RHISetGraphicsPipelineState(FRHIGraphicsPipelineState *, bool) D3D11Commands.cpp:193

FRHICommandSetGraphicsPipelineState::Execute(FRHICommandListBase &) RHICommandListCommandExecutes.inl:296

FRHICommand::ExecuteAndDestruct(FRHICommandListBase &, FRHICommandListDebugContext &) RHICommandList.h:765

FRHICommandListExecutor::ExecuteInner_DoExecute(FRHICommandListBase &) RHICommandList.cpp:372

FRHICommandListExecutor::ExecuteInner(FRHICommandListBase &) RHICommandList.cpp:657

FRHICommandListExecutor::ExecuteList(FRHICommandListImmediate &) RHICommandList.cpp:708

FRHICommandListImmediate::ImmediateFlush(Type) RHICommandList.inl:98

FRHICommandList::EndScene() RHICommandList.cpp:1574

TRDGLambdaPass::ExecuteImpl(FRHIComputeCommandList &) RenderGraphPass.h:424

FRDGPass::Execute(FRHIComputeCommandList &) RenderGraphPass.cpp:305

FRDGBuilder::ExecutePass(FRDGPass *) RenderGraphBuilder.cpp:1638

FRDGBuilder::Execute() RenderGraphBuilder.cpp:1308

FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate &) DeferredShadingRenderer.cpp:2709

RenderViewFamily_RenderThread(FRHICommandListImmediate &, FSceneRenderer *) SceneRendering.cpp:3739

<lambda_83aa676a...>::operator()(FRHICommandListImmediate &) SceneRendering.cpp:4007

TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`43'::FDrawSceneCommandName,<lambda_83aa676af25f62e876425d334fdfc6e4> >::DoTask(Type,const TRefCountPtr<FGraphEvent> &) RenderingThread.h:183

TGraphTask<TEnqueueUniqueRenderCommandType<`FRendererModule::BeginRenderingViewFamily'::`43'::FDrawSceneCommandName,<lambda_83aa676af25f62e876425d334fdfc6e4> > >::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32> > &,Type) TaskGraphInterfaces.h:886

FNamedTaskThread::ProcessTasksNamedThread(int, bool) TaskGraph.cpp:710

FNamedTaskThread::ProcessTasksUntilQuit(int) TaskGraph.cpp:601

FTaskGraphImplementation::ProcessThreadUntilRequestReturn(Type) TaskGraph.cpp:1480

RenderingThreadMain(FEvent *) RenderingThread.cpp:372

FRenderingThread::Run() RenderingThread.cpp:526

FRunnableThreadWin::Run() WindowsRunnableThread.cpp:84

FRunnableThreadWin::GuardedRun() WindowsRunnableThread.cpp:27

FRunnableThreadWin::_ThreadProc(void *) WindowsRunnableThread.h:37

They are called every frame.

Now I don’t know why element of TArray<TRefCountPtr<FRHIResource> > ResourceTable; becomes wild pointer? How does it relevent with texture reloading? Anyone has idea? Thank you!

Env

Debug Config, Game Target

UE 4.27.2

Windows build