Existing Niagara systems with GPU sims are failing to set GPU compute script when upgrading project from 5.5 to 5.6

Attempting to update a project from 5.5 to 5.6, we’re having an issue with certain VFX emitters not showing up in the build. The root cause of the issue seems to be that the Niagara system is not valid because one or more of its emitters is marked as GPU compute, but doesn’t have a GPUComputeScript set. That property is set by PostInitProperties, which doesn’t seem to be called during re-save. It is unclear how it is being set in editor and local cooks.

Creating new emitters in the system works, but this is breaking for systems/emitters existing in the project before the upgrade. Any ideas on why the compute script is not getting set?

Some clues, this code is returning false:

bool UNiagaraScript::CanBeRunOnGpu()const
{
 
	if (Usage != ENiagaraScriptUsage::ParticleGPUComputeScript)
	{
		return false;
	}
	if (!CachedScriptVM.IsValid())
	{
		return false; <======
	}
	for (const FNiagaraScriptDataInterfaceCompileInfo& InterfaceInfo : CachedScriptVM.DataInterfaceInfo)
	{
		if (InterfaceInfo.Type.IsValid() && !InterfaceInfo.CanExecuteOnTarget(ENiagaraSimTarget::GPUComputeSim))
		{
			return false;
		}
	}
	return true;
}

That CachedScriptVM is set by the result of SetVMCompilationResults which is called here:

// check the ddc first
if (!bForceCompile && GetDerivedDataCacheRef().GetSynchronous(*GetNiagaraDDCKeyString(ScriptVersion, ScriptPathName), OutData, ScriptPathName))
{
	FNiagaraVMExecutableData ExeData;
	if (BinaryToExecData(this, OutData, ExeData))
	{
		COOK_STAT(Timer.AddHit(OutData.Num()));
		SetVMCompilationResults(LastGeneratedVMId, ExeData, FString(), RequestDuplicateData->GetObjectNameMap(), false);
		return;
	}
}

In the case where it does not find a DDC hit, it will calculate it later:

if (!bForceCompile && GetDerivedDataCacheRef().GetSynchronous(*GetNiagaraDDCKeyString(ScriptVersion, ScriptPathName), OutData, ScriptPathName))
{...}
 
ActiveCompileRoots.Empty();
RequestDuplicateData->GetDuplicatedObjects(MutableView(ActiveCompileRoots));
 
FNiagaraCompileOptions Options(GetUsage(), GetUsageId(), ScriptData->ModuleUsageBitmask, ScriptPathName, GetFullName(), GetName());
int32 JobHandle = NiagaraModule.StartScriptCompileJob(RequestData.Get(), RequestDuplicateData.Get(), Options);
FNiagaraScriptCompileMetrics ScriptMetrics;
TSharedPtr<FNiagaraVMExecutableData> ExeData = NiagaraModule.GetCompileJobResult(JobHandle, true, ScriptMetrics);
if (ExeData)
{
	SetVMCompilationResults(LastGeneratedVMId, *ExeData, FString(), RequestDuplicateData->GetObjectNameMap(), false);
	// save result to the ddc
	if (ExecToBinaryData(this, OutData, *ExeData))
	{
		COOK_STAT(Timer.AddMiss(OutData.Num()));
		GetDerivedDataCacheRef().Put(*GetNiagaraDDCKeyString(ScriptVersion, ScriptPathName), OutData, ScriptPathName);
	}
}
ActiveCompileRoots.Empty();

This is done as part of cook. Is there a way to send a “force compile” flag to the cook? We seem to be missing the ScriptResource.

Upon further inspection of UNiagaraScript::CanBeRunOnGpu() it appears there may be an async race condition for when and how CachedScriptVM gets populated.

Upon comparing the Niagara plugin in 5.7 to 5.6.1 it appears that 5.7 has removed or refactored the async optimization system that was causing CachedScriptVM to be invalid during cooking. For now, we’ve implemented the approach below to bypass the async optimization, but would like to know if this approach is sound until we can possibly upgrade engine versions to 5.7

  //  In NiagaraScript.cpp PostLoad, we changed:
 
  if (!GNiagaraDelayScriptAsyncOptimization)
  {
      HandleByteCodeOptimization(true);
  }
 
  // To:
 
  // Force immediate optimization during cook to ensure CachedScriptVM is valid
  HandleByteCodeOptimization(true);;

I think it should be safe to make that change, though if you want to avoid making code changes (to avoid merge conflicts) you should also just be able to add fx.Niagara.DelayScriptAsyncOptimization=false to your project’s config files to change the default value of that setting