mirror of
https://github.com/Eragon-Brisingr/UShaderLab.git
synced 2026-09-15 14:54:36 +00:00
Enhance VSCode coding (Use shader-validator VSCode plugin
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// Per-pixel/vertex inputs are read through `UE::NodeName(...)` intrinsics (resolved by the graph
|
||||
// Per-pixel/vertex inputs are read through `UE_NodeName(...)` intrinsics (resolved by the graph
|
||||
// builder to real material expression nodes), not through a context struct.
|
||||
|
||||
// Surface description filled by the Surface(...) body. Defaults mirror the
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
// SL_SETTINGS : Domain, BlendMode, TwoSided, OpacityMaskClipValue, <reflection-allowlist keys...>
|
||||
// Domain = Surface | PostProcess | UI | Decal
|
||||
// BlendMode = Opaque | Masked | Translucent | Additive | Modulate
|
||||
// SL_PROPERTY : DisplayName, Group, SortPriority, Range (Scalar only), DefaultTexture (textures only)
|
||||
// SL_PROPERTY : DisplayName, Category, SortPriority, Range (Scalar only), DefaultTexture (textures only)
|
||||
//
|
||||
// Topology operators (used inside SL_FRONTMATERIAL):
|
||||
// VerticalLayer(Top, Base, Thickness) | HorizontalMix(Background, Foreground, Mix)
|
||||
10
Shaders/Private/ShaderLabUEFunctions.ush
Normal file
10
Shaders/Private/ShaderLabUEFunctions.ush
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Aggregator for the ShaderLab `UE_` HLSL library functions. This header only includes the per-category
|
||||
// implementation files under UEFunctions/. It is auto-included into every generated Custom node (so the
|
||||
// forwarders compile) and pulled in by the ShaderLab.ush authoring umbrella (so they complete in the IDE).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "/Plugin/ShaderLab/Private/UEFunctions/Noise.ush"
|
||||
#include "/Plugin/ShaderLab/Private/UEFunctions/Transform.ush"
|
||||
99
Shaders/Private/ShaderLabUENode.ush
Normal file
99
Shaders/Private/ShaderLabUENode.ush
Normal file
@@ -0,0 +1,99 @@
|
||||
// GENERATED by `ShaderLab.IDE.Prepare` — do not edit by hand.
|
||||
// UE_ node-intrinsic stubs + their enums, for IDE completion only (never compiled).
|
||||
#pragma once
|
||||
|
||||
enum EMaterialExpressionBoundsType
|
||||
{
|
||||
MEILB_InstanceLocal,
|
||||
MEILB_ObjectLocal,
|
||||
MEILB_PreSkinnedLocal,
|
||||
};
|
||||
enum EMaterialExposedViewProperty
|
||||
{
|
||||
MEVP_BufferSize,
|
||||
MEVP_FieldOfView,
|
||||
MEVP_TanHalfFieldOfView,
|
||||
MEVP_ViewSize,
|
||||
MEVP_WorldSpaceViewPosition,
|
||||
MEVP_WorldSpaceCameraPosition,
|
||||
MEVP_ViewportOffset,
|
||||
MEVP_TemporalSampleCount,
|
||||
MEVP_TemporalSampleIndex,
|
||||
MEVP_TemporalSampleOffset,
|
||||
MEVP_RuntimeVirtualTextureOutputLevel,
|
||||
MEVP_RuntimeVirtualTextureOutputDerivative,
|
||||
MEVP_PreExposure,
|
||||
MEVP_RuntimeVirtualTextureMaxLevel,
|
||||
MEVP_ResolutionFraction,
|
||||
MEVP_PostVolumeUserFlags,
|
||||
MEVP_FirstPersonFieldOfView,
|
||||
MEVP_FirstPersonTanHalfFieldOfView,
|
||||
MEVP_FirstPersonScale,
|
||||
MEVP_NearPlane,
|
||||
};
|
||||
|
||||
float3 UE_ActorPositionWS() { return (float3)0; }
|
||||
float3 UE_AtmosphericLightColor() { return (float3)0; }
|
||||
float3 UE_AtmosphericLightVector() { return (float3)0; }
|
||||
// Space is an EMaterialExpressionBoundsType token (e.g. MEILB_ObjectLocal).
|
||||
float3 UE_Bounds(EMaterialExpressionBoundsType Space) { return (float3)0; }
|
||||
float3 UE_CameraPositionWS() { return (float3)0; }
|
||||
float3 UE_CameraVectorWS() { return (float3)0; }
|
||||
float3 UE_DecalColor() { return (float3)0; }
|
||||
float2 UE_DecalDerivativeDDX() { return (float2)0; }
|
||||
float2 UE_DecalDerivativeDDY() { return (float2)0; }
|
||||
float UE_DecalLifetimeOpacity() { return (float)0; }
|
||||
float UE_DeltaTime() { return (float)0; }
|
||||
float UE_DistanceCullFade() { return (float)0; }
|
||||
float UE_EyeAdaptation() { return (float)0; }
|
||||
float UE_IsFirstPerson() { return (float)0; }
|
||||
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; }
|
||||
float3 UE_ObjectPositionWS() { return (float3)0; }
|
||||
float UE_ObjectRadius() { return (float)0; }
|
||||
float4 UE_ParticleColor() { return (float4)0; }
|
||||
float3 UE_ParticleDirection() { return (float3)0; }
|
||||
float2 UE_ParticleMacroUV() { return (float2)0; }
|
||||
float UE_ParticleMotionBlurFade() { return (float)0; }
|
||||
float3 UE_ParticlePositionWS() { return (float3)0; }
|
||||
float UE_ParticleRadius() { return (float)0; }
|
||||
float UE_ParticleRandom() { return (float)0; }
|
||||
float UE_ParticleRelativeTime() { return (float)0; }
|
||||
float2 UE_ParticleSize() { return (float2)0; }
|
||||
float UE_ParticleSpeed() { return (float)0; }
|
||||
float UE_ParticleSpriteRotation() { return (float)0; }
|
||||
float UE_ParticleSubUVBlend() { return (float)0; }
|
||||
float2 UE_ParticleSubUVTexCoord0() { return (float2)0; }
|
||||
float2 UE_ParticleSubUVTexCoord1() { return (float2)0; }
|
||||
float UE_PerInstanceCustomData(int DataIndex, float ConstDefaultValue = 0) { return (float)0; }
|
||||
float UE_PerInstanceFadeAmount() { return (float)0; }
|
||||
float UE_PerInstanceRandom() { return (float)0; }
|
||||
float UE_PixelDepth() { return (float)0; }
|
||||
float3 UE_PixelNormalWS() { return (float3)0; }
|
||||
float UE_PrecomputedAOMask() { return (float)0; }
|
||||
float3 UE_PreSkinnedLocalBounds() { return (float3)0; }
|
||||
float3 UE_PreSkinnedNormal() { return (float3)0; }
|
||||
float3 UE_PreSkinnedPosition() { return (float3)0; }
|
||||
float3 UE_ReflectionVectorWS() { return (float3)0; }
|
||||
float2 UE_SceneTexelSize() { return (float2)0; }
|
||||
float2 UE_ScreenPosition() { return (float2)0; }
|
||||
float3 UE_SkyAtmosphereLightDirection(int LightIndex = 0) { return (float3)0; }
|
||||
float UE_SphericalParticleOpacity() { return (float)0; }
|
||||
// UV channel `Index`, optionally tiled.
|
||||
float2 UE_TextureCoordinate(int Index, float UTiling = 1, float VTiling = 1) { return (float2)0; }
|
||||
float UE_Time() { return (float)0; }
|
||||
float UE_TwoSidedSign() { return (float)0; }
|
||||
float4 UE_VertexColor() { return (float4)0; }
|
||||
float3 UE_VertexNormalWS() { return (float3)0; }
|
||||
float3 UE_VertexTangentWS() { return (float3)0; }
|
||||
// Property is an EMaterialExposedViewProperty token (e.g. MEVP_FieldOfView).
|
||||
float4 UE_ViewProperty(EMaterialExposedViewProperty Property) { return (float4)0; }
|
||||
float2 UE_ViewSize() { return (float2)0; }
|
||||
float3 UE_WorldPosition() { return (float3)0; }
|
||||
73
Shaders/Private/UEFunctions/Noise.ush
Normal file
73
Shaders/Private/UEFunctions/Noise.ush
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// UE_ noise functions — forward to the engine's MaterialExpressionNoise / MaterialExpressionVectorNoise.
|
||||
//
|
||||
// Enum scheme B: in the editor (SHADERLAB_IDE, defined only by shader-validator) the function selectors
|
||||
// are real HLSL `enum`s for best completion/semantics; at compile they fall back to `int` + `#define`
|
||||
// (portable — the engine's own shaders avoid `enum` for cross-compiler safety). The engine functions we
|
||||
// forward to are invisible to the standalone IDE parser, so we also stub them under SHADERLAB_IDE; the
|
||||
// real shader compiler never defines SHADERLAB_IDE, so it uses the true engine functions instead.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef SHADERLAB_IDE
|
||||
enum ENoiseFunction
|
||||
{
|
||||
NOISEFUNCTION_SimplexTex,
|
||||
NOISEFUNCTION_GradientTex,
|
||||
NOISEFUNCTION_GradientTex3D,
|
||||
NOISEFUNCTION_GradientALU,
|
||||
NOISEFUNCTION_ValueALU,
|
||||
NOISEFUNCTION_VoronoiALU,
|
||||
};
|
||||
enum EVectorNoiseFunction
|
||||
{
|
||||
VNF_CellnoiseALU,
|
||||
VNF_VectorALU,
|
||||
VNF_GradientALU,
|
||||
VNF_CurlALU,
|
||||
VNF_VoronoiALU,
|
||||
};
|
||||
float MaterialExpressionNoise(float3 Position, float Scale, int Quality, int Function, bool bTurbulence, uint Levels, float OutputMin, float OutputMax, float LevelScale, float FilterWidth, bool bTiling, float RepeatSize) { return 0; }
|
||||
float4 MaterialExpressionVectorNoise(float3 Position, int Quality, int Function, bool bTiling, float TileSize) { return (float4)0; }
|
||||
#else
|
||||
#define ENoiseFunction int
|
||||
#define EVectorNoiseFunction int
|
||||
#define NOISEFUNCTION_SimplexTex 0
|
||||
#define NOISEFUNCTION_GradientTex 1
|
||||
#define NOISEFUNCTION_GradientTex3D 2
|
||||
#define NOISEFUNCTION_GradientALU 3
|
||||
#define NOISEFUNCTION_ValueALU 4
|
||||
#define NOISEFUNCTION_VoronoiALU 5
|
||||
#define VNF_CellnoiseALU 0
|
||||
#define VNF_VectorALU 1
|
||||
#define VNF_GradientALU 2
|
||||
#define VNF_CurlALU 3
|
||||
#define VNF_VoronoiALU 4
|
||||
#endif
|
||||
|
||||
// Full-control scalar noise — forwards directly to the engine implementation.
|
||||
float UE_NoiseAdvanced(float3 Position, float Scale, ENoiseFunction Function, int Quality, bool bTurbulence,
|
||||
uint Levels, float OutputMin, float OutputMax, float LevelScale, float FilterWidth, bool bTiling, float RepeatSize)
|
||||
{
|
||||
return MaterialExpressionNoise(Position, Scale, Quality, Function, bTurbulence, Levels,
|
||||
OutputMin, OutputMax, LevelScale, FilterWidth, bTiling, RepeatSize);
|
||||
}
|
||||
|
||||
// Simple scalar noise with the material Noise node's defaults (Simplex, turbulent, 6 octaves, [-1,1]).
|
||||
float UE_Noise(float3 Position, float Scale)
|
||||
{
|
||||
return UE_NoiseAdvanced(Position, Scale, NOISEFUNCTION_SimplexTex, 1, true, 6, -1.0, 1.0, 2.0, 0.0, false, 512.0);
|
||||
}
|
||||
|
||||
// Full-control vector noise — forwards directly to the engine implementation.
|
||||
float4 UE_VectorNoiseAdvanced(float3 Position, EVectorNoiseFunction Function, int Quality, bool bTiling, float TileSize)
|
||||
{
|
||||
return MaterialExpressionVectorNoise(Position, Quality, Function, bTiling, TileSize);
|
||||
}
|
||||
|
||||
// Simple vector noise with the material VectorNoise node's defaults (Cellnoise).
|
||||
float4 UE_VectorNoise(float3 Position)
|
||||
{
|
||||
return UE_VectorNoiseAdvanced(Position, VNF_CellnoiseALU, 1, false, 300.0);
|
||||
}
|
||||
17
Shaders/Private/UEFunctions/Transform.ush
Normal file
17
Shaders/Private/UEFunctions/Transform.ush
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// UE_ transform functions — forward to the engine's material HLSL. The engine function is invisible to
|
||||
// the standalone IDE parser, so it is stubbed under SHADERLAB_IDE (defined only by shader-validator);
|
||||
// the real shader compiler uses the true engine function.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef SHADERLAB_IDE
|
||||
float3 RotateAboutAxis(float4 NormalizedRotationAxisAndAngle, float3 PositionOnAxis, float3 Position) { return (float3)0; }
|
||||
#endif
|
||||
|
||||
// Rotate `Position` about the axis `NormalizedAxis` through `PivotPoint` by `Angle` radians.
|
||||
float3 UE_RotateAboutAxis(float3 NormalizedAxis, float3 PivotPoint, float Angle, float3 Position)
|
||||
{
|
||||
return RotateAboutAxis(float4(NormalizedAxis, Angle), PivotPoint, Position);
|
||||
}
|
||||
@@ -1,44 +1,16 @@
|
||||
// GENERATED by `ShaderLab.IDE.Prepare` — do not edit by hand.
|
||||
// Authoring umbrella header: include this from every .usl for IDE completion.
|
||||
// At real shader-compile time this header is NOT included (the parser strips it);
|
||||
// the UE:: stubs below are for the editor only.
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// ShaderLab authoring umbrella — `#include "/Plugin/ShaderLab/ShaderLab.ush"` at the top of every .usl
|
||||
// for IDE completion. This header is NOT compiled: the ShaderLab parser strips it (only entry-function
|
||||
// bodies reach the shader compiler). It just pulls in the pieces the IDE needs to resolve a .usl:
|
||||
// - ShaderLabDSL : the SL_* annotation macros
|
||||
// - ShaderLabCommon : the FShaderLabSurface/etc. structs
|
||||
// - ShaderLabUENode : the UE_ node-intrinsic stubs + their enums (generated by ShaderLab.IDE.Prepare)
|
||||
// - ShaderLabUEFunctions : the UE_ HLSL library functions (also auto-included at compile)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "/Plugin/ShaderLab/ShaderLabDSL.ush"
|
||||
#include "/Plugin/ShaderLab/Private/ShaderLabDSL.ush"
|
||||
#include "/Plugin/ShaderLab/Private/ShaderLabCommon.ush"
|
||||
|
||||
namespace UE
|
||||
{
|
||||
float3 ActorPositionWS() { return (float3)0; }
|
||||
float3 CameraPositionWS() { return (float3)0; }
|
||||
float3 CameraVectorWS() { return (float3)0; }
|
||||
float DeltaTime() { return (float)0; }
|
||||
float2 LightmapUVs() { return (float2)0; }
|
||||
float3 LightVector() { return (float3)0; }
|
||||
float3 LocalPosition() { return (float3)0; }
|
||||
float3 ObjectBounds() { return (float3)0; }
|
||||
float3 ObjectLocalBounds() { return (float3)0; }
|
||||
float3 ObjectOrientation() { return (float3)0; }
|
||||
float3 ObjectPositionWS() { return (float3)0; }
|
||||
float ObjectRadius() { return (float)0; }
|
||||
float4 ParticleColor() { return (float4)0; }
|
||||
float PerInstanceCustomData(int DataIndex, float ConstDefaultValue = 0) { return (float)0; }
|
||||
float PerInstanceFadeAmount() { return (float)0; }
|
||||
float PerInstanceRandom() { return (float)0; }
|
||||
float PixelDepth() { return (float)0; }
|
||||
float3 PixelNormalWS() { return (float3)0; }
|
||||
float3 PreSkinnedLocalBounds() { return (float3)0; }
|
||||
float3 PreSkinnedNormal() { return (float3)0; }
|
||||
float3 PreSkinnedPosition() { return (float3)0; }
|
||||
float2 ScreenPosition() { return (float2)0; }
|
||||
// UV channel `Index`, optionally tiled.
|
||||
float2 TextureCoordinate(int Index, float UTiling = 1, float VTiling = 1) { return (float2)0; }
|
||||
float Time() { return (float)0; }
|
||||
float TwoSidedSign() { return (float)0; }
|
||||
float4 VertexColor() { return (float4)0; }
|
||||
float3 VertexNormalWS() { return (float3)0; }
|
||||
float3 VertexTangentWS() { return (float3)0; }
|
||||
// Property is an EMaterialExposedViewProperty token (e.g. MEVP_FieldOfView).
|
||||
float4 ViewProperty(int Property) { return (float4)0; }
|
||||
float3 WorldPosition() { return (float3)0; }
|
||||
}
|
||||
#include "/Plugin/ShaderLab/Private/ShaderLabUENode.ush"
|
||||
#include "/Plugin/ShaderLab/Private/ShaderLabUEFunctions.ush"
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
// /Plugin/ShaderLab/ShaderLabDSL.ush as empty expansions). This parser scans the top-level
|
||||
// constructs — `#include`s, `SL_SETTINGS`, `SL_PROPERTY` + a following HLSL declaration, the
|
||||
// `SL_SURFACE/POSTPROCESS/UI/VERTEX/SLAB/VALUE` markers + their functions, and the standalone
|
||||
// `SL_FRONTMATERIAL/OPACITY/OPACITY_MASK` — and rebuilds the same FShaderLabModel the old DSL did.
|
||||
// Everything not annotated (the authoring-shim `#include`, the macros, the property globals' editor
|
||||
// presence) never reaches the shader compiler: the graph builder only consumes entry-function bodies
|
||||
// and rewrites UE:: calls into expression-node inputs.
|
||||
// `SL_FRONTMATERIAL/OPACITY/OPACITY_MASK` — into an FShaderLabModel.
|
||||
// Only entry-function bodies reach the shader compiler (the graph builder consumes them and rewrites
|
||||
// UE_ calls into expression-node inputs); the authoring-shim `#include`, the macros and the property
|
||||
// globals exist purely for the editor and are dropped here.
|
||||
|
||||
namespace ShaderLabParser_Private
|
||||
{
|
||||
@@ -273,7 +273,10 @@ namespace ShaderLabParser_Private
|
||||
}
|
||||
}
|
||||
|
||||
/** Read verbatim up to a top-level ';' (respecting parens); does not consume the ';'. */
|
||||
/**
|
||||
* Read verbatim up to a top-level ';' (paren depth 0), skipping over string/char literals and
|
||||
* comments so a ';' inside them does not terminate early. Does not consume the ';'.
|
||||
*/
|
||||
bool ReadUntilSemicolon(FString& Out)
|
||||
{
|
||||
FString T;
|
||||
@@ -281,6 +284,32 @@ namespace ShaderLabParser_Private
|
||||
while (!IsEnd())
|
||||
{
|
||||
const TCHAR C = Peek();
|
||||
if (C == TEXT('"') || C == TEXT('\''))
|
||||
{
|
||||
const TCHAR Quote = C;
|
||||
T.AppendChar(Advance());
|
||||
while (!IsEnd() && Peek() != Quote)
|
||||
{
|
||||
const TCHAR S = Advance();
|
||||
T.AppendChar(S);
|
||||
if (S == TEXT('\\') && !IsEnd()) { T.AppendChar(Advance()); }
|
||||
}
|
||||
if (!IsEnd()) { T.AppendChar(Advance()); }
|
||||
continue;
|
||||
}
|
||||
if (C == TEXT('/') && Peek(1) == TEXT('/'))
|
||||
{
|
||||
while (!IsEnd() && Peek() != TEXT('\n')) { T.AppendChar(Advance()); }
|
||||
continue;
|
||||
}
|
||||
if (C == TEXT('/') && Peek(1) == TEXT('*'))
|
||||
{
|
||||
T.AppendChar(Advance());
|
||||
T.AppendChar(Advance());
|
||||
while (!IsEnd() && !(Peek() == TEXT('*') && Peek(1) == TEXT('/'))) { T.AppendChar(Advance()); }
|
||||
if (!IsEnd()) { T.AppendChar(Advance()); T.AppendChar(Advance()); }
|
||||
continue;
|
||||
}
|
||||
if (C == TEXT(';') && Paren == 0)
|
||||
{
|
||||
break;
|
||||
@@ -450,7 +479,9 @@ namespace ShaderLabParser_Private
|
||||
const FString Leaf = FPaths::GetCleanFilename(IncludePath);
|
||||
return Leaf == TEXT("ShaderLab.ush")
|
||||
|| Leaf == TEXT("ShaderLabDSL.ush")
|
||||
|| Leaf == TEXT("ShaderLabCommon.ush");
|
||||
|| Leaf == TEXT("ShaderLabCommon.ush")
|
||||
|| Leaf == TEXT("ShaderLabUENode.ush")
|
||||
|| Leaf == TEXT("ShaderLabUEFunctions.ush");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,8 +571,8 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
/** Apply one `Key = Value` SL_PROPERTY specifier. */
|
||||
void ApplyPropertySpecifier(FScanner& S, const FString& ArgRaw, FShaderLabProperty& Prop)
|
||||
/** Apply one `Key = Value` SL_PROPERTY specifier. Diagnostics are reported at (ErrLine, ErrCol). */
|
||||
void ApplyPropertySpecifier(FScanner& S, const FString& ArgRaw, FShaderLabProperty& Prop, int32 ErrLine, int32 ErrCol)
|
||||
{
|
||||
const FString Arg = ArgRaw.TrimStartAndEnd();
|
||||
if (Arg.IsEmpty())
|
||||
@@ -552,7 +583,7 @@ namespace
|
||||
if (!Arg.Split(TEXT("="), &Key, &Value))
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Property '%s' has malformed specifier '%s' (expected Key = Value)"),
|
||||
*Prop.Name.ToString(), *Arg));
|
||||
*Prop.Name.ToString(), *Arg), ErrLine, ErrCol);
|
||||
return;
|
||||
}
|
||||
Key = Key.TrimStartAndEnd();
|
||||
@@ -562,7 +593,7 @@ namespace
|
||||
{
|
||||
Prop.DisplayName = Value.TrimQuotes();
|
||||
}
|
||||
else if (Key == TEXT("Group"))
|
||||
else if (Key == TEXT("Category"))
|
||||
{
|
||||
Prop.Group = Value.TrimQuotes();
|
||||
}
|
||||
@@ -576,11 +607,17 @@ namespace
|
||||
}
|
||||
else if (Key == TEXT("Range"))
|
||||
{
|
||||
const TArray<FString> Comps = SplitTopLevel(
|
||||
Value.Replace(TEXT("("), TEXT("")).Replace(TEXT(")"), TEXT("")), TEXT(','));
|
||||
// Trim exactly the one wrapping (...) then split — preserves paren-awareness of the interior.
|
||||
if (!Value.StartsWith(TEXT("(")) || !Value.EndsWith(TEXT(")")))
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Property '%s' Range expects (min,max)"), *Prop.Name.ToString()), ErrLine, ErrCol);
|
||||
return;
|
||||
}
|
||||
const FString Inner = Value.Mid(1, Value.Len() - 2);
|
||||
const TArray<FString> Comps = SplitTopLevel(Inner, TEXT(','));
|
||||
if (Comps.Num() != 2)
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Property '%s' Range expects (min,max)"), *Prop.Name.ToString()));
|
||||
S.Error(FString::Printf(TEXT("Property '%s' Range expects (min,max)"), *Prop.Name.ToString()), ErrLine, ErrCol);
|
||||
return;
|
||||
}
|
||||
Prop.bHasRange = true;
|
||||
@@ -589,13 +626,16 @@ namespace
|
||||
}
|
||||
else
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Property '%s' has unknown specifier '%s'"), *Prop.Name.ToString(), *Key));
|
||||
S.Error(FString::Printf(TEXT("Property '%s' has unknown specifier '%s'"), *Prop.Name.ToString(), *Key), ErrLine, ErrCol);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseProperty(FScanner& S, FShaderLabModel& Model)
|
||||
{
|
||||
// SL_PROPERTY( <specifiers> )
|
||||
// SL_PROPERTY( <specifiers> ) — record the '(' position so specifier errors point at the macro.
|
||||
S.SkipTrivia();
|
||||
const int32 SpecLine = S.Line;
|
||||
const int32 SpecCol = S.Column;
|
||||
FString SpecInner;
|
||||
if (!S.ReadBalanced(TEXT('('), TEXT(')'), SpecInner))
|
||||
{
|
||||
@@ -604,6 +644,8 @@ namespace
|
||||
|
||||
FShaderLabProperty Prop;
|
||||
Prop.SortPriority = Model.Properties.Num();
|
||||
int32 NameLine = SpecLine;
|
||||
int32 NameCol = SpecCol;
|
||||
|
||||
// The HLSL declaration following the macro determines name + type + default.
|
||||
S.SkipTrivia();
|
||||
@@ -623,13 +665,20 @@ namespace
|
||||
S.Error(TEXT("StaticBool `#define` is missing a name"));
|
||||
return;
|
||||
}
|
||||
// Remainder of the line is the value (1/0/true/false).
|
||||
NameLine = S.Line;
|
||||
NameCol = S.Column;
|
||||
// Remainder of the line is the value (1/0/true/false), minus any trailing comment.
|
||||
FString ValueLine;
|
||||
while (!S.IsEnd() && S.Peek() != TEXT('\n'))
|
||||
{
|
||||
ValueLine.AppendChar(S.Advance());
|
||||
}
|
||||
ValueLine = ValueLine.TrimStartAndEnd();
|
||||
int32 Cut = ValueLine.Len();
|
||||
const int32 LineComment = ValueLine.Find(TEXT("//"));
|
||||
const int32 BlockComment = ValueLine.Find(TEXT("/*"));
|
||||
if (LineComment != INDEX_NONE) { Cut = FMath::Min(Cut, LineComment); }
|
||||
if (BlockComment != INDEX_NONE) { Cut = FMath::Min(Cut, BlockComment); }
|
||||
ValueLine = ValueLine.Left(Cut).TrimStartAndEnd();
|
||||
Prop.Name = FName(*Name);
|
||||
Prop.Type = EShaderLabPropertyType::StaticBool;
|
||||
if (!ParseBool(ValueLine, Prop.bStaticBoolDefault))
|
||||
@@ -657,6 +706,8 @@ namespace
|
||||
S.Error(FString::Printf(TEXT("Property of type '%s' is missing a name"), *TypeTok));
|
||||
return;
|
||||
}
|
||||
NameLine = S.Line;
|
||||
NameCol = S.Column;
|
||||
Prop.Name = FName(*Name);
|
||||
|
||||
const bool bIsTexture = (Prop.Type == EShaderLabPropertyType::Texture2D || Prop.Type == EShaderLabPropertyType::TextureCube);
|
||||
@@ -717,7 +768,7 @@ namespace
|
||||
// Specifiers (override SortPriority/DisplayName/etc.).
|
||||
for (const FString& Arg : SplitTopLevel(SpecInner, TEXT(',')))
|
||||
{
|
||||
ApplyPropertySpecifier(S, Arg, Prop);
|
||||
ApplyPropertySpecifier(S, Arg, Prop, SpecLine, SpecCol);
|
||||
if (S.bFailed)
|
||||
{
|
||||
return;
|
||||
@@ -729,7 +780,7 @@ namespace
|
||||
}
|
||||
if (Model.FindProperty(Prop.Name) != nullptr)
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Duplicate property name '%s'"), *Prop.Name.ToString()));
|
||||
S.Error(FString::Printf(TEXT("Duplicate property name '%s'"), *Prop.Name.ToString()), NameLine, NameCol);
|
||||
return;
|
||||
}
|
||||
Model.Properties.Add(MoveTemp(Prop));
|
||||
@@ -802,6 +853,8 @@ namespace
|
||||
{
|
||||
return;
|
||||
}
|
||||
S.SkipTrivia();
|
||||
const int32 DeclLine = S.Line, DeclCol = S.Column;
|
||||
FString Name, SigInner, Body;
|
||||
int32 BodyLine = 0;
|
||||
if (!ParseAnnotatedFunction(S, Name, SigInner, Body, BodyLine))
|
||||
@@ -811,11 +864,17 @@ namespace
|
||||
TArray<FShaderLabEntryParam> Params;
|
||||
if (!ParseEntryParams(SigInner, Params) || Params.Num() < 1)
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Slab '%s' must take an (inout FShaderLabSurface) parameter"), *Name));
|
||||
S.Error(FString::Printf(TEXT("Slab '%s' must take an (inout FShaderLabSurface) parameter"), *Name), DeclLine, DeclCol);
|
||||
return;
|
||||
}
|
||||
const FName SlabName(*Name);
|
||||
if (Model.Slabs.ContainsByPredicate([&SlabName](const FShaderLabSlab& E) { return E.Name == SlabName; }))
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Duplicate slab name '%s'"), *Name), DeclLine, DeclCol);
|
||||
return;
|
||||
}
|
||||
FShaderLabSlab Slab;
|
||||
Slab.Name = FName(*Name);
|
||||
Slab.Name = SlabName;
|
||||
Slab.OutParamName = Params.Last().Name;
|
||||
Slab.Body = MoveTemp(Body);
|
||||
Slab.BodyLine = BodyLine;
|
||||
@@ -829,14 +888,22 @@ namespace
|
||||
{
|
||||
return;
|
||||
}
|
||||
S.SkipTrivia();
|
||||
const int32 DeclLine = S.Line, DeclCol = S.Column;
|
||||
FString Name, SigInner, Body;
|
||||
int32 BodyLine = 0;
|
||||
if (!ParseAnnotatedFunction(S, Name, SigInner, Body, BodyLine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const FName ValueName(*Name);
|
||||
if (Model.Values.ContainsByPredicate([&ValueName](const FShaderLabValue& E) { return E.Name == ValueName; }))
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Duplicate value name '%s'"), *Name), DeclLine, DeclCol);
|
||||
return;
|
||||
}
|
||||
FShaderLabValue Value;
|
||||
Value.Name = FName(*Name);
|
||||
Value.Name = ValueName;
|
||||
Value.Body = MoveTemp(Body);
|
||||
Value.BodyLine = BodyLine;
|
||||
Model.Values.Add(MoveTemp(Value));
|
||||
@@ -927,24 +994,30 @@ namespace
|
||||
Node.bHasFactor = true;
|
||||
if (!ParseTopoFactor(S, Node.Factor)) { return INDEX_NONE; }
|
||||
}
|
||||
S.SkipTrivia();
|
||||
if (S.Peek() == TEXT(','))
|
||||
{
|
||||
S.Error(FString::Printf(TEXT("Topology operator '%s' has too many arguments"), *Name));
|
||||
return INDEX_NONE;
|
||||
}
|
||||
if (!S.Expect(TEXT(')'), TEXT("to close topology operator"))) { return INDEX_NONE; }
|
||||
return Model.Topology.Add(Node);
|
||||
}
|
||||
|
||||
void ParseFrontMaterial(FScanner& S, FShaderLabModel& Model)
|
||||
{
|
||||
// SL_FRONTMATERIAL( <topology expression> )
|
||||
FString Inner;
|
||||
if (!S.ReadBalanced(TEXT('('), TEXT(')'), Inner))
|
||||
// SL_FRONTMATERIAL( <topology expression> ) — parsed in place on the main scanner so topology
|
||||
// diagnostics report the real .usl line/column.
|
||||
if (!S.Expect(TEXT('('), TEXT("after SL_FRONTMATERIAL")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
FScanner Sub(Inner, S.Errors);
|
||||
Model.TopologyRoot = ParseTopoExpr(Sub, Model);
|
||||
if (Sub.bFailed)
|
||||
Model.TopologyRoot = ParseTopoExpr(S, Model);
|
||||
if (S.bFailed)
|
||||
{
|
||||
S.bFailed = true;
|
||||
return;
|
||||
}
|
||||
S.Expect(TEXT(')'), TEXT("to close SL_FRONTMATERIAL"));
|
||||
}
|
||||
|
||||
/** SL_OPACITY(Name) / SL_OPACITY_MASK(Name): material-level outputs referencing a Value block. */
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
#include "MaterialExpressionShaderLabParameterAnchor.h"
|
||||
#include "ShaderLabIntrinsicRegistry.h"
|
||||
#include "ShaderLabRuntimeBuilder.h"
|
||||
#include "UObject/Class.h"
|
||||
#include "ShaderLabSettingsApplier.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
|
||||
#define SHADERLAB_COMMON_INCLUDE TEXT("/Plugin/ShaderLab/Private/ShaderLabCommon.ush")
|
||||
#define SHADERLAB_FUNCTIONS_INCLUDE TEXT("/Plugin/ShaderLab/Private/ShaderLabUEFunctions.ush")
|
||||
|
||||
namespace ShaderLabGraph
|
||||
{
|
||||
@@ -238,16 +240,38 @@ namespace ShaderLabGraph
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a body for `UE::Name(args)` intrinsic calls, create the backing expression node for each
|
||||
* Scan a body for `UE_Name(args)` intrinsic calls, create the backing expression node for each
|
||||
* unique (name,args), collect the resulting Custom-node inputs, and rewrite the body so each call
|
||||
* becomes its input variable — space-padded to the original call's length so line/column layout is
|
||||
* preserved (keeps `#line` compile-error mapping accurate). Returns false (and fills OutErrors) on
|
||||
* an unknown intrinsic, a stage/usage violation, or a bad argument.
|
||||
*/
|
||||
/** True if `Arg` is a numeric literal (the only non-enum arg an intrinsic's const config accepts). */
|
||||
static bool IsNumericLiteral(const FString& Arg)
|
||||
{
|
||||
const FString T = Arg.TrimStartAndEnd();
|
||||
if (T.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool bAnyDigit = false;
|
||||
for (int32 i = 0; i < T.Len(); ++i)
|
||||
{
|
||||
const TCHAR C = T[i];
|
||||
if (FChar::IsDigit(C)) { bAnyDigit = true; }
|
||||
else if (C == TEXT('.') || C == TEXT('+') || C == TEXT('-')
|
||||
|| C == TEXT('e') || C == TEXT('E') || C == TEXT('f') || C == TEXT('F')) { /* allowed */ }
|
||||
else { return false; }
|
||||
}
|
||||
return bAnyDigit;
|
||||
}
|
||||
|
||||
static bool EmitIntrinsics(
|
||||
UMaterial& Material,
|
||||
EShaderLabIntrinsicFrequency Stage,
|
||||
FString& InOutBody,
|
||||
int32 BodyLine,
|
||||
const FString& SrcPath,
|
||||
TArray<FIntrinsicWire>& OutWires,
|
||||
TArray<FString>& OutErrors)
|
||||
{
|
||||
@@ -262,16 +286,30 @@ namespace ShaderLabGraph
|
||||
|
||||
auto IsIdent = [](TCHAR C) { return FChar::IsAlnum(C) || C == TEXT('_'); };
|
||||
|
||||
// Format a `path.usl(line,col): error: ` prefix from a body offset, so intrinsic diagnostics map
|
||||
// to the real .usl location and flow through the same clickable/cook-failing path as compile errors.
|
||||
auto Loc = [&Body, BodyLine, &SrcPath](int32 Offset) -> FString
|
||||
{
|
||||
int32 Line = BodyLine;
|
||||
int32 Col = 1;
|
||||
for (int32 p = 0; p < Offset && p < Body.Len(); ++p)
|
||||
{
|
||||
if (Body[p] == TEXT('\n')) { ++Line; Col = 1; }
|
||||
else { ++Col; }
|
||||
}
|
||||
return FString::Printf(TEXT("%s(%d,%d): error: "), *SrcPath, Line, Col);
|
||||
};
|
||||
|
||||
int32 i = 0;
|
||||
while (i < Len)
|
||||
{
|
||||
const bool bBoundary = (i == 0) || !IsIdent(Body[i - 1]);
|
||||
if (bBoundary && i + 4 <= Len &&
|
||||
Body[i] == TEXT('U') && Body[i + 1] == TEXT('E') && Body[i + 2] == TEXT(':') && Body[i + 3] == TEXT(':'))
|
||||
if (bBoundary && i + 3 <= Len &&
|
||||
Body[i] == TEXT('U') && Body[i + 1] == TEXT('E') && Body[i + 2] == TEXT('_'))
|
||||
{
|
||||
int32 j = i + 4;
|
||||
int32 j = i + 3;
|
||||
while (j < Len && IsIdent(Body[j])) { ++j; }
|
||||
const FString Name = Body.Mid(i + 4, j - (i + 4));
|
||||
const FString Name = Body.Mid(i + 3, j - (i + 3));
|
||||
int32 k = j;
|
||||
while (k < Len && FChar::IsWhitespace(Body[k])) { ++k; }
|
||||
if (!Name.IsEmpty() && k < Len && Body[k] == TEXT('('))
|
||||
@@ -288,6 +326,20 @@ namespace ShaderLabGraph
|
||||
{
|
||||
const FString ArgsRaw = Body.Mid(k + 1, m - (k + 1));
|
||||
const int32 CallLen = (m + 1) - i;
|
||||
|
||||
// Only registered node-intrinsics are rewritten into material-expression inputs.
|
||||
// Any other `UE_X(...)` is left for the shader compiler: it's an HLSL library
|
||||
// function (e.g. UE_Noise from ShaderLabFunctions.ush, auto-included) or a typo.
|
||||
// Copy just the `UE_Name` identifier and keep scanning from the '(' — so any
|
||||
// intrinsic nested in the arguments (e.g. UE_Noise(UE_WorldPosition(), ...)) still
|
||||
// gets rewritten.
|
||||
if (!Registry.Find(FName(*Name)))
|
||||
{
|
||||
Result += Body.Mid(i, j - i);
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString ArgSig = MakeArgSig(ArgsRaw);
|
||||
const FString Key = Name + TEXT("|") + ArgSig;
|
||||
|
||||
@@ -299,29 +351,59 @@ namespace ShaderLabGraph
|
||||
else
|
||||
{
|
||||
const FShaderLabIntrinsicDesc* Desc = Registry.Find(FName(*Name));
|
||||
if (!Desc)
|
||||
if (Desc->Frequency == EShaderLabIntrinsicFrequency::PixelOnly && Stage == EShaderLabIntrinsicFrequency::VertexOnly)
|
||||
{
|
||||
OutErrors.Add(FString::Printf(TEXT("Unknown intrinsic 'UE::%s'"), *Name));
|
||||
bOk = false;
|
||||
}
|
||||
else if (Desc->Frequency == EShaderLabIntrinsicFrequency::PixelOnly && Stage == EShaderLabIntrinsicFrequency::VertexOnly)
|
||||
{
|
||||
OutErrors.Add(FString::Printf(TEXT("Intrinsic 'UE::%s' is pixel-only and cannot be used in a Vertex body"), *Name));
|
||||
OutErrors.Add(Loc(i) + FString::Printf(TEXT("intrinsic 'UE_%s' is pixel-only and cannot be used in a Vertex body"), *Name));
|
||||
bOk = false;
|
||||
}
|
||||
else if (Desc->Frequency == EShaderLabIntrinsicFrequency::VertexOnly && Stage == EShaderLabIntrinsicFrequency::PixelOnly)
|
||||
{
|
||||
OutErrors.Add(FString::Printf(TEXT("Intrinsic 'UE::%s' is vertex-only and cannot be used in a pixel body"), *Name));
|
||||
OutErrors.Add(Loc(i) + FString::Printf(TEXT("intrinsic 'UE_%s' is vertex-only and cannot be used in a pixel body"), *Name));
|
||||
bOk = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Usage is per-instance, so intrinsics emit their node with no base-level usage gate.
|
||||
FString MakeError;
|
||||
UMaterialExpression* Expr = Desc->MakeNode(Material, SplitArgs(ArgsRaw), MakeError);
|
||||
if (!Expr)
|
||||
// Config args are baked into the node at graph-build time, so they must be
|
||||
// compile-time constants (numeric literals, or the enum's token names) — a
|
||||
// variable can't configure a node field. Reject non-literals instead of
|
||||
// silently coercing them (e.g. Atoi("myVar") -> 0).
|
||||
const TArray<FString> CallArgs = SplitArgs(ArgsRaw);
|
||||
bool bArgsOk = true;
|
||||
if (CallArgs.Num() > Desc->Params.Num())
|
||||
{
|
||||
OutErrors.Add(FString::Printf(TEXT("Intrinsic 'UE::%s': %s"), *Name,
|
||||
OutErrors.Add(Loc(i) + FString::Printf(TEXT("intrinsic 'UE_%s' takes at most %d argument(s), got %d"),
|
||||
*Name, Desc->Params.Num(), CallArgs.Num()));
|
||||
bArgsOk = false;
|
||||
}
|
||||
for (int32 a = 0; bArgsOk && a < CallArgs.Num(); ++a)
|
||||
{
|
||||
const FShaderLabIntrinsicParam& P = Desc->Params[a];
|
||||
if (P.Enum)
|
||||
{
|
||||
if (P.Enum->GetValueByNameString(CallArgs[a]) == INDEX_NONE)
|
||||
{
|
||||
OutErrors.Add(Loc(i) + FString::Printf(TEXT("intrinsic 'UE_%s' argument '%s' must be a %s token, got '%s'"),
|
||||
*Name, *P.Name, *P.Enum->GetName(), *CallArgs[a]));
|
||||
bArgsOk = false;
|
||||
}
|
||||
}
|
||||
else if (!IsNumericLiteral(CallArgs[a]))
|
||||
{
|
||||
OutErrors.Add(Loc(i) + FString::Printf(TEXT("intrinsic 'UE_%s' argument '%s' must be a compile-time constant, got '%s'"),
|
||||
*Name, *P.Name, *CallArgs[a]));
|
||||
bArgsOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
FString MakeError;
|
||||
UMaterialExpression* Expr = bArgsOk ? Desc->MakeNode(Material, CallArgs, MakeError) : nullptr;
|
||||
if (!bArgsOk)
|
||||
{
|
||||
bOk = false;
|
||||
}
|
||||
else if (!Expr)
|
||||
{
|
||||
OutErrors.Add(Loc(i) + FString::Printf(TEXT("intrinsic 'UE_%s': %s"), *Name,
|
||||
MakeError.IsEmpty() ? TEXT("failed to create node") : *MakeError));
|
||||
bOk = false;
|
||||
}
|
||||
@@ -358,10 +440,13 @@ namespace ShaderLabGraph
|
||||
bool bIsTexture = false;
|
||||
};
|
||||
|
||||
/** Add the shared struct include plus any user `Includes { }` paths to a generated Custom node. */
|
||||
/** Add the shared struct + function-library includes plus any user `Includes { }` paths. */
|
||||
static void AddIncludes(UMaterialExpressionCustom& Custom, const FShaderLabModel& Model)
|
||||
{
|
||||
Custom.IncludeFilePaths.Add(SHADERLAB_COMMON_INCLUDE);
|
||||
// The function library provides the UE_ HLSL helpers (UE_Noise, UE_RotateAboutAxis, ...) that
|
||||
// are left verbatim in the body (not rewritten into nodes).
|
||||
Custom.IncludeFilePaths.Add(SHADERLAB_FUNCTIONS_INCLUDE);
|
||||
for (const FString& Include : Model.Includes)
|
||||
{
|
||||
if (!Include.IsEmpty())
|
||||
@@ -407,7 +492,7 @@ namespace ShaderLabGraph
|
||||
|
||||
FString Body = InBody;
|
||||
TArray<FIntrinsicWire> Wires;
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, Wires, OutErrors))
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, BodyLine, MakeLineDirectivePath(Model.SourceFilePath), Wires, OutErrors))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -499,7 +584,7 @@ namespace ShaderLabGraph
|
||||
|
||||
FString Body = InBody;
|
||||
TArray<FIntrinsicWire> Wires;
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, Wires, OutErrors))
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, BodyLine, MakeLineDirectivePath(Model.SourceFilePath), Wires, OutErrors))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -572,7 +657,7 @@ namespace ShaderLabGraph
|
||||
|
||||
FString Body = Value.Body;
|
||||
TArray<FIntrinsicWire> Wires;
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, Wires, OutErrors))
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::PixelOnly, Body, Value.BodyLine, MakeLineDirectivePath(Model.SourceFilePath), Wires, OutErrors))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -739,7 +824,7 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
return false;
|
||||
}
|
||||
|
||||
// Per-pixel context is read via UE::* intrinsics, so the Surface entry takes just the output
|
||||
// Per-pixel context is read via UE_* intrinsics, so the Surface entry takes just the output
|
||||
// struct: `Surface(inout FShaderLabSurface S)`. For multi-slab there is no Surface param.
|
||||
const FShaderLabEntryParam* SurfaceOutParam = Model.bHasSurface && Model.SurfaceParams.Num() > 0
|
||||
? &Model.SurfaceParams.Last() : nullptr;
|
||||
@@ -986,7 +1071,7 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
if (!ConnectMaterialOutput(EditorOnly->OpacityMask, Model.OpacityMaskValueName, TEXT("OpacityMask"))) { return false; }
|
||||
}
|
||||
|
||||
// 3) Optional Vertex stage. Per-pixel/vertex context is read via UE::* intrinsics, so the entry
|
||||
// 3) Optional Vertex stage. Per-pixel/vertex context is read via UE_* intrinsics, so the entry
|
||||
// takes just the output struct: `Vertex(inout FShaderLabVertex V)`.
|
||||
if (Model.bHasVertex && Model.VertexParams.Num() >= 1)
|
||||
{
|
||||
@@ -1013,7 +1098,7 @@ bool FShaderLabGraphBuilder::BuildInto(UMaterial& Material, const FShaderLabMode
|
||||
// Intrinsics (Stage = vertex).
|
||||
FString VertexBody = Model.VertexBody;
|
||||
TArray<FIntrinsicWire> VtxIntrinsicWires;
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::VertexOnly, VertexBody, VtxIntrinsicWires, OutErrors))
|
||||
if (!EmitIntrinsics(Material, EShaderLabIntrinsicFrequency::VertexOnly, VertexBody, Model.VertexBodyLine, MakeLineDirectivePath(Model.SourceFilePath), VtxIntrinsicWires, OutErrors))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
//
|
||||
// `ShaderLab.IDE.Prepare` console command — generates the IDE authoring assets so a `.usl` file
|
||||
// (which is plain HLSL plus SL_* annotation macros) gets full member/function/include completion
|
||||
// from Rider's UE shader support or VSCode + HLSL Tools. It writes, all from a single source of truth:
|
||||
// 1. <Plugin>/Shaders/ShaderLab.ush — umbrella header: DSL macros + structs + `namespace UE`
|
||||
// intrinsic stubs generated from FShaderLabIntrinsicRegistry.
|
||||
// 2. <Project>/shadertoolsconfig.json — HLSL Tools virtual-directory mappings, dumped from
|
||||
// AllShaderSourceDirectoryMappings() (the same table the
|
||||
// runtime registers). Machine-specific → gitignored.
|
||||
// 3. <Project>/.vscode/settings.json — merges *.usl/*.usf/*.ush -> hlsl into files.associations
|
||||
// (non-destructive: existing keys are preserved).
|
||||
// from Rider's UE shader support or VSCode + the shader-validator extension. It writes:
|
||||
// 1. <Plugin>/Shaders/Private/ShaderLabUENode.ush — the UE_ node-intrinsic stubs (+ their enums)
|
||||
// generated from FShaderLabIntrinsicRegistry. (The static
|
||||
// ShaderLab.ush umbrella includes it; it is IDE-only.)
|
||||
// 2. <Project>/.vscode/settings.json — merges (non-destructively) files.associations
|
||||
// (*.usl/*.usf/*.ush -> hlsl) and shader-validator.pathRemapping
|
||||
// (virtual shader roots -> disk, from AllShaderSourceDirectoryMappings).
|
||||
// pathRemapping is machine-specific; the command regenerates it.
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "UObject/Class.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
@@ -104,8 +105,12 @@ namespace ShaderLabIDEPrepare_Private
|
||||
return Out;
|
||||
}
|
||||
|
||||
/** Build the `namespace UE { ... }` block of stub declarations from the registry (sorted, deterministic). */
|
||||
static FString GenerateIntrinsicNamespace()
|
||||
/**
|
||||
* Build the `UE_Name(...)` stub declarations from the registry (sorted, deterministic). They are
|
||||
* free functions (not a `namespace UE`) because shader-validator's HLSL parser has no namespace
|
||||
* support; the intrinsics are written `UE_Name(...)` in .usl and matched by that prefix.
|
||||
*/
|
||||
static FString GenerateIntrinsicStubs()
|
||||
{
|
||||
TArray<FShaderLabIntrinsicDesc> Descs;
|
||||
FShaderLabIntrinsicRegistry::Get().ForEach([&Descs](const FShaderLabIntrinsicDesc& D) { Descs.Add(D); });
|
||||
@@ -115,27 +120,61 @@ namespace ShaderLabIDEPrepare_Private
|
||||
});
|
||||
|
||||
FString Body;
|
||||
Body += TEXT("namespace UE\n{\n");
|
||||
|
||||
// Emit each reflected enum used by an intrinsic arg once, as a real HLSL enum, so the param is
|
||||
// typed and its tokens complete (`UE_ViewProperty(MEVP_FieldOfView)`). The graph builder resolves
|
||||
// the token by name, so the enum's numeric values are irrelevant.
|
||||
{
|
||||
TSet<FString> EmittedEnums;
|
||||
FString Enums;
|
||||
for (const FShaderLabIntrinsicDesc& D : Descs)
|
||||
{
|
||||
for (const FShaderLabIntrinsicParam& P : D.Params)
|
||||
{
|
||||
if (!P.Enum || EmittedEnums.Contains(P.Enum->GetName()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
EmittedEnums.Add(P.Enum->GetName());
|
||||
Enums += FString::Printf(TEXT("enum %s\n{\n"), *P.Enum->GetName());
|
||||
for (int32 Index = 0; Index < P.Enum->NumEnums(); ++Index)
|
||||
{
|
||||
const FString Token = P.Enum->GetNameStringByIndex(Index);
|
||||
if (!Token.IsEmpty() && !Token.EndsWith(TEXT("_MAX")))
|
||||
{
|
||||
Enums += FString::Printf(TEXT("\t%s,\n"), *Token);
|
||||
}
|
||||
}
|
||||
Enums += TEXT("};\n");
|
||||
}
|
||||
}
|
||||
if (!Enums.IsEmpty())
|
||||
{
|
||||
Body += Enums;
|
||||
Body += TEXT("\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (const FShaderLabIntrinsicDesc& D : Descs)
|
||||
{
|
||||
if (!D.Doc.IsEmpty())
|
||||
{
|
||||
Body += FString::Printf(TEXT("\t// %s\n"), *D.Doc);
|
||||
Body += FString::Printf(TEXT("// %s\n"), *D.Doc);
|
||||
}
|
||||
FString Params;
|
||||
for (int32 i = 0; i < D.Params.Num(); ++i)
|
||||
{
|
||||
const FShaderLabIntrinsicParam& P = D.Params[i];
|
||||
if (i > 0) { Params += TEXT(", "); }
|
||||
Params += FString::Printf(TEXT("%s %s"), *P.Type, *P.Name);
|
||||
const FString ParamType = P.Enum ? P.Enum->GetName() : P.Type;
|
||||
Params += FString::Printf(TEXT("%s %s"), *ParamType, *P.Name);
|
||||
if (!P.DefaultLiteral.IsEmpty()) { Params += FString::Printf(TEXT(" = %s"), *P.DefaultLiteral); }
|
||||
}
|
||||
// Stub body returns a zero of the return type; this is authoring-only and never compiled
|
||||
// (the graph builder rewrites UE:: calls into material-expression inputs before compile).
|
||||
Body += FString::Printf(TEXT("\t%s %s(%s) { return (%s)0; }\n"),
|
||||
// (the graph builder rewrites UE_ calls into material-expression inputs before compile).
|
||||
Body += FString::Printf(TEXT("%s UE_%s(%s) { return (%s)0; }\n"),
|
||||
*D.ReturnType, *D.Name.ToString(), *Params, *D.ReturnType);
|
||||
}
|
||||
Body += TEXT("}\n");
|
||||
return Body;
|
||||
}
|
||||
|
||||
@@ -158,41 +197,41 @@ namespace ShaderLabIDEPrepare_Private
|
||||
return true;
|
||||
}
|
||||
|
||||
static void GenerateShaderLabHeader(const FString& PluginShaderDir)
|
||||
// Generates Private/ShaderLabUENode.ush: the UE_ node-intrinsic stubs (+ their enums), from the
|
||||
// registry. Included by the static ShaderLab.ush umbrella; editor-only (the parser strips the shim).
|
||||
static void GenerateUENodeHeader(const FString& PluginShaderDir)
|
||||
{
|
||||
FString H;
|
||||
H += TEXT("// GENERATED by `ShaderLab.IDE.Prepare` — do not edit by hand.\n");
|
||||
H += TEXT("// Authoring umbrella header: include this from every .usl for IDE completion.\n");
|
||||
H += TEXT("// At real shader-compile time this header is NOT included (the parser strips it);\n");
|
||||
H += TEXT("// the UE:: stubs below are for the editor only.\n");
|
||||
H += TEXT("// UE_ node-intrinsic stubs + their enums, for IDE completion only (never compiled).\n");
|
||||
H += TEXT("#pragma once\n\n");
|
||||
H += TEXT("#include \"/Plugin/ShaderLab/ShaderLabDSL.ush\"\n");
|
||||
H += TEXT("#include \"/Plugin/ShaderLab/Private/ShaderLabCommon.ush\"\n\n");
|
||||
H += GenerateIntrinsicNamespace();
|
||||
WriteIfChanged(FPaths::Combine(PluginShaderDir, TEXT("ShaderLab.ush")), H);
|
||||
H += GenerateIntrinsicStubs();
|
||||
WriteIfChanged(FPaths::Combine(PluginShaderDir, TEXT("Private"), TEXT("ShaderLabUENode.ush")), H);
|
||||
}
|
||||
|
||||
static void GenerateShaderToolsConfig(const FString& ProjectDir)
|
||||
static FString SerializeObject(const TSharedRef<FJsonObject>& Obj)
|
||||
{
|
||||
const TMap<FString, FString>& Mappings = AllShaderSourceDirectoryMappings();
|
||||
|
||||
const TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
||||
Root->SetBoolField(TEXT("root"), true);
|
||||
const TSharedRef<FJsonObject> Virt = MakeShared<FJsonObject>();
|
||||
for (const TPair<FString, FString>& Pair : Mappings)
|
||||
{
|
||||
// Engine mappings come back relative to the engine binaries dir; HLSL Tools needs
|
||||
// absolute (or config-relative) paths, so normalize to full paths.
|
||||
Virt->SetStringField(Pair.Key, FPaths::ConvertRelativePathToFull(Pair.Value));
|
||||
}
|
||||
Root->SetObjectField(TEXT("hlsl.virtualDirectoryMappings"), Virt);
|
||||
|
||||
FString Out;
|
||||
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Out);
|
||||
FJsonSerializer::Serialize(Root, Writer);
|
||||
WriteIfChanged(FPaths::Combine(ProjectDir, TEXT("shadertoolsconfig.json")), Out);
|
||||
FJsonSerializer::Serialize(Obj, Writer);
|
||||
return Out;
|
||||
}
|
||||
|
||||
// Virtual shader roots -> absolute disk paths, for shader-validator.pathRemapping.
|
||||
static TSharedRef<FJsonObject> BuildPathRemapping()
|
||||
{
|
||||
const TSharedRef<FJsonObject> Remap = MakeShared<FJsonObject>();
|
||||
for (const TPair<FString, FString>& Pair : AllShaderSourceDirectoryMappings())
|
||||
{
|
||||
// Engine mappings come back relative to the engine binaries dir; the extension needs
|
||||
// absolute paths, so normalize.
|
||||
Remap->SetStringField(Pair.Key, FPaths::ConvertRelativePathToFull(Pair.Value));
|
||||
}
|
||||
return Remap;
|
||||
}
|
||||
|
||||
// Merge files.associations (*.usl/*.usf/*.ush -> hlsl) and shader-validator.pathRemapping into
|
||||
// .vscode/settings.json, preserving all other keys. Rewrites only when something actually changed.
|
||||
static void MergeVSCodeSettings(const FString& ProjectDir)
|
||||
{
|
||||
const FString SettingsPath = FPaths::Combine(ProjectDir, TEXT(".vscode"), TEXT("settings.json"));
|
||||
@@ -205,7 +244,7 @@ namespace ShaderLabIDEPrepare_Private
|
||||
if (!FJsonSerializer::Deserialize(Reader, Settings) || !Settings.IsValid())
|
||||
{
|
||||
UE_LOG(LogShaderLabIDE, Warning,
|
||||
TEXT(" could not parse %s — add files.associations for *.usl/*.usf/*.ush -> hlsl manually."),
|
||||
TEXT(" could not parse %s — add files.associations and shader-validator.pathRemapping manually."),
|
||||
*SettingsPath);
|
||||
return; // Never clobber an unparseable user file.
|
||||
}
|
||||
@@ -215,51 +254,53 @@ namespace ShaderLabIDEPrepare_Private
|
||||
Settings = MakeShared<FJsonObject>();
|
||||
}
|
||||
|
||||
// Union into files.associations, preserving any existing entries. Only rewrite the file when
|
||||
// we actually add a mapping, so a hand-maintained settings.json (comments, formatting) is left
|
||||
// untouched once the associations are present.
|
||||
const TSharedPtr<FJsonObject>* AssocPtr = nullptr;
|
||||
TSharedPtr<FJsonObject> Assoc = Settings->TryGetObjectField(TEXT("files.associations"), AssocPtr)
|
||||
? *AssocPtr : MakeShared<FJsonObject>();
|
||||
bool bChanged = false;
|
||||
|
||||
// files.associations: union in the ShaderLab extensions.
|
||||
const TSharedPtr<FJsonObject>* AssocPtr = nullptr;
|
||||
const TSharedPtr<FJsonObject> Assoc = Settings->TryGetObjectField(TEXT("files.associations"), AssocPtr)
|
||||
? *AssocPtr : MakeShared<FJsonObject>();
|
||||
for (const TCHAR* Ext : { TEXT("*.usl"), TEXT("*.usf"), TEXT("*.ush") })
|
||||
{
|
||||
FString Existing2;
|
||||
if (!Assoc->TryGetStringField(Ext, Existing2) || Existing2 != TEXT("hlsl"))
|
||||
FString Value;
|
||||
if (!Assoc->TryGetStringField(Ext, Value) || Value != TEXT("hlsl"))
|
||||
{
|
||||
Assoc->SetStringField(Ext, TEXT("hlsl"));
|
||||
bChanged = true;
|
||||
}
|
||||
}
|
||||
Settings->SetObjectField(TEXT("files.associations"), Assoc);
|
||||
|
||||
// shader-validator.pathRemapping: replace with the current (machine-specific) mapping table.
|
||||
const TSharedRef<FJsonObject> Remap = BuildPathRemapping();
|
||||
const TSharedPtr<FJsonObject>* ExistingRemapPtr = nullptr;
|
||||
const bool bHasRemap = Settings->TryGetObjectField(TEXT("shader-validator.pathRemapping"), ExistingRemapPtr);
|
||||
if (!bHasRemap || SerializeObject(ExistingRemapPtr->ToSharedRef()) != SerializeObject(Remap))
|
||||
{
|
||||
Settings->SetObjectField(TEXT("shader-validator.pathRemapping"), Remap);
|
||||
bChanged = true;
|
||||
}
|
||||
|
||||
// shader-validator.defines: SHADERLAB_IDE gates the editor-only engine-function stubs in
|
||||
// ShaderLabFunctions.ush (so its UE_ forwarders resolve in the IDE without dragging in engine
|
||||
// headers). The real shader compiler never sees this define.
|
||||
const TSharedPtr<FJsonObject>* DefinesPtr = nullptr;
|
||||
const TSharedPtr<FJsonObject> Defines = Settings->TryGetObjectField(TEXT("shader-validator.defines"), DefinesPtr)
|
||||
? *DefinesPtr : MakeShared<FJsonObject>();
|
||||
FString DefineValue;
|
||||
if (!Defines->TryGetStringField(TEXT("SHADERLAB_IDE"), DefineValue) || DefineValue != TEXT("1"))
|
||||
{
|
||||
Defines->SetStringField(TEXT("SHADERLAB_IDE"), TEXT("1"));
|
||||
Settings->SetObjectField(TEXT("shader-validator.defines"), Defines);
|
||||
bChanged = true;
|
||||
}
|
||||
|
||||
if (!bChanged)
|
||||
{
|
||||
UE_LOG(LogShaderLabIDE, Log, TEXT(" unchanged: %s"), *SettingsPath);
|
||||
return;
|
||||
}
|
||||
Settings->SetObjectField(TEXT("files.associations"), Assoc);
|
||||
|
||||
FString Out;
|
||||
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Out);
|
||||
FJsonSerializer::Serialize(Settings.ToSharedRef(), Writer);
|
||||
WriteIfChanged(SettingsPath, Out);
|
||||
}
|
||||
|
||||
static void EnsureGitIgnore(const FString& ProjectDir)
|
||||
{
|
||||
const FString Path = FPaths::Combine(ProjectDir, TEXT(".gitignore"));
|
||||
FString GI;
|
||||
FFileHelper::LoadFileToString(GI, *Path); // missing file -> empty, fine
|
||||
if (GI.Contains(TEXT("shadertoolsconfig.json")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!GI.IsEmpty() && !GI.EndsWith(TEXT("\n")))
|
||||
{
|
||||
GI += TEXT("\n");
|
||||
}
|
||||
GI += TEXT("# Machine-specific HLSL Tools config (generated by ShaderLab.IDE.Prepare).\n");
|
||||
GI += TEXT("shadertoolsconfig.json\n");
|
||||
WriteIfChanged(Path, GI);
|
||||
WriteIfChanged(SettingsPath, SerializeObject(Settings.ToSharedRef()));
|
||||
}
|
||||
|
||||
static void Run(const TArray<FString>& /*Args*/)
|
||||
@@ -271,10 +312,8 @@ namespace ShaderLabIDEPrepare_Private
|
||||
const FString ProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
|
||||
|
||||
UE_LOG(LogShaderLabIDE, Log, TEXT("ShaderLab.IDE.Prepare:"));
|
||||
GenerateShaderLabHeader(PluginShaderDir);
|
||||
GenerateShaderToolsConfig(ProjectDir);
|
||||
GenerateUENodeHeader(PluginShaderDir);
|
||||
MergeVSCodeSettings(ProjectDir);
|
||||
EnsureGitIgnore(ProjectDir);
|
||||
UE_LOG(LogShaderLabIDE, Log, TEXT("ShaderLab.IDE.Prepare: done."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,50 +2,23 @@
|
||||
|
||||
#include "ShaderLabIntrinsicRegistry.h"
|
||||
|
||||
#include "Materials/Material.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
// Backing expression node types for the v1 builtin intrinsics.
|
||||
#include "Materials/MaterialExpressionActorPositionWS.h"
|
||||
#include "Materials/MaterialExpressionCameraPositionWS.h"
|
||||
#include "Materials/MaterialExpressionCameraVectorWS.h"
|
||||
#include "Materials/MaterialExpressionDeltaTime.h"
|
||||
#include "Materials/MaterialExpressionLightVector.h"
|
||||
#include "Materials/MaterialExpressionLightmapUVs.h"
|
||||
#include "Materials/MaterialExpressionLocalPosition.h"
|
||||
#include "Materials/MaterialExpressionObjectBounds.h"
|
||||
#include "Materials/MaterialExpressionObjectLocalBounds.h"
|
||||
#include "Materials/MaterialExpressionObjectOrientation.h"
|
||||
#include "Materials/MaterialExpressionObjectPositionWS.h"
|
||||
#include "Materials/MaterialExpressionObjectRadius.h"
|
||||
#include "Materials/MaterialExpressionParticleColor.h"
|
||||
#include "Materials/MaterialExpressionPerInstanceCustomData.h"
|
||||
#include "Materials/MaterialExpressionPerInstanceFadeAmount.h"
|
||||
#include "Materials/MaterialExpressionPerInstanceRandom.h"
|
||||
#include "Materials/MaterialExpressionPixelDepth.h"
|
||||
#include "Materials/MaterialExpressionPixelNormalWS.h"
|
||||
#include "Materials/MaterialExpressionPreSkinnedLocalBounds.h"
|
||||
#include "Materials/MaterialExpressionPreSkinnedNormal.h"
|
||||
#include "Materials/MaterialExpressionPreSkinnedPosition.h"
|
||||
#include "Materials/MaterialExpressionScreenPosition.h"
|
||||
#include "Materials/MaterialExpressionTextureCoordinate.h"
|
||||
#include "Materials/MaterialExpressionTime.h"
|
||||
#include "Materials/MaterialExpressionTwoSidedSign.h"
|
||||
#include "Materials/MaterialExpressionVertexColor.h"
|
||||
#include "Materials/MaterialExpressionVertexNormalWS.h"
|
||||
#include "Materials/MaterialExpressionVertexTangentWS.h"
|
||||
#include "Materials/MaterialExpressionViewProperty.h"
|
||||
#include "Materials/MaterialExpressionWorldPosition.h"
|
||||
|
||||
namespace ShaderLabIntrinsic_Private
|
||||
void ShaderLabIntrinsic_Private::AddSimple(
|
||||
FShaderLabIntrinsicRegistry& Registry,
|
||||
const TCHAR* Name,
|
||||
EShaderLabIntrinsicFrequency Frequency,
|
||||
const TCHAR* ReturnType,
|
||||
TFunction<UMaterialExpression*(UMaterial&)> Make,
|
||||
int32 OutputIndex)
|
||||
{
|
||||
template <typename T>
|
||||
UMaterialExpression* MakeSimple(UMaterial& Material)
|
||||
{
|
||||
T* Expr = NewObject<T>(&Material);
|
||||
Material.GetExpressionCollection().AddExpression(Expr);
|
||||
return Expr;
|
||||
}
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(Name);
|
||||
Desc.Frequency = Frequency;
|
||||
Desc.ReturnType = ReturnType;
|
||||
Desc.OutputIndex = OutputIndex;
|
||||
Desc.MakeNode = [Make = MoveTemp(Make)](UMaterial& M, const TArray<FString>&, FString&) { return Make(M); };
|
||||
Registry.Register(MoveTemp(Desc));
|
||||
}
|
||||
|
||||
FShaderLabIntrinsicRegistry& FShaderLabIntrinsicRegistry::Get()
|
||||
@@ -83,130 +56,12 @@ void FShaderLabIntrinsicRegistry::ForEach(TFunctionRef<void(const FShaderLabIntr
|
||||
|
||||
void FShaderLabIntrinsicRegistry::RegisterBuiltins()
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
// Helper for the common no-arg case (ReturnType drives the generated authoring stub).
|
||||
auto AddSimple = [this](const TCHAR* Name, EFreq Freq, const TCHAR* ReturnType, TFunction<UMaterialExpression*(UMaterial&)> Make)
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(Name);
|
||||
Desc.Frequency = Freq;
|
||||
Desc.ReturnType = ReturnType;
|
||||
Desc.MakeNode = [Make = MoveTemp(Make)](UMaterial& M, const TArray<FString>&, FString&) { return Make(M); };
|
||||
Register(MoveTemp(Desc));
|
||||
};
|
||||
|
||||
// --- A. Mesh / surface attributes ---
|
||||
AddSimple(TEXT("VertexNormalWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionVertexNormalWS>(M); });
|
||||
AddSimple(TEXT("VertexTangentWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionVertexTangentWS>(M); });
|
||||
AddSimple(TEXT("PixelNormalWS"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPixelNormalWS>(M); });
|
||||
AddSimple(TEXT("VertexColor"), EFreq::Any, TEXT("float4"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionVertexColor>(M); });
|
||||
AddSimple(TEXT("TwoSidedSign"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionTwoSidedSign>(M); });
|
||||
AddSimple(TEXT("LightmapUVs"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionLightmapUVs>(M); });
|
||||
AddSimple(TEXT("PreSkinnedNormal"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPreSkinnedNormal>(M); });
|
||||
AddSimple(TEXT("PreSkinnedPosition"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPreSkinnedPosition>(M); });
|
||||
|
||||
// TextureCoordinate(Index[, UTiling, VTiling]) — const args configure the node (mechanism: scalar/int args).
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("TextureCoordinate"));
|
||||
Desc.ReturnType = TEXT("float2");
|
||||
Desc.Params = { { TEXT("int"), TEXT("Index"), TEXT("") },
|
||||
{ TEXT("float"), TEXT("UTiling"), TEXT("1") },
|
||||
{ TEXT("float"), TEXT("VTiling"), TEXT("1") } };
|
||||
Desc.Doc = TEXT("UV channel `Index`, optionally tiled.");
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString&) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionTextureCoordinate* E = NewObject<UMaterialExpressionTextureCoordinate>(&M);
|
||||
if (Args.Num() >= 1) { E->CoordinateIndex = FCString::Atoi(*Args[0]); }
|
||||
if (Args.Num() >= 2) { E->UTiling = FCString::Atof(*Args[1]); }
|
||||
if (Args.Num() >= 3) { E->VTiling = FCString::Atof(*Args[2]); }
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Register(MoveTemp(Desc));
|
||||
}
|
||||
|
||||
// --- B. Positions / spaces ---
|
||||
AddSimple(TEXT("WorldPosition"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionWorldPosition>(M); });
|
||||
AddSimple(TEXT("LocalPosition"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionLocalPosition>(M); });
|
||||
AddSimple(TEXT("ActorPositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionActorPositionWS>(M); });
|
||||
AddSimple(TEXT("ObjectPositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectPositionWS>(M); });
|
||||
AddSimple(TEXT("CameraPositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionCameraPositionWS>(M); });
|
||||
AddSimple(TEXT("CameraVectorWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionCameraVectorWS>(M); });
|
||||
AddSimple(TEXT("LightVector"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionLightVector>(M); });
|
||||
AddSimple(TEXT("PixelDepth"), EFreq::PixelOnly, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPixelDepth>(M); });
|
||||
AddSimple(TEXT("ScreenPosition"), EFreq::PixelOnly, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionScreenPosition>(M); });
|
||||
|
||||
// --- C. Object / bounds (ObjectLocalBounds & PreSkinnedLocalBounds are multi-output; output 0) ---
|
||||
AddSimple(TEXT("ObjectRadius"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectRadius>(M); });
|
||||
AddSimple(TEXT("ObjectBounds"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectBounds>(M); });
|
||||
AddSimple(TEXT("ObjectLocalBounds"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectLocalBounds>(M); });
|
||||
AddSimple(TEXT("ObjectOrientation"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectOrientation>(M); });
|
||||
AddSimple(TEXT("PreSkinnedLocalBounds"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPreSkinnedLocalBounds>(M); });
|
||||
|
||||
// --- D. Instancing ---
|
||||
AddSimple(TEXT("PerInstanceRandom"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPerInstanceRandom>(M); });
|
||||
AddSimple(TEXT("PerInstanceFadeAmount"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPerInstanceFadeAmount>(M); });
|
||||
|
||||
// PerInstanceCustomData(DataIndex[, ConstDefaultValue]) — mechanism: const args + optional input pin left at default.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("PerInstanceCustomData"));
|
||||
Desc.ReturnType = TEXT("float");
|
||||
Desc.Params = { { TEXT("int"), TEXT("DataIndex"), TEXT("") },
|
||||
{ TEXT("float"), TEXT("ConstDefaultValue"), TEXT("0") } };
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString&) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionPerInstanceCustomData* E = NewObject<UMaterialExpressionPerInstanceCustomData>(&M);
|
||||
if (Args.Num() >= 1) { E->DataIndex = static_cast<uint32>(FCString::Atoi(*Args[0])); }
|
||||
if (Args.Num() >= 2) { E->ConstDefaultValue = FCString::Atof(*Args[1]); }
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Register(MoveTemp(Desc));
|
||||
}
|
||||
|
||||
// --- Time ---
|
||||
AddSimple(TEXT("Time"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionTime>(M); });
|
||||
AddSimple(TEXT("DeltaTime"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDeltaTime>(M); });
|
||||
|
||||
// ViewProperty(Property) — representative of the enum-by-name const-arg mechanism.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("ViewProperty"));
|
||||
Desc.ReturnType = TEXT("float4");
|
||||
Desc.Params = { { TEXT("int"), TEXT("Property"), TEXT("") } };
|
||||
Desc.Doc = TEXT("Property is an EMaterialExposedViewProperty token (e.g. MEVP_FieldOfView).");
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString& OutError) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionViewProperty* E = NewObject<UMaterialExpressionViewProperty>(&M);
|
||||
if (Args.Num() >= 1)
|
||||
{
|
||||
UEnum* Enum = StaticEnum<EMaterialExposedViewProperty>();
|
||||
check(Enum);
|
||||
const int64 Value = Enum->GetValueByNameString(Args[0]);
|
||||
if (Value == INDEX_NONE)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("unknown view property '%s'"), *Args[0]);
|
||||
return nullptr;
|
||||
}
|
||||
E->Property = static_cast<EMaterialExposedViewProperty>(Value);
|
||||
}
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Register(MoveTemp(Desc));
|
||||
}
|
||||
|
||||
// ParticleColor — only meaningful on particle vertex factories; the instance enables the matching
|
||||
// usage, so there is no base-level usage gate here.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("ParticleColor"));
|
||||
Desc.ReturnType = TEXT("float4");
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>&, FString&) { return MakeSimple<UMaterialExpressionParticleColor>(M); };
|
||||
Register(MoveTemp(Desc));
|
||||
}
|
||||
RegisterMeshIntrinsics(*this);
|
||||
RegisterViewIntrinsics(*this);
|
||||
RegisterObjectIntrinsics(*this);
|
||||
RegisterInstancingIntrinsics(*this);
|
||||
RegisterTimeIntrinsics(*this);
|
||||
RegisterParticleIntrinsics(*this);
|
||||
RegisterDecalIntrinsics(*this);
|
||||
RegisterAtmosphereIntrinsics(*this);
|
||||
}
|
||||
|
||||
46
Source/UShaderLabEditor/Private/ShaderLabIntrinsics.h
Normal file
46
Source/UShaderLabEditor/Private/ShaderLabIntrinsics.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Internal shared helpers + per-category registration entry points for the builtin `UE_` intrinsics.
|
||||
// Registration is split by category (mesh/view/object/instancing/time/particle/decal/atmosphere) into
|
||||
// separate .cpp files for maintainability; each defines one RegisterXxxIntrinsics(registry) that the
|
||||
// registry's RegisterBuiltins() calls.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "ShaderLabIntrinsicRegistry.h"
|
||||
|
||||
namespace ShaderLabIntrinsic_Private
|
||||
{
|
||||
/** NewObject a no-config expression node and add it to the material's collection. */
|
||||
template <typename T>
|
||||
UMaterialExpression* MakeSimple(UMaterial& Material)
|
||||
{
|
||||
T* Expr = NewObject<T>(&Material);
|
||||
Material.GetExpressionCollection().AddExpression(Expr);
|
||||
return Expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a no-arg intrinsic that maps to a source node. `OutputIndex` selects which output pin to
|
||||
* connect (non-zero for one facet of a multi-output node, e.g. a directional light's Direction).
|
||||
*/
|
||||
void AddSimple(
|
||||
FShaderLabIntrinsicRegistry& Registry,
|
||||
const TCHAR* Name,
|
||||
EShaderLabIntrinsicFrequency Frequency,
|
||||
const TCHAR* ReturnType,
|
||||
TFunction<UMaterialExpression*(UMaterial&)> Make,
|
||||
int32 OutputIndex = 0);
|
||||
}
|
||||
|
||||
// Per-category registration (defined in ShaderLabIntrinsics_<Category>.cpp).
|
||||
void RegisterMeshIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterViewIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterObjectIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterInstancingIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterTimeIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterParticleIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterDecalIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
void RegisterAtmosphereIntrinsics(FShaderLabIntrinsicRegistry& Registry);
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Atmosphere / sky-light intrinsics.
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionAtmosphericLightColor.h"
|
||||
#include "Materials/MaterialExpressionAtmosphericLightVector.h"
|
||||
#include "Materials/MaterialExpressionSkyAtmosphereLightDirection.h"
|
||||
|
||||
void RegisterAtmosphereIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("AtmosphericLightVector"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionAtmosphericLightVector>(M); });
|
||||
AddSimple(Registry, TEXT("AtmosphericLightColor"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionAtmosphericLightColor>(M); });
|
||||
|
||||
// SkyAtmosphereLightDirection(LightIndex) — const int selects the atmosphere light.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("SkyAtmosphereLightDirection"));
|
||||
Desc.ReturnType = TEXT("float3");
|
||||
Desc.Params = { { TEXT("int"), TEXT("LightIndex"), TEXT("0") } };
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString&) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionSkyAtmosphereLightDirection* E = NewObject<UMaterialExpressionSkyAtmosphereLightDirection>(&M);
|
||||
if (Args.Num() >= 1) { E->LightIndex = FCString::Atoi(*Args[0]); }
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Registry.Register(MoveTemp(Desc));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Decal intrinsics (meaningful in the Decal domain; the instance/domain gates usage).
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionDecalColor.h"
|
||||
#include "Materials/MaterialExpressionDecalDerivative.h"
|
||||
#include "Materials/MaterialExpressionDecalLifetimeOpacity.h"
|
||||
|
||||
void RegisterDecalIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
// DecalColor's default output pin is RGB (float3).
|
||||
AddSimple(Registry, TEXT("DecalColor"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDecalColor>(M); });
|
||||
AddSimple(Registry, TEXT("DecalLifetimeOpacity"), EFreq::PixelOnly, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDecalLifetimeOpacity>(M); });
|
||||
|
||||
// DecalDerivative is multi-output (DDX, DDY) — one intrinsic per output pin.
|
||||
AddSimple(Registry, TEXT("DecalDerivativeDDX"), EFreq::PixelOnly, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDecalDerivative>(M); }, 0);
|
||||
AddSimple(Registry, TEXT("DecalDerivativeDDY"), EFreq::PixelOnly, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDecalDerivative>(M); }, 1);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Per-instance intrinsics.
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionPerInstanceCustomData.h"
|
||||
#include "Materials/MaterialExpressionPerInstanceFadeAmount.h"
|
||||
#include "Materials/MaterialExpressionPerInstanceRandom.h"
|
||||
|
||||
void RegisterInstancingIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("PerInstanceRandom"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPerInstanceRandom>(M); });
|
||||
AddSimple(Registry, TEXT("PerInstanceFadeAmount"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPerInstanceFadeAmount>(M); });
|
||||
|
||||
// PerInstanceCustomData(DataIndex[, ConstDefaultValue]) — const args + optional input pin at default.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("PerInstanceCustomData"));
|
||||
Desc.ReturnType = TEXT("float");
|
||||
Desc.Params = { { TEXT("int"), TEXT("DataIndex"), TEXT("") },
|
||||
{ TEXT("float"), TEXT("ConstDefaultValue"), TEXT("0") } };
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString&) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionPerInstanceCustomData* E = NewObject<UMaterialExpressionPerInstanceCustomData>(&M);
|
||||
if (Args.Num() >= 1) { E->DataIndex = static_cast<uint32>(FCString::Atoi(*Args[0])); }
|
||||
if (Args.Num() >= 2) { E->ConstDefaultValue = FCString::Atof(*Args[1]); }
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Registry.Register(MoveTemp(Desc));
|
||||
}
|
||||
}
|
||||
51
Source/UShaderLabEditor/Private/ShaderLabIntrinsics_Mesh.cpp
Normal file
51
Source/UShaderLabEditor/Private/ShaderLabIntrinsics_Mesh.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Mesh / surface attribute intrinsics (vertex normals/tangents, colors, UVs).
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionLightmapUVs.h"
|
||||
#include "Materials/MaterialExpressionPixelNormalWS.h"
|
||||
#include "Materials/MaterialExpressionPreSkinnedNormal.h"
|
||||
#include "Materials/MaterialExpressionPreSkinnedPosition.h"
|
||||
#include "Materials/MaterialExpressionTextureCoordinate.h"
|
||||
#include "Materials/MaterialExpressionTwoSidedSign.h"
|
||||
#include "Materials/MaterialExpressionVertexColor.h"
|
||||
#include "Materials/MaterialExpressionVertexNormalWS.h"
|
||||
#include "Materials/MaterialExpressionVertexTangentWS.h"
|
||||
|
||||
void RegisterMeshIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("VertexNormalWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionVertexNormalWS>(M); });
|
||||
AddSimple(Registry, TEXT("VertexTangentWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionVertexTangentWS>(M); });
|
||||
AddSimple(Registry, TEXT("PixelNormalWS"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPixelNormalWS>(M); });
|
||||
AddSimple(Registry, TEXT("VertexColor"), EFreq::Any, TEXT("float4"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionVertexColor>(M); });
|
||||
AddSimple(Registry, TEXT("TwoSidedSign"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionTwoSidedSign>(M); });
|
||||
AddSimple(Registry, TEXT("LightmapUVs"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionLightmapUVs>(M); });
|
||||
AddSimple(Registry, TEXT("PreSkinnedNormal"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPreSkinnedNormal>(M); });
|
||||
AddSimple(Registry, TEXT("PreSkinnedPosition"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPreSkinnedPosition>(M); });
|
||||
|
||||
// TextureCoordinate(Index[, UTiling, VTiling]) — const args configure the node.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("TextureCoordinate"));
|
||||
Desc.ReturnType = TEXT("float2");
|
||||
Desc.Params = { { TEXT("int"), TEXT("Index"), TEXT("") },
|
||||
{ TEXT("float"), TEXT("UTiling"), TEXT("1") },
|
||||
{ TEXT("float"), TEXT("VTiling"), TEXT("1") } };
|
||||
Desc.Doc = TEXT("UV channel `Index`, optionally tiled.");
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString&) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionTextureCoordinate* E = NewObject<UMaterialExpressionTextureCoordinate>(&M);
|
||||
if (Args.Num() >= 1) { E->CoordinateIndex = FCString::Atoi(*Args[0]); }
|
||||
if (Args.Num() >= 2) { E->UTiling = FCString::Atof(*Args[1]); }
|
||||
if (Args.Num() >= 3) { E->VTiling = FCString::Atof(*Args[2]); }
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Registry.Register(MoveTemp(Desc));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Object / primitive / bounds intrinsics.
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionActorPositionWS.h"
|
||||
#include "Materials/MaterialExpressionBounds.h"
|
||||
#include "Materials/MaterialExpressionDistanceCullFade.h"
|
||||
#include "Materials/MaterialExpressionLocalPosition.h"
|
||||
#include "Materials/MaterialExpressionObjectBounds.h"
|
||||
#include "Materials/MaterialExpressionObjectLocalBounds.h"
|
||||
#include "Materials/MaterialExpressionObjectOrientation.h"
|
||||
#include "Materials/MaterialExpressionObjectPositionWS.h"
|
||||
#include "Materials/MaterialExpressionObjectRadius.h"
|
||||
#include "Materials/MaterialExpressionPrecomputedAOMask.h"
|
||||
#include "Materials/MaterialExpressionPreSkinnedLocalBounds.h"
|
||||
|
||||
void RegisterObjectIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("LocalPosition"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionLocalPosition>(M); });
|
||||
AddSimple(Registry, TEXT("ActorPositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionActorPositionWS>(M); });
|
||||
AddSimple(Registry, TEXT("ObjectPositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectPositionWS>(M); });
|
||||
AddSimple(Registry, TEXT("ObjectRadius"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectRadius>(M); });
|
||||
AddSimple(Registry, TEXT("ObjectBounds"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectBounds>(M); });
|
||||
// Multi-output nodes: connect output 0 (matches existing behavior).
|
||||
AddSimple(Registry, TEXT("ObjectLocalBounds"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectLocalBounds>(M); });
|
||||
AddSimple(Registry, TEXT("ObjectOrientation"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionObjectOrientation>(M); });
|
||||
AddSimple(Registry, TEXT("PreSkinnedLocalBounds"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPreSkinnedLocalBounds>(M); });
|
||||
AddSimple(Registry, TEXT("DistanceCullFade"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDistanceCullFade>(M); });
|
||||
AddSimple(Registry, TEXT("PrecomputedAOMask"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPrecomputedAOMask>(M); });
|
||||
|
||||
// Bounds(Space) — unified bounds node; Space is an EMaterialExpressionBoundsType token (MEILB_*).
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("Bounds"));
|
||||
Desc.ReturnType = TEXT("float3");
|
||||
Desc.Doc = TEXT("Space is an EMaterialExpressionBoundsType token (e.g. MEILB_ObjectLocal).");
|
||||
|
||||
FShaderLabIntrinsicParam SpaceParam;
|
||||
SpaceParam.Name = TEXT("Space");
|
||||
SpaceParam.Enum = StaticEnum<EMaterialExpressionBoundsType>();
|
||||
check(SpaceParam.Enum);
|
||||
Desc.Params = { MoveTemp(SpaceParam) };
|
||||
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString& OutError) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionBounds* E = NewObject<UMaterialExpressionBounds>(&M);
|
||||
if (Args.Num() >= 1)
|
||||
{
|
||||
UEnum* Enum = StaticEnum<EMaterialExpressionBoundsType>();
|
||||
check(Enum);
|
||||
const int64 Value = Enum->GetValueByNameString(Args[0]);
|
||||
if (Value == INDEX_NONE)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("unknown bounds space '%s'"), *Args[0]);
|
||||
return nullptr;
|
||||
}
|
||||
E->Type = static_cast<EMaterialExpressionBoundsType>(Value);
|
||||
}
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Registry.Register(MoveTemp(Desc));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Particle / sprite intrinsics. These only produce meaningful values on particle vertex factories;
|
||||
// the instance enables the matching usage, so there is no base-level usage gate here.
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionParticleColor.h"
|
||||
#include "Materials/MaterialExpressionParticleDirection.h"
|
||||
#include "Materials/MaterialExpressionParticleMacroUV.h"
|
||||
#include "Materials/MaterialExpressionParticleMotionBlurFade.h"
|
||||
#include "Materials/MaterialExpressionParticlePositionWS.h"
|
||||
#include "Materials/MaterialExpressionParticleRadius.h"
|
||||
#include "Materials/MaterialExpressionParticleRandom.h"
|
||||
#include "Materials/MaterialExpressionParticleRelativeTime.h"
|
||||
#include "Materials/MaterialExpressionParticleSize.h"
|
||||
#include "Materials/MaterialExpressionParticleSpeed.h"
|
||||
#include "Materials/MaterialExpressionParticleSpriteRotation.h"
|
||||
#include "Materials/MaterialExpressionParticleSubUVProperties.h"
|
||||
#include "Materials/MaterialExpressionSphericalParticleOpacity.h"
|
||||
|
||||
void RegisterParticleIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("ParticleColor"), EFreq::Any, TEXT("float4"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleColor>(M); });
|
||||
AddSimple(Registry, TEXT("ParticlePositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticlePositionWS>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleDirection"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleDirection>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleSpeed"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleSpeed>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleRadius"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleRadius>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleSize"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleSize>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleRelativeTime"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleRelativeTime>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleMotionBlurFade"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleMotionBlurFade>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleRandom"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleRandom>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleSpriteRotation"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleSpriteRotation>(M); });
|
||||
AddSimple(Registry, TEXT("ParticleMacroUV"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleMacroUV>(M); });
|
||||
AddSimple(Registry, TEXT("SphericalParticleOpacity"), EFreq::PixelOnly, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionSphericalParticleOpacity>(M); });
|
||||
|
||||
// ParticleSubUVProperties is multi-output (TexCoord0, TexCoord1, Blend) — one intrinsic per pin.
|
||||
AddSimple(Registry, TEXT("ParticleSubUVTexCoord0"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleSubUVProperties>(M); }, 0);
|
||||
AddSimple(Registry, TEXT("ParticleSubUVTexCoord1"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleSubUVProperties>(M); }, 1);
|
||||
AddSimple(Registry, TEXT("ParticleSubUVBlend"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionParticleSubUVProperties>(M); }, 2);
|
||||
}
|
||||
17
Source/UShaderLabEditor/Private/ShaderLabIntrinsics_Time.cpp
Normal file
17
Source/UShaderLabEditor/Private/ShaderLabIntrinsics_Time.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// Time intrinsics.
|
||||
|
||||
#include "ShaderLabIntrinsics.h"
|
||||
|
||||
#include "Materials/MaterialExpressionDeltaTime.h"
|
||||
#include "Materials/MaterialExpressionTime.h"
|
||||
|
||||
void RegisterTimeIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("Time"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionTime>(M); });
|
||||
AddSimple(Registry, TEXT("DeltaTime"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionDeltaTime>(M); });
|
||||
}
|
||||
79
Source/UShaderLabEditor/Private/ShaderLabIntrinsics_View.cpp
Normal file
79
Source/UShaderLabEditor/Private/ShaderLabIntrinsics_View.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright FlecsProj. All Rights Reserved.
|
||||
//
|
||||
// View / camera / screen intrinsics.
|
||||
|
||||
#include "ShaderLabIntrinsics.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"
|
||||
#include "Materials/MaterialExpressionPixelDepth.h"
|
||||
#include "Materials/MaterialExpressionReflectionVectorWS.h"
|
||||
#include "Materials/MaterialExpressionSceneTexelSize.h"
|
||||
#include "Materials/MaterialExpressionScreenPosition.h"
|
||||
#include "Materials/MaterialExpressionViewProperty.h"
|
||||
#include "Materials/MaterialExpressionViewSize.h"
|
||||
#include "Materials/MaterialExpressionWorldPosition.h"
|
||||
|
||||
void RegisterViewIntrinsics(FShaderLabIntrinsicRegistry& Registry)
|
||||
{
|
||||
using namespace ShaderLabIntrinsic_Private;
|
||||
using EFreq = EShaderLabIntrinsicFrequency;
|
||||
|
||||
AddSimple(Registry, TEXT("WorldPosition"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionWorldPosition>(M); });
|
||||
AddSimple(Registry, TEXT("CameraPositionWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionCameraPositionWS>(M); });
|
||||
AddSimple(Registry, TEXT("CameraVectorWS"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionCameraVectorWS>(M); });
|
||||
AddSimple(Registry, TEXT("LightVector"), EFreq::Any, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionLightVector>(M); });
|
||||
AddSimple(Registry, TEXT("PixelDepth"), EFreq::PixelOnly, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionPixelDepth>(M); });
|
||||
AddSimple(Registry, TEXT("ScreenPosition"), EFreq::PixelOnly, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionScreenPosition>(M); });
|
||||
AddSimple(Registry, TEXT("ReflectionVectorWS"), EFreq::PixelOnly, TEXT("float3"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionReflectionVectorWS>(M); });
|
||||
AddSimple(Registry, TEXT("ViewSize"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionViewSize>(M); });
|
||||
AddSimple(Registry, TEXT("SceneTexelSize"), EFreq::Any, TEXT("float2"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionSceneTexelSize>(M); });
|
||||
AddSimple(Registry, TEXT("IsOrthographic"), EFreq::Any, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionIsOrthographic>(M); });
|
||||
AddSimple(Registry, TEXT("EyeAdaptation"), EFreq::PixelOnly, TEXT("float"), [](UMaterial& M) { return MakeSimple<UMaterialExpressionEyeAdaptation>(M); });
|
||||
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.
|
||||
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);
|
||||
|
||||
// ViewProperty(Property) — Property is an EMaterialExposedViewProperty token.
|
||||
{
|
||||
FShaderLabIntrinsicDesc Desc;
|
||||
Desc.Name = FName(TEXT("ViewProperty"));
|
||||
Desc.ReturnType = TEXT("float4");
|
||||
Desc.Doc = TEXT("Property is an EMaterialExposedViewProperty token (e.g. MEVP_FieldOfView).");
|
||||
|
||||
FShaderLabIntrinsicParam PropertyParam;
|
||||
PropertyParam.Name = TEXT("Property");
|
||||
// Safe: UShaderLabEditor depends on Engine, whose reflected enums register before any editor
|
||||
// code runs, and this executes lazily at first registry use (build/command/test time).
|
||||
PropertyParam.Enum = StaticEnum<EMaterialExposedViewProperty>();
|
||||
check(PropertyParam.Enum);
|
||||
Desc.Params = { MoveTemp(PropertyParam) };
|
||||
|
||||
Desc.MakeNode = [](UMaterial& M, const TArray<FString>& Args, FString& OutError) -> UMaterialExpression*
|
||||
{
|
||||
UMaterialExpressionViewProperty* E = NewObject<UMaterialExpressionViewProperty>(&M);
|
||||
if (Args.Num() >= 1)
|
||||
{
|
||||
UEnum* Enum = StaticEnum<EMaterialExposedViewProperty>();
|
||||
check(Enum);
|
||||
const int64 Value = Enum->GetValueByNameString(Args[0]);
|
||||
if (Value == INDEX_NONE)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("unknown view property '%s'"), *Args[0]);
|
||||
return nullptr;
|
||||
}
|
||||
E->Property = static_cast<EMaterialExposedViewProperty>(Value);
|
||||
}
|
||||
M.GetExpressionCollection().AddExpression(E);
|
||||
return E;
|
||||
};
|
||||
Registry.Register(MoveTemp(Desc));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
class UMaterial;
|
||||
class UMaterialExpression;
|
||||
class UEnum;
|
||||
|
||||
/** Shader stage an intrinsic is valid in (used to reject e.g. PixelDepth inside a Vertex body). */
|
||||
enum class EShaderLabIntrinsicFrequency : uint8
|
||||
@@ -24,16 +25,23 @@ struct FShaderLabIntrinsicParam
|
||||
FString Name;
|
||||
/** Default-value literal for the stub (e.g. "1"); empty means the parameter is required. */
|
||||
FString DefaultLiteral;
|
||||
/**
|
||||
* When non-null, this parameter is a reflected enum passed by token name (e.g.
|
||||
* `UE_ViewProperty(MEVP_FieldOfView)`). The stub generator emits the enum from it and types the
|
||||
* parameter as it; the arg validator accepts only its token names. Points at a process-permanent
|
||||
* native `UEnum` (`StaticEnum<T>()`), so a raw pointer is safe. `Type` is ignored when this is set.
|
||||
*/
|
||||
const UEnum* Enum = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes one `UE::NodeName(...)` intrinsic: how to build the backing material expression node,
|
||||
* Describes one `UE_NodeName(...)` intrinsic: how to build the backing material expression node,
|
||||
* which of its outputs to read, and what it requires of the material. The Custom-node input type is
|
||||
* derived by the engine from the connected node's real output type, so no output type is declared here.
|
||||
*
|
||||
* The ReturnType/Params/Doc fields carry no runtime meaning — they exist so `ShaderLab.IDE.Prepare`
|
||||
* can emit a `namespace UE { ... }` HLSL stub header from this single source of truth, giving the IDE
|
||||
* member/signature completion for `UE::` calls without any drift from the real registration.
|
||||
* can emit `UE_Name(...)` HLSL stub declarations from this single source of truth, giving the IDE
|
||||
* member/signature completion for `UE_` calls without any drift from the real registration.
|
||||
*/
|
||||
struct FShaderLabIntrinsicDesc
|
||||
{
|
||||
@@ -56,7 +64,7 @@ struct FShaderLabIntrinsicDesc
|
||||
|
||||
/**
|
||||
* Create the backing expression node and add it to `Material`'s expression collection. `ConstArgs`
|
||||
* are the literal arguments parsed from the call (e.g. {"0","2.0"} for `UE::TextureCoordinate(0,2.0)`).
|
||||
* are the literal arguments parsed from the call (e.g. {"0","2.0"} for `UE_TextureCoordinate(0,2.0)`).
|
||||
* Return nullptr and set `OutError` on a bad argument. Contract: this only runs in the editor/cook
|
||||
* (graphs are editor-only); the cooked runtime never builds a graph.
|
||||
*/
|
||||
@@ -79,7 +87,7 @@ public:
|
||||
/** Lookup by name; nullptr if unknown. */
|
||||
const FShaderLabIntrinsicDesc* Find(FName Name) const;
|
||||
|
||||
/** Visit every registered intrinsic (used by ShaderLab.IDE.Prepare to generate the UE:: stub header). */
|
||||
/** Visit every registered intrinsic (used by ShaderLab.IDE.Prepare to generate the UE_ stub header). */
|
||||
void ForEach(TFunctionRef<void(const FShaderLabIntrinsicDesc&)> Fn) const;
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user