Support UE 5.7

This commit is contained in:
Eragon-Brisingr
2026-07-07 13:07:53 +08:00
parent 2bf500bd98
commit 49b04addb4
8 changed files with 198 additions and 13 deletions

View File

@@ -53,8 +53,6 @@ float UE_IsOrthographic() { return (float)0; }
float2 UE_LightmapUVs() { return (float2)0; }
float3 UE_LightVector() { return (float3)0; }
float3 UE_LocalPosition() { return (float3)0; }
float3 UE_MainDirectionalLightDirection() { return (float3)0; }
float3 UE_MainDirectionalLightIlluminance() { return (float3)0; }
float3 UE_ObjectBounds() { return (float3)0; }
float3 UE_ObjectLocalBounds() { return (float3)0; }
float3 UE_ObjectOrientation() { return (float3)0; }

View File

@@ -4,10 +4,17 @@
#include "Engine/SpecularProfile.h"
#include "Engine/SubsurfaceProfile.h"
#include "Engine/ToonProfile.h"
#include "Misc/EngineVersionComparison.h"
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
#include "Engine/ToonProfile.h" // Substrate Toon profile is 5.8+ (UMaterialInstance::bOverrideToonProfile too)
#endif
#include "MaterialCachedData.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstance.h"
#if UE_VERSION_OLDER_THAN(5, 8, 0)
#include "MaterialShared.h" // FMaterialResource / FMaterialShaderMapId (5.7 per-instance-usage backport)
#include "Misc/SecureHash.h" // FSHA1
#endif
#include "ShaderLabMaterialAssetUserData.h"
#include "ShaderLabMaterialRegistry.h"
#include "ShaderLabModel.h"
@@ -103,8 +110,10 @@ void UShaderLabMaterialInstanceConstant::RefreshShaderLabDerivedData()
SubsurfaceProfile = nullptr;
bOverrideSpecularProfile = false;
SpecularProfileOverride = nullptr;
bOverrideToonProfile = false;
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
bOverrideToonProfile = false; // Toon profile override is a 5.8+ UMaterialInstance member
ToonProfileOverride = nullptr;
#endif
BuildSelfContainedCachedExpressionData();
ApplyShaderLabProfileOverrides();
@@ -214,6 +223,7 @@ void UShaderLabMaterialInstanceConstant::ApplyShaderLabProfileOverrides()
bOverrideSpecularProfile = true;
SpecularProfileOverride = Profile;
}
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
if (!ToonPath.IsEmpty())
{
UToonProfile* Profile = LoadObject<UToonProfile>(nullptr, *ToonPath);
@@ -221,6 +231,11 @@ void UShaderLabMaterialInstanceConstant::ApplyShaderLabProfileOverrides()
bOverrideToonProfile = true;
ToonProfileOverride = Profile;
}
#else
// Toon profile requires Substrate 5.8+. On 5.7 a Toon profile in the .usl is unreachable (SL_TOON is
// rejected by the graph builder / legacy emitter), so nothing to apply here.
(void)ToonPath;
#endif
}
void UShaderLabMaterialInstanceConstant::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const
@@ -235,3 +250,104 @@ void UShaderLabMaterialInstanceConstant::GetAssetRegistryTags(FAssetRegistryTags
}
}
#endif
#if UE_VERSION_OLDER_THAN(5, 8, 0)
// ============================================================================================================
// UE 5.7 per-instance material-usage backport (reproduces UE 5.8's per-instance UsageFlags with no engine edit).
// 5.8's FMaterialResource::IsUsedWith*() prefers MaterialInstance->GetUsageByFlag(); 5.7 reads only the base
// material. Since a ShaderLab base is a transient /Script shell (usage=0, never cooked), we host per-instance
// usage on the MIC and feed it into a custom resource so the instance's OWN static-permutation shader map
// compiles only the vertex-factory permutations its recorded usages need (on-demand, minimal variants).
// Compiled out entirely on 5.8 (engine-native mechanism used there).
// ============================================================================================================
// Custom static-permutation resource: IsUsedWith*() report the owning instance's recorded usage (OR base).
// Adds no serialized members (usage read live from the MIC) → on-disk-compatible with FMaterialResource.
class FShaderLabMaterialResource : public FMaterialResource
{
public:
#define SHADERLAB_USAGE_OVERRIDE(Fn, Flag) \
virtual bool Fn() const override { return HasUsage(Flag) || FMaterialResource::Fn(); }
SHADERLAB_USAGE_OVERRIDE(IsUsedWithStaticMesh, MATUSAGE_StaticMesh)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithSkeletalMesh, MATUSAGE_SkeletalMesh)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithInstancedStaticMeshes, MATUSAGE_InstancedStaticMeshes)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithSplineMeshes, MATUSAGE_SplineMesh)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithStaticLighting, MATUSAGE_StaticLighting)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithMorphTargets, MATUSAGE_MorphTargets)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithGeometryCache, MATUSAGE_GeometryCache)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithGeometryCollections, MATUSAGE_GeometryCollections)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithNanite, MATUSAGE_Nanite)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithWater, MATUSAGE_Water)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithHairStrands, MATUSAGE_HairStrands)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithParticleSprites, MATUSAGE_ParticleSprites)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithBeamTrails, MATUSAGE_BeamTrails)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithMeshParticles, MATUSAGE_MeshParticles)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithNiagaraSprites, MATUSAGE_NiagaraSprites)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithNiagaraRibbons, MATUSAGE_NiagaraRibbons)
SHADERLAB_USAGE_OVERRIDE(IsUsedWithNiagaraMeshParticles, MATUSAGE_NiagaraMeshParticles)
#undef SHADERLAB_USAGE_OVERRIDE
// BuildShaderMapIdOverride is the engine's designated hook for subclasses to modify the DDC/shader-map key.
// Fold the per-instance usage mask into the id so a usage change invalidates the cached shader map (5.7 omits
// usage from the id; 5.8 includes it via the property-override hash). No-op when mask==0.
virtual void BuildShaderMapIdOverride(const FBuildShaderMapIdArgs& Args) const override
{
FMaterialResource::BuildShaderMapIdOverride(Args);
#if WITH_EDITOR
// The shader-map id is only (re)built at cook/editor time; a cooked runtime loads the baked map by its
// CookedShaderMapIdHash and never recomputes the id. FMaterialShaderMapId::BasePropertyOverridesHash is
// itself editor-only, so this fold is WITH_EDITOR-guarded.
if (const uint32 Mask = GetMask())
{
FSHA1 Hasher;
Hasher.Update(Args.OutId->BasePropertyOverridesHash.Hash, sizeof(Args.OutId->BasePropertyOverridesHash.Hash));
Hasher.Update(reinterpret_cast<const uint8*>(&Mask), sizeof(Mask));
Hasher.Final();
Hasher.GetHash(Args.OutId->BasePropertyOverridesHash.Hash);
}
#endif
}
private:
const UShaderLabMaterialInstanceConstant* GetOwner() const { return Cast<UShaderLabMaterialInstanceConstant>(GetMaterialInterface()); }
uint32 GetMask() const { const UShaderLabMaterialInstanceConstant* MI = GetOwner(); return MI ? MI->GetShaderLabUsageMask() : 0u; }
bool HasUsage(EMaterialUsage Usage) const { return (GetMask() & (1u << static_cast<uint32>(Usage))) != 0u; }
};
FMaterialResource* UShaderLabMaterialInstanceConstant::AllocatePermutationResource()
{
return new FShaderLabMaterialResource();
}
bool UShaderLabMaterialInstanceConstant::CheckMaterialUsage(const EMaterialUsage Usage)
{
check(IsInGameThread());
const uint32 Bit = 1u << static_cast<uint32>(Usage);
if ((ShaderLabUsageMask & Bit) == 0u)
{
ShaderLabUsageMask |= Bit;
#if WITH_EDITOR
// Recompile the self-contained static permutation so the newly-needed vertex-factory permutation is
// baked (its ShaderMapId now differs via BuildShaderMapIdOverride); persist the recorded usage.
// UpdateStaticPermutation is the public/exported recompile entry (CacheResourceShadersForRendering is
// protected + non-exported). Cooked runtime never reaches here for an already-baked usage.
UpdateStaticPermutation();
MarkPackageDirty();
#endif
}
return true;
}
bool UShaderLabMaterialInstanceConstant::CheckMaterialUsage_Concurrent(const EMaterialUsage Usage) const
{
if (HasShaderLabUsage(Usage))
{
return true;
}
// Not recorded yet: defer to stock behavior, which (on the game thread) routes to CheckMaterialUsage above
// to record + recompile. Keeps editor auto-usage capture (dragging the material onto a mesh) working.
return Super::CheckMaterialUsage_Concurrent(Usage);
}
// ShaderLabUsageMask is a UPROPERTY (see the header) — persisted by the standard tagged property serialization,
// so no custom Serialize override is needed (and none should append raw data: that broke the export serial size).
#endif // UE_VERSION_OLDER_THAN(5, 8, 0)

View File

@@ -3,6 +3,7 @@
#include "ShaderLabSubsystem.h"
#include "Misc/CoreDelegates.h"
#include "Misc/EngineVersionComparison.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "ShaderLabDiscovery.h"
@@ -12,12 +13,20 @@
DEFINE_LOG_CATEGORY_STATIC(LogShaderLabSubsystem, Log, All);
// FCoreDelegates::OnPostEngineInit became the accessor GetOnPostEngineInit() in UE 5.8; 5.7 exposes the
// delegate as a direct static member. Both resolve to the same FSimpleMulticastDelegate lvalue.
#if UE_VERSION_OLDER_THAN(5, 8, 0)
#define SHADERLAB_ON_POST_ENGINE_INIT FCoreDelegates::OnPostEngineInit
#else
#define SHADERLAB_ON_POST_ENGINE_INIT FCoreDelegates::GetOnPostEngineInit()
#endif
void UShaderLabSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// Defer the scan until the engine (and material/shader systems) are fully up.
PostEngineInitHandle = FCoreDelegates::GetOnPostEngineInit().AddWeakLambda(this, [this]()
PostEngineInitHandle = SHADERLAB_ON_POST_ENGINE_INIT.AddWeakLambda(this, [this]()
{
DiscoverAndRegisterAll();
});
@@ -27,7 +36,7 @@ void UShaderLabSubsystem::Deinitialize()
{
if (PostEngineInitHandle.IsValid())
{
FCoreDelegates::GetOnPostEngineInit().Remove(PostEngineInitHandle);
SHADERLAB_ON_POST_ENGINE_INIT.Remove(PostEngineInitHandle);
PostEngineInitHandle.Reset();
}
Super::Deinitialize();

View File

@@ -4,6 +4,7 @@
#include "CoreMinimal.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Misc/EngineVersionComparison.h"
#include "ShaderLabMaterialInstanceConstant.generated.h"
/**
@@ -36,6 +37,29 @@ public:
virtual void PostLoad() override;
//~ End UObject interface
#if UE_VERSION_OLDER_THAN(5, 8, 0)
// --- Per-instance material usage (UE 5.7 backport of 5.8's per-instance UsageFlags) ------------------
// UE 5.7 has no instance-level usage override: FMaterialResource::IsUsedWith*() reads the BASE material's
// bUsedWith* only. Since a ShaderLab base is a transient /Script shell (usage=0, never cooked), per-instance
// usage must live on THIS instance. UE 5.8 added exactly this (BasePropertyOverrides.UsageFlags + resource
// reading MaterialInstance->GetUsageByFlag). We reproduce that here — entirely in the subclass, no engine
// change — so the instance's own static-permutation shader map compiles only the vertex-factory permutations
// its recorded usages need (on-demand, minimal variants). On 5.8 this whole block is compiled out and the
// engine's native mechanism is used instead.
//
// CheckMaterialUsage records the requested usage into ShaderLabUsageMask (and recompiles); the custom
// FShaderLabMaterialResource (returned from AllocatePermutationResource) reads the mask in its IsUsedWith*
// overrides (compile-time gating) and folds it into GetShaderMapId (cache invalidation); CheckMaterialUsage_
// Concurrent answers the runtime draw-time check from the mask.
virtual bool CheckMaterialUsage(const EMaterialUsage Usage) override;
virtual bool CheckMaterialUsage_Concurrent(const EMaterialUsage Usage) const override;
virtual FMaterialResource* AllocatePermutationResource() override;
/** True if the given usage bit is recorded on this instance. */
bool HasShaderLabUsage(EMaterialUsage Usage) const { return (ShaderLabUsageMask & (1u << static_cast<uint32>(Usage))) != 0u; }
uint32 GetShaderLabUsageMask() const { return ShaderLabUsageMask; }
#endif // UE_VERSION_OLDER_THAN(5, 8, 0)
#if WITH_EDITOR
//~ UObject interface
virtual void GetAssetRegistryTags(FAssetRegistryTagsContext Context) const override;
@@ -110,4 +134,16 @@ private:
*/
void ApplyShaderLabProfileOverrides();
#endif
#if UE_VERSION_OLDER_THAN(5, 8, 0)
/**
* Per-instance material-usage bitmask (bit i = 1<<EMaterialUsage). Backports UE 5.8's per-instance usage to
* 5.7. A UPROPERTY (tagged serialization) so it persists across save/cook AND upgrades safely: on a later
* 5.8 the `#if` drops this property, and the engine's tagged property serialization simply skips the unknown
* tag when loading a 5.7-authored asset (a raw FArchive append here instead corrupted the export serial size
* — "Serial size mismatch" — and was NOT upgrade-safe). On 5.8 the whole block is excluded (zero footprint).
*/
UPROPERTY()
uint32 ShaderLabUsageMask = 0;
#endif
};

View File

@@ -6,6 +6,7 @@
#include "MaterialShared.h"
#include "Materials/Material.h"
#include "Misc/CoreDelegates.h"
#include "Misc/EngineVersionComparison.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "ShaderCore.h"
@@ -16,6 +17,15 @@
#include "ShaderLabModel.h"
#include "UObject/UObjectIterator.h"
// FCoreDelegates::OnPostEngineInit became the accessor GetOnPostEngineInit() in UE 5.8; 5.7 exposes the
// delegate member directly. Mirror the same bridge the runtime subsystem uses so the shared plugin builds
// on both engines.
#if UE_VERSION_OLDER_THAN(5, 8, 0)
#define SHADERLAB_ON_POST_ENGINE_INIT FCoreDelegates::OnPostEngineInit
#else
#define SHADERLAB_ON_POST_ENGINE_INIT FCoreDelegates::GetOnPostEngineInit()
#endif
DEFINE_LOG_CATEGORY_STATIC(LogShaderLabBuilder, Log, All);
namespace
@@ -154,7 +164,7 @@ public:
// (a fresh clone lacks .vscode — it is git-ignored). Deferred to post-engine-init so shader-source
// mappings (added above) and UMaterial reflection are ready; the callback self-gates to an interactive
// editor session (no cook / -game / commandlet / unattended).
PostEngineInitHandle = FCoreDelegates::GetOnPostEngineInit().AddStatic(&ShaderLabIDE_EnsureGeneratedIfMissing);
PostEngineInitHandle = SHADERLAB_ON_POST_ENGINE_INIT.AddStatic(&ShaderLabIDE_EnsureGeneratedIfMissing);
UE_LOG(LogShaderLabBuilder, Log, TEXT("ShaderLabBuilder module started."));
}
@@ -168,7 +178,7 @@ public:
}
if (PostEngineInitHandle.IsValid())
{
FCoreDelegates::GetOnPostEngineInit().Remove(PostEngineInitHandle);
SHADERLAB_ON_POST_ENGINE_INIT.Remove(PostEngineInitHandle);
PostEngineInitHandle.Reset();
}
}

View File

@@ -27,7 +27,10 @@
#include "Materials/MaterialExpressionSceneTexture.h"
#include "Engine/SubsurfaceProfile.h"
#include "Engine/SpecularProfile.h"
#include "Engine/ToonProfile.h"
#include "Misc/EngineVersionComparison.h"
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
#include "Engine/ToonProfile.h" // Substrate Toon BSDF/profile is 5.8+; the whole Toon path is version-guarded out on 5.7
#endif
#include "Materials/MaterialExpressionTextureObjectParameter.h"
#include "Materials/MaterialExpressionTextureSampleParameter2D.h"
#include "Materials/MaterialExpressionTextureCoordinate.h"
@@ -505,7 +508,8 @@ namespace ShaderLabGraph
return nullptr;
}
// --- Toon (experimental) ---
// --- Toon (experimental; Substrate 5.8+ only — UMaterialExpressionSubstrateToonBSDF does not exist in 5.7) ---
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
static const FSlabFieldDef GToonFields[] = {
{ TEXT("BaseColor"), CMOT_Float3 }, { TEXT("Metallic"), CMOT_Float1 }, { TEXT("Specular"), CMOT_Float1 },
{ TEXT("Roughness"), CMOT_Float1 }, { TEXT("Normal"), CMOT_Float3 }, { TEXT("EmissiveColor"), CMOT_Float3 },
@@ -526,6 +530,7 @@ namespace ShaderLabGraph
if (F == TEXT("Tangent")) return &B->Tangent;
return nullptr;
}
#endif // !UE_VERSION_OLDER_THAN(5, 8, 0) — Toon
// --- Light Function ---
static const FSlabFieldDef GLightFunctionFields[] = {
@@ -554,8 +559,10 @@ namespace ShaderLabGraph
GVolumeFields, UE_ARRAY_COUNT(GVolumeFields), &MakeVolumeNode, &GetVolumePin },
{ EShaderLabBsdfType::ClearCoat, TEXT("ShaderLab ClearCoat"), TEXT("FShaderLabClearCoat"), TEXT("ShaderLabDefaultClearCoat"),
GClearCoatFields, UE_ARRAY_COUNT(GClearCoatFields), &MakeClearCoatNode, &GetClearCoatPin },
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
{ EShaderLabBsdfType::Toon, TEXT("ShaderLab Toon"), TEXT("FShaderLabToon"), TEXT("ShaderLabDefaultToon"),
GToonFields, UE_ARRAY_COUNT(GToonFields), &MakeToonNode, &GetToonPin },
#endif
{ EShaderLabBsdfType::LightFunction, TEXT("ShaderLab LightFunction"), TEXT("FShaderLabLightFunction"), TEXT("ShaderLabDefaultLightFunction"),
GLightFunctionFields, UE_ARRAY_COUNT(GLightFunctionFields), &MakeLightFunctionNode, &GetLightFunctionPin },
};
@@ -1825,10 +1832,12 @@ namespace ShaderLabGraph
{
if (!LoadProfileAsset(M.SubsurfaceProfilePath, TEXT("SubsurfaceProfile"), Eye->SubsurfaceProfile, OutErrors)) { return false; }
}
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
else if (UMaterialExpressionSubstrateToonBSDF* Toon = Cast<UMaterialExpressionSubstrateToonBSDF>(Bsdf))
{
if (!LoadProfileAsset(M.ToonProfilePath, TEXT("ToonProfile"), Toon->ToonProfile, OutErrors)) { return false; }
}
#endif
return true;
}

View File

@@ -19,8 +19,9 @@
// ShaderLab-controlled set). Substrate-only constructs are omitted at
// r.Substrate=0. Regenerated per engine version + project mode.
#include "CoreMinimal.h"
#include "ShaderLabIDEPrepare.h"
#include "CoreMinimal.h"
#include "Dom/JsonObject.h"
#include "HAL/IConsoleManager.h"
#include "Materials/Material.h"
@@ -31,7 +32,6 @@
#include "Misc/App.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "ShaderLabIDEPrepare.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"

View File

@@ -4,13 +4,16 @@
#include "ShaderLabIntrinsics.h"
#include "Misc/EngineVersionComparison.h"
#include "Materials/MaterialExpressionCameraPositionWS.h"
#include "Materials/MaterialExpressionCameraVectorWS.h"
#include "Materials/MaterialExpressionEyeAdaptation.h"
#include "Materials/MaterialExpressionIsFirstPerson.h"
#include "Materials/MaterialExpressionIsOrthographic.h"
#include "Materials/MaterialExpressionLightVector.h"
#include "Materials/MaterialExpressionMainDirectionalLight.h"
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
#include "Materials/MaterialExpressionMainDirectionalLight.h" // 5.8+ only
#endif
#include "Materials/MaterialExpressionPixelDepth.h"
#include "Materials/MaterialExpressionReflectionVectorWS.h"
#include "Materials/MaterialExpressionSceneTexelSize.h"
@@ -38,8 +41,12 @@ void RegisterViewIntrinsics(FShaderLabIntrinsicRegistry& Registry)
AddSimple(Registry, TEXT("IsFirstPerson"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionIsFirstPerson>(M); });
// MainDirectionalLight is multi-output (Illuminance, Direction) — one intrinsic per output pin.
// UMaterialExpressionMainDirectionalLight is 5.8+; the intrinsics are simply not registered on 5.7
// (a .usl using them fails the build with "unknown UE_ intrinsic", which is the correct contract).
#if !UE_VERSION_OLDER_THAN(5, 8, 0)
AddSimple(Registry, TEXT("MainDirectionalLightIlluminance"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionMainDirectionalLight>(M); }, 0);
AddSimple(Registry, TEXT("MainDirectionalLightDirection"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionMainDirectionalLight>(M); }, 1);
#endif
// ViewProperty(Property) — Property is an EMaterialExposedViewProperty token.
{