578 lines
24 KiB
C#
578 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using MCPForUnity.Editor.Helpers;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using MCPForUnity.Runtime.Helpers;
|
|
|
|
namespace MCPForUnity.Editor.Tools.Graphics
|
|
{
|
|
internal static class RendererFeatureOps
|
|
{
|
|
// Cached URP types (resolved via reflection to avoid hard dependency)
|
|
private static Type _scriptableRendererDataType;
|
|
private static Type _scriptableRendererFeatureType;
|
|
private static Type _universalRenderPipelineAssetType;
|
|
private static bool _typesResolved;
|
|
|
|
private static void EnsureTypes()
|
|
{
|
|
if (_typesResolved) return;
|
|
_typesResolved = true;
|
|
|
|
_scriptableRendererDataType = Type.GetType(
|
|
"UnityEngine.Rendering.Universal.ScriptableRendererData, Unity.RenderPipelines.Universal.Runtime");
|
|
_scriptableRendererFeatureType = Type.GetType(
|
|
"UnityEngine.Rendering.Universal.ScriptableRendererFeature, Unity.RenderPipelines.Universal.Runtime");
|
|
_universalRenderPipelineAssetType = Type.GetType(
|
|
"UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset, Unity.RenderPipelines.Universal.Runtime");
|
|
}
|
|
|
|
// === feature_list ===
|
|
internal static object ListFeatures(JObject @params)
|
|
{
|
|
var rendererData = GetRendererData(@params);
|
|
if (rendererData == null)
|
|
return new ErrorResponse("Could not find URP ScriptableRendererData. Ensure URP is active.");
|
|
|
|
var featuresProp = rendererData.GetType().GetProperty("rendererFeatures",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
if (featuresProp == null)
|
|
return new ErrorResponse("rendererFeatures property not found on renderer data.");
|
|
|
|
var featuresList = featuresProp.GetValue(rendererData) as System.Collections.IList;
|
|
if (featuresList == null)
|
|
return new { success = true, message = "No renderer features.", data = new { features = new object[0] } };
|
|
|
|
var features = new List<object>();
|
|
for (int i = 0; i < featuresList.Count; i++)
|
|
{
|
|
var feature = featuresList[i] as ScriptableObject;
|
|
if (feature == null) continue;
|
|
|
|
var isActiveProp = feature.GetType().GetProperty("isActive",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
features.Add(new
|
|
{
|
|
index = i,
|
|
name = feature.name,
|
|
type = feature.GetType().Name,
|
|
isActive = isActiveProp != null ? (bool)isActiveProp.GetValue(feature) : true,
|
|
properties = GetFeatureProperties(feature)
|
|
});
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Found {features.Count} renderer feature(s).",
|
|
data = new
|
|
{
|
|
rendererDataName = (rendererData as ScriptableObject)?.name,
|
|
features
|
|
}
|
|
};
|
|
}
|
|
|
|
// === feature_add ===
|
|
internal static object AddFeature(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string typeName = p.Get("type");
|
|
if (string.IsNullOrEmpty(typeName))
|
|
return new ErrorResponse("'type' parameter required (e.g., 'FullScreenPassRendererFeature', 'RenderObjects').");
|
|
|
|
var rendererData = GetRendererData(@params);
|
|
if (rendererData == null)
|
|
return new ErrorResponse("Could not find URP ScriptableRendererData.");
|
|
|
|
EnsureTypes();
|
|
if (_scriptableRendererFeatureType == null)
|
|
return new ErrorResponse("ScriptableRendererFeature type not found. Is URP installed?");
|
|
|
|
// Resolve the feature type
|
|
var featureType = ResolveFeatureType(typeName);
|
|
if (featureType == null)
|
|
{
|
|
var available = GetAvailableFeatureTypes();
|
|
return new ErrorResponse(
|
|
$"Feature type '{typeName}' not found. Available: {string.Join(", ", available.Select(t => t.Name))}");
|
|
}
|
|
|
|
// Create the feature instance
|
|
var feature = ScriptableObject.CreateInstance(featureType);
|
|
if (feature == null)
|
|
return new ErrorResponse($"Failed to create instance of '{featureType.Name}'.");
|
|
|
|
string displayName = p.Get("name") ?? featureType.Name;
|
|
feature.name = displayName;
|
|
|
|
// Add to the renderer data asset
|
|
Undo.RecordObject(rendererData as UnityEngine.Object, "Add Renderer Feature");
|
|
AssetDatabase.AddObjectToAsset(feature, rendererData as UnityEngine.Object);
|
|
|
|
// Add to the features list via SerializedObject
|
|
using (var so = new SerializedObject(rendererData as UnityEngine.Object))
|
|
{
|
|
var rendererFeaturesProp = so.FindProperty("m_RendererFeatures");
|
|
if (rendererFeaturesProp != null)
|
|
{
|
|
rendererFeaturesProp.arraySize++;
|
|
var element = rendererFeaturesProp.GetArrayElementAtIndex(rendererFeaturesProp.arraySize - 1);
|
|
element.objectReferenceValue = feature;
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
|
|
// Also update the map (m_RendererFeatureMap) if it exists
|
|
// Map stores persistent local file IDs, not transient instance IDs
|
|
var mapProp = so.FindProperty("m_RendererFeatureMap");
|
|
if (mapProp != null)
|
|
{
|
|
long localId = 0;
|
|
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(feature, out _, out localId);
|
|
mapProp.arraySize++;
|
|
var mapElement = mapProp.GetArrayElementAtIndex(mapProp.arraySize - 1);
|
|
mapElement.longValue = localId;
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
// Configure initial properties if provided
|
|
var propertiesToken = p.GetRaw("properties") as JObject;
|
|
if (propertiesToken != null)
|
|
ApplyFeatureProperties(feature, propertiesToken);
|
|
|
|
// Set material if provided (common for FullScreenPass)
|
|
string materialPath = p.Get("material");
|
|
if (!string.IsNullOrEmpty(materialPath))
|
|
TrySetMaterial(feature, materialPath);
|
|
|
|
EditorUtility.SetDirty(rendererData as UnityEngine.Object);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Added renderer feature '{displayName}' ({featureType.Name}).",
|
|
data = new
|
|
{
|
|
name = displayName,
|
|
type = featureType.Name,
|
|
instanceId = feature.GetInstanceIDCompat()
|
|
}
|
|
};
|
|
}
|
|
|
|
// === feature_remove ===
|
|
internal static object RemoveFeature(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
int? index = p.GetInt("index");
|
|
string name = p.Get("name");
|
|
|
|
var rendererData = GetRendererData(@params);
|
|
if (rendererData == null)
|
|
return new ErrorResponse("Could not find URP ScriptableRendererData.");
|
|
|
|
var featuresProp = rendererData.GetType().GetProperty("rendererFeatures",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
if (featuresProp == null)
|
|
return new ErrorResponse("rendererFeatures property not found.");
|
|
|
|
var featuresList = featuresProp.GetValue(rendererData) as System.Collections.IList;
|
|
if (featuresList == null || featuresList.Count == 0)
|
|
return new ErrorResponse("No renderer features to remove.");
|
|
|
|
int targetIndex = ResolveFeatureIndex(featuresList, index, name);
|
|
if (targetIndex < 0)
|
|
return new ErrorResponse($"Feature not found. Specify 'index' (0-{featuresList.Count - 1}) or 'name'.");
|
|
|
|
var feature = featuresList[targetIndex] as ScriptableObject;
|
|
string featureName = feature?.name ?? "Unknown";
|
|
|
|
Undo.RecordObject(rendererData as UnityEngine.Object, "Remove Renderer Feature");
|
|
|
|
// Remove from the list via SerializedObject
|
|
using (var so = new SerializedObject(rendererData as UnityEngine.Object))
|
|
{
|
|
var rendererFeaturesPropSo = so.FindProperty("m_RendererFeatures");
|
|
if (rendererFeaturesPropSo != null)
|
|
{
|
|
rendererFeaturesPropSo.DeleteArrayElementAtIndex(targetIndex);
|
|
// SerializedProperty.DeleteArrayElementAtIndex sets to null first for ObjectReference
|
|
if (rendererFeaturesPropSo.arraySize > targetIndex)
|
|
{
|
|
var element = rendererFeaturesPropSo.GetArrayElementAtIndex(targetIndex);
|
|
if (element.objectReferenceValue == null)
|
|
rendererFeaturesPropSo.DeleteArrayElementAtIndex(targetIndex);
|
|
}
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
|
|
// Clean up the map
|
|
var mapProp = so.FindProperty("m_RendererFeatureMap");
|
|
if (mapProp != null && targetIndex < mapProp.arraySize)
|
|
{
|
|
mapProp.DeleteArrayElementAtIndex(targetIndex);
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
// Remove the sub-asset
|
|
if (feature != null)
|
|
AssetDatabase.RemoveObjectFromAsset(feature);
|
|
|
|
EditorUtility.SetDirty(rendererData as UnityEngine.Object);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Removed renderer feature '{featureName}' at index {targetIndex}."
|
|
};
|
|
}
|
|
|
|
// === feature_configure ===
|
|
internal static object ConfigureFeature(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
int? index = p.GetInt("index");
|
|
string name = p.Get("name");
|
|
var propertiesToken = (p.GetRaw("properties") ?? p.GetRaw("settings")) as JObject;
|
|
|
|
if (propertiesToken == null)
|
|
return new ErrorResponse("'properties' (or 'settings') dict is required.");
|
|
|
|
var rendererData = GetRendererData(@params);
|
|
if (rendererData == null)
|
|
return new ErrorResponse("Could not find URP ScriptableRendererData.");
|
|
|
|
var featuresProp = rendererData.GetType().GetProperty("rendererFeatures",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
var featuresList = featuresProp?.GetValue(rendererData) as System.Collections.IList;
|
|
if (featuresList == null || featuresList.Count == 0)
|
|
return new ErrorResponse("No renderer features to configure.");
|
|
|
|
int targetIndex = ResolveFeatureIndex(featuresList, index, name);
|
|
if (targetIndex < 0)
|
|
return new ErrorResponse($"Feature not found. Specify 'index' (0-{featuresList.Count - 1}) or 'name'.");
|
|
|
|
var feature = featuresList[targetIndex] as ScriptableObject;
|
|
if (feature == null)
|
|
return new ErrorResponse($"Feature at index {targetIndex} is null.");
|
|
|
|
Undo.RecordObject(feature, "Configure Renderer Feature");
|
|
var result = ApplyFeatureProperties(feature, propertiesToken);
|
|
EditorUtility.SetDirty(feature);
|
|
EditorUtility.SetDirty(rendererData as UnityEngine.Object);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Configured '{feature.name}': {result.changed.Count} set, {result.failed.Count} failed.",
|
|
data = new { result.changed, result.failed }
|
|
};
|
|
}
|
|
|
|
// === feature_toggle ===
|
|
internal static object ToggleFeature(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
int? index = p.GetInt("index");
|
|
string name = p.Get("name");
|
|
bool? active = p.GetBool("active");
|
|
|
|
var rendererData = GetRendererData(@params);
|
|
if (rendererData == null)
|
|
return new ErrorResponse("Could not find URP ScriptableRendererData.");
|
|
|
|
var featuresProp = rendererData.GetType().GetProperty("rendererFeatures",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
var featuresList = featuresProp?.GetValue(rendererData) as System.Collections.IList;
|
|
if (featuresList == null || featuresList.Count == 0)
|
|
return new ErrorResponse("No renderer features.");
|
|
|
|
int targetIndex = ResolveFeatureIndex(featuresList, index, name);
|
|
if (targetIndex < 0)
|
|
return new ErrorResponse($"Feature not found. Specify 'index' or 'name'.");
|
|
|
|
var feature = featuresList[targetIndex] as ScriptableObject;
|
|
if (feature == null)
|
|
return new ErrorResponse($"Feature at index {targetIndex} is null.");
|
|
|
|
// ScriptableRendererFeature.SetActive(bool) is public
|
|
var setActiveMethod = feature.GetType().GetMethod("SetActive",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
if (setActiveMethod == null)
|
|
return new ErrorResponse("SetActive method not found on feature.");
|
|
|
|
bool newState = active ?? true;
|
|
Undo.RecordObject(feature, "Toggle Renderer Feature");
|
|
setActiveMethod.Invoke(feature, new object[] { newState });
|
|
EditorUtility.SetDirty(feature);
|
|
EditorUtility.SetDirty(rendererData as UnityEngine.Object);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Feature '{feature.name}' {(newState ? "enabled" : "disabled")}."
|
|
};
|
|
}
|
|
|
|
// === feature_reorder ===
|
|
internal static object ReorderFeatures(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
var orderToken = p.GetRaw("order") as JArray;
|
|
if (orderToken == null)
|
|
return new ErrorResponse("'order' parameter required (array of indices, e.g. [2, 0, 1]).");
|
|
|
|
var rendererData = GetRendererData(@params);
|
|
if (rendererData == null)
|
|
return new ErrorResponse("Could not find URP ScriptableRendererData.");
|
|
|
|
var featuresProp = rendererData.GetType().GetProperty("rendererFeatures",
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
var featuresList = featuresProp?.GetValue(rendererData) as System.Collections.IList;
|
|
if (featuresList == null || featuresList.Count == 0)
|
|
return new ErrorResponse("No renderer features to reorder.");
|
|
|
|
var newOrder = orderToken.Select(t => (int)t).ToList();
|
|
if (newOrder.Count != featuresList.Count)
|
|
return new ErrorResponse(
|
|
$"Order array length ({newOrder.Count}) must match feature count ({featuresList.Count}).");
|
|
|
|
// Validate all indices are present
|
|
var sorted = newOrder.OrderBy(x => x).ToList();
|
|
for (int i = 0; i < sorted.Count; i++)
|
|
{
|
|
if (sorted[i] != i)
|
|
return new ErrorResponse("Order array must contain each index exactly once (0 to N-1).");
|
|
}
|
|
|
|
Undo.RecordObject(rendererData as UnityEngine.Object, "Reorder Renderer Features");
|
|
|
|
using (var so = new SerializedObject(rendererData as UnityEngine.Object))
|
|
{
|
|
var rendererFeaturesPropSo = so.FindProperty("m_RendererFeatures");
|
|
if (rendererFeaturesPropSo == null)
|
|
return new ErrorResponse("m_RendererFeatures property not found.");
|
|
|
|
// Read current features
|
|
var current = new UnityEngine.Object[featuresList.Count];
|
|
for (int i = 0; i < featuresList.Count; i++)
|
|
current[i] = rendererFeaturesPropSo.GetArrayElementAtIndex(i).objectReferenceValue;
|
|
|
|
// Apply new order
|
|
for (int i = 0; i < newOrder.Count; i++)
|
|
rendererFeaturesPropSo.GetArrayElementAtIndex(i).objectReferenceValue = current[newOrder[i]];
|
|
|
|
// Also reorder the feature map to keep it in sync
|
|
var mapProp = so.FindProperty("m_RendererFeatureMap");
|
|
if (mapProp != null && mapProp.arraySize == featuresList.Count)
|
|
{
|
|
var currentMap = new long[featuresList.Count];
|
|
for (int i = 0; i < featuresList.Count; i++)
|
|
currentMap[i] = mapProp.GetArrayElementAtIndex(i).longValue;
|
|
for (int i = 0; i < newOrder.Count; i++)
|
|
mapProp.GetArrayElementAtIndex(i).longValue = currentMap[newOrder[i]];
|
|
}
|
|
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
|
|
EditorUtility.SetDirty(rendererData as UnityEngine.Object);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Reordered {featuresList.Count} renderer features."
|
|
};
|
|
}
|
|
|
|
// ==================== Helpers ====================
|
|
|
|
private static object GetRendererData(JObject @params)
|
|
{
|
|
EnsureTypes();
|
|
if (_universalRenderPipelineAssetType == null || _scriptableRendererDataType == null)
|
|
return null;
|
|
|
|
var pipelineAsset = GraphicsSettings.currentRenderPipeline;
|
|
if (pipelineAsset == null || !_universalRenderPipelineAssetType.IsInstanceOfType(pipelineAsset))
|
|
return null;
|
|
|
|
var p = new ToolParams(@params);
|
|
int rendererIndex = p.GetInt("renderer_index") ?? -1;
|
|
|
|
// Get renderer data from the URP asset
|
|
// Try scriptableRendererData property or GetRenderer method
|
|
if (rendererIndex >= 0)
|
|
{
|
|
// Use SerializedObject to get specific renderer
|
|
using (var so = new SerializedObject(pipelineAsset))
|
|
{
|
|
var renderersProp = so.FindProperty("m_RendererDataList");
|
|
if (renderersProp == null || rendererIndex >= renderersProp.arraySize)
|
|
return null;
|
|
|
|
var element = renderersProp.GetArrayElementAtIndex(rendererIndex);
|
|
return element.objectReferenceValue;
|
|
}
|
|
}
|
|
|
|
// Default: get the active renderer (index from m_DefaultRendererIndex)
|
|
using (var so = new SerializedObject(pipelineAsset))
|
|
{
|
|
var defaultIndex = so.FindProperty("m_DefaultRendererIndex");
|
|
int idx = defaultIndex != null ? defaultIndex.intValue : 0;
|
|
|
|
var renderersProp = so.FindProperty("m_RendererDataList");
|
|
if (renderersProp != null && idx < renderersProp.arraySize)
|
|
{
|
|
var element = renderersProp.GetArrayElementAtIndex(idx);
|
|
return element.objectReferenceValue;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static Type ResolveFeatureType(string typeName)
|
|
{
|
|
EnsureTypes();
|
|
if (_scriptableRendererFeatureType == null) return null;
|
|
|
|
var derivedTypes = TypeCache.GetTypesDerivedFrom(_scriptableRendererFeatureType);
|
|
foreach (var t in derivedTypes)
|
|
{
|
|
if (t.IsAbstract) continue;
|
|
if (string.Equals(t.Name, typeName, StringComparison.OrdinalIgnoreCase))
|
|
return t;
|
|
}
|
|
|
|
// Try partial match (e.g., "FullScreenPass" matches "FullScreenPassRendererFeature")
|
|
foreach (var t in derivedTypes)
|
|
{
|
|
if (t.IsAbstract) continue;
|
|
if (t.Name.StartsWith(typeName, StringComparison.OrdinalIgnoreCase))
|
|
return t;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static List<Type> GetAvailableFeatureTypes()
|
|
{
|
|
EnsureTypes();
|
|
if (_scriptableRendererFeatureType == null) return new List<Type>();
|
|
|
|
return TypeCache.GetTypesDerivedFrom(_scriptableRendererFeatureType)
|
|
.Where(t => !t.IsAbstract && !t.IsGenericType)
|
|
.OrderBy(t => t.Name)
|
|
.ToList();
|
|
}
|
|
|
|
private static int ResolveFeatureIndex(System.Collections.IList featuresList, int? index, string name)
|
|
{
|
|
if (index.HasValue && index.Value >= 0 && index.Value < featuresList.Count)
|
|
return index.Value;
|
|
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
for (int i = 0; i < featuresList.Count; i++)
|
|
{
|
|
var feature = featuresList[i] as ScriptableObject;
|
|
if (feature == null) continue;
|
|
if (string.Equals(feature.name, name, StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(feature.GetType().Name, name, StringComparison.OrdinalIgnoreCase))
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private static Dictionary<string, object> GetFeatureProperties(ScriptableObject feature)
|
|
{
|
|
var props = new Dictionary<string, object>();
|
|
using (var so = new SerializedObject(feature))
|
|
{
|
|
var iterator = so.GetIterator();
|
|
if (iterator.NextVisible(true)) // Enter children
|
|
{
|
|
do
|
|
{
|
|
// Skip Unity internal properties
|
|
if (iterator.name == "m_Script" || iterator.name == "m_ObjectHideFlags" || iterator.name == "m_Name")
|
|
continue;
|
|
|
|
props[iterator.name] = GraphicsHelpers.ReadSerializedValue(iterator);
|
|
} while (iterator.NextVisible(false));
|
|
}
|
|
}
|
|
return props;
|
|
}
|
|
|
|
private static (List<string> changed, List<string> failed) ApplyFeatureProperties(
|
|
ScriptableObject feature, JObject propertiesToken)
|
|
{
|
|
var changed = new List<string>();
|
|
var failed = new List<string>();
|
|
|
|
using (var so = new SerializedObject(feature))
|
|
{
|
|
foreach (var prop in propertiesToken.Properties())
|
|
{
|
|
var sProp = so.FindProperty(prop.Name);
|
|
if (sProp != null)
|
|
{
|
|
if (GraphicsHelpers.SetSerializedValue(sProp, prop.Value))
|
|
changed.Add(prop.Name);
|
|
else
|
|
failed.Add(prop.Name);
|
|
}
|
|
else
|
|
{
|
|
// Try nested: "settings.fieldName"
|
|
string nested = $"settings.{prop.Name}";
|
|
sProp = so.FindProperty(nested);
|
|
if (sProp != null && GraphicsHelpers.SetSerializedValue(sProp, prop.Value))
|
|
changed.Add(prop.Name);
|
|
else
|
|
failed.Add(prop.Name);
|
|
}
|
|
}
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
|
|
return (changed, failed);
|
|
}
|
|
|
|
private static void TrySetMaterial(ScriptableObject feature, string materialPath)
|
|
{
|
|
var mat = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
|
|
if (mat == null) return;
|
|
|
|
using (var so = new SerializedObject(feature))
|
|
{
|
|
// FullScreenPassRendererFeature uses "m_PassMaterial" or "passMaterial"
|
|
var matProp = so.FindProperty("m_PassMaterial") ?? so.FindProperty("passMaterial");
|
|
if (matProp != null && matProp.propertyType == SerializedPropertyType.ObjectReference)
|
|
{
|
|
matProp.objectReferenceValue = mat;
|
|
so.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|