diff --git a/VL.Stride.Runtime/src/Rendering/Effects/EffectPins.cs b/VL.Stride.Runtime/src/Rendering/Effects/EffectPins.cs index 13a33a96..8db76543 100644 --- a/VL.Stride.Runtime/src/Rendering/Effects/EffectPins.cs +++ b/VL.Stride.Runtime/src/Rendering/Effects/EffectPins.cs @@ -77,16 +77,26 @@ class ParameterPinDescription : EffectPinDescription public readonly ParameterKey Key; public readonly int Count; public readonly bool IsPermutationKey; - - // This value gets passed to the live pin - for example SetVar (and not just the plain vector) public readonly object RuntimeDefaultValue; + private readonly string? nodeInstanceId; + private readonly bool isStage; + private readonly ParameterKey internalKey; - public ParameterPinDescription(HashSet usedNames, ParameterKey key, int count = 1, object compilationDefaultValue = null, bool isPermutationKey = false, string name = null, Type typeInPatch = null, object runtimeDefaultValue = null) + // Accept isStage as a parameter, determined by the caller from shader metadata/variable attributes + public ParameterPinDescription(HashSet usedNames, ParameterKey key, int count = 1, object compilationDefaultValue = null, bool isPermutationKey = false, string name = null, Type typeInPatch = null, object runtimeDefaultValue = null, string? nodeInstanceId = null, bool isStage = false) { Key = key; IsPermutationKey = isPermutationKey; Count = count; + this.nodeInstanceId = nodeInstanceId; + this.isStage = isStage; + // User-facing name should not include nodeInstanceId Name = name ?? key.GetPinName(usedNames); + // Internal shader key: unique per node unless 'stage' + if (!isStage && nodeInstanceId != null) + internalKey = key.WithUniqueInstance(nodeInstanceId); + else + internalKey = key; var elementType = typeInPatch ?? key.PropertyType; compilationDefaultValue = compilationDefaultValue ?? key.DefaultValueMetadata?.GetDefaultValue(); // TODO: This should be fixed in Stride @@ -110,12 +120,11 @@ public ParameterPinDescription(HashSet usedNames, ParameterKey key, int public override string Name { get; } public override Type Type { get; } - - // The plain value read by the compiler, for example in case of IComputeValue this would be just the vector itself public override object DefaultValueBoxed { get; } public override IVLPin CreatePin(GraphicsDevice graphicsDevice, ParameterCollection parameters) { - return EffectPins.CreatePin(graphicsDevice, parameters, Key, Count, IsPermutationKey, RuntimeDefaultValue, Type); + // Use the internalKey for shader parameter assignment + return EffectPins.CreatePin(graphicsDevice, parameters, internalKey, Count, IsPermutationKey, RuntimeDefaultValue, Type); } public override string ToString() diff --git a/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.ShaderFX.cs b/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.ShaderFX.cs index 29caaee9..7738d03b 100644 --- a/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.ShaderFX.cs +++ b/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.ShaderFX.cs @@ -28,9 +28,11 @@ static IVLNodeDescription NewShaderFXNode(this IVLNodeDescriptionFactory factory invalidated: changes, init: buildContext => { + // Generate a unique node instance identifier + var nodeInstanceId = Guid.NewGuid().ToString("N"); var outputType = shaderMetadata.GetShaderFXOutputType(out var innerType); - var (_effect, _messages, _) = - CreateEffectInstance("ShaderFXEffect", shaderName, shaderMetadata, serviceRegistry, graphicsDevice); + var (_effect, _messages, _) + = CreateEffectInstance("ShaderFXEffect", shaderName, shaderMetadata, serviceRegistry, graphicsDevice); var _inputs = new List(); var _outputs = new List() { buildContext.Pin("Output", outputType) }; @@ -44,14 +46,15 @@ static IVLNodeDescription NewShaderFXNode(this IVLNodeDescriptionFactory factory var key = parameter.Key; var name = key.Name; + // Expose World only - all other world dependent parameters we can compute on our own if (WellKnownParameters.PerDrawMap.ContainsKey(name)) { - // Expose World only - all other world dependent parameters we can compute on our own needsWorld = true; continue; } - _inputs.Add(CreatePinDescription(in parameter, usedNames, shaderMetadata)); + // Pass nodeInstanceId to pin description for unique naming + _inputs.Add(CreatePinDescription(in parameter, usedNames, shaderMetadata, nodeInstanceId: nodeInstanceId)); } // local input values @@ -59,14 +62,14 @@ static IVLNodeDescription NewShaderFXNode(this IVLNodeDescriptionFactory factory { var name = key.Name; + // Expose World only - all other world dependent parameters we can compute on our own if (WellKnownParameters.PerDrawMap.ContainsKey(name)) { - // Expose World only - all other world dependent parameters we can compute on our own needsWorld = true; continue; } - _inputs.Add(CreatePinDescription(key, 1, usedNames, shaderMetadata)); + _inputs.Add(CreatePinDescription(key, 1, usedNames, shaderMetadata, nodeInstanceId: nodeInstanceId)); } if (needsWorld) diff --git a/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.cs b/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.cs index 99438a94..11431b20 100644 --- a/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.cs +++ b/VL.Stride.Runtime/src/Rendering/Effects/EffectShaderNodes.cs @@ -223,12 +223,19 @@ static IEnumerable GetNodeDescriptions(ServiceRegistry servi } } - private static ParameterPinDescription CreatePinDescription(in ParameterKeyInfo keyInfo, HashSet usedNames, ShaderMetadata shaderMetadata, string? name = null, bool? isOptionalOverride = default) + private static ParameterPinDescription CreatePinDescription(in ParameterKeyInfo keyInfo, HashSet usedNames, ShaderMetadata shaderMetadata, string? name = null, bool? isOptionalOverride = default, string? nodeInstanceId = null) { - return CreatePinDescription(keyInfo.Key, keyInfo.Count, usedNames, shaderMetadata, name, isOptionalOverride); + // Determine if this variable is a 'stage' variable using the Stride shader AST variable's Qualifiers + bool isStage = false; + if (shaderMetadata.ParsedShader != null && shaderMetadata.ParsedShader.VariablesByName.TryGetValue(keyInfo.Key.GetVariableName(), out var variable)) + { + // Check for 'stage' in the Qualifiers collection (Stride AST) + isStage = variable.Qualifiers != null && variable.Qualifiers.Any(q => q.Name.Text.Equals("stage", StringComparison.OrdinalIgnoreCase)); + } + return CreatePinDescription(keyInfo.Key, keyInfo.Count, usedNames, shaderMetadata, name, isOptionalOverride, nodeInstanceId, isStage); } - private static ParameterPinDescription CreatePinDescription(ParameterKey key, int count, HashSet usedNames, ShaderMetadata shaderMetadata, string? name = null, bool? isOptionalOverride = default) + private static ParameterPinDescription CreatePinDescription(ParameterKey key, int count, HashSet usedNames, ShaderMetadata shaderMetadata, string? name = null, bool? isOptionalOverride = default, string? nodeInstanceId = null, bool isStage = false) { var typeInPatch = shaderMetadata.GetPinType(key, out var runtimeDefaultValue, out var compilationDefaultValue); shaderMetadata.GetPinDocuAndVisibility(key, out var summary, out var remarks, out var isOptional); @@ -240,7 +247,9 @@ private static ParameterPinDescription CreatePinDescription(ParameterKey key, in compilationDefaultValue: compilationDefaultValue, name: name, typeInPatch: typeInPatch, - runtimeDefaultValue: runtimeDefaultValue) + runtimeDefaultValue: runtimeDefaultValue, + nodeInstanceId: nodeInstanceId, + isStage: isStage) { IsVisible = !isOptional, Summary = summary, diff --git a/VL.Stride.Runtime/src/Rendering/Effects/EffectUtils.cs b/VL.Stride.Runtime/src/Rendering/Effects/EffectUtils.cs index d81fcbdb..6a37567a 100644 --- a/VL.Stride.Runtime/src/Rendering/Effects/EffectUtils.cs +++ b/VL.Stride.Runtime/src/Rendering/Effects/EffectUtils.cs @@ -147,12 +147,13 @@ internal static void SelectPin(this IVLPin[] pins, IVLPinDescription descr pin = pins.OfType().FirstOrDefault(p => p.Name == description.Name); } - public static string GetPinName(this ParameterKey key, HashSet usedNames) + public static string GetPinName(this ParameterKey key, HashSet usedNames, string? nodeInstanceId = null) { var variableName = key.GetVariableName(); var shaderName = key.GetShaderName(); var camelCasedName = FCamelCasePattern.Replace(variableName, match => $"{match.Value[0]} {match.Value[1]}"); var result = char.ToUpper(camelCasedName[0]) + camelCasedName.Substring(1); + // Do NOT append nodeInstanceId to the user-facing name if (usedNames.Add(result)) return result; return $"{shaderName} {result}"; @@ -795,4 +796,28 @@ enum TexturingParameters Texture8TexelSize, Texture9TexelSize } + + static class ParameterKeyExtensions + { + public static ParameterKey WithUniqueInstance(this ParameterKey key, string nodeInstanceId) + { + // Only change the key name for uniqueness, keep all other properties + var uniqueName = $"{key.Name}__{nodeInstanceId}"; + var keyType = key.GetType(); + // Try to create a new key of the same type with the unique name + if (keyType.IsGenericType) + { + var genericDef = keyType.GetGenericTypeDefinition(); + var arg = keyType.GetGenericArguments()[0]; + if (genericDef == typeof(ValueParameterKey<>)) + return (ParameterKey)Activator.CreateInstance(typeof(ValueParameterKey<>).MakeGenericType(arg), uniqueName, key.DefaultValueMetadata); + if (genericDef == typeof(PermutationParameterKey<>)) + return (ParameterKey)Activator.CreateInstance(typeof(PermutationParameterKey<>).MakeGenericType(arg), uniqueName, key.DefaultValueMetadata); + if (genericDef == typeof(ObjectParameterKey<>)) + return (ParameterKey)Activator.CreateInstance(typeof(ObjectParameterKey<>).MakeGenericType(arg), uniqueName, key.DefaultValueMetadata); + } + // fallback: just return the original key + return key; + } + } }