SCWindow.Project.GUI.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. /// <summary>
  2. /// Shader Control - (C) Copyright 2016-2022 Ramiro Oliva (Kronnect)
  3. /// </summary>
  4. ///
  5. using UnityEngine;
  6. using UnityEditor;
  7. using System;
  8. using System.Collections.Generic;
  9. namespace ShaderControl {
  10. public partial class SCWindow : EditorWindow {
  11. enum SortType {
  12. VariantsCount = 0,
  13. EnabledKeywordsCount = 1,
  14. ShaderFileName = 2,
  15. Keyword = 3,
  16. Material = 4
  17. }
  18. enum KeywordScopeFilter {
  19. Any = 0,
  20. GlobalKeywords = 1,
  21. LocalKeywords = 2
  22. }
  23. enum PragmaTypeFilter {
  24. Any = 0,
  25. MultiCompile = 1,
  26. ShaderFeature = 2
  27. }
  28. enum ModifiedStatus {
  29. Any = 0,
  30. OnlyModified = 1,
  31. NonModified = 2
  32. }
  33. SortType sortType = SortType.VariantsCount;
  34. GUIStyle blackStyle, commentStyle, disabledStyle, foldoutBold, foldoutNormal, foldoutDim, foldoutRTF;
  35. GUIStyle titleStyle, redButtonStyle, labelDim, labelNormal;
  36. Vector2 scrollViewPosProject;
  37. bool firstTime;
  38. GUIContent matIcon, shaderIcon;
  39. bool scanAllShaders;
  40. string keywordFilter;
  41. KeywordScopeFilter keywordScopeFilter;
  42. PragmaTypeFilter pragmaTypeFilter;
  43. ModifiedStatus modifiedStatus;
  44. bool showShadersNotUsedInBuild;
  45. bool showKeywordsNotUsedInBuild;
  46. bool hideReadOnlyShaders;
  47. string projectShaderNameFilter;
  48. string projectShaderPathFilter;
  49. void DrawProjectGUI() {
  50. GUILayout.Box(new GUIContent("List of shader files in your project and modify their keywords.\nOnly shaders with source code or referenced by materials are included in this tab.\nUse the <b>Build View</b> tab for a list of shaders and keywords used in your build, including hidden/internal Unity shaders."), titleStyle, GUILayout.ExpandWidth(true));
  51. EditorGUILayout.BeginHorizontal();
  52. GUILayout.FlexibleSpace();
  53. scanAllShaders = EditorGUILayout.Toggle(new GUIContent("Force scan all shaders", "Also includes shaders that are not located within a Resources folder"), scanAllShaders);
  54. EditorGUILayout.EndHorizontal();
  55. EditorGUILayout.BeginHorizontal();
  56. if (GUILayout.Button(new GUIContent("Scan Project Folders", "Quickly scans the project and finds shaders that use keywords."))) {
  57. ScanProject();
  58. GUIUtility.ExitGUI();
  59. return;
  60. }
  61. if (shaders != null && shaders.Count > 0 && GUILayout.Button(new GUIContent("Clean All Materials", "Removes all disabled keywords in all materials. This option is provided to ensure no existing materials are referencing a disabled shader keyword.\n\nTo disable keywords, first expand any shader from the list and uncheck the unwanted keywords (press 'Save' to modify the shader file and to clean any existing material that uses that specific shader)."), GUILayout.Width(130))) {
  62. CleanAllMaterials();
  63. GUIUtility.ExitGUI();
  64. return;
  65. }
  66. if (GUILayout.Button("Help", GUILayout.Width(40))) {
  67. ShowHelpWindowProjectView();
  68. }
  69. EditorGUILayout.EndHorizontal();
  70. EditorGUILayout.EndVertical();
  71. if (shaders == null) {
  72. return;
  73. }
  74. string totalGlobalKeywordsTxt = totalGlobalKeywords != totalKeywords ? " (" + totalGlobalKeywords + " global) " : " ";
  75. string statsText;
  76. if (totalKeywords == totalUsedKeywords || totalKeywords == 0) {
  77. statsText = "Shaders Found: " + totalShaderCount.ToString() + " Shaders Using Keywords: " + shaders.Count.ToString() + "\nKeywords: " + totalKeywords.ToString() + totalGlobalKeywordsTxt + " Variants: " + totalVariants.ToString();
  78. } else {
  79. int keywordsPerc = totalUsedKeywords * 100 / totalKeywords;
  80. int variantsPerc = totalBuildVariants * 100 / totalVariants;
  81. statsText = "Shaders Found: " + totalShaderCount.ToString() + " Shaders Using Keywords: " + shaders.Count.ToString() + "\nUsed Keywords: " + totalUsedKeywords.ToString() + " of " + totalKeywords.ToString() + " (" + keywordsPerc.ToString() + "%) " + totalGlobalKeywordsTxt + " Actual Variants: " + totalBuildVariants.ToString() + " of " + totalVariants.ToString() + " (" + variantsPerc.ToString() + "%)";
  82. }
  83. if (totalBuildShaders == 0) {
  84. statsText += "\nNote: Total keyword count maybe higher. Use 'Build View' tab to detect additional shaders/keywords used by Unity.";
  85. } else if (plusBuildKeywords > 0) {
  86. statsText += "\n+ " + plusBuildKeywords + " additional keywords detected in last build.";
  87. }
  88. EditorGUILayout.HelpBox(statsText, MessageType.Info);
  89. EditorGUILayout.Separator();
  90. int shaderCount = shaders.Count;
  91. if (shaderCount > 0) {
  92. EditorGUILayout.BeginHorizontal();
  93. EditorGUILayout.LabelField("Sort By", GUILayout.Width(100));
  94. SortType prevSortType = sortType;
  95. sortType = (SortType)EditorGUILayout.EnumPopup(sortType);
  96. if (sortType != prevSortType) {
  97. ScanProject();
  98. GUIUtility.ExitGUI();
  99. return;
  100. }
  101. EditorGUILayout.EndHorizontal();
  102. if (sortType != SortType.Keyword) {
  103. EditorGUILayout.BeginHorizontal();
  104. EditorGUILayout.LabelField("Shader Name", GUILayout.Width(100));
  105. projectShaderNameFilter = EditorGUILayout.TextField(projectShaderNameFilter);
  106. if (GUILayout.Button(new GUIContent("Clear", "Clear filter."), EditorStyles.miniButton, GUILayout.Width(60))) {
  107. projectShaderNameFilter = "";
  108. GUIUtility.keyboardControl = 0;
  109. }
  110. EditorGUILayout.EndHorizontal();
  111. EditorGUILayout.BeginHorizontal();
  112. EditorGUILayout.LabelField("Path", GUILayout.Width(100));
  113. projectShaderPathFilter = EditorGUILayout.TextField(projectShaderPathFilter);
  114. if (GUILayout.Button(new GUIContent("Clear", "Clear filter."), EditorStyles.miniButton, GUILayout.Width(60))) {
  115. projectShaderPathFilter = "";
  116. GUIUtility.keyboardControl = 0;
  117. }
  118. EditorGUILayout.EndHorizontal();
  119. EditorGUILayout.BeginHorizontal();
  120. EditorGUILayout.LabelField("Keywords >=", GUILayout.Width(100));
  121. minimumKeywordCount = EditorGUILayout.IntSlider(minimumKeywordCount, 0, maxKeywordsCountFound);
  122. EditorGUILayout.EndHorizontal();
  123. }
  124. EditorGUILayout.BeginHorizontal();
  125. EditorGUILayout.LabelField("Keyword Name", GUILayout.Width(100));
  126. keywordFilter = EditorGUILayout.TextField(keywordFilter);
  127. if (GUILayout.Button(new GUIContent("Clear", "Clear filter."), EditorStyles.miniButton, GUILayout.Width(60))) {
  128. keywordFilter = "";
  129. GUIUtility.keyboardControl = 0;
  130. }
  131. EditorGUILayout.EndHorizontal();
  132. if (sortType != SortType.Material) {
  133. EditorGUILayout.BeginHorizontal();
  134. EditorGUILayout.LabelField("Keyword Scope", GUILayout.Width(100));
  135. keywordScopeFilter = (KeywordScopeFilter)EditorGUILayout.EnumPopup(keywordScopeFilter);
  136. EditorGUILayout.EndHorizontal();
  137. EditorGUILayout.BeginHorizontal();
  138. EditorGUILayout.LabelField("Pragma Type", GUILayout.Width(100));
  139. pragmaTypeFilter = (PragmaTypeFilter)EditorGUILayout.EnumPopup(pragmaTypeFilter);
  140. EditorGUILayout.EndHorizontal();
  141. if (sortType != SortType.Keyword) {
  142. EditorGUILayout.BeginHorizontal();
  143. EditorGUILayout.LabelField("Modified", GUILayout.Width(100));
  144. modifiedStatus = (ModifiedStatus)EditorGUILayout.EnumPopup(modifiedStatus);
  145. EditorGUILayout.EndHorizontal();
  146. showShadersNotUsedInBuild = EditorGUILayout.ToggleLeft("List shaders not used in last build", showShadersNotUsedInBuild);
  147. if (showShadersNotUsedInBuild) {
  148. if (totalBuildShaders == 0) {
  149. EditorGUILayout.HelpBox("No build data yet. Perform a build to detect which shaders are being included in your build. Use 'Quick Build' from the Build View tab to make a fast build.", MessageType.Error);
  150. }
  151. }
  152. } else {
  153. showKeywordsNotUsedInBuild = EditorGUILayout.ToggleLeft("List keywords not used in last build", showKeywordsNotUsedInBuild);
  154. }
  155. if (sortType != SortType.Material) {
  156. hideReadOnlyShaders = EditorGUILayout.ToggleLeft("Hide read-only/internal shaders", hideReadOnlyShaders);
  157. }
  158. if (keywordScopeFilter != KeywordScopeFilter.GlobalKeywords || pragmaTypeFilter != PragmaTypeFilter.ShaderFeature) {
  159. if (totalGlobalShaderFeaturesNonReadonly > 0) {
  160. if (GUILayout.Button(new GUIContent("Click to show " + totalGlobalShaderFeaturesNonReadonly + " global keyword(s) found of type 'shader_feature'", "Lists all shaders using global keywords that can be converted to local safely."))) {
  161. sortType = SortType.Keyword;
  162. keywordScopeFilter = KeywordScopeFilter.GlobalKeywords;
  163. pragmaTypeFilter = PragmaTypeFilter.ShaderFeature;
  164. keywordFilter = "";
  165. hideReadOnlyShaders = true;
  166. GUIUtility.keyboardControl = 0;
  167. minimumKeywordCount = 0;
  168. }
  169. }
  170. } else {
  171. if (sortType == SortType.Keyword && totalGlobalShaderFeaturesNonReadonly > 1) {
  172. if (GUILayout.Button(new GUIContent("Convert " + totalGlobalShaderFeaturesNonReadonly + " keyword(s) to local", "Converts all global keywords of type Shader_Feature to local keywords, reducing the total keyword usage count.\n A local keyword does not count towards the limit of 256 total keywords in project."))) {
  173. if (EditorUtility.DisplayDialog("Convert " + totalGlobalShaderFeaturesNonReadonly + " global keyword(s) to local", "The keywords will be converted to local. This means these keywords won't count toward the maximum global keyword limit (256). Continue?", "Ok", "Cancel")) {
  174. ConvertToLocalAll();
  175. EditorUtility.DisplayDialog("Process complete!", "Please restart Unity to refresh keyword changes.", "Ok");
  176. ScanProject();
  177. GUIUtility.ExitGUI();
  178. }
  179. }
  180. }
  181. }
  182. }
  183. }
  184. EditorGUILayout.Separator();
  185. scrollViewPosProject = EditorGUILayout.BeginScrollView(scrollViewPosProject);
  186. bool usesShaderNameFilter = !string.IsNullOrEmpty(projectShaderNameFilter);
  187. bool usesShaderPathFilter = !string.IsNullOrEmpty(projectShaderPathFilter);
  188. bool needsTitle = true;
  189. if (sortType == SortType.Material) {
  190. if (projectMaterials != null) {
  191. int matCount = projectMaterials.Count;
  192. for (int s = 0; s < matCount; s++) {
  193. SCMaterial mat = projectMaterials[s];
  194. int keywordCount = mat.keywords.Count;
  195. if (keywordCount < minimumKeywordCount) continue;
  196. if (usesShaderNameFilter && mat.unityMaterial.name.IndexOf(projectShaderNameFilter, StringComparison.InvariantCultureIgnoreCase) < 0)
  197. continue;
  198. if (needsTitle) {
  199. needsTitle = false;
  200. GUILayout.Label("Materials found:");
  201. }
  202. mat.foldout = EditorGUILayout.Foldout(mat.foldout, new GUIContent("Material <b>" + mat.unityMaterial.name + "</b> referencing " + keywordCount + " keywords."), foldoutRTF);
  203. if (mat.foldout) {
  204. for (int m = 0; m < keywordCount; m++) {
  205. EditorGUILayout.BeginHorizontal();
  206. EditorGUILayout.LabelField("", GUILayout.Width(30));
  207. EditorGUILayout.LabelField(shaderIcon, GUILayout.Width(18));
  208. EditorGUILayout.LabelField(mat.keywords[m].name);
  209. if (GUILayout.Button(new GUIContent("Locate", "Locates material in project."), EditorStyles.miniButton, GUILayout.Width(60))) {
  210. Selection.activeObject = mat.unityMaterial;
  211. EditorGUIUtility.PingObject(mat.unityMaterial);
  212. }
  213. if (GUILayout.Button(new GUIContent("Prune", "Removes keyword from this material."), EditorStyles.miniButton, GUILayout.Width(60))) {
  214. if (EditorUtility.DisplayDialog("Prune Keyword", "Remove this keyword from the material?", "Yes", "No")) {
  215. mat.unityMaterial.DisableKeyword(mat.keywords[m].name);
  216. EditorUtility.SetDirty(mat.unityMaterial);
  217. mat.RemoveKeyword(mat.keywords[m].name);
  218. GUIUtility.ExitGUI();
  219. return;
  220. }
  221. }
  222. if (GUILayout.Button(new GUIContent("Prune All", "Removes this keyword from all materials."), EditorStyles.miniButton, GUILayout.Width(80))) {
  223. if (EditorUtility.DisplayDialog("Prune All", "Remove this keyword from all materials?", "Yes", "No")) {
  224. PruneMaterials(mat.keywords[m].name);
  225. UpdateProjectStats();
  226. GUIUtility.ExitGUI();
  227. return;
  228. }
  229. }
  230. EditorGUILayout.EndHorizontal();
  231. }
  232. }
  233. }
  234. }
  235. } else if (sortType == SortType.Keyword) {
  236. if (keywordView != null) {
  237. int kvCount = keywordView.Count;
  238. bool usesKeywordFilter = !string.IsNullOrEmpty(keywordFilter);
  239. int rowNumber = 1;
  240. for (int s = 0; s < kvCount; s++) {
  241. KeywordView kv = keywordView[s];
  242. SCKeyword keyword = kv.keyword;
  243. if (hideReadOnlyShaders && !keyword.canBeModified) continue;
  244. if (keywordScopeFilter != KeywordScopeFilter.Any && ((keywordScopeFilter == KeywordScopeFilter.GlobalKeywords && !keyword.isGlobal) || (keywordScopeFilter == KeywordScopeFilter.LocalKeywords && keyword.isGlobal))) continue;
  245. if (pragmaTypeFilter != PragmaTypeFilter.Any && ((pragmaTypeFilter == PragmaTypeFilter.MultiCompile && !keyword.isMultiCompile) || (pragmaTypeFilter == PragmaTypeFilter.ShaderFeature && keyword.isMultiCompile))) continue;
  246. if (usesKeywordFilter && keyword.name.IndexOf(keywordFilter, StringComparison.InvariantCultureIgnoreCase) < 0)
  247. continue;
  248. if (showKeywordsNotUsedInBuild && totalBuildShaders > 0) {
  249. bool included = false;
  250. for (int sb = 0; sb < totalBuildShaders; sb++) {
  251. ShaderBuildInfo sbi = shadersBuildInfo.shaders[sb];
  252. int count = sbi.keywords != null ? sbi.keywords.Count : 0;
  253. for (int kb = 0; kb < count; kb++) {
  254. if (sbi.keywords[kb].includeInBuild && sbi.keywords[kb].keyword.Equals(keyword.name)) {
  255. included = true;
  256. break;
  257. }
  258. }
  259. if (included) break;
  260. }
  261. if (included) continue;
  262. }
  263. if (needsTitle) {
  264. needsTitle = false;
  265. GUILayout.Label("Keywords found:");
  266. }
  267. EditorGUILayout.BeginHorizontal();
  268. int kvShadersCount = kv.shaders.Count;
  269. sb.Length = 0;
  270. sb.Append("Keyword ");
  271. sb.Append(rowNumber++);
  272. sb.Append(": <b>");
  273. sb.Append(kv.keyword);
  274. sb.Append("</b> found in ");
  275. sb.Append(kvShadersCount);
  276. if (!keyword.canBeConvertedToLocal) {
  277. sb.Append(" read-only");
  278. }
  279. sb.Append(" shader(s)");
  280. kv.foldout = EditorGUILayout.Foldout(kv.foldout, new GUIContent(sb.ToString()), foldoutRTF);
  281. GUILayout.FlexibleSpace();
  282. if (keyword.canBeConvertedToLocal) {
  283. if (GUILayout.Button("Convert To Local Keyword", EditorStyles.miniButtonRight, GUILayout.Width(190))) {
  284. if (EditorUtility.DisplayDialog("Convert global keyword to local", "Keyword " + keyword.name + " will be converted to local. This means the keyword won't count toward the maximum global keyword limit (256). Continue?", "Ok", "Cancel")) {
  285. ConvertToLocalStarted();
  286. ConvertToLocal(keyword);
  287. ConvertToLocalFinished();
  288. ScanProject();
  289. GUIUtility.ExitGUI();
  290. }
  291. }
  292. } else if (!keyword.isGlobal) {
  293. GUILayout.Label("(Local keyword)");
  294. }
  295. EditorGUILayout.EndHorizontal();
  296. if (kv.foldout) {
  297. for (int m = 0; m < kvShadersCount; m++) {
  298. SCShader shader = kv.shaders[m];
  299. if (hideReadOnlyShaders && shader.isReadOnly) continue;
  300. EditorGUILayout.BeginHorizontal();
  301. EditorGUILayout.LabelField("", GUILayout.Width(30));
  302. EditorGUILayout.LabelField(shaderIcon, GUILayout.Width(18));
  303. string shaderName = shader.name;
  304. if (shader.isReadOnly) shaderName += " (read-only)";
  305. GUIStyle shaderNameStyle = shader.isReadOnly ? labelDim : labelNormal;
  306. EditorGUILayout.LabelField(shaderName, shaderNameStyle);
  307. GUI.enabled = shader.hasSource;
  308. if (GUILayout.Button(new GUIContent("Locate", "Locates the shader in the project panel."), EditorStyles.miniButton, GUILayout.Width(60))) {
  309. Shader theShader = AssetDatabase.LoadAssetAtPath<Shader>(shader.path);
  310. Selection.activeObject = theShader;
  311. EditorGUIUtility.PingObject(theShader);
  312. }
  313. GUI.enabled = true;
  314. if (GUILayout.Button(new GUIContent("Filter", "Show shaders using this keyword."), EditorStyles.miniButton, GUILayout.Width(60))) {
  315. keywordFilter = kv.keyword.name;
  316. sortType = SortType.VariantsCount;
  317. GUIUtility.ExitGUI();
  318. return;
  319. }
  320. if (!shader.hasBackup)
  321. GUI.enabled = false;
  322. if (GUILayout.Button(new GUIContent("Restore", "Restores shader from the backup copy."), EditorStyles.miniButton, GUILayout.Width(70))) {
  323. RestoreShader(shader);
  324. GUIUtility.ExitGUI();
  325. return;
  326. }
  327. GUI.enabled = true;
  328. EditorGUILayout.EndHorizontal();
  329. }
  330. }
  331. }
  332. }
  333. int bkvCount = !hideReadOnlyShaders && !showKeywordsNotUsedInBuild && keywordViewExtra != null ? keywordViewExtra.Count : 0;
  334. if (bkvCount > 0) {
  335. needsTitle = true;
  336. EditorGUILayout.Separator();
  337. bool usesKeywordFilter = !string.IsNullOrEmpty(keywordFilter);
  338. int rowNumber = 1;
  339. for (int s = 0; s < bkvCount; s++) {
  340. BuildKeywordView bkv = keywordViewExtra[s];
  341. string keywordName = bkv.keyword;
  342. if (usesKeywordFilter && keywordName.IndexOf(keywordFilter, StringComparison.InvariantCultureIgnoreCase) < 0)
  343. continue;
  344. if (needsTitle) {
  345. needsTitle = false;
  346. GUILayout.Label("Additional keywords used in last build:");
  347. }
  348. EditorGUILayout.BeginHorizontal();
  349. keywordViewExtra[s].foldout = EditorGUILayout.Foldout(bkv.foldout, new GUIContent("Keyword " + (rowNumber++) + ": <b>" + keywordName + "</b> found in " + bkv.shaders.Count + " shader(s)"), foldoutRTF);
  350. EditorGUILayout.EndHorizontal();
  351. if (bkv.foldout) {
  352. int kvShadersCount = bkv.shaders.Count;
  353. for (int m = 0; m < kvShadersCount; m++) {
  354. EditorGUILayout.BeginHorizontal();
  355. EditorGUILayout.LabelField("", GUILayout.Width(30));
  356. EditorGUILayout.LabelField(shaderIcon, GUILayout.Width(18));
  357. ShaderBuildInfo shader = bkv.shaders[m];
  358. string shaderName = shader.name;
  359. if (shader.isInternal) shaderName += " (internal)";
  360. GUIStyle shaderNameStyle = shader.isInternal ? labelDim : labelNormal;
  361. EditorGUILayout.LabelField(shaderName, shaderNameStyle);
  362. EditorGUILayout.EndHorizontal();
  363. }
  364. }
  365. }
  366. }
  367. } else {
  368. int shadersListedCount = 0;
  369. bool filterByKeywordName = !string.IsNullOrEmpty(keywordFilter);
  370. for (int s = 0; s < shaderCount; s++) {
  371. SCShader shader = shaders[s];
  372. if (hideReadOnlyShaders && shader.isReadOnly) continue;
  373. if (shader.keywordEnabledCount < minimumKeywordCount)
  374. continue;
  375. if (modifiedStatus == ModifiedStatus.OnlyModified && !shader.editedByShaderControl) continue;
  376. if (modifiedStatus == ModifiedStatus.NonModified && shader.editedByShaderControl) continue;
  377. if (filterByKeywordName || keywordScopeFilter != KeywordScopeFilter.Any || pragmaTypeFilter != PragmaTypeFilter.Any) {
  378. int kwCount = shader.keywords.Count;
  379. bool found = false;
  380. for (int w = 0; w < kwCount; w++) {
  381. found = true;
  382. SCKeyword keyword = shader.keywords[w];
  383. if (filterByKeywordName) {
  384. if (keyword.name.IndexOf(keywordFilter, StringComparison.InvariantCultureIgnoreCase) < 0) {
  385. found = false;
  386. }
  387. }
  388. if (keywordScopeFilter != KeywordScopeFilter.Any && ((keywordScopeFilter == KeywordScopeFilter.GlobalKeywords && !keyword.isGlobal) || (keywordScopeFilter == KeywordScopeFilter.LocalKeywords && keyword.isGlobal))) {
  389. found = false;
  390. }
  391. if (pragmaTypeFilter != PragmaTypeFilter.Any && ((pragmaTypeFilter == PragmaTypeFilter.MultiCompile && !keyword.isMultiCompile) || (pragmaTypeFilter == PragmaTypeFilter.ShaderFeature && keyword.isMultiCompile))) {
  392. found = false;
  393. }
  394. if (found) break;
  395. }
  396. if (!found)
  397. continue;
  398. }
  399. if (usesShaderNameFilter && shader.name.IndexOf(projectShaderNameFilter, StringComparison.InvariantCultureIgnoreCase) < 0) {
  400. continue;
  401. }
  402. if (usesShaderPathFilter && shader.path.IndexOf(projectShaderPathFilter, StringComparison.InvariantCultureIgnoreCase) < 0) {
  403. continue;
  404. }
  405. if (showShadersNotUsedInBuild && totalBuildShaders > 0) {
  406. bool included = false;
  407. for (int sb = 0; sb < totalBuildShaders; sb++) {
  408. if (shadersBuildInfo.shaders[sb].name.Equals(shader.fullName)) {
  409. included = true;
  410. break;
  411. }
  412. }
  413. if (included) continue;
  414. }
  415. if (needsTitle) {
  416. needsTitle = false;
  417. GUILayout.Label("Shaders found:");
  418. }
  419. shadersListedCount++;
  420. EditorGUILayout.BeginHorizontal();
  421. string shaderName = shader.isReadOnly ? shader.name + " (read-only)" : shader.name;
  422. if (shader.hasSource) {
  423. shader.foldout = EditorGUILayout.Foldout(shader.foldout, new GUIContent(shaderName + " (" + shader.keywords.Count + " keywords, " + shader.keywordEnabledCount + " used, " + shader.actualBuildVariantCount + " variants)"), shader.editedByShaderControl ? foldoutBold : foldoutNormal);
  424. } else {
  425. shader.foldout = EditorGUILayout.Foldout(shader.foldout, new GUIContent(shaderName + " (" + shader.keywordEnabledCount + " keywords used by materials)"), foldoutDim);
  426. }
  427. EditorGUILayout.EndHorizontal();
  428. if (shader.foldout) {
  429. EditorGUILayout.BeginHorizontal();
  430. EditorGUILayout.LabelField("", GUILayout.Width(15));
  431. if (shader.hasSource) {
  432. if (GUILayout.Button(new GUIContent("Locate", "Locate the shader in the project panel."), EditorStyles.miniButton)) {
  433. Shader theShader = AssetDatabase.LoadAssetAtPath<Shader>(shader.path);
  434. Selection.activeObject = theShader;
  435. EditorGUIUtility.PingObject(theShader);
  436. }
  437. if (!shader.isShaderGraph) {
  438. if (GUILayout.Button(new GUIContent("Open", "Open the shader with the system default editor."), EditorStyles.miniButton)) {
  439. EditorUtility.OpenWithDefaultApp(shader.path);
  440. }
  441. if (!shader.pendingChanges)
  442. GUI.enabled = false;
  443. if (GUILayout.Button(new GUIContent("Save", "Saves any keyword change to the shader file (disable/enable the keywords just clicking on the toggle next to the keywords shown below). A backup is created in the same folder."), EditorStyles.miniButton)) {
  444. UpdateShader(shader);
  445. }
  446. GUI.enabled = true;
  447. }
  448. if (!shader.hasBackup)
  449. GUI.enabled = false;
  450. if (GUILayout.Button(new GUIContent("Restore", "Restores shader from the backup copy."), EditorStyles.miniButton)) {
  451. RestoreShader(shader);
  452. GUIUtility.ExitGUI();
  453. return;
  454. }
  455. GUI.enabled = true;
  456. } else {
  457. EditorGUILayout.LabelField("(Shader source not available)");
  458. }
  459. if (showShadersNotUsedInBuild) {
  460. if (redButtonStyle == null) {
  461. redButtonStyle = new GUIStyle(GUI.skin.button);
  462. redButtonStyle.normal.textColor = Color.red;
  463. }
  464. if (GUILayout.Button(new GUIContent("Delete", "Deletes shader file."), redButtonStyle)) {
  465. if (shader.isReadOnly) {
  466. EditorUtility.DisplayDialog("Error", "Shader file is read-only.", "Ok");
  467. } else if (EditorUtility.DisplayDialog("Delete Shader", "Are you sure you want to delete the shader " + shader.name + " at " + shader.path + "?\n\nThis operation is not reversible - make sure you have a backup or way to import the shader again if you need it later.", "Yes - Delete It", "Cancel")) {
  468. DeleteShader(shader);
  469. GUIUtility.ExitGUI();
  470. return;
  471. }
  472. }
  473. }
  474. if (shader.materials.Count > 0) {
  475. if (GUILayout.Button(new GUIContent(shader.showMaterials ? "Hide Materials" : "List Materials", "Show or hide the materials that use these keywords."), EditorStyles.miniButton)) {
  476. shader.showMaterials = !shader.showMaterials;
  477. GUIUtility.ExitGUI();
  478. return;
  479. }
  480. if (shader.showMaterials) {
  481. EditorGUILayout.EndHorizontal();
  482. EditorGUILayout.BeginHorizontal();
  483. EditorGUILayout.LabelField("", GUILayout.Width(15));
  484. if (GUILayout.Button("Select Materials In Project", EditorStyles.miniButton)) {
  485. int matCount = shader.materials.Count;
  486. List<Material> allMaterials = new List<Material>();
  487. for (int m = 0; m < matCount; m++) {
  488. SCMaterial material = shader.materials[m];
  489. allMaterials.Add(material.unityMaterial);
  490. }
  491. if (allMaterials.Count > 0) {
  492. Selection.objects = allMaterials.ToArray();
  493. EditorGUIUtility.PingObject(allMaterials[0]);
  494. } else {
  495. EditorUtility.DisplayDialog("Select Materials In Project", "No matching materials found in project.", "Ok");
  496. }
  497. }
  498. if (GUILayout.Button("Select Materials In Scene", EditorStyles.miniButton)) {
  499. int matCount = shader.materials.Count;
  500. List<GameObject> gos = new List<GameObject>();
  501. Renderer[] rr = FindObjectsOfType<Renderer>();
  502. foreach (Renderer r in rr) {
  503. GameObject go = r.gameObject;
  504. if (go == null) continue;
  505. if ((go.hideFlags & HideFlags.NotEditable) != 0 || (go.hideFlags & HideFlags.HideAndDontSave) != 0)
  506. continue;
  507. Material[] mats = r.sharedMaterials;
  508. if (mats == null) continue;
  509. for (int m = 0; m < matCount; m++) {
  510. Material mat = shader.materials[m].unityMaterial;
  511. for (int sm = 0; sm < mats.Length; sm++) {
  512. if (mats[sm] == mat) {
  513. gos.Add(go);
  514. m = matCount;
  515. break;
  516. }
  517. }
  518. }
  519. }
  520. if (gos.Count > 0) {
  521. Selection.objects = gos.ToArray();
  522. } else {
  523. EditorUtility.DisplayDialog("Select Materials In Scene", "No matching materials found in objects of this scene.", "Ok");
  524. }
  525. }
  526. }
  527. }
  528. EditorGUILayout.EndHorizontal();
  529. for (int k = 0; k < shader.keywords.Count; k++) {
  530. SCKeyword keyword = shader.keywords[k];
  531. if (keyword.isUnderscoreKeyword)
  532. continue;
  533. EditorGUILayout.BeginHorizontal();
  534. EditorGUILayout.LabelField("", GUILayout.Width(15));
  535. if (shader.hasSource && !shader.isShaderGraph) {
  536. bool prevState = keyword.enabled;
  537. keyword.enabled = EditorGUILayout.Toggle(prevState, GUILayout.Width(18));
  538. if (prevState != keyword.enabled) {
  539. shader.pendingChanges = true;
  540. shader.UpdateVariantCount();
  541. UpdateProjectStats();
  542. GUIUtility.ExitGUI();
  543. return;
  544. }
  545. } else {
  546. EditorGUILayout.Toggle(true, GUILayout.Width(18));
  547. }
  548. EditorGUILayout.LabelField(keyword.verboseName);
  549. if (keyword.enabled) {
  550. if (!shader.hasSource && GUILayout.Button(new GUIContent("Prune Keyword", "Removes the keyword from all materials that reference it."), EditorStyles.miniButton, GUILayout.Width(110))) {
  551. if (EditorUtility.DisplayDialog("Prune Keyword", "This option will disable the keyword " + keyword.name + " in all materials that use " + shader.name + " shader.\nDo you want to continue?", "Ok", "Cancel")) {
  552. PruneMaterials(keyword.name);
  553. UpdateProjectStats();
  554. }
  555. }
  556. if (keyword.canBeConvertedToLocal) {
  557. if (GUILayout.Button("Convert To Local Keyword", EditorStyles.miniButtonRight, GUILayout.Width(190))) {
  558. if (EditorUtility.DisplayDialog("Convert global keyword to local", "Keyword " + keyword.name + " will be converted to local. This means the keyword won't count toward the maximum global keyword limit (256). Continue?", "Ok", "Cancel")) {
  559. ConvertToLocalStarted();
  560. ConvertToLocal(keyword);
  561. ConvertToLocalFinished();
  562. ScanProject();
  563. GUIUtility.ExitGUI();
  564. }
  565. }
  566. }
  567. }
  568. EditorGUILayout.EndHorizontal();
  569. if (shader.showMaterials) {
  570. // show materials using this shader
  571. int matCount = shader.materials.Count;
  572. for (int m = 0; m < matCount; m++) {
  573. SCMaterial material = shader.materials[m];
  574. if (material.ContainsKeyword(keyword.name)) {
  575. EditorGUILayout.BeginHorizontal();
  576. EditorGUILayout.LabelField("", GUILayout.Width(30));
  577. EditorGUILayout.LabelField(matIcon, GUILayout.Width(18));
  578. EditorGUILayout.LabelField(material.unityMaterial.name);
  579. if (GUILayout.Button(new GUIContent("Locate", "Locates the material in the project panel."), EditorStyles.miniButton, GUILayout.Width(60))) {
  580. Material theMaterial = AssetDatabase.LoadAssetAtPath<Material>(material.path) as Material;
  581. Selection.activeObject = theMaterial;
  582. EditorGUIUtility.PingObject(theMaterial);
  583. }
  584. EditorGUILayout.EndHorizontal();
  585. }
  586. }
  587. }
  588. }
  589. if (shader.showMaterials) {
  590. // show materials using this shader that does not use any keywords
  591. bool first = true;
  592. int matCount = shader.materials.Count;
  593. for (int m = 0; m < matCount; m++) {
  594. SCMaterial material = shader.materials[m];
  595. if (material.keywords.Count == 0) {
  596. if (first) {
  597. first = false;
  598. EditorGUILayout.BeginHorizontal();
  599. EditorGUILayout.LabelField("", GUILayout.Width(15));
  600. EditorGUILayout.LabelField("Materials using this shader and no keywords:");
  601. EditorGUILayout.EndHorizontal();
  602. }
  603. EditorGUILayout.BeginHorizontal();
  604. EditorGUILayout.LabelField("", GUILayout.Width(15));
  605. EditorGUILayout.LabelField(matIcon, GUILayout.Width(18));
  606. EditorGUILayout.LabelField(material.unityMaterial.name);
  607. if (GUILayout.Button(new GUIContent("Locate", "Locates the material in the project panel."), EditorStyles.miniButton, GUILayout.Width(60))) {
  608. Material theMaterial = AssetDatabase.LoadAssetAtPath<Material>(material.path) as Material;
  609. Selection.activeObject = theMaterial;
  610. EditorGUIUtility.PingObject(theMaterial);
  611. }
  612. EditorGUILayout.EndHorizontal();
  613. }
  614. }
  615. }
  616. }
  617. EditorGUILayout.Separator();
  618. }
  619. if (shadersListedCount == 0) {
  620. if (usesShaderNameFilter && usesShaderPathFilter) {
  621. EditorGUILayout.HelpBox("No shader matching '" + projectShaderNameFilter + "' name in '" + projectShaderPathFilter + "' path found. Either the shader doesn't exist in the path as source file or the shader does not use explicit keywords.", MessageType.Info);
  622. } else if (usesShaderNameFilter) {
  623. EditorGUILayout.HelpBox("No shader matching '" + projectShaderNameFilter + "' name found. Either the shader doesn't exist in the project as source file or the shader does not use explicit keywords.", MessageType.Info);
  624. } else {
  625. EditorGUILayout.HelpBox("No shader matching criteria found in '" + projectShaderPathFilter + "' path found. Either the shader doesn't exist in the path as source file or the shader does not use explicit keywords.", MessageType.Info);
  626. }
  627. }
  628. }
  629. EditorGUILayout.EndScrollView();
  630. }
  631. }
  632. }