mirror of
https://github.com/Eragon-Brisingr/UShaderLab.git
synced 2026-09-15 23:04:37 +00:00
Add SL_FUNCTION and .uslfunc
This commit is contained in:
@@ -12,10 +12,13 @@
|
||||
#include "Misc/Paths.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "ShaderCore.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "ShaderLabDiscovery.h"
|
||||
#include "ShaderLabGraphBuilder.h"
|
||||
#include "ShaderLabImportResolver.h"
|
||||
#include "ShaderLabMaterialRegistry.h"
|
||||
#include "ShaderLabModel.h"
|
||||
#include "ShaderLabParser.h"
|
||||
#include "ShaderLabSubsystem.h"
|
||||
#include "ShaderLabVSCodeButton.h"
|
||||
#include "UObject/Package.h"
|
||||
@@ -191,6 +194,78 @@ private:
|
||||
WatchedRoots.Reset();
|
||||
}
|
||||
|
||||
/** Resolve a virtual shader path to disk via the registered shader-source directory mappings. */
|
||||
static bool ResolveVirtualToDisk(const FString& VirtualPath, FString& OutDiskPath)
|
||||
{
|
||||
FString Best, BestDir;
|
||||
for (const TPair<FString, FString>& Pair : AllShaderSourceDirectoryMappings())
|
||||
{
|
||||
if ((VirtualPath.StartsWith(Pair.Key + TEXT("/")) || VirtualPath == Pair.Key) && Pair.Key.Len() > Best.Len())
|
||||
{
|
||||
Best = Pair.Key;
|
||||
BestDir = Pair.Value;
|
||||
}
|
||||
}
|
||||
if (Best.IsEmpty()) { return false; }
|
||||
FString Rest = VirtualPath.Mid(Best.Len());
|
||||
Rest.RemoveFromStart(TEXT("/"));
|
||||
OutDiskPath = FPaths::Combine(BestDir, Rest);
|
||||
return true;
|
||||
}
|
||||
|
||||
static FString NormalizePath(const FString& Path)
|
||||
{
|
||||
FString N = FPaths::ConvertRelativePathToFull(Path);
|
||||
N.ReplaceInline(TEXT("\\"), TEXT("/"));
|
||||
return N;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every discovered `.usl` shader whose transitive `.uslfunc` import closure contains ChangedDiskPath —
|
||||
* i.e. the shaders that must be rebuilt when that library changes. Resolves each shader's imports so
|
||||
* both direct and nested (library-imports-library) dependencies are caught.
|
||||
*/
|
||||
static TArray<FString> FindShadersImporting(const FString& ChangedDiskPathNormalized)
|
||||
{
|
||||
TArray<FString> Result;
|
||||
for (const FString& Usl : FShaderLabDiscovery::FindShaderLabFiles())
|
||||
{
|
||||
FString Source;
|
||||
if (!FFileHelper::LoadFileToString(Source, *Usl))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FShaderLabModel Model;
|
||||
TArray<FShaderLabParseError> ParseErrors;
|
||||
if (!FShaderLabParser::Parse(Source, Usl, Model, ParseErrors) || Model.Imports.Num() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FShaderLabImportResolver::FSourceLoader Loader =
|
||||
[](const FString& VPath, FString& OutSrc, FString& OutDisk, FString& OutErr) -> bool
|
||||
{
|
||||
if (!ResolveVirtualToDisk(VPath, OutDisk)) { OutErr = TEXT("unmapped"); return false; }
|
||||
if (!FFileHelper::LoadFileToString(OutSrc, *OutDisk)) { OutErr = TEXT("read failed"); return false; }
|
||||
return true;
|
||||
};
|
||||
FShaderLabResolvedProgram Program;
|
||||
TArray<FString> ResolveErrors;
|
||||
if (!FShaderLabImportResolver::Resolve(Model, Loader, Program, ResolveErrors))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const FShaderLabResolvedLibrary& Lib : Program.Libraries)
|
||||
{
|
||||
if (NormalizePath(Lib.Model.SourceFilePath) == ChangedDiskPathNormalized)
|
||||
{
|
||||
Result.Add(Usl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
static void OnDirectoryChanged(const TArray<FFileChangeData>& Changes)
|
||||
{
|
||||
UShaderLabSubsystem* Subsystem = GEngine ? GEngine->GetEngineSubsystem<UShaderLabSubsystem>() : nullptr;
|
||||
@@ -199,24 +274,35 @@ private:
|
||||
return;
|
||||
}
|
||||
TSet<FString> Rebuilt;
|
||||
auto RebuildOnce = [&](const FString& Normalized)
|
||||
{
|
||||
if (Rebuilt.Contains(Normalized)) { return; }
|
||||
Rebuilt.Add(Normalized);
|
||||
Subsystem->RebuildFromFile(Normalized);
|
||||
UE_LOG(LogShaderLabEditor, Log, TEXT("ShaderLab hot-reloaded '%s'"), *Normalized);
|
||||
};
|
||||
|
||||
for (const FFileChangeData& Change : Changes)
|
||||
{
|
||||
if (!Change.Filename.EndsWith(TEXT(".usl")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Change.Action == FFileChangeData::FCA_Removed)
|
||||
{
|
||||
continue; // Leave the existing in-memory material in place on delete.
|
||||
}
|
||||
const FString Normalized = FPaths::ConvertRelativePathToFull(Change.Filename);
|
||||
if (Rebuilt.Contains(Normalized))
|
||||
const FString Ext = FPaths::GetExtension(Change.Filename);
|
||||
const FString Normalized = NormalizePath(Change.Filename);
|
||||
if (Ext.Equals(TEXT("usl"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
RebuildOnce(Normalized);
|
||||
}
|
||||
else if (Ext.Equals(TEXT("uslfunc"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
// A library changed: rebuild every shader that (transitively) imports it, so their promoted
|
||||
// parameters and emitted function HLSL refresh — the dependency-aware hot reload.
|
||||
for (const FString& Dependent : FindShadersImporting(Normalized))
|
||||
{
|
||||
RebuildOnce(NormalizePath(Dependent));
|
||||
}
|
||||
}
|
||||
Rebuilt.Add(Normalized);
|
||||
Subsystem->RebuildFromFile(Normalized);
|
||||
UE_LOG(LogShaderLabEditor, Log, TEXT("ShaderLab hot-reloaded '%s'"), *Normalized);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user