We’re hitting a crash in FMaterialShader::GetShaderBindings (ShaderBaseClasses.cpp) when changing scalability settings at runtime.
checkf(UniformExpressionCache.CachedUniformExpressionShaderMap == Material.GetRenderingThreadShaderMap())
Root cause analysis:
In ScalabilityCVarsSinkCallback() (UnrealEngine.cpp), when MaterialQualityLevel changes, AllMaterialsCacheResourceShadersForRendering() is called, which internally calls CacheUniformExpressions()
on all material proxies. This synchronously invalidates each proxy’s UniformExpressionCache (sets CachedUniformExpressionShaderMap = nullptr) and adds it to the DeferredUniformExpressionCacheRequests set — but does not re-evaluate.
When the FGlobalComponentRecreateRenderStateContext destructor triggers FScene::Update(), two things happen:
Line 5291: UpdateDeferredCachedUniformExpressions() is called, potentially as an async task (UpdateUniformExpressionsTask)
Line 6404: CacheMeshDrawCommandsTask is launched with dependencies on AddStaticMeshesTask only — not on UpdateUniformExpressionsTask
It would seem that there is no dependency between these tasks, CacheMeshDrawCommands can execute while uniform expression caches are still stale.
GetMaterialNoFallback() returns the new quality level’s FMaterial (new shader map), but the proxy’s cache still references the old shader map (or nullptr), triggering the checkf.
Suggested fix:
Add UpdateUniformExpressionsTask as a dependency of CacheMeshDrawCommandsTask in RendererScene.cpp:
CacheMeshDrawCommandsTask = GraphBuilder.AddSetupTask([...] {
FPrimitiveSceneInfo::CacheMeshDrawCommands(this, SceneInfosWithStaticDrawListUpdate);
}, MakeArrayView({ AddStaticMeshesTask, UpdateUniformExpressionsTask, ... }), ...);
This would ensures uniform expression caches are fully evaluated before mesh draw command caching begins.
Is the dependency fix the correct approach, or would you recommend a different synchronization strategy?
Note: I think [this [Content removed] is describing the same issue.
[Attachment Removed]