Editor/UpgradedShaderGraph/Nodes/UpgradedNodes/TextureSample.cs

An editor Shader Graph node for upgraded texture sampling. Defines a Texture Sample node supporting 2D and cubemap modes, dynamically builds its inputs based on mode and serialized properties, and emits HLSL-like sampling code for vertex and fragment stages.

Reflection
namespace Editor.ShaderGraphExtras.Nodes;

[Title("SGE - Texture Sample"), Category( "Shader Graph Extras - Upgraded" ), Icon("colorize")]
public sealed class SGETextureSampleNode : ShaderNode
{
	public enum SGETextureSampleMode
	{
		[Title("2D")]
		Texture2D,
		[Title("Cubemap")]
		TextureCubemap
	}

	public SGETextureSampleMode Mode { get; set; } = SGETextureSampleMode.Texture2D;

	[Hide]
	private bool IsTexture2DMode => Mode == SGETextureSampleMode.Texture2D;

	[Hide]
	private bool IsTextureCubemapMode => Mode == SGETextureSampleMode.TextureCubemap;

	[Hide, JsonIgnore]
	int _lastHashCode = 0;

	public override void OnFrame()
	{
		base.OnFrame();

		var hashCode = new HashCode();
		hashCode.Add( Mode );
		var hc = hashCode.ToHashCode();
		if ( hc != _lastHashCode )
		{
			_lastHashCode = hc;
			CreateInputs();
			Update();
		}
	}

	private void CreateInputs()
	{
		var plugs = new List<IPlugIn>();
		var serialized = this.GetSerialized();
		foreach ( var property in serialized )
		{
			if ( property.TryGetAttribute<InputAttribute>( out var inputAttr ) )
			{
				if ( property.TryGetAttribute<ConditionalVisibilityAttribute>( out var conditionalVisibilityAttr ) )
				{
					if ( conditionalVisibilityAttr.TestCondition( this.GetSerialized() ) )
					{
						continue;
					}
				}
				var propertyInfo = typeof( SGETextureSampleNode ).GetProperty( property.Name );
				if ( propertyInfo is null ) continue;
				var info = new PlugInfo( propertyInfo );
				var displayInfo = info.DisplayInfo;
				displayInfo.Name = property.DisplayName;
				info.DisplayInfo = displayInfo;

				// Try to find existing plug to preserve connections
				var oldPlug = Inputs.FirstOrDefault( x => x is BasePlugIn plugIn && plugIn.Info.Name == property.Name ) as BasePlugIn;
				if ( oldPlug is not null )
				{
					oldPlug.Info.Name = info.Name;
					oldPlug.Info.Type = info.Type;
					oldPlug.Info.DisplayInfo = info.DisplayInfo;
					plugs.Add( oldPlug );
				}
				else
				{
					var plug = new BasePlugIn( this, info, info.Type );
					plugs.Add( plug );
				}
			}
		}
		Inputs = plugs;
	}

	[Hide]
	[Title( "Coordinates" )]
	[Input( typeof( Vector2 ) )]
	[ShowIf( nameof( IsTexture2DMode ), true )]
	public NodeInput Texture2DCoordinates { get; set; }

	[Hide]
	[Title( "Coordinates" )]
	[Input( typeof( Vector3 ) )]
	[ShowIf( nameof( IsTextureCubemapMode ), true )]
	public NodeInput TextureCubemapCoordinates { get; set; }

	[Hide]
	[Title("Texture")]
	[Input(typeof(Texture2DObject))]
	[ShowIf( nameof( IsTexture2DMode ), true )]
	public NodeInput Texture2DInput { get; set; }

	[Hide]
	[Title("Sampler")]
	[Input(typeof(Sampler))]
	[ShowIf( nameof( IsTexture2DMode ), true )]
	public NodeInput Texture2DSampler { get; set; }

	[Hide]
	[Title("Texture")]
	[Input(typeof(TextureCubemapObject))]
	[ShowIf( nameof( IsTextureCubemapMode ), true )]
	public NodeInput TextureCubemapInput { get; set; }

	[Hide]
	[Title("Sampler")]
	[Input(typeof(Sampler))]
	[ShowIf( nameof( IsTextureCubemapMode ), true )]
	public NodeInput TextureCubemapSampler { get; set; }

	[Hide]
	[Output(typeof(Color)), Title("Output")]
	public NodeResult.Func Output => (GraphCompiler compiler) =>
	{
		NodeResult result = new NodeResult();

		switch (Mode)
		{
			case SGETextureSampleMode.Texture2D:
			{
				var coordinates = compiler.Result(Texture2DCoordinates);
				var texture = compiler.Result(Texture2DInput);
				var sampler = compiler.Result(Texture2DSampler);

				if (!texture.IsValid)
				{
					return new NodeResult(4, "float4(1, 0, 1, 1)", true);
				}

				if (compiler.Stage == GraphCompiler.ShaderStage.Vertex)
				{
					result = new NodeResult(4, $"{texture}.SampleLevel(" +
						$" {sampler}," +
						$" {(coordinates.IsValid ? $"{coordinates.Cast(2)}" : "i.vTextureCoordinates.xy")}, 0 )");
				}
				else
				{
					result = new NodeResult(4, $"Tex2DS( {texture}," +
						$" {sampler}," +
						$" {(coordinates.IsValid ? $"{coordinates.Cast(2)}" : "i.vTextureCoordinates.xy")} )");
				}
				break;
			}

			case SGETextureSampleMode.TextureCubemap:
			{
				var coordinates = compiler.Result(TextureCubemapCoordinates);
				var texture = compiler.Result(TextureCubemapInput);
				var sampler = compiler.Result(TextureCubemapSampler);

				if (!texture.IsValid)
				{
					return new NodeResult(4, "float4(1, 0, 1, 1)", true);
				}

				if (compiler.Stage == GraphCompiler.ShaderStage.Vertex)
				{
					result = new NodeResult(4, $"{texture}.SampleLevel(" +
						$" {sampler}," +
						$" {(coordinates.IsValid ? $"{coordinates.Cast(3)}" : "i.vNormalWs")}, 0 )");
				}
				else
				{
					result = new NodeResult(4, $"TexCubeS( {texture}," +
						$" {sampler}," +
						$" {(coordinates.IsValid ? $"{coordinates.Cast(3)}" : "i.vNormalWs")} )");
				}
				break;
			}
		}

		return result;
	};
}