using System; using System.Collections.Generic; using System.Reflection; #if UNITY_EDITOR using System.Linq; using UnityEditor; using ShaderKeywordFilter = UnityEditor.ShaderKeywordFilter; #endif namespace UnityEngine.Rendering.Universal { /// /// Class ScriptableRendererData contains resources for a ScriptableRenderer. /// /// [Icon("UnityEngine/Rendering/RenderPipelineAsset Icon")] public abstract partial class ScriptableRendererData : ScriptableObject { internal bool isInvalidated { get; set; } /// /// Creates the instance of the ScriptableRenderer. /// /// The instance of ScriptableRenderer protected abstract ScriptableRenderer Create(); [SerializeField] internal List m_RendererFeatures = new List(10); [SerializeField] internal List m_RendererFeatureMap = new List(10); [SerializeField] bool m_UseNativeRenderPass = false; /// /// List of additional render pass features for this renderer. /// public List rendererFeatures { get => m_RendererFeatures; } /// /// Use SetDirty when changing seeings in the ScriptableRendererData. /// It will rebuild the render passes with the new data. /// public new void SetDirty() { isInvalidated = true; } internal ScriptableRenderer InternalCreateRenderer() { isInvalidated = false; return Create(); } /// /// Editor-only function that Unity calls when the script is loaded or a value changes in the Inspector. /// protected virtual void OnValidate() { SetDirty(); #if UNITY_EDITOR // Only validate ScriptableRendererFeatures when all scripts have finished compiling (to avoid false-negatives // when ScriptableRendererFeatures haven't been compiled before this check). if (!EditorApplication.isCompiling && m_RendererFeatures.Contains(null)) ValidateRendererFeatures(); #endif } /// /// This function is called when the object becomes enabled and active. /// protected virtual void OnEnable() { SetDirty(); } /// /// Specifies whether the renderer should use Native Render Pass. /// public bool useNativeRenderPass { get => m_UseNativeRenderPass; set { SetDirty(); m_UseNativeRenderPass = value; } } /// /// Returns true if contains renderer feature with specified type. /// /// RenderFeature output parameter. /// Renderer Feature type. /// public bool TryGetRendererFeature(out T rendererFeature) where T : ScriptableRendererFeature { foreach (var target in rendererFeatures) { if (target.GetType() == typeof(T)) { rendererFeature = target as T; return true; } } rendererFeature = null; return false; } #if UNITY_EDITOR internal virtual Material GetDefaultMaterial(DefaultMaterialType materialType) { return null; } internal virtual Shader GetDefaultShader() { return null; } internal bool ValidateRendererFeatures() { // Get all Subassets var subassets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(this)); var linkedIds = new List(); var loadedAssets = new Dictionary(); var mapValid = m_RendererFeatureMap != null && m_RendererFeatureMap?.Count == m_RendererFeatures?.Count; var debugOutput = $"{name}\nValid Sub-assets:\n"; // Collect valid, compiled sub-assets foreach (var asset in subassets) { if (asset == null || !asset.GetType().IsSubclassOf(typeof(ScriptableRendererFeature))) continue; AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out var guid, out long localId); loadedAssets.Add(localId, asset); debugOutput += $"-{asset.name}\n--localId={localId}\n"; } // Collect assets that are connected to the list for (var i = 0; i < m_RendererFeatures?.Count; i++) { if (!m_RendererFeatures[i]) continue; if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(m_RendererFeatures[i], out var guid, out long localId)) { linkedIds.Add(localId); } } var mapDebug = mapValid ? "Linking" : "Map missing, will attempt to re-map"; debugOutput += $"Feature List Status({mapDebug}):\n"; // Try fix missing references for (var i = 0; i < m_RendererFeatures?.Count; i++) { if (m_RendererFeatures[i] == null) { if (mapValid && m_RendererFeatureMap[i] != 0) { var localId = m_RendererFeatureMap[i]; loadedAssets.TryGetValue(localId, out var asset); m_RendererFeatures[i] = (ScriptableRendererFeature)asset; } else { m_RendererFeatures[i] = (ScriptableRendererFeature)GetUnusedAsset(ref linkedIds, ref loadedAssets); } } debugOutput += m_RendererFeatures[i] != null ? $"-{i}:Linked\n" : $"-{i}:Missing\n"; } UpdateMap(); if (!m_RendererFeatures.Contains(null)) return true; Debug.LogError($"{name} is missing RendererFeatures\nThis could be due to missing scripts or compile error.", this); return false; } internal bool DuplicateFeatureCheck(Type type) { Attribute isSingleFeature = type.GetCustomAttribute(typeof(DisallowMultipleRendererFeature)); if (isSingleFeature == null) return false; if (m_RendererFeatures == null) return false; for (int i = 0; i < m_RendererFeatures.Count; i++) { ScriptableRendererFeature feature = m_RendererFeatures[i]; if (feature == null) continue; if (feature.GetType() == type) return true; } return false; } private static object GetUnusedAsset(ref List usedIds, ref Dictionary assets) { foreach (var asset in assets) { var alreadyLinked = usedIds.Any(used => asset.Key == used); if (alreadyLinked) continue; usedIds.Add(asset.Key); return asset.Value; } return null; } private void UpdateMap() { if (m_RendererFeatureMap.Count != m_RendererFeatures.Count) { m_RendererFeatureMap.Clear(); m_RendererFeatureMap.AddRange(new long[m_RendererFeatures.Count]); } for (int i = 0; i < rendererFeatures.Count; i++) { if (m_RendererFeatures[i] == null) continue; if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(m_RendererFeatures[i], out var guid, out long localId)) continue; m_RendererFeatureMap[i] = localId; } } #endif } }