public static Material NewShaderAndMaterial(string path, string name)
    {
        string shaderPath     = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.shader");
        string shaderBasePath = shaderPath.Replace(".shader", "_Base.shader");
        string matPath        = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.mat");

        MicroSplatCompiler compiler = new MicroSplatCompiler();

        compiler.Init();

        string baseName = "Hidden/MicroSplat/" + name + "_Base";

        string baseShader    = compiler.Compile(new string[0], baseName);
        string regularShader = compiler.Compile(new string[0], name, baseName);

        System.IO.File.WriteAllText(shaderPath, regularShader);
        System.IO.File.WriteAllText(shaderBasePath, baseShader);
        AssetDatabase.Refresh();
        Shader s = AssetDatabase.LoadAssetAtPath <Shader>(shaderPath);

        Material m = new Material(s);

        AssetDatabase.CreateAsset(m, matPath);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        return(AssetDatabase.LoadAssetAtPath <Material>(matPath));
    }
Exemple #2
0
    public static Shader NewShader()
    {
        string path = "Assets";

        foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
        {
            path = AssetDatabase.GetAssetPath(obj);
            if (System.IO.File.Exists(path))
            {
                path = System.IO.Path.GetDirectoryName(path);
            }
            break;
        }
        path = path.Replace("\\", "/");
        path = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.shader");
        string name = path.Substring(path.LastIndexOf("/"));

        name = name.Substring(0, name.IndexOf("."));
        MicroSplatCompiler compiler = new MicroSplatCompiler();

        compiler.Init();
        string ret = compiler.Compile(new string[1] {
            "_MSRENDERLOOP_SURFACESHADER"
        }, name, name);

        System.IO.File.WriteAllText(path, ret);
        AssetDatabase.Refresh();
        return(AssetDatabase.LoadAssetAtPath <Shader> (path));
    }
    public static Shader NewShader()
    {
        string path = "Assets";

        foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
        {
            path = AssetDatabase.GetAssetPath(obj);
            if (System.IO.File.Exists(path))
            {
                path = System.IO.Path.GetDirectoryName(path);
            }
            break;
        }
        path = path.Replace("\\", "/");

        path = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.shader");
        string name = path.Substring(path.LastIndexOf("/"));

        name = name.Substring(0, name.IndexOf("."));
        name = name.Replace("/", "");

        ;
        string [] keywords = new string [0];
        var       pipeline = MicroSplatUtilities.DetectPipeline();

        if (pipeline == MicroSplatUtilities.PipelineType.HDPipeline)
        {
            System.Array.Resize(ref keywords, keywords.Length + 2);
            keywords [keywords.Length - 2] = "_MSRENDERLOOP_UNITYHD";
            keywords [keywords.Length - 1] = "_MSRENDERLOOP_UNITYHDRP2020";
        }
        else if (pipeline == MicroSplatUtilities.PipelineType.UniversalPipeline)
        {
            System.Array.Resize(ref keywords, keywords.Length + 2);
            keywords [keywords.Length - 2] = "_MSRENDERLOOP_UNITYLD";
            keywords [keywords.Length - 1] = "_MSRENDERLOOP_UNITYURP2020";
        }


        MicroSplatCompiler compiler = new MicroSplatCompiler();

        compiler.Init();
        string ret = compiler.Compile(keywords, name, null);

        System.IO.File.WriteAllText(path, ret);
        AssetDatabase.Refresh();
        return(AssetDatabase.LoadAssetAtPath <Shader> (path));
    }
    public static Material NewShaderAndMaterial(string path, string name, string[] keywords = null)
    {
        string shaderPath     = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.shader");
        string shaderBasePath = shaderPath.Replace(".shader", "_Base.shader");
        string matPath        = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.mat");

        MicroSplatCompiler compiler = new MicroSplatCompiler();

        compiler.Init();

        if (keywords == null)
        {
            keywords = new string[0];
        }

        string baseName = "Hidden/MicroSplat/" + name + "_Base";

        string baseShader    = compiler.Compile(keywords, baseName);
        string regularShader = compiler.Compile(keywords, name, baseName);

        System.IO.File.WriteAllText(shaderPath, regularShader);
        System.IO.File.WriteAllText(shaderBasePath, baseShader);


        if (keywords.Contains("_MESHOVERLAYSPLATS"))
        {
            string meshOverlayShader = compiler.Compile(keywords, name, null, true);
            System.IO.File.WriteAllText(shaderPath.Replace(".shader", "_MeshOverlay.shader"), meshOverlayShader);
        }

        AssetDatabase.Refresh();
        Shader s = AssetDatabase.LoadAssetAtPath <Shader>(shaderPath);

        Material m = new Material(s);

        AssetDatabase.CreateAsset(m, matPath);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        var kwds = MicroSplatUtilities.FindOrCreateKeywords(m);

        kwds.keywords = new List <string>(keywords);
        EditorUtility.SetDirty(kwds);

        return(AssetDatabase.LoadAssetAtPath <Material>(matPath));
    }
Exemple #5
0
    public static Material NewShaderAndMaterial(string path, string name, string[] keywords = null)
    {
        string shaderPath     = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.shader");
        string shaderBasePath = shaderPath.Replace(".shader", "_Base.shader");
        string matPath        = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.mat");

        shaderPath     = shaderPath.Replace("//", "/");
        shaderBasePath = shaderBasePath.Replace("//", "/");
        matPath        = matPath.Replace("//", "/");

        MicroSplatCompiler compiler = new MicroSplatCompiler();

        compiler.Init();

        if (keywords == null)
        {
            keywords = new string[0];
        }

        var pipeline = MicroSplatUtilities.DetectPipeline();

        if (pipeline == MicroSplatUtilities.PipelineType.HDPipeline)
        {
            System.Array.Resize(ref keywords, keywords.Length + 1);
            keywords [keywords.Length - 1] = "_MSRENDERLOOP_UNITYHD";
        }
        else if (pipeline == MicroSplatUtilities.PipelineType.UniversalPipeline)
        {
            System.Array.Resize(ref keywords, keywords.Length + 1);
            keywords [keywords.Length - 1] = "_MSRENDERLOOP_UNITYLD";
        }


        string baseName = "Hidden/MicroSplat/" + name + "_Base";

        string baseShader    = compiler.Compile(keywords, baseName);
        string regularShader = compiler.Compile(keywords, name, baseName);

        System.IO.File.WriteAllText(shaderPath, regularShader);
        System.IO.File.WriteAllText(shaderBasePath, baseShader);


        if (keywords.Contains("_MESHOVERLAYSPLATS"))
        {
            string meshOverlayShader = compiler.Compile(keywords, name, null, true);
            System.IO.File.WriteAllText(shaderPath.Replace(".shader", "_MeshOverlay.shader"), meshOverlayShader);
        }

        AssetDatabase.Refresh();
        Shader s = AssetDatabase.LoadAssetAtPath <Shader> (shaderPath);

        if (s == null)
        {
            Debug.LogError("Shader not found at path " + shaderPath);
        }
        Material m = new Material(s);

        AssetDatabase.CreateAsset(m, matPath);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        var kwds = MicroSplatUtilities.FindOrCreateKeywords(m);

        kwds.keywords = new List <string> (keywords);
        EditorUtility.SetDirty(kwds);
        var propData = MicroSplatShaderGUI.FindOrCreatePropTex(m);

        if (propData != null)
        {
            EditorUtility.SetDirty(propData);
        }

        return(AssetDatabase.LoadAssetAtPath <Material> (matPath));
    }
Exemple #6
0
    public static Material NewShaderAndMaterial(string path, string name, string[] keywords = null)
    {
        // if no branch sampling is specified, go straight to aggressive. Usually defaults are not done this way, but this seems
        // to make the most sense..
        if (!keywords.Contains("_BRANCHSAMPLES"))
        {
            System.Array.Resize(ref keywords, keywords.Length + 2);
            keywords [keywords.Length - 2] = "_BRANCHSAMPLES";
            keywords [keywords.Length - 1] = "_BRANCHSAMPLESARG";
        }
        string shaderPath     = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.shader");
        string shaderBasePath = shaderPath.Replace(".shader", "_Base.shader");
        string matPath        = AssetDatabase.GenerateUniqueAssetPath(path + "/MicroSplat.mat");

        shaderPath     = shaderPath.Replace("//", "/");
        shaderBasePath = shaderBasePath.Replace("//", "/");
        matPath        = matPath.Replace("//", "/");

        MicroSplatCompiler compiler = new MicroSplatCompiler();

        compiler.Init();

        if (keywords == null)
        {
            keywords = new string[0];
        }

        var pipeline = MicroSplatUtilities.DetectPipeline();

        if (pipeline == MicroSplatUtilities.PipelineType.HDPipeline)
        {
            System.Array.Resize(ref keywords, keywords.Length + 1);
            keywords [keywords.Length - 1] = "_MSRENDERLOOP_UNITYHD";
        }
        else if (pipeline == MicroSplatUtilities.PipelineType.UniversalPipeline)
        {
            System.Array.Resize(ref keywords, keywords.Length + 1);
            keywords [keywords.Length - 1] = "_MSRENDERLOOP_UNITYLD";
        }


        string baseName = "Hidden/MicroSplat/" + name + "_Base";

        string baseShader    = compiler.Compile(keywords, baseName);
        string regularShader = compiler.Compile(keywords, name, baseName);

        System.IO.File.WriteAllText(shaderPath, regularShader);
        System.IO.File.WriteAllText(shaderBasePath, baseShader);

        compiler.GenerateAuxShaders(name, shaderPath, new List <string>(keywords));

        AssetDatabase.Refresh();
        Shader s = AssetDatabase.LoadAssetAtPath <Shader> (shaderPath);

        if (s == null)
        {
            Debug.LogError("Shader not found at path " + shaderPath);
        }
        Material m = new Material(s);

        AssetDatabase.CreateAsset(m, matPath);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        var kwds = MicroSplatUtilities.FindOrCreateKeywords(m);

        kwds.keywords = new List <string> (keywords);
        EditorUtility.SetDirty(kwds);
        var propData = MicroSplatShaderGUI.FindOrCreatePropTex(m);

        if (propData != null)
        {
            EditorUtility.SetDirty(propData);
        }
        AssetDatabase.SaveAssets();

        return(AssetDatabase.LoadAssetAtPath <Material> (matPath));
    }
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
    {
        Undo.undoRedoPerformed -= Undo_UndoRedoPerformed;
        Undo.undoRedoPerformed += Undo_UndoRedoPerformed;
        Material targetMat = materialEditor.target as Material;

        if (cachedTitle == null)
        {
            cachedTitle = "Shader Generator        v:" + MicroSplatVersion;
        }
        if (moduleLabelStyle == null)
        {
            moduleLabelStyle           = new GUIStyle(EditorStyles.foldout);
            moduleLabelStyle.fontStyle = FontStyle.Bold;
        }
        if (GUI.enabled == false || string.IsNullOrEmpty(AssetDatabase.GetAssetPath(targetMat)))
        {
            EditorGUILayout.HelpBox("You must edit the template material, not the instance being used. You can find this in the MicroSplatData directory, or assigned on your MicroSplatTerrain component", MessageType.Info);
            return;
        }
        EditorGUI.BeginChangeCheck(); // sync materials

        var keywordSO = MicroSplatUtilities.FindOrCreateKeywords(targetMat);

        cachedKeywordCount = keywordSO.keywords.Count; // for undo
        cachedMaterial     = targetMat;

        compiler.Init();
        // must unpack everything before the generator draws- otherwise we get IMGUI errors
        for (int i = 0; i < compiler.extensions.Count; ++i)
        {
            var ext = compiler.extensions[i];
            ext.Unpack(keywordSO.keywords.ToArray());
        }

        string shaderName = targetMat.shader.name;

        DrawModules();

        EditorGUI.BeginChangeCheck(); // needs compile
        var propTex = FindOrCreatePropTex(targetMat);

        Undo.RecordObjects(new Object[3] {
            targetMat, keywordSO, propTex
        }, "MicroSplat Material Edit");
        using (new GUILayout.VerticalScope(MicroSplatUtilities.boxStyle))
        {
            if (MicroSplatUtilities.DrawRollup(cachedTitle))
            {
                GUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                if (bulkEditMode)
                {
                    if (GUILayout.Button("Exit Bulk Shader Feature Edit Mode", GUILayout.Width(230)))
                    {
                        bulkEditMode = false;
                        needsCompile = true;
                    }
                }
                else
                {
                    if (GUILayout.Button("Enter Bulk Shader Feature Edit Mode", GUILayout.Width(230)))
                    {
                        bulkEditMode = true;
                    }
                }
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
                if (bulkEditMode)
                {
                    EditorGUILayout.HelpBox("Shader is in bulk edit mode, allowing you to change many options without recompiling the shader. No material properties will be shown during bulk editing, and the shader will be recompiled and properties shown once you exit this mode", MessageType.Warning);
                }

                shaderName = EditorGUILayout.DelayedTextField(CShaderName, shaderName);

                if (DrawRenderLoopGUI(keywordSO, targetMat))
                {
                    needsCompile = true;
                }

                for (int i = 0; i < compiler.extensions.Count; ++i)
                {
                    var e = compiler.extensions [i];
                    if (e.GetVersion() == MicroSplatVersion)
                    {
                        needsCompile = EditorGUI.EndChangeCheck() || needsCompile;

                        if (!moduleFoldoutState.ContainsKey(e.ModuleName()))
                        {
                            moduleFoldoutState [e.ModuleName()] = true;
                        }
                        // hack for invisible modules- need to make this more formal somehow?
                        bool empty = (e.ModuleName() == "Mesh" && (!keywordSO.IsKeywordEnabled("_MICROMESH") && !keywordSO.IsKeywordEnabled("_MICROVERTEXMESH")) || (e.ModuleName() == "MeshTerrain" && !keywordSO.IsKeywordEnabled("_MICROMESHTERRAIN")));


                        if (!empty)
                        {
                            using (new GUILayout.VerticalScope(MicroSplatUtilities.boxStyle))
                            {
                                EditorGUI.indentLevel++;
                                moduleFoldoutState [e.ModuleName()] = EditorGUILayout.Foldout(moduleFoldoutState [e.ModuleName()], e.ModuleName(), moduleLabelStyle);
                                //EditorGUILayout.LabelField (e.ModuleName (), moduleLabelStyle);
                                EditorGUI.BeginChangeCheck();
                                if (moduleFoldoutState [e.ModuleName()])
                                {
                                    //EditorGUI.indentLevel++;
                                    e.DrawFeatureGUI(keywordSO);
                                    //EditorGUI.indentLevel--;
                                }
                                EditorGUI.indentLevel--;
                            }
                        }
                        else
                        {
                            EditorGUI.BeginChangeCheck();
                        }
                    }
                    else
                    {
                        EditorGUILayout.HelpBox("Extension : " + e.ModuleName() + " is version " + e.GetVersion() + " and MicroSplat is version " + MicroSplatVersion + ", please update", MessageType.Error);
                    }
                }

                for (int i = 0; i < availableRenderLoops.Count; ++i)
                {
                    var rl = availableRenderLoops [i];
                    if (rl.GetVersion() != MicroSplatVersion)
                    {
                        EditorGUILayout.HelpBox("Render Loop : " + rl.GetDisplayName() + " is version " + rl.GetVersion() + " and MicroSplat is version " + MicroSplatVersion + ", please update", MessageType.Error);
                    }
                }
            }
            if (bulkEditMode)
            {
                if (!keywordSO.IsKeywordEnabled("_DISABLESPLATMAPS"))
                {
                    Texture2DArray diff = targetMat.GetTexture("_Diffuse") as Texture2DArray;
                    if (diff != null && MicroSplatUtilities.DrawRollup("Per Texture Properties"))
                    {
                        perTexIndex = MicroSplatUtilities.DrawTextureSelector(perTexIndex, diff);

                        for (int i = 0; i < compiler.extensions.Count; ++i)
                        {
                            var ext = compiler.extensions [i];
                            if (ext.GetVersion() == MicroSplatVersion)
                            {
                                ext.DrawPerTextureGUI(perTexIndex, keywordSO, targetMat, propTex);
                            }
                        }
                    }
                }

                needsCompile = needsCompile || EditorGUI.EndChangeCheck();
                if (needsCompile)
                {
                    keywordSO.keywords.Clear();
                    for (int i = 0; i < compiler.extensions.Count; ++i)
                    {
                        compiler.extensions [i].Pack(keywordSO);
                    }
                    if (compiler.renderLoop != null)
                    {
                        keywordSO.EnableKeyword(compiler.renderLoop.GetRenderLoopKeyword());
                    }
                    needsCompile = false;
                }
                return; // Don't draw rest of GUI
            }
        }
        needsCompile = needsCompile || EditorGUI.EndChangeCheck();

        int featureCount = keywordSO.keywords.Count;

        // note, ideally we wouldn't draw the GUI for the rest of stuff if we need to compile.
        // But we can't really do that without causing IMGUI to split warnings about
        // mismatched GUILayout blocks
        if (!needsCompile)
        {
            for (int i = 0; i < compiler.extensions.Count; ++i)
            {
                var ext = compiler.extensions [i];
                if (ext.GetVersion() == MicroSplatVersion)
                {
                    ext.DrawShaderGUI(this, keywordSO, targetMat, materialEditor, props);
                }
                else
                {
                    EditorGUILayout.HelpBox("Extension : " + ext.ModuleName() + " is version " + ext.GetVersion() + " and MicroSplat is version " + MicroSplatVersion + ", please update so that all modules are using the same version.", MessageType.Error);
                }
            }

            if (!keywordSO.IsKeywordEnabled("_DISABLESPLATMAPS"))
            {
                Texture2DArray diff = targetMat.GetTexture("_Diffuse") as Texture2DArray;
                if (diff != null && MicroSplatUtilities.DrawRollup("Per Texture Properties"))
                {
                    perTexIndex = MicroSplatUtilities.DrawTextureSelector(perTexIndex, diff);

                    for (int i = 0; i < compiler.extensions.Count; ++i)
                    {
                        var ext = compiler.extensions [i];
                        if (ext.GetVersion() == MicroSplatVersion)
                        {
                            ext.DrawPerTextureGUI(perTexIndex, keywordSO, targetMat, propTex);
                        }
                    }
                }
            }
        }

        if (!needsCompile)
        {
            if (featureCount != keywordSO.keywords.Count)
            {
                needsCompile = true;
            }
        }


        int arraySampleCount   = 0;
        int textureSampleCount = 0;
        int maxSamples         = 0;
        int tessSamples        = 0;
        int depTexReadLevel    = 0;

        builder.Length = 0;
        for (int i = 0; i < compiler.extensions.Count; ++i)
        {
            var ext = compiler.extensions [i];
            if (ext.GetVersion() == MicroSplatVersion)
            {
                ext.ComputeSampleCounts(keywordSO.keywords.ToArray(), ref arraySampleCount, ref textureSampleCount, ref maxSamples, ref tessSamples, ref depTexReadLevel);
            }
        }
        if (MicroSplatUtilities.DrawRollup("Debug"))
        {
            string shaderModel = compiler.GetShaderModel(keywordSO.keywords.ToArray());
            builder.Append("Shader Model : ");
            builder.AppendLine(shaderModel);
            if (maxSamples != arraySampleCount)
            {
                builder.Append("Texture Array Samples : ");
                builder.AppendLine(arraySampleCount.ToString());

                builder.Append("Regular Samples : ");
                builder.AppendLine(textureSampleCount.ToString());
            }
            else
            {
                builder.Append("Texture Array Samples : ");
                builder.AppendLine(arraySampleCount.ToString());
                builder.Append("Regular Samples : ");
                builder.AppendLine(textureSampleCount.ToString());
            }
            if (tessSamples > 0)
            {
                builder.Append("Tessellation Samples : ");
                builder.AppendLine(tessSamples.ToString());
            }
            if (depTexReadLevel > 0)
            {
                builder.Append(depTexReadLevel.ToString());
                builder.AppendLine(" areas with dependent texture reads");
            }

            EditorGUILayout.HelpBox(builder.ToString(), MessageType.Info);
        }

        if (EditorGUI.EndChangeCheck() && !needsCompile)
        {
            MicroSplatObject.SyncAll();
        }

        if (needsCompile)
        {
            needsCompile = false;
            keywordSO.keywords.Clear();
            for (int i = 0; i < compiler.extensions.Count; ++i)
            {
                compiler.extensions [i].Pack(keywordSO);
            }
            if (compiler.renderLoop != null)
            {
                keywordSO.EnableKeyword(compiler.renderLoop.GetRenderLoopKeyword());
            }

            // horrible workaround to GUI warning issues
            compileMat     = targetMat;
            compileName    = shaderName;
            targetCompiler = compiler;
            EditorApplication.delayCall += TriggerCompile;
        }
    }
Exemple #8
0
    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props)
    {
        if (cachedTitle == null)
        {
            cachedTitle = "Shader Generator        v:" + MicroSplatVersion;
        }
        if (GUI.enabled == false)
        {
            EditorGUILayout.HelpBox("You must edit the template material, not the instance being used", MessageType.Info);
            return;
        }
        EditorGUI.BeginChangeCheck(); // sync materials
        Material       targetMat = materialEditor.target as Material;
        Texture2DArray diff      = targetMat.GetTexture("_Diffuse") as Texture2DArray;

        var keywordSO = MicroSplatUtilities.FindOrCreateKeywords(targetMat);

        compiler.Init();
        // must unpack everything before the generator draws- otherwise we get IMGUI errors
        for (int i = 0; i < compiler.extensions.Count; ++i)
        {
            var ext = compiler.extensions[i];
            ext.Unpack(keywordSO.keywords.ToArray());
        }

        string shaderName = targetMat.shader.name;

        DrawModules();

        EditorGUI.BeginChangeCheck(); // needs compile

        if (MicroSplatUtilities.DrawRollup(cachedTitle))
        {
            shaderName = EditorGUILayout.DelayedTextField(CShaderName, shaderName);

            if (DrawRenderLoopGUI(keywordSO, targetMat))
            {
                needsCompile = true;
            }

            for (int i = 0; i < compiler.extensions.Count; ++i)
            {
                var e = compiler.extensions[i];
                if (e.GetVersion() == MicroSplatVersion)
                {
                    //using (new GUILayout.VerticalScope(GUI.skin.box))
                    {
                        e.DrawFeatureGUI(keywordSO);
                    }
                }
                else
                {
                    EditorGUILayout.HelpBox("Extension : " + e + " is version " + e.GetVersion() + " and MicroSplat is version " + MicroSplatVersion + ", please update", MessageType.Error);
                }
            }

            for (int i = 0; i < availableRenderLoops.Count; ++i)
            {
                var rl = availableRenderLoops[i];
                if (rl.GetVersion() != MicroSplatVersion)
                {
                    EditorGUILayout.HelpBox("Render Loop : " + rl.GetDisplayName() + " is version " + rl.GetVersion() + " and MicroSplat is version " + MicroSplatVersion + ", please update", MessageType.Error);
                }
            }
        }
        needsCompile = needsCompile || EditorGUI.EndChangeCheck();

        int featureCount = keywordSO.keywords.Count;

        // note, ideally we wouldn't draw the GUI for the rest of stuff if we need to compile.
        // But we can't really do that without causing IMGUI to split warnings about
        // mismatched GUILayout blocks
        if (!needsCompile)
        {
            for (int i = 0; i < compiler.extensions.Count; ++i)
            {
                var ext = compiler.extensions[i];
                if (ext.GetVersion() == MicroSplatVersion)
                {
                    ext.DrawShaderGUI(this, keywordSO, targetMat, materialEditor, props);
                }
                else
                {
                    EditorGUILayout.HelpBox("Extension : " + ext + " is version " + ext.GetVersion() + " and MicroSplat is version " + MicroSplatVersion + ", please update so that all modules are using the same version.", MessageType.Error);
                }
            }


            if (diff != null && MicroSplatUtilities.DrawRollup("Per Texture Properties"))
            {
                var propTex = FindOrCreatePropTex(targetMat);
                perTexIndex = MicroSplatUtilities.DrawTextureSelector(perTexIndex, diff);
                for (int i = 0; i < compiler.extensions.Count; ++i)
                {
                    var ext = compiler.extensions[i];
                    if (ext.GetVersion() == MicroSplatVersion)
                    {
                        ext.DrawPerTextureGUI(perTexIndex, keywordSO, targetMat, propTex);
                    }
                }
            }
        }

        if (!needsCompile)
        {
            if (featureCount != keywordSO.keywords.Count)
            {
                needsCompile = true;
            }
        }


        int arraySampleCount   = 0;
        int textureSampleCount = 0;
        int maxSamples         = 0;
        int tessSamples        = 0;
        int depTexReadLevel    = 0;

        builder.Length = 0;
        for (int i = 0; i < compiler.extensions.Count; ++i)
        {
            var ext = compiler.extensions[i];
            if (ext.GetVersion() == MicroSplatVersion)
            {
                ext.ComputeSampleCounts(keywordSO.keywords.ToArray(), ref arraySampleCount, ref textureSampleCount, ref maxSamples, ref tessSamples, ref depTexReadLevel);
            }
        }
        if (MicroSplatUtilities.DrawRollup("Debug"))
        {
            string shaderModel = compiler.GetShaderModel(keywordSO.keywords.ToArray());
            builder.Append("Shader Model : ");
            builder.AppendLine(shaderModel);
            if (maxSamples != arraySampleCount)
            {
                builder.Append("Texture Array Samples : ");
                builder.AppendLine(arraySampleCount.ToString());

                builder.Append("Regular Samples : ");
                builder.AppendLine(textureSampleCount.ToString());
            }
            else
            {
                builder.Append("Texture Array Samples : ");
                builder.AppendLine(arraySampleCount.ToString());
                builder.Append("Regular Samples : ");
                builder.AppendLine(textureSampleCount.ToString());
            }
            if (tessSamples > 0)
            {
                builder.Append("Tessellation Samples : ");
                builder.AppendLine(tessSamples.ToString());
            }
            if (depTexReadLevel > 0)
            {
                builder.Append(depTexReadLevel.ToString());
                builder.AppendLine(" areas with dependent texture reads");
            }

            EditorGUILayout.HelpBox(builder.ToString(), MessageType.Info);
        }

        if (EditorGUI.EndChangeCheck() && !needsCompile)
        {
            MicroSplatTerrain.SyncAll();
#if __MICROSPLAT_MESH__
            MicroSplatMesh.SyncAll();
#endif
        }

        if (needsCompile)
        {
            needsCompile = false;
            keywordSO.keywords.Clear();
            for (int i = 0; i < compiler.extensions.Count; ++i)
            {
                compiler.extensions[i].Pack(keywordSO);
            }
            if (compiler.renderLoop != null)
            {
                keywordSO.EnableKeyword(compiler.renderLoop.GetRenderLoopKeyword());
            }

            // horrible workaround to GUI warning issues
            compileMat     = targetMat;
            compileName    = shaderName;
            targetCompiler = compiler;
            EditorApplication.delayCall += TriggerCompile;
        }
    }