EM_Task/UnrealEd/Private/Commandlets/CookGlobalShaderCommandlet.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

200 lines
8.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Commandlets/CookGlobalShadersCommandlet.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "Misc/ConfigCacheIni.h"
#include "HAL/PlatformFilemanager.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Class.h"
#include "ShaderCompiler.h"
#include "CookOnTheSide/CookOnTheFlyServer.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Interfaces/ITargetPlatform.h"
#include "DerivedDataCacheInterface.h"
DEFINE_LOG_CATEGORY_STATIC(LogCookGlobalShaders, Log, All);
bool UCookGlobalShadersDeviceHelperStaged::CopyFilesToDevice(class ITargetDevice* Device, const TArray<TPair<FString, FString>>& FilesToCopy) const
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
bool bSuccess = true;
for (const TPair<FString, FString>& FileToCopy: FilesToCopy)
{
FString LocalFile = FileToCopy.Key;
FString RemoteFile = FPaths::Combine(StagedBuildPath, FileToCopy.Value);
bSuccess &= PlatformFile.CopyFile(*RemoteFile, *LocalFile);
}
return bSuccess;
}
int32 UCookGlobalShadersCommandlet::Main(const FString& Params)
{
TArray<FString> Tokens;
TArray<FString> Switches;
TMap<FString, FString> ParamVals;
UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals);
// Display help
if (Switches.Contains("help"))
{
UE_LOG(LogCookGlobalShaders, Log, TEXT("CookGlobalShaders"));
UE_LOG(LogCookGlobalShaders, Log, TEXT("This commandlet will allow you to generate the global shaders file which can be used to override what is used in a cooked build by deploying the loose file."));
UE_LOG(LogCookGlobalShaders, Log, TEXT("Options:"));
UE_LOG(LogCookGlobalShaders, Log, TEXT(" Required: -platform=<platform> (Which platform you want to cook for, i.e. windows)"));
UE_LOG(LogCookGlobalShaders, Log, TEXT(" Optional: -device=<name> (Set which device to use, when enabled the reload command will be sent to the device once the shaders are cooked)"));
UE_LOG(LogCookGlobalShaders, Log, TEXT(" Optional: -deploy (Must be used with -device and will deploy the shader file onto the device rather than in the staged builds folder)"));
UE_LOG(LogCookGlobalShaders, Log, TEXT(" Optional: -stage=<optional path> (Moved the shader file into the staged builds folder, destination can be overriden)"));
UE_LOG(LogCookGlobalShaders, Log, TEXT(" Optional: -reload (Execute a shader reload on the device, only works if the device is valid or a default one was found"));
UE_LOG(LogCookGlobalShaders, Log, TEXT(" Optional: -shaderpdb=<path> (Sets the shader pdb root)"));
return 0;
}
const bool bDeployToDevice = Switches.Contains("deploy");
const bool bCopyToStaged = Switches.Contains("stage");
const bool bExecuteReload = Switches.Contains("reload");
// Parse platform
FString PlatformName;
ITargetPlatform* TargetPlatform = nullptr;
{
if (!FParse::Value(*Params, TEXT("platform="), PlatformName, true))
{
UE_LOG(LogCookGlobalShaders, Warning, TEXT("You must include a target platform with -platform=xxx"));
return 1;
}
ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
TargetPlatform = TPM.FindTargetPlatform(PlatformName);
if (TargetPlatform == nullptr)
{
UE_LOG(LogCookGlobalShaders, Warning, TEXT("Target platform '%s' was not found"), *PlatformName);
return 1;
}
TargetPlatform->RefreshSettings();
}
// Set PDB path
FString ShaderPDBPath;
if (FParse::Value(*Params, TEXT("shaderpdb="), ShaderPDBPath, true))
{
GConfig->SetString(TEXT("DevOptions.Shaders"), TEXT("ShaderPDBRoot"), *ShaderPDBPath, GEngineIni);
}
// Get target device
ITargetDevicePtr TargetDevice;
FString TargetDeviceName;
if (FParse::Value(*Params, TEXT("device="), TargetDeviceName, true))
{
TArray<ITargetDevicePtr> TargetDevices;
TargetPlatform->GetAllDevices(TargetDevices);
for (int i = 0; i < TargetDevices.Num(); ++i)
{
if (TargetDevices[i]->GetName().Equals(TargetDeviceName, ESearchCase::IgnoreCase))
{
TargetDevice = TargetDevices[i];
break;
}
}
if (!TargetDevice.IsValid())
{
UE_LOG(LogCookGlobalShaders, Warning, TEXT("Failed to find target device '%s', reload / deploy will not be valid"), *TargetDeviceName);
}
}
else
{
TargetDevice = TargetPlatform->GetDefaultDevice();
}
if (!TargetDevice.IsValid() && (bDeployToDevice || bExecuteReload))
{
UE_LOG(LogCookGlobalShaders, Warning, TEXT("No device found to use for reload / deploy"));
}
// Find DeviceHelper class to use
UCookGlobalShadersDeviceHelperBase* DeviceHelper = nullptr;
if (TargetDevice.IsValid() && bDeployToDevice)
{
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
if (ClassIt->IsChildOf(UCookGlobalShadersDeviceHelperBase::StaticClass()))
{
FString ClassName = ClassIt->GetName();
ClassName.RemoveAt(0, UE_ARRAY_COUNT(TEXT("CookGlobalShadersDeviceHelperBase")), false);
if (ClassName.Equals(PlatformName))
{
DeviceHelper = NewObject<UCookGlobalShadersDeviceHelperBase>(GetTransientPackage(), *ClassIt);
break;
}
}
}
if (DeviceHelper == nullptr)
{
UE_LOG(LogCookGlobalShaders, Warning, TEXT("Failed to find Device Specific Implementation for '%s' global shaders will not be deployed to the device!"), *PlatformName);
}
}
else if (bCopyToStaged)
{
UCookGlobalShadersDeviceHelperStaged* StagedDeviceHelper = NewObject<UCookGlobalShadersDeviceHelperStaged>();
if (!FParse::Value(*Params, TEXT("stage="), StagedDeviceHelper->StagedBuildPath, true))
{
StagedDeviceHelper->StagedBuildPath = FPaths::ProjectSavedDir() / TEXT("StagedBuilds") / PlatformName;
}
DeviceHelper = StagedDeviceHelper;
}
// Cook shaders
TArray<FName> ShaderFormats;
TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats);
// const bool bContinous = Switches.Contains(TEXT("continuous"));
// while (bContinous)
{
// Cook shaders
UE_LOG(LogCookGlobalShaders, Log, TEXT("Cooking Global Shaders..."));
FString OutputDir = FPaths::ProjectSavedDir() / TEXT("CookGlobalShaders") / PlatformName;
const TArray<FString> MaterialsToLoad;
const TArray<FODSCRequestPayload> ShadersToLoad;
RecompileShadersForRemote(PlatformName, SP_NumPlatforms, OutputDir, MaterialsToLoad, ShadersToLoad, nullptr, nullptr);
// Are we copying the built files somewhere?
if (DeviceHelper != nullptr)
{
// Build list of files to copy
TArray<TPair<FString, FString>> FilesToCopy;
for (FName ShaderFormat: ShaderFormats)
{
const FString GlobalShaderCacheName = FPaths::Combine(OutputDir, TEXT("Engine"), TEXT("GlobalShaderCache-") + ShaderFormat.ToString() + TEXT(".bin"));
const FString OverrideGlobalShaderCacheName = FPaths::Combine(TEXT("Engine"), TEXT("OverrideGlobalShaderCache-") + ShaderFormat.ToString() + TEXT(".bin"));
FilesToCopy.Emplace(GlobalShaderCacheName, OverrideGlobalShaderCacheName);
}
// Execute Copy
UE_LOG(LogCookGlobalShaders, Log, TEXT("Copying Cooked Files..."));
if (DeviceHelper->CopyFilesToDevice(TargetDevice.Get(), FilesToCopy) == true)
{
// Execute Reload
if (bExecuteReload && TargetDevice.IsValid())
{
UE_LOG(LogCookGlobalShaders, Log, TEXT("Sending Reload Command..."));
TargetDevice->ExecuteConsoleCommand("ReloadGlobalShaders");
}
}
}
}
UE_LOG(LogCookGlobalShaders, Log, TEXT("Complete"));
DeviceHelper = nullptr;
// Wait for any DDC writes to complete
GetDerivedDataCacheRef().WaitForQuiescence(true);
return 0;
}