From 49b04addb42055ca3f18cc8c65fe63014aa4d793 Mon Sep 17 00:00:00 2001 From: Eragon-Brisingr <450614754@qq.com> Date: Tue, 7 Jul 2026 13:07:53 +0800 Subject: [PATCH] Support UE 5.7 --- Shaders/Private/ShaderLabUENode.ush | 2 - .../ShaderLabMaterialInstanceConstant.cpp | 120 +++++++++++++++++- .../UShaderLab/Private/ShaderLabSubsystem.cpp | 13 +- .../ShaderLabMaterialInstanceConstant.h | 36 ++++++ .../Private/ShaderLabBuilderModule.cpp | 14 +- .../Private/ShaderLabGraphBuilder.cpp | 13 +- .../Private/ShaderLabIDEPrepare.cpp | 4 +- .../Private/ShaderLabIntrinsics_View.cpp | 9 +- 8 files changed, 198 insertions(+), 13 deletions(-) diff --git a/Shaders/Private/ShaderLabUENode.ush b/Shaders/Private/ShaderLabUENode.ush index 75726f2..6bf1c76 100644 --- a/Shaders/Private/ShaderLabUENode.ush +++ b/Shaders/Private/ShaderLabUENode.ush @@ -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; } diff --git a/Source/UShaderLab/Private/ShaderLabMaterialInstanceConstant.cpp b/Source/UShaderLab/Private/ShaderLabMaterialInstanceConstant.cpp index 51cd8a6..6ffb032 100644 --- a/Source/UShaderLab/Private/ShaderLabMaterialInstanceConstant.cpp +++ b/Source/UShaderLab/Private/ShaderLabMaterialInstanceConstant.cpp @@ -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(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(&Mask), sizeof(Mask)); + Hasher.Final(); + Hasher.GetHash(Args.OutId->BasePropertyOverridesHash.Hash); + } +#endif + } + +private: + const UShaderLabMaterialInstanceConstant* GetOwner() const { return Cast(GetMaterialInterface()); } + uint32 GetMask() const { const UShaderLabMaterialInstanceConstant* MI = GetOwner(); return MI ? MI->GetShaderLabUsageMask() : 0u; } + bool HasUsage(EMaterialUsage Usage) const { return (GetMask() & (1u << static_cast(Usage))) != 0u; } +}; + +FMaterialResource* UShaderLabMaterialInstanceConstant::AllocatePermutationResource() +{ + return new FShaderLabMaterialResource(); +} + +bool UShaderLabMaterialInstanceConstant::CheckMaterialUsage(const EMaterialUsage Usage) +{ + check(IsInGameThread()); + const uint32 Bit = 1u << static_cast(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) diff --git a/Source/UShaderLab/Private/ShaderLabSubsystem.cpp b/Source/UShaderLab/Private/ShaderLabSubsystem.cpp index 9407952..aaf36c1 100644 --- a/Source/UShaderLab/Private/ShaderLabSubsystem.cpp +++ b/Source/UShaderLab/Private/ShaderLabSubsystem.cpp @@ -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(); diff --git a/Source/UShaderLab/Public/ShaderLabMaterialInstanceConstant.h b/Source/UShaderLab/Public/ShaderLabMaterialInstanceConstant.h index c3641bd..52b88f6 100644 --- a/Source/UShaderLab/Public/ShaderLabMaterialInstanceConstant.h +++ b/Source/UShaderLab/Public/ShaderLabMaterialInstanceConstant.h @@ -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(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<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(Bsdf)) { if (!LoadProfileAsset(M.ToonProfilePath, TEXT("ToonProfile"), Toon->ToonProfile, OutErrors)) { return false; } } +#endif return true; } diff --git a/Source/UShaderLabBuilder/Private/ShaderLabIDEPrepare.cpp b/Source/UShaderLabBuilder/Private/ShaderLabIDEPrepare.cpp index a07b4c9..70ab8b5 100644 --- a/Source/UShaderLabBuilder/Private/ShaderLabIDEPrepare.cpp +++ b/Source/UShaderLabBuilder/Private/ShaderLabIDEPrepare.cpp @@ -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" diff --git a/Source/UShaderLabBuilder/Private/ShaderLabIntrinsics_View.cpp b/Source/UShaderLabBuilder/Private/ShaderLabIntrinsics_View.cpp index 114cee0..b6c638e 100644 --- a/Source/UShaderLabBuilder/Private/ShaderLabIntrinsics_View.cpp +++ b/Source/UShaderLabBuilder/Private/ShaderLabIntrinsics_View.cpp @@ -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(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(M); }, 0); AddSimple(Registry, TEXT("MainDirectionalLightDirection"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple(M); }, 1); +#endif // ViewProperty(Property) — Property is an EMaterialExposedViewProperty token. {