using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using UnityEditor.Build; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering { abstract class ShaderPreprocessor where TShader : UnityEngine.Object { IVariantStripper[] m_Strippers = null; protected virtual IVariantStripper[] strippers { get => m_Strippers ??= FetchShaderStrippers(); private set => m_Strippers = value; } Lazy m_GlobalRenderPipeline = new (FetchGlobalRenderPipelineShaderTag); string globalRenderPipeline => m_GlobalRenderPipeline.Value; IVariantStripperScope[] m_Scopes = null; protected virtual IVariantStripperScope[] scopes => m_Scopes ??= strippers.OfType>().ToArray(); protected ShaderPreprocessor() { } protected ShaderPreprocessor(IVariantStripper[] strippers) { this.strippers = strippers ?? throw new ArgumentNullException(nameof(strippers)); } private static string FetchGlobalRenderPipelineShaderTag() { // We can not rely on Shader.globalRenderPipeline as if there wasn't a Camera.Render the variable won't be initialized. // Therefore, we fetch the RenderPipeline assets that are included by the Quality Settings and or Graphics Settings string globalRenderPipelineTag = string.Empty; using (ListPool.Get(out List rpAssets)) { if (EditorUserBuildSettings.activeBuildTarget.TryGetRenderPipelineAssets(rpAssets)) { // As all the RP assets must be from the same pipeline we can simply obtain the shader tag from the first one var asset = rpAssets.FirstOrDefault(); if (asset != null) globalRenderPipelineTag = asset.renderPipelineShaderTag; } } return globalRenderPipelineTag; } bool TryGetShaderVariantRenderPipelineTag([DisallowNull] TShader shader, TShaderVariant shaderVariant, out string renderPipelineTag) { var inputShader = (Shader)Convert.ChangeType(shader, typeof(Shader)); var snippetData = (ShaderSnippetData)Convert.ChangeType(shaderVariant, typeof(ShaderSnippetData)); return inputShader.TryGetRenderPipelineTag(snippetData, out renderPipelineTag); } private static IVariantStripper[] FetchShaderStrippers() { var validStrippers = new List>(); // Gather all the implementations of IVariantStripper and add them as the strippers foreach (var stripper in TypeCache.GetTypesDerivedFrom>()) { if (stripper.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null) != null) { var stripperInstance = Activator.CreateInstance(stripper) as IVariantStripper; if (stripperInstance.active) validStrippers.Add(stripperInstance); } } return validStrippers.ToArray(); } bool CanRemoveVariant([DisallowNull] TShader shader, TShaderVariant shaderVariant, ShaderCompilerData shaderCompilerData) { return strippers .Where(s => s is not IVariantStripperSkipper skipper || !skipper.SkipShader(shader, shaderVariant)) .All(s => s.CanRemoveVariant(shader, shaderVariant, shaderCompilerData)); } /// /// Strips the given /// /// The that might be stripped. /// The /// A list of [CollectionAccess(CollectionAccessType.ModifyExistingContent)] [MustUseReturnValue] protected bool TryStripShaderVariants([DisallowNull] TShader shader, TShaderVariant shaderVariant, IList compilerDataList, [NotNullWhen(false)] out Exception error) { if (shader == null) { error = new ArgumentNullException(nameof(shader)); return false; } if (compilerDataList == null) { error = new ArgumentNullException(nameof(compilerDataList)); return false; } var beforeStrippingInputShaderVariantCount = compilerDataList.Count; var afterStrippingShaderVariantCount = beforeStrippingInputShaderVariantCount; string renderPipelineTag = string.Empty; if (typeof(TShader) == typeof(Shader) && typeof(TShaderVariant) == typeof(ShaderSnippetData)) { if (TryGetShaderVariantRenderPipelineTag(shader, shaderVariant, out renderPipelineTag)) { if (!renderPipelineTag.Equals(globalRenderPipeline, StringComparison.OrdinalIgnoreCase)) afterStrippingShaderVariantCount = 0; } } bool strippersAvailable = strippers.Any(); double stripTimeMs = 0.0; using (TimedScope.FromRef(ref stripTimeMs)) { if (strippersAvailable) { for (int i = 0; i < scopes.Length; ++i) scopes[i].BeforeShaderStripping(shader); // Go through all the shader variants for (var i = 0; i < afterStrippingShaderVariantCount;) { // Remove at swap back if (CanRemoveVariant(shader, shaderVariant, compilerDataList[i])) compilerDataList[i] = compilerDataList[--afterStrippingShaderVariantCount]; else ++i; } } // Remove the shader variants that will be at the back if (!compilerDataList.TryRemoveElementsInRange(afterStrippingShaderVariantCount, compilerDataList.Count - afterStrippingShaderVariantCount, out error)) return false; if (strippersAvailable) { for (int i = 0; i < scopes.Length; ++i) scopes[i].AfterShaderStripping(shader); } } ShaderStripping.reporter.OnShaderProcessed(shader, shaderVariant, renderPipelineTag, (uint)beforeStrippingInputShaderVariantCount, (uint)compilerDataList.Count, stripTimeMs); ShaderStrippingWatcher.OnShaderProcessed(shader, shaderVariant, (uint)compilerDataList.Count, stripTimeMs); error = null; return true; } } class ShaderVariantStripper : ShaderPreprocessor, IPreprocessShaders { public int callbackOrder => 0; public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList inputData) { if (!TryStripShaderVariants(shader, snippet, inputData, out var error)) Debug.LogError(error); } } class ComputeShaderVariantStripper : ShaderPreprocessor, IPreprocessComputeShaders { public int callbackOrder => 0; public void OnProcessComputeShader(ComputeShader shader, string kernelName, IList inputData) { if (!TryStripShaderVariants(shader, kernelName, inputData, out var error)) Debug.LogError(error); } } }