123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- /// <summary>
- /// Shader Control - (C) Copyright 2016-2022 Ramiro Oliva (Kronnect)
- /// </summary>
- ///
- using UnityEngine;
- using UnityEditor;
- using System;
- using System.IO;
- using System.Collections.Generic;
- using System.Text;
- namespace ShaderControl {
- public partial class SCWindow : EditorWindow {
- class KeywordView {
- public SCKeyword keyword;
- public List<SCShader> shaders;
- public bool foldout;
- }
- const string PRAGMA_COMMENT_MARK = "// Edited by Shader Control: ";
- const string PRAGMA_DISABLED_MARK = "// Disabled by Shader Control: ";
- const string BACKUP_SUFFIX = "_backup";
- const string PRAGMA_UNDERSCORE = "__ ";
- List<SCShader> shaders;
- Dictionary<int, SCShader> shadersDict;
- int minimumKeywordCount;
- int totalShaderCount;
- int maxKeywordsCountFound = 0;
- int totalKeywords, totalGlobalKeywords, totalVariants, totalUsedKeywords, totalBuildVariants, totalGlobalShaderFeatures, totalGlobalShaderFeaturesNonReadonly;
- int plusBuildKeywords;
- Dictionary<string, List<SCShader>> uniqueKeywords, uniqueEnabledKeywords;
- Dictionary<string, SCKeyword> keywordsDict;
- List<KeywordView> keywordView;
- List<BuildKeywordView> keywordViewExtra;
- readonly StringBuilder convertToLocalLog = new StringBuilder();
- #region Shader handling
- void ScanProject() {
- try {
- if (shaders == null) {
- shaders = new List<SCShader>();
- } else {
- shaders.Clear();
- }
- // Add shaders from Resources folder
- string[] guids = AssetDatabase.FindAssets("t:Shader");
- totalShaderCount = guids.Length;
- for (int k = 0; k < totalShaderCount; k++) {
- string guid = guids[k];
- string path = AssetDatabase.GUIDToAssetPath(guid);
- if (path != null) {
- string pathUpper = path.ToUpper();
- if (scanAllShaders || pathUpper.Contains("\\RESOURCES\\") || pathUpper.Contains("/RESOURCES/")) { // this shader will be included in build
- Shader unityShader = AssetDatabase.LoadAssetAtPath<Shader>(path);
- if (unityShader != null) {
- SCShader shader = new SCShader();
- shader.fullName = unityShader.name;
- shader.name = SCShader.GetSimpleName(shader.fullName); // Path.GetFileNameWithoutExtension(path);
- shader.path = path;
- shader.isReadOnly = path.Contains("Packages/com.unity") || IsFileReadonly(path);
- shader.GUID = unityShader.GetInstanceID();
- ScanShader(shader);
- if (shader.keywords.Count > 0) {
- shaders.Add(shader);
- }
- }
- }
- }
- }
- // Load and reference materials
- if (shadersDict == null) {
- shadersDict = new Dictionary<int, SCShader>(shaders.Count);
- } else {
- shadersDict.Clear();
- }
- shaders.ForEach(shader => {
- shadersDict[shader.GUID] = shader;
- });
- string[] matGuids = AssetDatabase.FindAssets("t:Material");
- if (projectMaterials == null) {
- projectMaterials = new List<SCMaterial>();
- } else {
- projectMaterials.Clear();
- }
- for (int k = 0; k < matGuids.Length; k++) {
- string matGUID = matGuids[k];
- string matPath = AssetDatabase.GUIDToAssetPath(matGUID);
- Material mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
- if (mat.shader == null)
- continue;
- SCMaterial scMat = new SCMaterial(mat, matPath, matGUID);
- scMat.SetKeywords(mat.shaderKeywords);
- if (mat.shaderKeywords != null && mat.shaderKeywords.Length > 0) {
- projectMaterials.Add(scMat);
- }
- string path = AssetDatabase.GetAssetPath(mat.shader);
- int shaderGUID = mat.shader.GetInstanceID();
- SCShader shader;
- if (!shadersDict.TryGetValue(shaderGUID, out shader)) {
- if (mat.shaderKeywords == null || mat.shaderKeywords.Length == 0)
- continue;
- Shader shad = AssetDatabase.LoadAssetAtPath<Shader>(path);
- // add non-sourced shader
- shader = new SCShader();
- shader.isReadOnly = path.Contains("Packages/com.unity") || IsFileReadonly(path);
- shader.GUID = shaderGUID;
- if (shad != null) {
- shader.fullName = shad.name;
- shader.name = SCShader.GetSimpleName(shader.fullName);
- if (string.IsNullOrEmpty(shader.name)) {
- shader.name = Path.GetFileNameWithoutExtension(path);
- }
- shader.path = path;
- ScanShader(shader);
- } else {
- shader.fullName = mat.shader.name;
- shader.name = SCShader.GetSimpleName(shader.fullName);
- }
- shaders.Add(shader);
- shadersDict[shaderGUID] = shader;
- totalShaderCount++;
- }
- shader.materials.Add(scMat);
- shader.AddKeywordsByName(mat.shaderKeywords);
- }
- // sort materials by name
- projectMaterials.Sort(CompareMaterialsName);
- // refresh variant and keywords count due to potential additional added keywords from materials (rogue keywords) and shader features count
- maxKeywordsCountFound = 0;
- shaders.ForEach((SCShader shader) => {
- if (shader.keywordEnabledCount > maxKeywordsCountFound) {
- maxKeywordsCountFound = shader.keywordEnabledCount;
- }
- shader.UpdateVariantCount();
- });
- switch (sortType) {
- case SortType.VariantsCount:
- shaders.Sort((SCShader x, SCShader y) => {
- return y.actualBuildVariantCount.CompareTo(x.actualBuildVariantCount);
- });
- break;
- case SortType.EnabledKeywordsCount:
- shaders.Sort((SCShader x, SCShader y) => {
- return y.keywordEnabledCount.CompareTo(x.keywordEnabledCount);
- });
- break;
- case SortType.ShaderFileName:
- shaders.Sort((SCShader x, SCShader y) => {
- return x.name.CompareTo(y.name);
- });
- break;
- }
- UpdateProjectStats();
- } catch (Exception ex) {
- Debug.LogError("Unexpected exception caught while scanning project: " + ex.Message);
- }
- }
- static int CompareMaterialsName(SCMaterial m1, SCMaterial m2) {
- return m1.unityMaterial.name.CompareTo(m2.unityMaterial.name);
- }
- void ScanShader(SCShader shader) {
- // Inits shader
- shader.passes.Clear();
- shader.keywords.Clear();
- shader.hasBackup = File.Exists(shader.path + BACKUP_SUFFIX);
- shader.pendingChanges = false;
- shader.editedByShaderControl = shader.hasBackup;
- if (shader.path.EndsWith(".shadergraph")) {
- shader.isShaderGraph = true;
- try {
- ScanShaderGraph(shader);
- } catch (Exception ex) {
- Debug.LogError("Couldn't analyze shader graph at " + shader.path + ". Error found: " + ex.ToString());
- }
- } else {
- try {
- ScanShaderNonGraph(shader);
- } catch (Exception ex) {
- Debug.LogError("Couldn't analyze shader at " + shader.path + ". Error found: " + ex.ToString());
- }
- }
- }
- void UpdateProjectStats() {
- totalKeywords = 0;
- totalGlobalKeywords = 0;
- totalUsedKeywords = 0;
- totalVariants = 0;
- totalBuildVariants = 0;
- totalGlobalShaderFeatures = 0;
- totalGlobalShaderFeaturesNonReadonly = 0;
- if (shaders == null)
- return;
- if (keywordsDict == null) {
- keywordsDict = new Dictionary<string, SCKeyword>();
- } else {
- keywordsDict.Clear();
- }
- if (uniqueKeywords == null) {
- uniqueKeywords = new Dictionary<string, List<SCShader>>();
- } else {
- uniqueKeywords.Clear();
- }
- if (uniqueEnabledKeywords == null) {
- uniqueEnabledKeywords = new Dictionary<string, List<SCShader>>();
- } else {
- uniqueEnabledKeywords.Clear();
- }
- int shadersCount = shaders.Count;
- for (int k = 0; k < shadersCount; k++) {
- SCShader shader = shaders[k];
- int keywordsCount = shader.keywords.Count;
- for (int w = 0; w < keywordsCount; w++) {
- SCKeyword keyword = shader.keywords[w];
- List<SCShader> shadersWithThisKeyword;
- if (!uniqueKeywords.TryGetValue(keyword.name, out shadersWithThisKeyword)) {
- shadersWithThisKeyword = new List<SCShader>();
- uniqueKeywords[keyword.name] = shadersWithThisKeyword;
- totalKeywords++;
- if (keyword.isGlobal) totalGlobalKeywords++;
- if (keyword.isGlobal && !keyword.isMultiCompile && keyword.enabled) {
- totalGlobalShaderFeatures++;
- if (!shader.isReadOnly) {
- totalGlobalShaderFeaturesNonReadonly++;
- }
- }
- keywordsDict[keyword.name] = keyword;
- }
- shadersWithThisKeyword.Add(shader);
- if (keyword.enabled) {
- List<SCShader> shadersWithThisKeywordEnabled;
- if (!uniqueEnabledKeywords.TryGetValue(keyword.name, out shadersWithThisKeywordEnabled)) {
- shadersWithThisKeywordEnabled = new List<SCShader>();
- uniqueEnabledKeywords[keyword.name] = shadersWithThisKeywordEnabled;
- totalUsedKeywords++;
- }
- shadersWithThisKeywordEnabled.Add(shader);
- }
- if (!shader.isReadOnly) {
- keyword.canBeModified = true;
- if (keyword.isGlobal && !keyword.isMultiCompile) {
- keyword.canBeConvertedToLocal = true;
- }
- }
- }
- totalVariants += shader.totalVariantCount;
- totalBuildVariants += shader.actualBuildVariantCount;
- }
- if (keywordView == null) {
- keywordView = new List<KeywordView>();
- } else {
- keywordView.Clear();
- }
- foreach (KeyValuePair<string, List<SCShader>> kvp in uniqueEnabledKeywords) {
- SCKeyword kw;
- if (!keywordsDict.TryGetValue(kvp.Key, out kw)) continue;
- KeywordView kv = new KeywordView { keyword = kw, shaders = kvp.Value };
- keywordView.Add(kv);
- }
- keywordView.Sort(delegate (KeywordView x, KeywordView y) {
- return y.shaders.Count.CompareTo(x.shaders.Count);
- });
- // Compute which keywords in build are not present in project
- if (keywordViewExtra == null) {
- keywordViewExtra = new List<BuildKeywordView>();
- } else {
- keywordViewExtra.Clear();
- }
- plusBuildKeywords = 0;
- if (buildKeywordView != null) {
- int count = buildKeywordView.Count;
- for (int k = 0; k < count; k++) {
- BuildKeywordView bkv = buildKeywordView[k];
- if (!uniqueKeywords.ContainsKey(bkv.keyword)) {
- keywordViewExtra.Add(bkv);
- plusBuildKeywords++;
- }
- }
- }
- }
- bool IsFileReadonly(string path) {
- FileStream stream = null;
- try {
- FileAttributes fileAttributes = File.GetAttributes(path);
- if ((fileAttributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) {
- return true;
- }
- FileInfo file = new FileInfo(path);
- stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
- } catch {
- //the file is unavailable because it is:
- //still being written to
- //or being processed by another thread
- //or does not exist (has already been processed)
- return true;
- } finally {
- if (stream != null)
- stream.Close();
- }
- //file is not locked
- return false;
- }
- void MakeBackup(SCShader shader) {
- string backupPath = shader.path + BACKUP_SUFFIX;
- if (!File.Exists(backupPath)) {
- AssetDatabase.CopyAsset(shader.path, backupPath);
- shader.hasBackup = true;
- }
- }
- void UpdateShader(SCShader shader) {
- if (shader.isReadOnly) {
- EditorUtility.DisplayDialog("Locked file", "Shader file " + shader.name + " is read-only.", "Ok");
- return;
- }
- try {
- // Create backup
- MakeBackup(shader);
- if (shader.isShaderGraph) {
- UpdateShaderGraph(shader);
- } else {
- UpdateShaderNonGraph(shader);
- }
- // Also update materials
- CleanMaterials(shader);
- ScanShader(shader); // Rescan shader
- // do not include in build (sync with Build View)
- BuildUpdateShaderKeywordsState(shader);
- } catch (Exception ex) {
- Debug.LogError("Unexpected exception caught while updating shader: " + ex.Message);
- }
- }
- void RestoreShader(SCShader shader) {
- try {
- string shaderBackupPath = shader.path + BACKUP_SUFFIX;
- if (!File.Exists(shaderBackupPath)) {
- EditorUtility.DisplayDialog("Restore shader", "Shader backup is missing!", "OK");
- return;
- }
- File.Copy(shaderBackupPath, shader.path, true);
- File.Delete(shaderBackupPath);
- if (File.Exists(shaderBackupPath + ".meta"))
- File.Delete(shaderBackupPath + ".meta");
- AssetDatabase.Refresh();
- ScanShader(shader); // Rescan shader
- UpdateProjectStats();
- } catch (Exception ex) {
- Debug.LogError("Unexpected exception caught while restoring shader: " + ex.Message);
- }
- }
- void DeleteShader(SCShader shader) {
- try {
- if (File.Exists(shader.path)) {
- File.Delete(shader.path);
- } else {
- EditorUtility.DisplayDialog("Error", "Shader file was not found at " + shader.path + "!?", "Weird");
- return;
- }
- File.Delete(shader.path);
- if (File.Exists(shader.path + ".meta")) {
- File.Delete(shader.path + ".meta");
- }
- AssetDatabase.Refresh();
- ScanProject();
- } catch (Exception ex) {
- Debug.LogError("Unexpected exception caught while deleting shader: " + ex.Message);
- }
- }
- void ConvertToLocalStarted() {
- convertToLocalLog.Length = 0;
- }
- void ConvertToLocalFinished() {
- if (convertToLocalLog.Length > 0) {
- EditorUtility.DisplayDialog("Convert To Local Keyword", "The operation finished with the following results:\n\n" + convertToLocalLog.ToString(), "Ok");
- } else {
- EditorUtility.DisplayDialog("Convert To Local Keyword", "The operation finished successfully.", "Ok");
- }
- AssetDatabase.Refresh();
- }
- void ConvertToLocal(SCKeyword keyword) {
- List<SCShader> shaders;
- if (!uniqueKeywords.TryGetValue(keyword.name, out shaders)) return;
- if (shaders == null) return;
- StringBuilder sb = new StringBuilder();
- for (int k = 0; k < shaders.Count; k++) {
- SCShader shader = shaders[k];
- if (shader.isReadOnly) {
- if (sb.Length > 0) {
- sb.Append(", ");
- }
- sb.Append(shader.name);
- } else {
- ConvertToLocal(keyword, shaders[k]);
- }
- }
- if (sb.Length > 0) {
- convertToLocalLog.AppendLine("The following shaders couldn't be modified because they're read-only: " + sb.ToString());
- }
- }
- void ConvertToLocal(SCKeyword keyword, SCShader shader) {
- // Check total local keyword does not exceed 64 limit
- int potentialCount = 0;
- int kwCount = shader.keywords.Count;
- for (int k = 0; k < kwCount; k++) {
- SCKeyword kw = shader.keywords[k];
- if (!kw.isMultiCompile) potentialCount++;
- }
- if (potentialCount > 64) return;
- if (shader.isReadOnly) {
- convertToLocalLog.AppendLine("The keyword " + keyword.name + " can't be converted to local in shader " + shader.name + " at " + shader.path + " because file is readonly.");
- return;
- }
- if (shader.isShaderGraph) {
- ConvertToLocalGraph(keyword, shader);
- } else {
- ConvertToLocalNonGraph(keyword, shader);
- }
- }
- void ConvertToLocalAll() {
- int kvCount = keywordView.Count;
- ConvertToLocalStarted();
- for (int s = 0; s < kvCount; s++) {
- SCKeyword keyword = keywordView[s].keyword;
- if (keyword.isGlobal && !keyword.isMultiCompile) {
- ConvertToLocal(keyword);
- }
- }
- ConvertToLocalFinished();
- }
- SCShader GetShaderByName(string shaderName) {
- if (shaders == null) {
- ScanProject();
- }
- foreach (SCShader shader in shaders) {
- if (shader.fullName.Equals(shaderName)) {
- return shader;
- }
- }
- return null;
- }
- #endregion
- }
- }
|