Fix : 닐로툰 버전 업데이트

This commit is contained in:
user 2026-05-27 02:50:48 +09:00
parent 6f16a69a4d
commit ce7f7c51c9
283 changed files with 13890 additions and 7684 deletions

BIN
Assets/NiloToonURP/CHANGELOG.md (Stored with Git LFS)

Binary file not shown.

View File

@ -101,6 +101,7 @@ namespace NiloToon.NiloToonURP
// Mapping from original materials to cloned materials
Dictionary<Material, Material> materialMap = new Dictionary<Material, Material>();
List<string> createdMaterialAssetPaths = new List<string>();
foreach (Material mat in materialsSet)
{
@ -118,6 +119,7 @@ namespace NiloToon.NiloToonURP
AssetDatabase.CreateAsset(matClone, newMatPath);
Debug.Log("Created Material Asset: " + newMatPath);
materialMap[mat] = matClone;
createdMaterialAssetPaths.Add(newMatPath);
}
catch (System.Exception e)
{
@ -131,6 +133,18 @@ namespace NiloToon.NiloToonURP
// Instantiate the selected prefab
GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
if (instance == null)
{
Debug.LogError($"Failed to instantiate prefab before generating NiloToon variant: {assetPath}");
CleanupGeneratedMaterialAssets(createdMaterialAssetPaths);
return;
}
int removedMissingScriptCount = RemoveMissingMonoBehavioursRecursive(instance);
if (removedMissingScriptCount > 0)
{
Debug.LogWarning($"Removed {removedMissingScriptCount} missing MonoBehaviour references from temporary prefab instance before generating NiloToon variant.");
}
Debug.Log($"Attempting to save prefab variant at path: {variantPath}");
@ -144,6 +158,7 @@ namespace NiloToon.NiloToonURP
Debug.LogError($"Failed to save prefab variant at path: {variantPath}");
Debug.LogException(e);
GameObject.DestroyImmediate(instance);
CleanupGeneratedMaterialAssets(createdMaterialAssetPaths);
return;
}
@ -177,7 +192,10 @@ namespace NiloToon.NiloToonURP
}
// Run auto setup for the prefab variant
NiloToonEditorPerCharacterRenderControllerCustomEditor.AutoSetupCharacterGameObject(prefabVariantContents);
NiloToonEditorPerCharacterRenderControllerCustomEditor.AutoSetupCharacterGameObject(
prefabVariantContents,
shouldPromptMaterialEditConfirmation: false,
shouldEditMaterialWhenConfirmationSkipped: true);
// Save and unload prefab variant
PrefabUtility.SaveAsPrefabAsset(prefabVariantContents, variantPath);
@ -187,6 +205,17 @@ namespace NiloToon.NiloToonURP
EditorUtility.DisplayDialog("Success", "Prefab variant created with cloned materials.", "OK");
}
static void CleanupGeneratedMaterialAssets(List<string> createdMaterialAssetPaths)
{
foreach (string materialAssetPath in createdMaterialAssetPaths)
{
AssetDatabase.DeleteAsset(materialAssetPath);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
[MenuItem("Window/NiloToonURP/[Prefab] Create NiloToon Prefab Variant and Materials", priority = 0, validate = true)]
[MenuItem("Assets/NiloToon/[Prefab] Create NiloToon Prefab Variant and Materials", priority = 1100 + 0, validate = true)]
public static bool ValidateCreatePrefabVariantAndCloneMaterials()
@ -270,5 +299,17 @@ namespace NiloToon.NiloToonURP
AssetDatabase.Refresh();
}
static int RemoveMissingMonoBehavioursRecursive(GameObject root)
{
int removedCount = 0;
foreach (Transform transform in root.GetComponentsInChildren<Transform>(true))
{
removedCount += GameObjectUtility.RemoveMonoBehavioursWithMissingScript(transform.gameObject);
}
return removedCount;
}
}
}
}

View File

@ -168,6 +168,14 @@ namespace NiloToon.NiloToonURP
}
public static void AutoSetupCharacterGameObject(GameObject gameobject)
{
AutoSetupCharacterGameObject(gameobject, shouldPromptMaterialEditConfirmation: true);
}
public static void AutoSetupCharacterGameObject(
GameObject gameobject,
bool shouldPromptMaterialEditConfirmation,
bool shouldEditMaterialWhenConfirmationSkipped = true)
{
var charScript = gameobject.GetComponent<NiloToonPerCharacterRenderController>();
if (!charScript)
@ -175,9 +183,20 @@ namespace NiloToon.NiloToonURP
charScript = gameobject.AddComponent<NiloToonPerCharacterRenderController>();
}
AutoSetupCharacterGameObject(charScript);
AutoSetupCharacterGameObject(
charScript,
shouldPromptMaterialEditConfirmation,
shouldEditMaterialWhenConfirmationSkipped);
}
public static void AutoSetupCharacterGameObject(NiloToonPerCharacterRenderController perCharScript)
{
AutoSetupCharacterGameObject(perCharScript, shouldPromptMaterialEditConfirmation: true);
}
public static void AutoSetupCharacterGameObject(
NiloToonPerCharacterRenderController perCharScript,
bool shouldPromptMaterialEditConfirmation,
bool shouldEditMaterialWhenConfirmationSkipped = true)
{
if (!perCharScript)
{
@ -188,14 +207,17 @@ namespace NiloToon.NiloToonURP
}
perCharScript.RefillAllRenderers();
bool shouldEditMaterial = EditorUtility.DisplayDialog(
$"Auto setup character - {perCharScript.gameObject.name}",
"Set up NiloToon's script on this character completed.\n\n" +
"Do you want to convert the materials to NiloToon also?\n" +
"(convert from lilToon/MToon/UniUnlit/URP Lit/URP Unlit to NiloToon_Character)",
"Yes, convert the materials to NiloToon.",
"No, DO NOT edit any materials."
);
bool shouldEditMaterial = ResolveShouldEditMaterialDecision(
shouldPromptMaterialEditConfirmation,
shouldEditMaterialWhenConfirmationSkipped,
() => EditorUtility.DisplayDialog(
$"Auto setup character - {perCharScript.gameObject.name}",
"Set up NiloToon's script on this character completed.\n\n" +
"Do you want to convert the materials to NiloToon also?\n" +
"(convert from lilToon/MToon/UniUnlit/URP Lit/URP Unlit to NiloToon_Character)",
"Yes, convert the materials to NiloToon.",
"No, DO NOT edit any materials."
));
if (shouldEditMaterial)
{
@ -255,6 +277,19 @@ namespace NiloToon.NiloToonURP
//Debug.Log($"NiloToon: Auto setup {perCharScript.gameObject.name} completed.");
}
static bool ResolveShouldEditMaterialDecision(
bool shouldPromptMaterialEditConfirmation,
bool shouldEditMaterialWhenConfirmationSkipped,
Func<bool> showConfirmationDialog)
{
if (!shouldPromptMaterialEditConfirmation)
{
return shouldEditMaterialWhenConfirmationSkipped;
}
return showConfirmationDialog();
}
static void DiscardAndRefillAllRenderersList(NiloToonPerCharacterRenderController t)
{
@ -414,4 +449,3 @@ namespace NiloToon.NiloToonURP
}
}

View File

@ -1,4 +1,4 @@
// using System.Collections.Generic;
// using System.Collections.Generic;
// using System.IO;
// using UnityEditor;
// using UnityEditor.Build;

View File

@ -1,6 +1,7 @@
// Copyright (c) Jason Ma
using System;
using System.IO;
using LWGUI.PerformanceMonitor;
using UnityEditor;
using UnityEngine;
@ -15,10 +16,14 @@ namespace LWGUI
{
foreach (var assetPath in importedAssets)
{
if (Path.GetExtension(assetPath).Equals(".shader", StringComparison.OrdinalIgnoreCase))
var ext = Path.GetExtension(assetPath);
if (ext.Equals(".shader", StringComparison.OrdinalIgnoreCase)
|| ext.Equals(".shadergraph", StringComparison.OrdinalIgnoreCase)
)
{
var shader = AssetDatabase.LoadAssetAtPath<Shader>(assetPath);
MetaDataHelper.ReleaseShaderMetadataCache(shader);
ShaderPerfMonitor.ClearShaderPerfCache(shader);
ReflectionHelper.InvalidatePropertyCache(shader);
}
}

View File

@ -1,4 +1,4 @@
using UnityEngine;
using UnityEngine;
using UnityEditor;
namespace LWGUI.CustomGUISample

View File

@ -0,0 +1,308 @@
// Copyright (c) Jason Ma
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace LWGUI
{
public class ContextMenuHelper
{
#region Copy and Paste
private static Material _copiedMaterial;
private static List<string> _copiedProps = new();
public static void CopyMaterial(Material mat)
{
_copiedMaterial = Object.Instantiate(mat);
}
public static void PastePropertiesToMaterials(LWGUIMetaDatas metaDatas, uint valueMask)
{
if (!_copiedMaterial)
{
Debug.LogError("LWGUI: Please copy Material Properties first!");
return;
}
var targetMaterials = metaDatas.GetMaterialEditor().targets;
if (!VersionControlHelper.Checkout(targetMaterials))
{
Debug.LogError("LWGUI: One or more materials unable to write!");
return;
}
Undo.RecordObjects(targetMaterials, "LWGUI: Paste Material Properties");
foreach (Material material in targetMaterials)
{
PastePropertiesToMaterial(material, valueMask);
}
}
public static void PastePropertiesToMaterial(Material target, uint valueMask = ToolbarHelper.CopyMaterialValueMaskAll)
{
if (!_copiedMaterial)
{
Debug.LogError("LWGUI: Please copy Material Properties first!");
return;
}
if (!VersionControlHelper.Checkout(target))
{
Debug.LogError("LWGUI: Unable to write material!");
return;
}
Undo.RecordObject(target, "LWGUI: Paste Material Properties");
for (int i = 0; i < _copiedMaterial.shader.GetPropertyCount(); i++)
{
var name = _copiedMaterial.shader.GetPropertyName(i);
var type = _copiedMaterial.shader.GetPropertyType(i);
PastePropertyValueToMaterial(target, name, name, type, valueMask);
}
if ((valueMask & (uint)ToolbarHelper.CopyMaterialValueMask.Keyword) != 0)
target.shaderKeywords = _copiedMaterial.shaderKeywords;
if ((valueMask & (uint)ToolbarHelper.CopyMaterialValueMask.RenderQueue) != 0)
target.renderQueue = _copiedMaterial.renderQueue;
}
private static void PastePropertyValueToMaterial(Material material, string srcName, string dstName)
{
for (int i = 0; i < _copiedMaterial.shader.GetPropertyCount(); i++)
{
var name = _copiedMaterial.shader.GetPropertyName(i);
if (name == srcName)
{
var type = _copiedMaterial.shader.GetPropertyType(i);
PastePropertyValueToMaterial(material, srcName, dstName, type);
return;
}
}
}
private static void PastePropertyValueToMaterial(Material material, string srcName, string dstName, ShaderPropertyType type, uint valueMask = (uint)ToolbarHelper.CopyMaterialValueMask.All)
{
switch (type)
{
case ShaderPropertyType.Color:
if ((valueMask & (uint)ToolbarHelper.CopyMaterialValueMask.Vector) != 0)
material.SetColor(dstName, _copiedMaterial.GetColor(srcName));
break;
case ShaderPropertyType.Vector:
if ((valueMask & (uint)ToolbarHelper.CopyMaterialValueMask.Vector) != 0)
material.SetVector(dstName, _copiedMaterial.GetVector(srcName));
break;
case ShaderPropertyType.Texture:
if ((valueMask & (uint)ToolbarHelper.CopyMaterialValueMask.Texture) != 0)
material.SetTexture(dstName, _copiedMaterial.GetTexture(srcName));
break;
// Float
default:
if ((valueMask & (uint)ToolbarHelper.CopyMaterialValueMask.Float) != 0)
material.SetFloat(dstName, _copiedMaterial.GetFloat(srcName));
break;
}
}
#endregion
private static void EditPresetEvent(string mode, LwguiShaderPropertyPreset presetAsset, List<LwguiShaderPropertyPreset.Preset> targetPresets, MaterialProperty prop, LWGUIMetaDatas metaDatas)
{
if (!VersionControlHelper.Checkout(presetAsset))
{
Debug.LogError("LWGUI: Can not edit the preset: " + presetAsset);
return;
}
foreach (var targetPreset in targetPresets)
{
switch (mode)
{
case "Add":
case "Update":
targetPreset.AddOrUpdateIncludeExtraProperties(metaDatas, prop);
break;
case "Remove":
targetPreset.RemoveIncludeExtraProperties(metaDatas, prop.name);
break;
}
}
EditorUtility.SetDirty(presetAsset);
metaDatas.perMaterialData.InvalidateDefaultMaterialCache();
MetaDataHelper.ForceUpdateMaterialMetadataCache(metaDatas.GetMaterial());
}
public static void DoPropertyContextMenus(Rect rect, MaterialProperty prop, LWGUIMetaDatas metaDatas)
{
if (Event.current.type != EventType.ContextClick || !rect.Contains(Event.current.mousePosition)) return;
Event.current.Use();
var (perShaderData, perMaterialData, perInspectorData) = metaDatas.GetDatas();
var (propStaticData, propDynamicData) = metaDatas.GetPropDatas(prop);
var menu = new GenericMenu();
// 2022+ Material Varant Menus
#if UNITY_2022_1_OR_NEWER
ReflectionHelper.HandleApplyRevert(menu, prop);
#endif
// Copy
menu.AddItem(new GUIContent("Copy"), false, () =>
{
_copiedMaterial = UnityEngine.Object.Instantiate(metaDatas.GetMaterial());
_copiedProps.Clear();
_copiedProps.Add(prop.name);
foreach (var extraPropName in propStaticData.extraPropNames)
{
_copiedProps.Add(extraPropName);
}
// Copy Children
foreach (var childPropStaticData in propStaticData.children)
{
_copiedProps.Add(childPropStaticData.name);
foreach (var extraPropName in childPropStaticData.extraPropNames)
{
_copiedProps.Add(extraPropName);
}
foreach (var childChildPropStaticData in childPropStaticData.children)
{
_copiedProps.Add(childChildPropStaticData.name);
foreach (var extraPropName in childChildPropStaticData.extraPropNames)
{
_copiedProps.Add(extraPropName);
}
}
}
});
// Paste
GenericMenu.MenuFunction pasteAction = () =>
{
if (!VersionControlHelper.Checkout(prop.targets))
{
Debug.LogError("LWGUI: One or more materials unable to write!");
return;
}
Undo.RecordObjects(prop.targets, "LWGUI: Paste Material Properties");
foreach (Material material in prop.targets)
{
var index = 0;
PastePropertyValueToMaterial(material, _copiedProps[index++], prop.name);
foreach (var extraPropName in propStaticData.extraPropNames)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], extraPropName);
}
// Paste Children
foreach (var childPropStaticData in propStaticData.children)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], childPropStaticData.name);
foreach (var extraPropName in childPropStaticData.extraPropNames)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], extraPropName);
}
foreach (var childChildPropStaticData in childPropStaticData.children)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], childChildPropStaticData.name);
foreach (var extraPropName in childChildPropStaticData.extraPropNames)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], extraPropName);
}
}
}
MetaDataHelper.ForceUpdateMaterialMetadataCache(material);
}
};
if (_copiedMaterial != null && _copiedProps.Count > 0 && GUI.enabled)
menu.AddItem(new GUIContent("Paste"), false, pasteAction);
else
menu.AddDisabledItem(new GUIContent("Paste"));
menu.AddSeparator("");
// Copy Display Name
menu.AddItem(new GUIContent("Copy Display Name"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = propStaticData.displayName;
});
// Copy Property Names
menu.AddItem(new GUIContent("Copy Property Names"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = prop.name;
foreach (var extraPropName in propStaticData.extraPropNames)
{
EditorGUIUtility.systemCopyBuffer += ", " + extraPropName;
}
});
// menus.AddSeparator("");
//
// // Add to Favorites
// menus.AddItem(new GUIContent("Add to Favorites"), false, () =>
// {
// });
//
// // Remove from Favorites
// menus.AddItem(new GUIContent("Remove from Favorites"), false, () =>
// {
// });
// Preset
if (GUI.enabled)
{
menu.AddSeparator("");
foreach (var activePresetData in perMaterialData.activePresetDatas)
{
// Cull self
if (activePresetData.property == prop) continue;
var activePreset = activePresetData.preset;
var (presetPropStaticData, presetPropDynamicData) = metaDatas.GetPropDatas(activePresetData.property);
var presetAsset = presetPropStaticData.propertyPresetAsset;
var presetPropDisplayName = presetPropStaticData.displayName;
// Cull invisible presets
if (!presetPropDynamicData.isShowing) continue;
if (activePreset.GetPropertyValue(prop.name) != null)
{
menu.AddItem(new GUIContent("Update to Preset/" + presetPropDisplayName + "/" + "All"), false, () => EditPresetEvent("Update", presetAsset, presetAsset.GetPresets(), prop, metaDatas));
menu.AddItem(new GUIContent("Update to Preset/" + presetPropDisplayName + "/" + activePreset.presetName), false, () => EditPresetEvent("Update", presetAsset, new List<LwguiShaderPropertyPreset.Preset>(){activePreset}, prop, metaDatas));
menu.AddItem(new GUIContent("Remove from Preset/" + presetPropDisplayName + "/" + "All"), false, () => EditPresetEvent("Remove", presetAsset, presetAsset.GetPresets(), prop, metaDatas));
menu.AddItem(new GUIContent("Remove from Preset/" + presetPropDisplayName + "/" + activePreset.presetName), false, () => EditPresetEvent("Remove", presetAsset, new List<LwguiShaderPropertyPreset.Preset>(){activePreset}, prop, metaDatas));
}
else
{
menu.AddItem(new GUIContent("Add to Preset/" + presetPropDisplayName + "/" + "All"), false, () => EditPresetEvent("Add", presetAsset, presetAsset.GetPresets(), prop, metaDatas));
menu.AddItem(new GUIContent("Add to Preset/" + presetPropDisplayName + "/" + activePreset.presetName), false, () => EditPresetEvent("Add", presetAsset, new List<LwguiShaderPropertyPreset.Preset>(){activePreset}, prop, metaDatas));
}
}
}
// Custom
if (propStaticData.baseDrawers != null)
{
foreach (var baseDrawer in propStaticData.baseDrawers)
{
baseDrawer.GetCustomContextMenus(menu, rect, prop, metaDatas);
}
}
menu.ShowAsContext();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aedae295d18e4884bc12b86aadfbab80
timeCreated: 1760781897

View File

@ -0,0 +1,84 @@
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
public static class GUIStyles
{
// Tips: Use properties to fix null reference errors
private static GUIStyle _title;
private static GUIStyle _iconButton;
private static GUIStyle _foldout;
private static GUIStyle _helpbox;
private static GUIStyle _rampSelectButton;
private static GUIStyle _objectFieldButton;
private static GUIStyle _toolbarSearchTextFieldPopup;
private static GUIStyle _label_monospace;
public static GUIStyle title => _title ?? new GUIStyle(EditorStyles.boldLabel)
{
alignment = TextAnchor.LowerLeft,
border =
{
bottom = 2
}
};
public static GUIStyle iconButton => _iconButton ?? new GUIStyle(EditorStyles.iconButton) { fixedHeight = 0, fixedWidth = 0 };
public static GUIStyle foldout => _foldout ?? new GUIStyle(EditorStyles.miniButton)
{
contentOffset = new Vector2(22, 0),
fixedHeight = 27,
alignment = TextAnchor.MiddleLeft,
font = EditorStyles.boldLabel.font,
fontSize = EditorStyles.boldLabel.fontSize + 1
};
public static GUIStyle helpbox => _helpbox ?? new GUIStyle(EditorStyles.helpBox) { fontSize = 12 };
public static GUIStyle rampSelectButton => _rampSelectButton ?? new GUIStyle(EditorStyles.miniButton)
{
fixedHeight = 0,
stretchHeight = true,
alignment = TextAnchor.MiddleLeft
};
public static GUIStyle objectFieldButton => _objectFieldButton ?? new GUIStyle("ObjectFieldButton")
{
fixedHeight = 0,
stretchHeight = true
};
public static GUIStyle toolbarSearchTextFieldPopup
{
get
{
if (_toolbarSearchTextFieldPopup == null)
{
string toolbarSeachTextFieldPopupStr = "ToolbarSeachTextFieldPopup";
{
// ToolbarSeachTextFieldPopup has renamed at Unity 2021.3.28+
#if !UNITY_2022_3_OR_NEWER
string[] versionParts = Application.unityVersion.Split('.');
int majorVersion = int.Parse(versionParts[0]);
int minorVersion = int.Parse(versionParts[1]);
Match patchVersionMatch = Regex.Match(versionParts[2], @"\d+");
int patchVersion = int.Parse(patchVersionMatch.Value);
if (majorVersion >= 2021 && minorVersion >= 3 && patchVersion >= 28)
#endif
{
toolbarSeachTextFieldPopupStr = "ToolbarSearchTextFieldPopup";
}
}
_toolbarSearchTextFieldPopup = new GUIStyle(toolbarSeachTextFieldPopupStr);
}
return _toolbarSearchTextFieldPopup;
}
}
public static GUIStyle label_monospace => _label_monospace ?? new GUIStyle(EditorStyles.label) { font = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf") as Font };
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 09e9a4a53c4e4d36b20c466825f07a63
timeCreated: 1760968069

View File

@ -20,29 +20,11 @@ namespace LWGUI
#region Misc
public static readonly string ProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
public static bool IsPropertyHideInInspector(MaterialProperty prop)
{
return (prop.GetPropertyFlags() & ShaderPropertyFlags.HideInInspector) != 0;
}
public static bool StringToBool(string str) => str?.ToLower() is "on" or "true";
public static string GetKeywordName(string keyword, string propName)
{
string k;
if (string.IsNullOrEmpty(keyword) || keyword == "__")
{
k = propName.ToUpperInvariant() + "_ON";
}
else
{
k = keyword.ToUpperInvariant();
}
return k;
}
public static void SetShaderKeywordEnabled(Object[] materials, string keywordName, bool isEnable)
{
if (string.IsNullOrEmpty(keywordName) || string.IsNullOrEmpty(keywordName)) return;
@ -90,6 +72,7 @@ namespace LWGUI
{
material.SetShaderPassEnabled(lightModeNames[i], enabled);
}
EditorUtility.SetDirty(material);
}
}
@ -110,13 +93,6 @@ namespace LWGUI
public static LWGUIMetaDatas GetLWGUIMetadatas(MaterialEditor editor) => GetLWGUI(editor).metaDatas;
public static void AdaptiveFieldWidth(GUIStyle style, GUIContent content)
{
var extraTextWidth = Mathf.Max(0, style.CalcSize(content).x - (EditorGUIUtility.fieldWidth - RevertableHelper.revertButtonWidth));
EditorGUIUtility.labelWidth -= extraTextWidth;
EditorGUIUtility.fieldWidth += extraTextWidth;
}
public static void BeginProperty(Rect rect, MaterialProperty property, LWGUIMetaDatas metaDatas)
{
#if UNITY_2022_1_OR_NEWER
@ -148,6 +124,43 @@ namespace LWGUI
#endregion
#region String
public static bool StringToBool(string str) => str?.ToLower() is "on" or "true";
public static string FillStringLengthBySpace(string str, int minStringLength)
{
if (str.Length >= minStringLength)
return str;
return str + string.Concat(Enumerable.Repeat(' ', minStringLength - str.Length));
}
public static string GetKeywordName(string keyword, string propName)
{
string k;
if (string.IsNullOrEmpty(keyword) || keyword == "__")
{
k = propName.ToUpperInvariant() + "_ON";
}
else
{
k = keyword.ToUpperInvariant();
}
return k;
}
public static void AdaptiveFieldWidth(GUIStyle style, GUIContent content)
{
var extraTextWidth = Mathf.Max(0, style.CalcSize(content).x - (EditorGUIUtility.fieldWidth - RevertableHelper.revertButtonWidth));
EditorGUIUtility.labelWidth -= extraTextWidth;
EditorGUIUtility.fieldWidth += extraTextWidth;
}
#endregion
#region Math
public const double Float_Epsilon = 1e-10;
@ -178,60 +191,6 @@ namespace LWGUI
#endregion
#region GUI Styles
// Tips: Use properties to fix null reference errors
private static GUIStyle _guiStyles_IconButton;
public static GUIStyle guiStyles_IconButton => _guiStyles_IconButton ?? new GUIStyle(EditorStyles.iconButton) { fixedHeight = 0, fixedWidth = 0 };
private static GUIStyle _guiStyle_Foldout;
public static GUIStyle guiStyle_Foldout => _guiStyle_Foldout ?? new GUIStyle(EditorStyles.miniButton)
{
contentOffset = new Vector2(22, 0),
fixedHeight = 27,
alignment = TextAnchor.MiddleLeft,
font = EditorStyles.boldLabel.font,
fontSize = EditorStyles.boldLabel.fontSize + 1
};
private static GUIStyle _guiStyle_Helpbox;
public static GUIStyle guiStyle_Helpbox => _guiStyle_Helpbox ?? new GUIStyle(EditorStyles.helpBox) { fontSize = 12 };
private static GUIStyle _guiStyle_RampSelectButton;
public static GUIStyle guiStyle_RampSelectButton => _guiStyle_RampSelectButton ?? new GUIStyle(EditorStyles.miniButton) { alignment = TextAnchor.MiddleLeft };
private static GUIStyle _guiStyles_ToolbarSearchTextFieldPopup;
public static GUIStyle guiStyles_ToolbarSearchTextFieldPopup
{
get
{
if (_guiStyles_ToolbarSearchTextFieldPopup == null)
{
string toolbarSeachTextFieldPopupStr = "ToolbarSeachTextFieldPopup";
{
// ToolbarSeachTextFieldPopup has renamed at Unity 2021.3.28+
#if !UNITY_2022_3_OR_NEWER
string[] versionParts = Application.unityVersion.Split('.');
int majorVersion = int.Parse(versionParts[0]);
int minorVersion = int.Parse(versionParts[1]);
Match patchVersionMatch = Regex.Match(versionParts[2], @"\d+");
int patchVersion = int.Parse(patchVersionMatch.Value);
if (majorVersion >= 2021 && minorVersion >= 3 && patchVersion >= 28)
#endif
{
toolbarSeachTextFieldPopupStr = "ToolbarSearchTextFieldPopup";
}
}
_guiStyles_ToolbarSearchTextFieldPopup = new GUIStyle(toolbarSeachTextFieldPopupStr);
}
return _guiStyles_ToolbarSearchTextFieldPopup;
}
}
#endregion
#region Draw GUI for Drawers
// TODO: use Reflection
@ -285,7 +244,7 @@ namespace LWGUI
GUI.enabled = true;
var guiColor = GUI.backgroundColor;
GUI.backgroundColor = isFolding ? Color.white : new Color(0.85f, 0.85f, 0.85f);
if (GUI.Button(rect, label, guiStyle_Foldout))
if (GUI.Button(rect, label, GUIStyles.foldout))
{
isFolding = !isFolding;
GUI.changed = false;
@ -349,10 +308,10 @@ namespace LWGUI
{
var content = new GUIContent(helpboxStr, _helpboxIcon);
var textWidth = GetCurrentPropertyLayoutWidth();
var textHeight = guiStyle_Helpbox.CalcHeight(content, textWidth);
var textHeight = GUIStyles.helpbox.CalcHeight(content, textWidth);
var helpboxRect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(true, textHeight));
helpboxRect.xMax -= RevertableHelper.revertButtonWidth;
GUI.Label(helpboxRect, content, guiStyle_Helpbox);
GUI.Label(helpboxRect, content, GUIStyles.helpbox);
// EditorGUI.HelpBox(helpboxRect, helpboxStr, MessageType.Info);
}
}
@ -372,610 +331,20 @@ namespace LWGUI
logoRect.xMin += w * 0.5f - _logo.width * 0.5f;
logoRect.xMax -= w * 0.5f - _logo.width * 0.5f;
if (EditorGUIUtility.currentViewWidth >= logoRect.width && GUI.Button(logoRect, _logoGuiContent, guiStyles_IconButton))
if (EditorGUIUtility.currentViewWidth >= logoRect.width && GUI.Button(logoRect, _logoGuiContent, GUIStyles.iconButton))
{
Application.OpenURL("https://github.com/JasonMa0012/LWGUI");
}
}
#endregion
#region Toolbar Buttons
private static Material _copiedMaterial;
private static List<string> _copiedProps = new List<string>();
private const string _iconCopyGUID = "9cdef444d18d2ce4abb6bbc4fed4d109";
private const string _iconPasteGUID = "8e7a78d02e4c3574998524a0842a8ccb";
private const string _iconSelectGUID = "6f44e40b24300974eb607293e4224ecc";
private const string _iconCheckoutGUID = "72488141525eaa8499e65e52755cb6d0";
private const string _iconExpandGUID = "2382450e7f4ddb94c9180d6634c41378";
private const string _iconCollapseGUID = "929b6e5dfacc42b429d715a3e1ca2b57";
private const string _iconVisibilityGUID = "9576e23a695b35d49a9fc55c9a948b4f";
private const string _iconCopyTooltip = "Copy Material Properties";
private const string _iconPasteTooltip = "Paste Material Properties\n\nRight-click to paste values by type.";
private const string _iconSelectTooltip = "Select the Material Asset\n\nUsed to jump from a Runtime Material Instance to a Material Asset.";
private const string _iconCheckoutTooltip = "Checkout selected Material Assets";
private const string _iconExpandTooltip = "Expand All Groups";
private const string _iconCollapseTooltip = "Collapse All Groups";
private const string _iconVisibilityTooltip = "Display Mode";
private static GUIContent _guiContentCopyCache;
private static GUIContent _guiContentPasteCache;
private static GUIContent _guiContentSelectCache;
private static GUIContent _guiContentChechoutCache;
private static GUIContent _guiContentExpandCache;
private static GUIContent _guiContentCollapseCache;
private static GUIContent _guiContentVisibilityCache;
private static GUIContent _guiContentCopy => _guiContentCopyCache = _guiContentCopyCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconCopyGUID)), _iconCopyTooltip);
private static GUIContent _guiContentPaste => _guiContentPasteCache = _guiContentPasteCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconPasteGUID)), _iconPasteTooltip);
private static GUIContent _guiContentSelect => _guiContentSelectCache = _guiContentSelectCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconSelectGUID)), _iconSelectTooltip);
private static GUIContent _guiContentChechout => _guiContentChechoutCache = _guiContentChechoutCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconCheckoutGUID)), _iconCheckoutTooltip);
private static GUIContent _guiContentExpand => _guiContentExpandCache = _guiContentExpandCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconExpandGUID)), _iconExpandTooltip);
private static GUIContent _guiContentCollapse => _guiContentCollapseCache = _guiContentCollapseCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconCollapseGUID)), _iconCollapseTooltip);
private static GUIContent _guiContentVisibility => _guiContentVisibilityCache = _guiContentVisibilityCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconVisibilityGUID)), _iconVisibilityTooltip);
private enum CopyMaterialValueMask
{
Float = 1 << 0,
Vector = 1 << 1,
Texture = 1 << 2,
Keyword = 1 << 3,
RenderQueue = 1 << 4,
Number = Float | Vector,
All = (1 << 5) - 1,
}
private static GUIContent[] _pasteMaterialMenus = new[]
{
new GUIContent("Paste Number Values"),
new GUIContent("Paste Texture Values"),
new GUIContent("Paste Keywords"),
new GUIContent("Paste RenderQueue"),
};
private static uint[] _pasteMaterialMenuValueMasks = new[]
{
(uint)CopyMaterialValueMask.Number,
(uint)CopyMaterialValueMask.Texture,
(uint)CopyMaterialValueMask.Keyword,
(uint)CopyMaterialValueMask.RenderQueue,
};
private static void DoPasteMaterialProperties(LWGUIMetaDatas metaDatas, uint valueMask)
{
if (!_copiedMaterial)
{
Debug.LogError("LWGUI: Please copy Material Properties first!");
return;
}
var targetMaterials = metaDatas.GetMaterialEditor().targets;
if (!VersionControlHelper.Checkout(targetMaterials))
{
Debug.LogError("LWGUI: One or more materials unable to write!");
return;
}
Undo.RecordObjects(targetMaterials, "LWGUI: Paste Material Properties");
foreach (Material material in targetMaterials)
{
for (int i = 0; i < _copiedMaterial.shader.GetPropertyCount(); i++)
{
var name = _copiedMaterial.shader.GetPropertyName(i);
var type = _copiedMaterial.shader.GetPropertyType(i);
PastePropertyValueToMaterial(material, name, name, type, valueMask);
}
if ((valueMask & (uint)CopyMaterialValueMask.Keyword) != 0)
material.shaderKeywords = _copiedMaterial.shaderKeywords;
if ((valueMask & (uint)CopyMaterialValueMask.RenderQueue) != 0)
material.renderQueue = _copiedMaterial.renderQueue;
}
}
private static void PastePropertyValueToMaterial(Material material, string srcName, string dstName)
{
for (int i = 0; i < _copiedMaterial.shader.GetPropertyCount(); i++)
{
var name = _copiedMaterial.shader.GetPropertyName(i);
if (name == srcName)
{
var type = _copiedMaterial.shader.GetPropertyType(i);
PastePropertyValueToMaterial(material, srcName, dstName, type);
return;
}
}
}
private static void PastePropertyValueToMaterial(Material material, string srcName, string dstName, ShaderPropertyType type, uint valueMask = (uint)CopyMaterialValueMask.All)
{
switch (type)
{
case ShaderPropertyType.Color:
if ((valueMask & (uint)CopyMaterialValueMask.Vector) != 0)
material.SetColor(dstName, _copiedMaterial.GetColor(srcName));
break;
case ShaderPropertyType.Vector:
if ((valueMask & (uint)CopyMaterialValueMask.Vector) != 0)
material.SetVector(dstName, _copiedMaterial.GetVector(srcName));
break;
case ShaderPropertyType.Texture:
if ((valueMask & (uint)CopyMaterialValueMask.Texture) != 0)
material.SetTexture(dstName, _copiedMaterial.GetTexture(srcName));
break;
// Float
default:
if ((valueMask & (uint)CopyMaterialValueMask.Float) != 0)
material.SetFloat(dstName, _copiedMaterial.GetFloat(srcName));
break;
}
}
public static void DrawToolbarButtons(ref Rect toolBarRect, LWGUIMetaDatas metaDatas)
{
var (perShaderData, perMaterialData, perInspectorData) = metaDatas.GetDatas();
// Copy
var buttonRectOffset = toolBarRect.height + 2;
var buttonRect = new Rect(toolBarRect.x, toolBarRect.y, toolBarRect.height, toolBarRect.height);
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentCopy, Helper.guiStyles_IconButton))
{
_copiedMaterial = UnityEngine.Object.Instantiate(metaDatas.GetMaterial());
}
// Paste
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
// Right Click
if (Event.current.type == EventType.MouseDown
&& Event.current.button == 1
&& buttonRect.Contains(Event.current.mousePosition))
{
EditorUtility.DisplayCustomMenu(new Rect(Event.current.mousePosition.x, Event.current.mousePosition.y, 0, 0), _pasteMaterialMenus, -1,
(data, options, selected) =>
{
DoPasteMaterialProperties(metaDatas, _pasteMaterialMenuValueMasks[selected]);
}, null);
Event.current.Use();
}
// Left Click
if (GUI.Button(buttonRect, _guiContentPaste, Helper.guiStyles_IconButton))
{
DoPasteMaterialProperties(metaDatas, (uint)CopyMaterialValueMask.All);
}
// Select Material Asset, jump from a Runtime Material Instance to a Material Asset
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentSelect, Helper.guiStyles_IconButton))
{
var material = metaDatas.GetMaterial();
if (AssetDatabase.Contains(material))
{
Selection.activeObject = material;
}
else
{
if (FindMaterialAssetByMaterialInstance(material, metaDatas, out var materialAsset))
{
Selection.activeObject = materialAsset;
}
}
}
// Checkout
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentChechout, Helper.guiStyles_IconButton))
{
VersionControlHelper.Checkout(metaDatas.GetMaterialEditor().targets);
}
// Expand
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentExpand, Helper.guiStyles_IconButton))
{
foreach (var propStaticDataKVPair in perShaderData.propStaticDatas)
{
if (propStaticDataKVPair.Value.isMain || propStaticDataKVPair.Value.isAdvancedHeader)
propStaticDataKVPair.Value.isExpanding = true;
}
}
// Collapse
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentCollapse, Helper.guiStyles_IconButton))
{
foreach (var propStaticDataKVPair in perShaderData.propStaticDatas)
{
if (propStaticDataKVPair.Value.isMain || propStaticDataKVPair.Value.isAdvancedHeader)
propStaticDataKVPair.Value.isExpanding = false;
}
}
// Display Mode
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
var color = GUI.color;
var displayModeData = perShaderData.displayModeData;
if (!displayModeData.IsDefaultDisplayMode())
GUI.color = Color.yellow;
if (GUI.Button(buttonRect, _guiContentVisibility, Helper.guiStyles_IconButton))
{
// Build Display Mode Menu Items
var displayModeMenus = new[]
{
$"Show All Advanced Properties ({ displayModeData.advancedCount } - { perShaderData.propStaticDatas.Count })",
$"Show All Hidden Properties ({ displayModeData.hiddenCount } - { perShaderData.propStaticDatas.Count })",
$"Show Only Modified Properties ({ perMaterialData.modifiedCount } - { perShaderData.propStaticDatas.Count })",
$"Show Only Modified Properties by Group ({ perMaterialData.modifiedCount } - { perShaderData.propStaticDatas.Count })",
};
var enabled = new[] { true, true, true, true };
var separator = new bool[4];
var selected = new[]
{
displayModeData.showAllAdvancedProperties ? 0 : -1,
displayModeData.showAllHiddenProperties ? 1 : -1,
displayModeData.showOnlyModifiedProperties ? 2 : -1,
displayModeData.showOnlyModifiedGroups ? 3 : -1,
};
// Click Event
void OnSwitchDisplayMode(object data, string[] options, int selectedIndex)
{
switch (selectedIndex)
{
case 0: // Show All Advanced Properties
displayModeData.showAllAdvancedProperties = !displayModeData.showAllAdvancedProperties;
perShaderData.ToggleShowAllAdvancedProperties();
break;
case 1: // Show All Hidden Properties
displayModeData.showAllHiddenProperties = !displayModeData.showAllHiddenProperties;
break;
case 2: // Show Only Modified Properties
displayModeData.showOnlyModifiedProperties = !displayModeData.showOnlyModifiedProperties;
if (displayModeData.showOnlyModifiedProperties) displayModeData.showOnlyModifiedGroups = false;
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(metaDatas.GetShader());
break;
case 3: // Show Only Modified Groups
displayModeData.showOnlyModifiedGroups = !displayModeData.showOnlyModifiedGroups;
if (displayModeData.showOnlyModifiedGroups) displayModeData.showOnlyModifiedProperties = false;
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(metaDatas.GetShader());
break;
}
}
ReflectionHelper.DisplayCustomMenuWithSeparators(new Rect(Event.current.mousePosition.x, Event.current.mousePosition.y, 0, 0),
displayModeMenus, enabled, separator, selected, OnSwitchDisplayMode);
}
GUI.color = color;
toolBarRect.xMin += 2;
}
public static Func<Renderer, Material, Material> onFindMaterialAssetInRendererByMaterialInstance;
private static bool FindMaterialAssetByMaterialInstance(Material material, LWGUIMetaDatas metaDatas, out Material materialAsset)
{
materialAsset = null;
var renderers = metaDatas.perInspectorData.materialEditor.GetMeshRenderersByMaterialEditor();
foreach (var renderer in renderers)
{
if (onFindMaterialAssetInRendererByMaterialInstance != null)
{
materialAsset = onFindMaterialAssetInRendererByMaterialInstance(renderer, material);
}
if (materialAsset == null)
{
int index = renderer.materials.ToList().FindIndex(materialInstance => materialInstance == material);
if (index >= 0 && index < renderer.sharedMaterials.Length)
{
materialAsset = renderer.sharedMaterials[index];
}
}
if (materialAsset != null && AssetDatabase.Contains(materialAsset))
return true;
}
Debug.LogError("LWGUI: Can not find the Material Assets of: " + material.name);
return false;
}
#endregion
#region Search Field
private static readonly int s_TextFieldHash = "EditorTextField".GetHashCode();
private static readonly GUIContent[] _searchModeMenus = Enumerable.Range(0, (int)SearchMode.Num - 1).Select(i =>
new GUIContent(((SearchMode)i).ToString())).ToArray();
/// <returns>is has changed?</returns>
public static bool DrawSearchField(Rect rect, LWGUIMetaDatas metaDatas)
{
var (perShaderData, perMaterialData, perInspectorData) = metaDatas.GetDatas();
bool hasChanged = false;
EditorGUI.BeginChangeCheck();
var revertButtonRect = RevertableHelper.SplitRevertButtonRect(ref rect);
// Get internal TextField ControlID
int controlId = GUIUtility.GetControlID(s_TextFieldHash, FocusType.Keyboard, rect) + 1;
// searching mode
Rect modeRect = new Rect(rect);
modeRect.width = 20f;
if (Event.current.type == UnityEngine.EventType.MouseDown && modeRect.Contains(Event.current.mousePosition))
{
EditorUtility.DisplayCustomMenu(rect, _searchModeMenus, (int)perShaderData.searchMode,
(data, options, selected) =>
{
perShaderData.searchMode = (SearchMode)selected;
hasChanged = true;
}, null);
Event.current.Use();
}
perShaderData.searchString = EditorGUI.TextField(rect, String.Empty, perShaderData.searchString, guiStyles_ToolbarSearchTextFieldPopup);
if (EditorGUI.EndChangeCheck())
hasChanged = true;
// revert button
if (!string.IsNullOrEmpty(perShaderData.searchString)
&& RevertableHelper.DrawRevertButton(revertButtonRect))
{
perShaderData.searchString = string.Empty;
hasChanged = true;
GUIUtility.keyboardControl = 0;
}
// display search mode
if (GUIUtility.keyboardControl != controlId
&& string.IsNullOrEmpty(perShaderData.searchString)
&& Event.current.type == UnityEngine.EventType.Repaint)
{
using (new EditorGUI.DisabledScope(true))
{
var disableTextRect = new Rect(rect.x, rect.y, rect.width,
guiStyles_ToolbarSearchTextFieldPopup.fixedHeight > 0.0
? guiStyles_ToolbarSearchTextFieldPopup.fixedHeight
: rect.height);
disableTextRect = guiStyles_ToolbarSearchTextFieldPopup.padding.Remove(disableTextRect);
int fontSize = EditorStyles.label.fontSize;
EditorStyles.label.fontSize = guiStyles_ToolbarSearchTextFieldPopup.fontSize;
EditorStyles.label.Draw(disableTextRect, new GUIContent(perShaderData.searchMode.ToString()), false, false, false, false);
EditorStyles.label.fontSize = fontSize;
}
}
if (hasChanged) perShaderData.UpdateSearchFilter();
return hasChanged;
}
#endregion
#region Context Menu
private static void EditPresetEvent(string mode, LwguiShaderPropertyPreset presetAsset, List<LwguiShaderPropertyPreset.Preset> targetPresets, MaterialProperty prop, LWGUIMetaDatas metaDatas)
{
if (!VersionControlHelper.Checkout(presetAsset))
{
Debug.LogError("LWGUI: Can not edit the preset: " + presetAsset);
return;
}
foreach (var targetPreset in targetPresets)
{
switch (mode)
{
case "Add":
case "Update":
targetPreset.AddOrUpdateIncludeExtraProperties(metaDatas, prop);
break;
case "Remove":
targetPreset.RemoveIncludeExtraProperties(metaDatas, prop.name);
break;
}
}
EditorUtility.SetDirty(presetAsset);
MetaDataHelper.ForceUpdateMaterialMetadataCache(metaDatas.GetMaterial());
}
public static void DoPropertyContextMenus(Rect rect, MaterialProperty prop, LWGUIMetaDatas metaDatas)
{
if (Event.current.type != EventType.ContextClick || !rect.Contains(Event.current.mousePosition)) return;
Event.current.Use();
var (perShaderData, perMaterialData, perInspectorData) = metaDatas.GetDatas();
var (propStaticData, propDynamicData) = metaDatas.GetPropDatas(prop);
var menu = new GenericMenu();
// 2022+ Material Varant Menus
#if UNITY_2022_1_OR_NEWER
ReflectionHelper.HandleApplyRevert(menu, prop);
#endif
// Copy
menu.AddItem(new GUIContent("Copy"), false, () =>
{
_copiedMaterial = UnityEngine.Object.Instantiate(metaDatas.GetMaterial());
_copiedProps.Clear();
_copiedProps.Add(prop.name);
foreach (var extraPropName in propStaticData.extraPropNames)
{
_copiedProps.Add(extraPropName);
}
// Copy Children
foreach (var childPropStaticData in propStaticData.children)
{
_copiedProps.Add(childPropStaticData.name);
foreach (var extraPropName in childPropStaticData.extraPropNames)
{
_copiedProps.Add(extraPropName);
}
foreach (var childChildPropStaticData in childPropStaticData.children)
{
_copiedProps.Add(childChildPropStaticData.name);
foreach (var extraPropName in childChildPropStaticData.extraPropNames)
{
_copiedProps.Add(extraPropName);
}
}
}
});
// Paste
GenericMenu.MenuFunction pasteAction = () =>
{
if (!VersionControlHelper.Checkout(prop.targets))
{
Debug.LogError("LWGUI: One or more materials unable to write!");
return;
}
Undo.RecordObjects(prop.targets, "LWGUI: Paste Material Properties");
foreach (Material material in prop.targets)
{
var index = 0;
PastePropertyValueToMaterial(material, _copiedProps[index++], prop.name);
foreach (var extraPropName in propStaticData.extraPropNames)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], extraPropName);
}
// Paste Children
foreach (var childPropStaticData in propStaticData.children)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], childPropStaticData.name);
foreach (var extraPropName in childPropStaticData.extraPropNames)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], extraPropName);
}
foreach (var childChildPropStaticData in childPropStaticData.children)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], childChildPropStaticData.name);
foreach (var extraPropName in childChildPropStaticData.extraPropNames)
{
if (index == _copiedProps.Count) break;
PastePropertyValueToMaterial(material, _copiedProps[index++], extraPropName);
}
}
}
MetaDataHelper.ForceUpdateMaterialMetadataCache(material);
}
};
if (_copiedMaterial != null && _copiedProps.Count > 0 && GUI.enabled)
menu.AddItem(new GUIContent("Paste"), false, pasteAction);
else
menu.AddDisabledItem(new GUIContent("Paste"));
menu.AddSeparator("");
// Copy Display Name
menu.AddItem(new GUIContent("Copy Display Name"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = propStaticData.displayName;
});
// Copy Property Names
menu.AddItem(new GUIContent("Copy Property Names"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = prop.name;
foreach (var extraPropName in propStaticData.extraPropNames)
{
EditorGUIUtility.systemCopyBuffer += ", " + extraPropName;
}
});
// menus.AddSeparator("");
//
// // Add to Favorites
// menus.AddItem(new GUIContent("Add to Favorites"), false, () =>
// {
// });
//
// // Remove from Favorites
// menus.AddItem(new GUIContent("Remove from Favorites"), false, () =>
// {
// });
// Preset
if (GUI.enabled)
{
menu.AddSeparator("");
foreach (var activePresetData in perMaterialData.activePresetDatas)
{
// Cull self
if (activePresetData.property == prop) continue;
var activePreset = activePresetData.preset;
var (presetPropStaticData, presetPropDynamicData) = metaDatas.GetPropDatas(activePresetData.property);
var presetAsset = presetPropStaticData.propertyPresetAsset;
var presetPropDisplayName = presetPropStaticData.displayName;
// Cull invisible presets
if (!presetPropDynamicData.isShowing) continue;
if (activePreset.GetPropertyValue(prop.name) != null)
{
menu.AddItem(new GUIContent("Update to Preset/" + presetPropDisplayName + "/" + "All"), false, () => EditPresetEvent("Update", presetAsset, presetAsset.GetPresets(), prop, metaDatas));
menu.AddItem(new GUIContent("Update to Preset/" + presetPropDisplayName + "/" + activePreset.presetName), false, () => EditPresetEvent("Update", presetAsset, new List<LwguiShaderPropertyPreset.Preset>(){activePreset}, prop, metaDatas));
menu.AddItem(new GUIContent("Remove from Preset/" + presetPropDisplayName + "/" + "All"), false, () => EditPresetEvent("Remove", presetAsset, presetAsset.GetPresets(), prop, metaDatas));
menu.AddItem(new GUIContent("Remove from Preset/" + presetPropDisplayName + "/" + activePreset.presetName), false, () => EditPresetEvent("Remove", presetAsset, new List<LwguiShaderPropertyPreset.Preset>(){activePreset}, prop, metaDatas));
}
else
{
menu.AddItem(new GUIContent("Add to Preset/" + presetPropDisplayName + "/" + "All"), false, () => EditPresetEvent("Add", presetAsset, presetAsset.GetPresets(), prop, metaDatas));
menu.AddItem(new GUIContent("Add to Preset/" + presetPropDisplayName + "/" + activePreset.presetName), false, () => EditPresetEvent("Add", presetAsset, new List<LwguiShaderPropertyPreset.Preset>(){activePreset}, prop, metaDatas));
}
}
}
// Custom
if (propStaticData.baseDrawers != null)
{
foreach (var baseDrawer in propStaticData.baseDrawers)
{
baseDrawer.GetCustomContextMenus(menu, rect, prop, metaDatas);
}
}
menu.ShowAsContext();
}
#endregion
#region Importer
// https://docs.unity3d.com/ScriptReference/TextureImporter.GetPlatformTextureSettings.html
public static string[] platformNamesForTextureSettings => new[] { "DefaultTexturePlatform", "Standalone", "Web", "iPhone", "Android", "WebGL", "Windows Store Apps", "PS4", "XboxOne", "Nintendo Switch", "tvOS" };
public static string[] platformNamesForTextureSettings =>
new[] { "DefaultTexturePlatform", "Standalone", "Web", "iPhone", "Android", "WebGL", "Windows Store Apps", "PS4", "XboxOne", "Nintendo Switch", "tvOS" };
#endregion
}

View File

@ -0,0 +1,215 @@
// Copyright (c) Jason Ma
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using LWGUI.PerformanceMonitor;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace LWGUI
{
public static class IOHelper
{
#region Paths
private static string _cachedProjectPath;
// D:/Unity/ProjectName/
public static string ProjectPath
{
get
{
if (string.IsNullOrEmpty(_cachedProjectPath))
_cachedProjectPath = Application.dataPath[..^6];
return _cachedProjectPath;
}
}
public static string GetAbsPath(string unityProjectRelativePath) => Path.Combine(ProjectPath, unityProjectRelativePath);
public static string GetRelativePath(string absPath) => Path.GetFullPath(absPath).Replace(Path.GetFullPath(ProjectPath), string.Empty);
private static string _cachedCompiledShaderCachePath;
public static string CompiledShaderCacheRootPath
{
get
{
if (string.IsNullOrEmpty(_cachedCompiledShaderCachePath))
_cachedCompiledShaderCachePath = Path.Combine(ProjectPath, "Library", "LWGUI", "ShaderPerfCache");
return _cachedCompiledShaderCachePath;
}
}
public static string GetValidFileName(string text)
{
StringBuilder str = new StringBuilder();
var invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (var c in text)
{
if (!invalidFileNameChars.Contains(c))
{
str.Append(c);
}
}
return str.ToString();
}
#endregion
#region File
public static bool ExistAndNotEmpty(string filePath) => File.Exists(filePath) && new FileInfo(filePath).Length > 1;
public static void WriteBinaryFile(string filePath, byte[] bytes)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? string.Empty);
File.WriteAllBytes(filePath, bytes ?? Array.Empty<byte>());
}
catch (Exception e)
{
Debug.LogError(e.Message);
}
}
public static void WriteTextFile(string filePath, string text)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? string.Empty);
File.WriteAllText(filePath, text ?? string.Empty, Encoding.UTF8);
}
catch (Exception e)
{
Debug.LogError(e.Message);
}
}
public static string ReadTextFile(string filePath)
{
try
{
return File.ReadAllText(filePath, Encoding.UTF8);
}
catch (Exception e)
{
Debug.LogError(e.Message);
return null;
}
}
#endregion
#region Process
public static bool RunProcess(string file, string args,
out string output)
{
output = string.Empty;
var p = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = file,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
}
};
var stdout = new StringBuilder();
var stderr = new StringBuilder();
p.OutputDataReceived += (_, e) => stdout.AppendLine(e.Data);
p.ErrorDataReceived += (_, e) => stderr.AppendLine(e.Data);
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
output = stdout.ToString();
if (p.ExitCode != 0)
{
output = stderr.ToString() + output;
Debug.LogError($"LWGUI: Process Exit Code {p.ExitCode}: {output}" +
$"File: {file}\n" +
$"Args: {args}");
return false;
}
if (string.IsNullOrWhiteSpace(output))
{
output = stderr.ToString();
return false;
}
return true;
}
public static bool RunCMD(string args,
out string output)
{
return RunProcess("cmd.exe", $"/C {args}",
out output);
}
public static void OpenFile(string filePath)
{
Process.Start(filePath);
}
#endregion
#region Performance Monitor
public static string GetCompiledShaderCacheRootDirectory(Shader shader)
{
var shaderPath = AssetDatabase.Contains(shader) ? AssetDatabase.GetAssetPath(shader) : null;
var shaderCachePath = shaderPath != null
// Assets/Shaders/Lit.shader => Shaders/Lit
? Path.Combine(Path.GetDirectoryName(shaderPath[7..]) ?? string.Empty, Path.GetFileNameWithoutExtension(shaderPath))
: GetValidFileName(shader.name.Replace('/', '_').Replace('\\', '_'));
return Path.Combine(CompiledShaderCacheRootPath, shaderCachePath);
}
public static string GetCompiledShaderVariantCacheDirectory(Shader shader, ShaderPerfData shaderPerfData)
{
return Path.Combine(
GetCompiledShaderCacheRootDirectory(shader),
shaderPerfData.subshaderIndex.ToString(),
shaderPerfData.passName,
shaderPerfData.hash);
}
public static void ClearShaderPerfCache(Shader shader)
{
try
{
var shaderDir = GetCompiledShaderCacheRootDirectory(shader);
if (Directory.Exists(shaderDir))
Directory.Delete(shaderDir, true);
}
catch (Exception e)
{
Debug.LogWarning($"LWGUI: Cleaning the Shader({shader.name}) cache failed: {e.Message}");
}
}
#endregion
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d97df5d9f08d48ac9dedc5b421b936dc
timeCreated: 1760615660

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
@ -14,7 +14,6 @@ namespace LWGUI
public PerMaterialData perMaterialData;
public PerInspectorData perInspectorData;
#region Get Prop Data
public PropertyStaticData GetPropStaticData(string propName) => perShaderData?.GetPropStaticData(propName);
@ -27,7 +26,7 @@ namespace LWGUI
public MaterialProperty GetProperty(string propName) => GetPropDynamicData(propName)?.property;
public MaterialProperty GetDefaultProperty(string propName) => GetPropDynamicData(propName)?.defualtProperty;
public MaterialProperty GetDefaultProperty(string propName) => GetPropDynamicData(propName)?.defaultProperty;
#endregion
@ -47,6 +46,8 @@ namespace LWGUI
public Material GetMaterial() => perMaterialData.material;
public Shader GetShader() => perShaderData.shader;
public string GetShaderUID() => perShaderData.shaderUID;
public MaterialEditor GetMaterialEditor() => perInspectorData.materialEditor;
}

View File

@ -53,14 +53,18 @@ namespace LWGUI
if (!_loadedPresets.ContainsKey(presetFileName) || !_loadedPresets[presetFileName])
{
Debug.LogError("LWGUI: Invalid ShaderPropertyPreset path: " + presetFileName + " !");
if (!BuildPipeline.isBuildingPlayer)
Debug.LogError("LWGUI: Invalid ShaderPropertyPreset path: " + presetFileName + " !");
return null;
}
return _loadedPresets[presetFileName];
}
// For Developers: Call this after a material has modified in code
// For Developers: Call this function after creating a material,
// This applies all active presets and may modify some other properties.
// Usually called after the material is created, otherwise the material default value will not contain the results of Preset Drawers.
// If you only want to apply Keywords without modifying other properties, call UnityEditorExtension.ApplyMaterialPropertyAndDecoratorDrawers()
public static void ApplyPresetsInMaterial(Material material)
{
var props = MaterialEditor.GetMaterialProperties(new UnityEngine.Object[] { material });
@ -69,11 +73,10 @@ namespace LWGUI
var drawer = ReflectionHelper.GetPropertyDrawer(material.shader, prop, out _);
// Apply active preset
if (drawer != null && drawer is IPresetDrawer)
if (drawer is IPresetDrawer presetDrawer)
{
var activePreset = (drawer as IPresetDrawer).GetActivePreset(prop, PresetHelper.GetPresetAsset((drawer as PresetDrawer).presetFileName));
if (activePreset != null)
activePreset.ApplyToDefaultMaterial(material);
var activePreset = presetDrawer.GetActivePreset(prop, GetPresetAsset(presetDrawer.GetPresetFileName()));
activePreset?.ApplyToDefaultMaterial(material);
}
}
UnityEditorExtension.ApplyMaterialPropertyAndDecoratorDrawers(material);

View File

@ -1,12 +1,10 @@
// Copyright (c) Jason Ma
using System;
// Copyright (c) Jason Ma
using System.IO;
using System.Linq;
using LWGUI.LwguiGradientEditor;
using LWGUI.Runtime.LwguiGradient;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace LWGUI
{
@ -14,10 +12,13 @@ namespace LWGUI
{
#region RampEditor
private static readonly GUIContent _iconAdd = new GUIContent(EditorGUIUtility.IconContent("d_Toolbar Plus").image, "Add"),
_iconEdit = new GUIContent(EditorGUIUtility.IconContent("editicon.sml").image, "Edit"),
_iconDiscard = new GUIContent(EditorGUIUtility.IconContent("d_TreeEditor.Refresh").image, "Discard"),
_iconSave = new GUIContent(EditorGUIUtility.IconContent("SaveActive").image, "Save");
private const string _iconCloneGUID = "9cdef444d18d2ce4abb6bbc4fed4d109";
private static readonly GUIContent _iconAdd = new (EditorGUIUtility.IconContent("d_Toolbar Plus").image, "Add"),
_iconClone = new (EditorGUIUtility.IconContent("AnimatorController Icon").image, "Clone"),
_iconEdit = new (EditorGUIUtility.IconContent("editicon.sml").image, "Edit"),
_iconDiscard = new (EditorGUIUtility.IconContent("d_TreeEditor.Refresh").image, "Discard"),
_iconSave = new (EditorGUIUtility.IconContent("SaveActive").image, "Save");
public static void RampEditor(
Rect buttonRect,
@ -29,6 +30,7 @@ namespace LWGUI
out bool hasChange,
out bool doEditWhenNoGradient,
out bool doRegisterUndo,
out bool doClone,
out bool doCreate,
out bool doSave,
out bool doDiscard,
@ -38,11 +40,12 @@ namespace LWGUI
var hasNoGradient = gradient == null;
var _doEditWhenNoGradient = false;
var doOpenWindow = false;
var singleButtonWidth = buttonRect.width * 0.25f;
var singleButtonWidth = buttonRect.width * 0.2f;
var editRect = new Rect(buttonRect.x + singleButtonWidth * 0, buttonRect.y, singleButtonWidth, buttonRect.height);
var saveRect = new Rect(buttonRect.x + singleButtonWidth * 1, buttonRect.y, singleButtonWidth, buttonRect.height);
var addRect = new Rect(buttonRect.x + singleButtonWidth * 2, buttonRect.y, singleButtonWidth, buttonRect.height);
var discardRect = new Rect(buttonRect.x + singleButtonWidth * 3, buttonRect.y, singleButtonWidth, buttonRect.height);
var cloneRect = new Rect(buttonRect.x + singleButtonWidth * 2, buttonRect.y, singleButtonWidth, buttonRect.height);
var addRect = new Rect(buttonRect.x + singleButtonWidth * 3, buttonRect.y, singleButtonWidth, buttonRect.height);
var discardRect = new Rect(buttonRect.x + singleButtonWidth * 4, buttonRect.y, singleButtonWidth, buttonRect.height);
// Edit button event
hasChange = false;
@ -76,6 +79,8 @@ namespace LWGUI
}
doEditWhenNoGradient = _doEditWhenNoGradient;
// Clone button
doClone = GUI.Button(cloneRect, _iconClone);
// Create button
doCreate = GUI.Button(addRect, _iconAdd);
@ -143,9 +148,8 @@ namespace LWGUI
// Save texture to disk
if (doSaveToDisk)
{
var systemPath = Helper.ProjectPath + path;
VersionControlHelper.Checkout(path);
File.WriteAllBytes(systemPath, texture2D.EncodeToPNG());
File.WriteAllBytes(IOHelper.GetAbsPath(path), texture2D.EncodeToPNG());
assetImporter.SaveAndReimport();
}
}
@ -190,15 +194,14 @@ namespace LWGUI
return subJSONs[0] != subJSONs[1];
}
public static bool CreateAndSaveNewGradientTexture(int width, int height, string unityPath, bool isLinear)
public static bool CreateAndSaveNewGradientTexture(int width, int height, string unityPath, bool isLinear, LwguiGradient sourceGradient = null)
{
var gradient = new LwguiGradient();
var gradient = sourceGradient != null ? new LwguiGradient(sourceGradient) : new LwguiGradient();
var ramp = gradient.GetPreviewRampTexture(width, height, ColorSpace.Linear);
var png = ramp.EncodeToPNG();
var systemPath = Helper.ProjectPath + unityPath;
File.WriteAllBytes(systemPath, png);
File.WriteAllBytes(IOHelper.GetAbsPath(unityPath), png);
AssetDatabase.ImportAsset(unityPath);
SetRampTextureImporter(unityPath, true, isLinear, EncodeGradientToJSON(gradient, gradient));
@ -262,33 +265,40 @@ namespace LWGUI
}
}
public static void RampIndexSelectorOverride(Rect rect, MaterialProperty prop, LwguiRampAtlas rampAtlas, RampSelectorWindow.SwitchRampMapCallback switchRampMapEvent)
{
if (!rampAtlas)
return;
var e = Event.current;
if (e.type == UnityEngine.EventType.MouseDown && rect.Contains(e.mousePosition))
{
e.Use();
RampSelectorWindow.ShowWindow(prop, rampAtlas.GetTexture2Ds(LwguiGradient.ChannelMask.RGB), switchRampMapEvent);
}
}
#endregion
}
public class RampSelectorWindow : EditorWindow
{
public delegate void SwitchRampMapCallback(MaterialProperty prop, Texture2D newRampMap, int index);
public delegate void SwitchRampCallback(MaterialProperty prop, int rampIndex);
private LwguiRampAtlas _rampAtlas;
private Texture2D[] _rampMaps;
private Vector2 _scrollPosition;
private MaterialProperty _prop;
private SwitchRampCallback _switchRampEvent;
private SwitchRampMapCallback _switchRampMapEvent;
private const float RowHeight = 18f;
private const float RowSpacing = 2f;
public static void ShowWindow(MaterialProperty prop, LwguiRampAtlas rampAtlas, SwitchRampCallback switchRampEvent)
{
LwguiGradientWindow.CloseWindow();
var window = CreateInstance<RampSelectorWindow>();
window.titleContent = new GUIContent("Ramp Selector (Atlas)");
window.minSize = new Vector2(400, 500);
window._rampAtlas = rampAtlas;
window._prop = prop;
window._switchRampEvent = switchRampEvent;
window.ShowAuxWindow();
}
public static void ShowWindow(MaterialProperty prop, Texture2D[] rampMaps, SwitchRampMapCallback switchRampMapEvent)
{
RampSelectorWindow window = ScriptableObject.CreateInstance<RampSelectorWindow>();
LwguiGradientWindow.CloseWindow();
var window = CreateInstance<RampSelectorWindow>();
window.titleContent = new GUIContent("Ramp Selector");
window.minSize = new Vector2(400, 500);
window._rampMaps = rampMaps;
@ -298,6 +308,62 @@ namespace LWGUI
}
private void OnGUI()
{
if (_rampAtlas != null)
DrawRampAtlasSelector();
else if (_rampMaps != null)
DrawRampMapSelector();
else
EditorGUILayout.HelpBox("No Ramp data available", MessageType.Error);
}
private void DrawRampAtlasSelector()
{
EditorGUILayout.BeginVertical();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
for (int i = 0; i < _rampAtlas.RampCount; i++)
{
var ramp = _rampAtlas.GetRamp(i);
if (ramp == null) continue;
var previewTextures = ramp.GetPreviewTexturesForRampSelector(_rampAtlas.rampAtlasWidth);
var textureCount = previewTextures?.Length ?? 0;
var totalHeight = Mathf.Max(1, textureCount) * RowHeight + Mathf.Max(0, textureCount - 1) * RowSpacing;
var rect = EditorGUILayout.GetControlRect(GUILayout.Height(totalHeight));
var guiContent = new GUIContent($"{i}. {ramp.Name}");
var buttonWidth = Mathf.Min(300f, Mathf.Max(GUI.skin.button.CalcSize(guiContent).x, rect.width * 0.35f));
var buttonRect = new Rect(rect.x + rect.width - buttonWidth, rect.y, buttonWidth, totalHeight);
var previewWidth = rect.width - buttonWidth - 3.0f;
// Draw preview textures vertically
if (previewTextures != null)
{
for (int j = 0; j < previewTextures.Length; j++)
{
if (previewTextures[j] == null) continue;
var previewRect = new Rect(rect.x, rect.y + j * (RowHeight + RowSpacing), previewWidth, RowHeight);
EditorGUI.DrawPreviewTexture(previewRect, previewTextures[j]);
}
}
// Draw button (stretches to cover all preview rows)
if (GUI.Button(buttonRect, guiContent, GUIStyles.rampSelectButton) && _switchRampEvent != null)
{
_switchRampEvent(_prop, i);
LwguiGradientWindow.CloseWindow();
Close();
}
GUILayout.Space(RowSpacing);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void DrawRampMapSelector()
{
EditorGUILayout.BeginVertical();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
@ -305,26 +371,26 @@ namespace LWGUI
for (int i = 0; i < _rampMaps.Length; i++)
{
var rampMap = _rampMaps[i];
EditorGUILayout.BeginHorizontal();
if (rampMap != null)
if (rampMap == null) continue;
var rect = EditorGUILayout.GetControlRect(GUILayout.Height(RowHeight));
var guiContent = new GUIContent($"{i}. {rampMap.name}");
var buttonWidth = Mathf.Min(300f, Mathf.Max(GUI.skin.button.CalcSize(guiContent).x, rect.width * 0.35f));
var buttonRect = new Rect(rect.x + rect.width - buttonWidth, rect.y, buttonWidth, RowHeight);
var previewRect = new Rect(rect.x, rect.y, rect.width - buttonWidth - 3.0f, RowHeight);
EditorGUI.DrawPreviewTexture(previewRect, rampMap);
if (GUI.Button(buttonRect, guiContent, GUIStyles.rampSelectButton) && _switchRampMapEvent != null)
{
var guiContent = new GUIContent($"{ i }. { rampMap.name }");
var rect = EditorGUILayout.GetControlRect();
var buttonWidth = Mathf.Min(300f, Mathf.Max(GUI.skin.button.CalcSize(guiContent).x, rect.width * 0.35f));
var buttonRect = new Rect(rect.x + rect.width - buttonWidth, rect.y, buttonWidth, rect.height);
var previewRect = new Rect(rect.x, rect.y, rect.width - buttonWidth - 3.0f, rect.height);
if (GUI.Button(buttonRect, guiContent, Helper.guiStyle_RampSelectButton) && _switchRampMapEvent != null)
{
_switchRampMapEvent(_prop, rampMap, i);
LwguiGradientWindow.CloseWindow();
Close();
}
EditorGUI.DrawPreviewTexture(previewRect, rampMap);
_switchRampMapEvent(_prop, rampMap, i);
LwguiGradientWindow.CloseWindow();
Close();
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(RowSpacing);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma
// Copyright (c) Jason Ma
using System;
using System.Linq;
@ -136,11 +136,24 @@ namespace LWGUI
if (isHeader)
{
foreach (var childStaticData in propStaticData.children)
RevertGroupChildren(propStaticData, metaDatas);
// Reverting a group that contains a preset may change which preset is active,
// making the first-pass defaults stale. Re-init with fresh MaterialProperty
// objects to recalculate defaults, then re-revert to get correct final values.
// (Fresh props are needed because MaterialProperty.floatValue getter may
// return stale cached values after writes within the same GUI event.)
if (GroupContainsPreset(propStaticData))
{
DoRevertProperty(metaDatas.GetProperty(childStaticData.name), metaDatas);
foreach (var childChildStaticData in childStaticData.children)
DoRevertProperty(metaDatas.GetProperty(childChildStaticData.name), metaDatas);
var perMaterialData = metaDatas.perMaterialData;
var freshProps = MaterialEditor.GetMaterialProperties(
new UnityEngine.Object[] { perMaterialData.material });
perMaterialData.InvalidateDefaultMaterialCache();
perMaterialData.Init(metaDatas.GetShader(), perMaterialData.material,
metaDatas.GetMaterialEditor(), freshProps, metaDatas.perShaderData);
DoRevertProperty(prop, metaDatas);
RevertGroupChildren(propStaticData, metaDatas);
}
}
@ -149,16 +162,43 @@ namespace LWGUI
return false;
}
private static void RevertGroupChildren(PropertyStaticData propStaticData, LWGUIMetaDatas metaDatas)
{
foreach (var childStaticData in propStaticData.children)
{
DoRevertProperty(metaDatas.GetProperty(childStaticData.name), metaDatas);
foreach (var childChildStaticData in childStaticData.children)
DoRevertProperty(metaDatas.GetProperty(childChildStaticData.name), metaDatas);
}
}
private static bool GroupContainsPreset(PropertyStaticData propStaticData)
{
if (propStaticData.presetDrawer != null)
return true;
foreach (var child in propStaticData.children)
{
if (child.presetDrawer != null)
return true;
foreach (var grandchild in child.children)
{
if (grandchild.presetDrawer != null)
return true;
}
}
return false;
}
private static void DoRevertProperty(MaterialProperty prop, LWGUIMetaDatas metaDatas)
{
var propDynamicData = metaDatas.GetPropDynamicData(prop.name);
propDynamicData.hasRevertChanged = true;
SetPropertyToDefault(propDynamicData.defualtProperty, prop);
SetPropertyToDefault(propDynamicData.defaultProperty, prop);
foreach (var extraPropName in metaDatas.GetPropStaticData(prop.name).extraPropNames)
{
var extraPropDynamicData = metaDatas.GetPropDynamicData(extraPropName);
extraPropDynamicData.hasRevertChanged = true;
SetPropertyToDefault(extraPropDynamicData.defualtProperty, extraPropDynamicData.property);
SetPropertyToDefault(extraPropDynamicData.defaultProperty, extraPropDynamicData.property);
}
}

View File

@ -0,0 +1,578 @@
// Copyright (c) Jason Ma
using System;
using System.Linq;
using UnityEditor;
using UnityEngine;
using LWGUI.PerformanceMonitor;
namespace LWGUI
{
public static class ToolbarHelper
{
#region Toolbar Buttons
internal enum CopyMaterialValueMask
{
Float = 1 << 0,
Vector = 1 << 1,
Texture = 1 << 2,
Keyword = 1 << 3,
RenderQueue = 1 << 4,
Number = Float | Vector,
All = (1 << 5) - 1,
}
public const uint CopyMaterialValueMaskAll = (uint)CopyMaterialValueMask.All;
private static GUIContent[] _pasteMaterialMenus = new[]
{
new GUIContent("Paste Number Values"),
new GUIContent("Paste Texture Values"),
new GUIContent("Paste Keywords"),
new GUIContent("Paste RenderQueue"),
};
private static uint[] _pasteMaterialMenuValueMasks = new[]
{
(uint)CopyMaterialValueMask.Number,
(uint)CopyMaterialValueMask.Texture,
(uint)CopyMaterialValueMask.Keyword,
(uint)CopyMaterialValueMask.RenderQueue,
};
private const string _iconCopyGUID = "9cdef444d18d2ce4abb6bbc4fed4d109";
private const string _iconPasteGUID = "8e7a78d02e4c3574998524a0842a8ccb";
private const string _iconSelectGUID = "6f44e40b24300974eb607293e4224ecc";
private const string _iconCheckoutGUID = "72488141525eaa8499e65e52755cb6d0";
private const string _iconExpandGUID = "2382450e7f4ddb94c9180d6634c41378";
private const string _iconCollapseGUID = "929b6e5dfacc42b429d715a3e1ca2b57";
private const string _iconStatsGUID = "88909414120107547a673b8fcddc5236";
private const string _iconVisibilityGUID = "9576e23a695b35d49a9fc55c9a948b4f";
private const string _iconCopyTooltip = "Copy Material Properties";
private const string _iconPasteTooltip = "Paste Material Properties\n\nRight-click to paste values by type.";
private const string _iconSelectTooltip = "Select the Material Asset\n\nUsed to jump from a Runtime Material Instance to a Material Asset.";
private const string _iconCheckoutTooltip = "Checkout selected Material Assets";
private const string _iconExpandTooltip = "Expand All Groups";
private const string _iconCollapseTooltip = "Collapse All Groups";
private const string _iconStatsTooltip = "Display Shader Performance Stats";
private const string _iconVisibilityTooltip = "Display Mode";
private static GUIContent _guiContentCopyCache;
private static GUIContent _guiContentPasteCache;
private static GUIContent _guiContentSelectCache;
private static GUIContent _guiContentCheckoutCache;
private static GUIContent _guiContentExpandCache;
private static GUIContent _guiContentCollapseCache;
private static GUIContent _guiContentStatsCache;
private static GUIContent _guiContentVisibilityCache;
private static GUIContent _guiContentCopy => _guiContentCopyCache = _guiContentCopyCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconCopyGUID)), _iconCopyTooltip);
private static GUIContent _guiContentPaste => _guiContentPasteCache = _guiContentPasteCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconPasteGUID)), _iconPasteTooltip);
private static GUIContent _guiContentSelect => _guiContentSelectCache = _guiContentSelectCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconSelectGUID)), _iconSelectTooltip);
private static GUIContent _guiContentChechout => _guiContentCheckoutCache = _guiContentCheckoutCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconCheckoutGUID)), _iconCheckoutTooltip);
private static GUIContent _guiContentExpand => _guiContentExpandCache = _guiContentExpandCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconExpandGUID)), _iconExpandTooltip);
private static GUIContent _guiContentCollapse => _guiContentCollapseCache = _guiContentCollapseCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconCollapseGUID)), _iconCollapseTooltip);
private static GUIContent _guiContentStats => _guiContentStatsCache = _guiContentStatsCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconStatsGUID)), _iconStatsTooltip);
private static GUIContent _guiContentVisibility => _guiContentVisibilityCache = _guiContentVisibilityCache ?? new GUIContent("", AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(_iconVisibilityGUID)), _iconVisibilityTooltip);
public static void DrawToolbarButtons(ref Rect toolBarRect, LWGUIMetaDatas metaDatas)
{
var (perShaderData, perMaterialData, perInspectorData) = metaDatas.GetDatas();
var shader = metaDatas.GetShader();
//----------------------------------------------------------------------------------------------------------------
// Copy
var buttonRectOffset = toolBarRect.height + 2;
var buttonRect = new Rect(toolBarRect.x, toolBarRect.y, toolBarRect.height, toolBarRect.height);
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentCopy, GUIStyles.iconButton))
{
ContextMenuHelper.CopyMaterial(metaDatas.GetMaterial());
}
//----------------------------------------------------------------------------------------------------------------
// Paste
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
// Right Click
if (Event.current.type == EventType.MouseDown
&& Event.current.button == 1
&& buttonRect.Contains(Event.current.mousePosition))
{
EditorUtility.DisplayCustomMenu(new Rect(Event.current.mousePosition.x, Event.current.mousePosition.y, 0, 0), _pasteMaterialMenus, -1,
(data, options, selected) => { ContextMenuHelper.PastePropertiesToMaterials(metaDatas, _pasteMaterialMenuValueMasks[selected]); }, null);
Event.current.Use();
}
// Left Click
if (GUI.Button(buttonRect, _guiContentPaste, GUIStyles.iconButton))
{
ContextMenuHelper.PastePropertiesToMaterials(metaDatas, (uint)CopyMaterialValueMask.All);
}
//----------------------------------------------------------------------------------------------------------------
// Select Material Asset, jump from a Runtime Material Instance to a Material Asset
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentSelect, GUIStyles.iconButton))
{
var material = metaDatas.GetMaterial();
if (AssetDatabase.Contains(material))
{
Selection.activeObject = material;
}
else
{
if (FindMaterialAssetByMaterialInstance(material, metaDatas, out var materialAsset))
{
Selection.activeObject = materialAsset;
}
}
}
//----------------------------------------------------------------------------------------------------------------
// Checkout
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentChechout, GUIStyles.iconButton))
{
VersionControlHelper.Checkout(metaDatas.GetMaterialEditor().targets);
}
//----------------------------------------------------------------------------------------------------------------
// Expand
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentExpand, GUIStyles.iconButton))
{
foreach (var propStaticDataKVPair in perShaderData.propStaticDatas)
{
if (propStaticDataKVPair.Value.isMain || propStaticDataKVPair.Value.isAdvancedHeader)
propStaticDataKVPair.Value.isExpanding = true;
}
}
//----------------------------------------------------------------------------------------------------------------
// Collapse
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
if (GUI.Button(buttonRect, _guiContentCollapse, GUIStyles.iconButton))
{
foreach (var propStaticDataKVPair in perShaderData.propStaticDatas)
{
if (propStaticDataKVPair.Value.isMain || propStaticDataKVPair.Value.isAdvancedHeader)
propStaticDataKVPair.Value.isExpanding = false;
}
}
//----------------------------------------------------------------------------------------------------------------
// Shader Perf Stats
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
{
var color = GUI.color;
if (IsDisplayShaderPerfStatsEnabled(metaDatas.GetShaderUID()))
GUI.color = Color.yellow;
if (GUI.Button(buttonRect, _guiContentStats, GUIStyles.iconButton))
SwitchDisplayShaderPerfStatsEnabled(shader, metaDatas.GetShaderUID());
GUI.color = color;
}
//----------------------------------------------------------------------------------------------------------------
// Display Mode
buttonRect.x += buttonRectOffset;
toolBarRect.xMin += buttonRectOffset;
{
var color = GUI.color;
var displayModeData = perShaderData.displayModeData;
if (!displayModeData.IsDefaultDisplayMode())
GUI.color = Color.yellow;
if (GUI.Button(buttonRect, _guiContentVisibility, GUIStyles.iconButton))
{
// Build Display Mode Menu Items
var displayModeMenus = new[]
{
$"Show All Advanced Properties ({displayModeData.advancedCount} - {perShaderData.propStaticDatas.Count})",
$"Show All Hidden Properties ({displayModeData.hiddenCount} - {perShaderData.propStaticDatas.Count})",
$"Show Only Modified Properties ({perMaterialData.modifiedCount} - {perShaderData.propStaticDatas.Count})",
$"Show Only Modified Properties by Group ({perMaterialData.modifiedCount} - {perShaderData.propStaticDatas.Count})",
};
var enabled = new[] { true, true, true, true };
var separator = new bool[4];
var selected = new[]
{
displayModeData.showAllAdvancedProperties ? 0 : -1,
displayModeData.showAllHiddenProperties ? 1 : -1,
displayModeData.showOnlyModifiedProperties ? 2 : -1,
displayModeData.showOnlyModifiedGroups ? 3 : -1,
};
// Click Event
void OnSwitchDisplayMode(object data, string[] options, int selectedIndex)
{
switch (selectedIndex)
{
case 0: // Show All Advanced Properties
displayModeData.showAllAdvancedProperties = !displayModeData.showAllAdvancedProperties;
perShaderData.ToggleShowAllAdvancedProperties();
break;
case 1: // Show All Hidden Properties
displayModeData.showAllHiddenProperties = !displayModeData.showAllHiddenProperties;
break;
case 2: // Show Only Modified Properties
displayModeData.showOnlyModifiedProperties = !displayModeData.showOnlyModifiedProperties;
if (displayModeData.showOnlyModifiedProperties) displayModeData.showOnlyModifiedGroups = false;
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(shader);
break;
case 3: // Show Only Modified Groups
displayModeData.showOnlyModifiedGroups = !displayModeData.showOnlyModifiedGroups;
if (displayModeData.showOnlyModifiedGroups) displayModeData.showOnlyModifiedProperties = false;
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(shader);
break;
}
}
ReflectionHelper.DisplayCustomMenuWithSeparators(new Rect(Event.current.mousePosition.x, Event.current.mousePosition.y, 0, 0),
displayModeMenus, enabled, separator, selected, OnSwitchDisplayMode);
}
GUI.color = color;
}
toolBarRect.xMin += 2;
}
public static Func<Renderer, Material, Material> onFindMaterialAssetInRendererByMaterialInstance;
public static bool FindMaterialAsset(LWGUIMetaDatas metaDatas, out Material materialAsset)
{
return FindMaterialAssetByMaterialInstance(metaDatas.GetMaterial(), metaDatas, out materialAsset);
}
private static bool FindMaterialAssetByMaterialInstance(Material material, LWGUIMetaDatas metaDatas, out Material materialAsset)
{
materialAsset = null;
var renderers = metaDatas.perInspectorData.materialEditor.GetMeshRenderersByMaterialEditor();
foreach (var renderer in renderers)
{
if (onFindMaterialAssetInRendererByMaterialInstance != null)
{
materialAsset = onFindMaterialAssetInRendererByMaterialInstance(renderer, material);
}
if (materialAsset == null)
{
int index = renderer.materials.ToList().FindIndex(materialInstance => materialInstance == material);
if (index >= 0 && index < renderer.sharedMaterials.Length)
{
materialAsset = renderer.sharedMaterials[index];
}
}
if (materialAsset != null && AssetDatabase.Contains(materialAsset))
return true;
}
Debug.LogError("LWGUI: Can not find the Material Assets of: " + material.name);
return false;
}
#endregion
#region Search Field
private static readonly int s_TextFieldHash = "EditorTextField".GetHashCode();
private static readonly GUIContent[] _searchModeMenus = Enumerable.Range(0, (int)SearchMode.Num - 1).Select(i =>
new GUIContent(((SearchMode)i).ToString())).ToArray();
/// <returns>is has changed?</returns>
public static bool DrawSearchField(Rect rect, LWGUIMetaDatas metaDatas)
{
var (perShaderData, perMaterialData, perInspectorData) = metaDatas.GetDatas();
bool hasChanged = false;
EditorGUI.BeginChangeCheck();
var revertButtonRect = RevertableHelper.SplitRevertButtonRect(ref rect);
// Get internal TextField ControlID
int controlId = GUIUtility.GetControlID(s_TextFieldHash, FocusType.Keyboard, rect) + 1;
// searching mode
Rect modeRect = new Rect(rect);
modeRect.width = 20f;
if (Event.current.type == UnityEngine.EventType.MouseDown && modeRect.Contains(Event.current.mousePosition))
{
EditorUtility.DisplayCustomMenu(rect, _searchModeMenus, (int)perShaderData.searchMode,
(data, options, selected) =>
{
perShaderData.searchMode = (SearchMode)selected;
hasChanged = true;
}, null);
Event.current.Use();
}
perShaderData.searchString = EditorGUI.TextField(rect, String.Empty, perShaderData.searchString, GUIStyles.toolbarSearchTextFieldPopup);
if (EditorGUI.EndChangeCheck())
hasChanged = true;
// revert button
if (!string.IsNullOrEmpty(perShaderData.searchString)
&& RevertableHelper.DrawRevertButton(revertButtonRect))
{
perShaderData.searchString = string.Empty;
hasChanged = true;
GUIUtility.keyboardControl = 0;
}
// display search mode
if (GUIUtility.keyboardControl != controlId
&& string.IsNullOrEmpty(perShaderData.searchString)
&& Event.current.type == UnityEngine.EventType.Repaint)
{
using (new EditorGUI.DisabledScope(true))
{
var disableTextRect = new Rect(rect.x, rect.y, rect.width,
GUIStyles.toolbarSearchTextFieldPopup.fixedHeight > 0.0
? GUIStyles.toolbarSearchTextFieldPopup.fixedHeight
: rect.height);
disableTextRect = GUIStyles.toolbarSearchTextFieldPopup.padding.Remove(disableTextRect);
int fontSize = EditorStyles.label.fontSize;
EditorStyles.label.fontSize = GUIStyles.toolbarSearchTextFieldPopup.fontSize;
EditorStyles.label.Draw(disableTextRect, new GUIContent(perShaderData.searchMode.ToString()), false, false, false, false);
EditorStyles.label.fontSize = fontSize;
}
}
if (hasChanged) perShaderData.UpdateSearchFilter();
return hasChanged;
}
#endregion
#region Shader Perf Stats
#region Keyword Overrides
private static string GetShowKeywordOverridesPreferenceKey(string shaderUID) => $"LWGUI/{shaderUID}/ShowKeywordOverrides";
private static string GetKeywordOverridePreferenceKey(string shaderUID, string keyword) => $"LWGUI/{shaderUID}/KeywordOverride/{keyword}/IsOverride";
private static string GetKeywordEnabledPreferenceKey(string shaderUID, string keyword) => $"LWGUI/{shaderUID}/KeywordOverride/{keyword}/IsEnabled";
private static bool IsShowKeywordOverridesEnabled(string shaderUID) => EditorPrefs.GetBool(GetShowKeywordOverridesPreferenceKey(shaderUID), false);
public static bool IsUserKeywordOverride(string shaderUID, string keyword) => EditorPrefs.HasKey(GetKeywordOverridePreferenceKey(shaderUID, keyword));
public static bool IsUserKeywordEnabled(string shaderUID, string keyword) => EditorPrefs.GetBool(GetKeywordEnabledPreferenceKey(shaderUID, keyword), false);
private static void SetShowKeywordOverridesEnabled(string shaderUID, bool enabled)
{
if (enabled)
EditorPrefs.SetBool(GetShowKeywordOverridesPreferenceKey(shaderUID), true);
else
EditorPrefs.DeleteKey(GetShowKeywordOverridesPreferenceKey(shaderUID));
}
private static void SetUserKeywordOverride(Shader shader, string shaderUID, string keyword, bool isOverride)
{
var overrideKey = GetKeywordOverridePreferenceKey(shaderUID, keyword);
if (isOverride)
EditorPrefs.SetBool(overrideKey, true);
else
EditorPrefs.DeleteKey(overrideKey);
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(shader);
}
private static void SetUserKeywordEnabled(Shader shader, string shaderUID, string keyword, bool isEnabled)
{
EditorPrefs.SetBool(GetKeywordEnabledPreferenceKey(shaderUID, keyword), isEnabled);
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(shader);
}
private static void DrawKeywordOverridesList(LWGUIMetaDatas metaDatas)
{
var shader = metaDatas.GetShader();
var shaderUID = metaDatas.GetShaderUID();
if (!shader) return;
var showKeywordOverrides = IsShowKeywordOverridesEnabled(shaderUID);
EditorGUI.indentLevel++;
var newShowKeywordOverrides = EditorGUILayout.Foldout(showKeywordOverrides, "Keyword Overrides");
if (newShowKeywordOverrides != showKeywordOverrides)
SetShowKeywordOverridesEnabled(shaderUID, newShowKeywordOverrides);
if (newShowKeywordOverrides)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Enabled");
EditorGUILayout.LabelField(" Override");
EditorGUILayout.EndHorizontal();
var activeKeywords = metaDatas.perMaterialData.activeKeywords;
var allKeywords = shader.keywordSpace.keywords.Select(k => k.name).ToList();
foreach (var keyword in allKeywords)
{
var rect = EditorGUILayout.BeginHorizontal();
// Context Menu
if (Event.current.type == EventType.ContextClick && rect.Contains(Event.current.mousePosition))
{
Event.current.Use();
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Copy Keyword"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = keyword;
});
menu.ShowAsContext();
}
bool currentOverride = IsUserKeywordOverride(shaderUID, keyword);
bool currentEnabled = currentOverride ? IsUserKeywordEnabled(shaderUID, keyword) : activeKeywords.Contains(keyword);
using (new EditorGUI.DisabledGroupScope(!currentOverride))
{
var newEnabled = EditorGUILayout.ToggleLeft(keyword, currentEnabled);
if (newEnabled != currentEnabled)
SetUserKeywordEnabled(shader, shaderUID, keyword, newEnabled);
}
bool newOverride = EditorGUILayout.Toggle(string.Empty, currentOverride);
if (newOverride != currentOverride)
SetUserKeywordOverride(shader, shaderUID, keyword, newOverride);
EditorGUILayout.EndHorizontal();
}
}
EditorGUI.indentLevel--;
}
#endregion
#region Compiler Settings
private static string GetShowCompilerSettingsPreferenceKey(string shaderUID) => $"LWGUI/{shaderUID}/ShowCompilerSettings";
private static bool IsShowCompilerSettingsEnabled(string shaderUID) => EditorPrefs.GetBool(GetShowCompilerSettingsPreferenceKey(shaderUID), false);
private static void SetShowCompilerSettingsEnabled(string shaderUID, bool enabled)
{
if (enabled)
EditorPrefs.SetBool(GetShowCompilerSettingsPreferenceKey(shaderUID), true);
else
EditorPrefs.DeleteKey(GetShowCompilerSettingsPreferenceKey(shaderUID));
}
private static void DrawCompilerSettings(LWGUIMetaDatas metaDatas)
{
var shaderUID = metaDatas.GetShaderUID();
var showCompilerSettings = IsShowCompilerSettingsEnabled(shaderUID);
EditorGUI.indentLevel++;
var newShowCompilerSettings = EditorGUILayout.Foldout(showCompilerSettings, "Compiler Settings");
if (newShowCompilerSettings != showCompilerSettings)
SetShowCompilerSettingsEnabled(shaderUID, newShowCompilerSettings);
if (newShowCompilerSettings)
{
if (GUILayout.Button("Install Mali Offline Compiler", GUILayout.ExpandWidth(false)))
{
Application.OpenURL("https://developer.arm.com/documentation/101863/8-8/Using-Mali-Offline-Compiler/Install-Mali-Offline-Compiler");
}
}
EditorGUI.indentLevel--;
}
#endregion
private static string GetDisplayShaderPerfStatsPreferenceKey(string shaderUID) => $"LWGUI/{shaderUID}/DisplayShaderPerformanceStats";
public static bool IsDisplayShaderPerfStatsEnabled(string shaderUID) => EditorPrefs.HasKey(GetDisplayShaderPerfStatsPreferenceKey(shaderUID));
public static void SetDisplayShaderPerfStatsEnabled(Shader shader, string shaderUID, bool enabled)
{
if (enabled)
EditorPrefs.SetBool(GetDisplayShaderPerfStatsPreferenceKey(shaderUID), true);
else
EditorPrefs.DeleteKey(GetDisplayShaderPerfStatsPreferenceKey(shaderUID));
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(shader);
}
public static void SwitchDisplayShaderPerfStatsEnabled(Shader shader, string shaderUID)
{
var key = GetDisplayShaderPerfStatsPreferenceKey(shaderUID);
if (EditorPrefs.HasKey(key))
EditorPrefs.DeleteKey(key);
else
EditorPrefs.SetBool(key, true);
MetaDataHelper.ForceUpdateAllMaterialsMetadataCache(shader);
}
public static void DrawShaderPerformanceStats(LWGUIMetaDatas metaDatas)
{
if (!IsDisplayShaderPerfStatsEnabled(metaDatas.GetShaderUID()) || metaDatas.perMaterialData.shaderPerfDatas == null)
return;
var fieldWidth = EditorGUIUtility.fieldWidth;
EditorGUIUtility.fieldWidth = 0;
var compiler = ShaderPerfMonitor.GetActiveCompiler();
if (compiler != null)
EditorGUILayout.LabelField($"Shader Performance Stats (Compiler: {compiler?.compilerName ?? "NULL"}, API: {compiler.api}, Target: {compiler.target})", GUIStyles.title);
else
EditorGUILayout.LabelField($"Shader Performance Stats (Compiler: NULL)", GUIStyles.title);
if (compiler != null)
{
DrawCompilerSettings(metaDatas);
DrawKeywordOverridesList(metaDatas);
compiler.DrawShaderPerformanceStatsHeader(metaDatas);
var lastPassName = string.Empty;
foreach (var shaderPerfData in metaDatas.perMaterialData.shaderPerfDatas)
{
if (lastPassName == string.Empty)
lastPassName = shaderPerfData.passName;
if (lastPassName != shaderPerfData.passName)
{
lastPassName = shaderPerfData.passName;
EditorGUILayout.Space();
}
compiler.DrawShaderPerformanceStatsLine(metaDatas, shaderPerfData);
}
compiler.DrawShaderPerformanceStatsFooter(metaDatas);
}
else
{
Debug.LogError("LWGUI: Can NOT get Shader Compiler!");
}
EditorGUIUtility.fieldWidth = fieldWidth;
EditorGUILayout.Space();
Helper.DrawSplitLine();
}
public static void DrawShaderPerformanceStatsLineButtons(ShaderPerfData shaderPerfData)
{
if (GUILayout.Button("Find", GUILayout.MaxWidth(40)))
EditorUtility.RevealInFinder(shaderPerfData.compiledShaderPath);
// if (GUILayout.Button("Open", GUILayout.MaxWidth(40)))
// IOHelper.OpenFile(shaderPerfData.compiledShaderPath);
}
#endregion
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a055ea55b4f0432d872562e7c2e2c296
timeCreated: 1760781507

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 72488141525eaa8499e65e52755cb6d0
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 929b6e5dfacc42b429d715a3e1ca2b57
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 9cdef444d18d2ce4abb6bbc4fed4d109
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 2382450e7f4ddb94c9180d6634c41378
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -3,7 +3,7 @@ guid: 2a220687c380819438534e206c94960d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
@ -20,11 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -63,10 +64,12 @@ TextureImporter:
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
maxTextureSize: 256
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
@ -74,6 +77,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -86,6 +90,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -98,6 +103,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -110,6 +116,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -122,6 +129,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -134,6 +142,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
@ -150,9 +159,8 @@ TextureImporter:
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
mipmapLimitGroupName:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,14 +1,13 @@
fileFormatVersion: 2
guid: 26b9d845eb7b1a747bf04dc84e5bcc2c
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
mipMapLayout: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -21,8 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 1
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -31,12 +34,12 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
filterMode: 1
aniso: 1
mipBias: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: -1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
@ -53,11 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 256
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -66,9 +90,11 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
- buildTarget: Standalone
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -77,9 +103,11 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
- buildTarget: iPhone
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: tvOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -88,9 +116,11 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
- buildTarget: tvOS
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -99,9 +129,11 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
- buildTarget: Android
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -110,9 +142,11 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
- buildTarget: WebGL
forceMaximumCompressionQuality_BC6H_BC7: 1
- serializedVersion: 3
buildTarget: PS4
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -121,27 +155,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
- buildTarget: PS4
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
platformFilterMode: 1
forceMaximumCompressionQuality_BC6H_BC7: 1
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
useMipMapOverrideTexture: 0
mipMapOverrideTextureNames: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 8e7a78d02e4c3574998524a0842a8ccb
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -3,7 +3,7 @@ guid: e7bc1130858d984488bca32b8512ca96
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
@ -20,11 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -63,10 +64,12 @@ TextureImporter:
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
@ -74,6 +77,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -86,6 +90,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -98,6 +103,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -110,6 +116,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -122,6 +129,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
@ -134,6 +142,7 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
@ -150,9 +159,8 @@ TextureImporter:
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
mipmapLimitGroupName:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 6f44e40b24300974eb607293e4224ecc
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: 88909414120107547a673b8fcddc5236
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +1,13 @@
fileFormatVersion: 2
guid: 9576e23a695b35d49a9fc55c9a948b4f
TextureImporter:
fileIDToRecycleName: {}
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 4
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
@ -29,12 +34,12 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
filterMode: 1
aniso: 1
mipBias: -1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: -1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 128
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -4,7 +4,8 @@
"references": [
"LWGUI.Runtime",
"LWGUI.Timeline",
"Unity.InternalAPIEditorBridge.020"
"Unity.InternalAPIEditorBridge.020",
"LWGUI.Runtime.Timeline"
],
"includePlatforms": [
"Editor"

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
@ -7,6 +7,7 @@ using UnityEngine.Rendering;
namespace LWGUI
{
public delegate void LWGUICustomGUIEvent(LWGUI lwgui);
public delegate void LWGUIToolbarExtensionEvent(LWGUI lwgui, ref Rect toolBarRect);
public class LWGUI : ShaderGUI
{
@ -15,6 +16,8 @@ namespace LWGUI
public static LWGUICustomGUIEvent onDrawCustomHeader;
public static LWGUICustomGUIEvent onDrawCustomFooter;
public static LWGUIToolbarExtensionEvent onDrawToolbarLeft;
public static LWGUIToolbarExtensionEvent onDrawToolbarRight;
/// <summary>
/// Called when switch to a new Material Window, each window has a LWGUI instance
@ -44,19 +47,24 @@ namespace LWGUI
onDrawCustomHeader(this);
// Toolbar
bool enabled = GUI.enabled;
GUI.enabled = true;
var toolBarRect = EditorGUILayout.GetControlRect();
toolBarRect.xMin = 2;
{
bool enabled = GUI.enabled;
GUI.enabled = true;
var toolBarRect = EditorGUILayout.GetControlRect();
toolBarRect.xMin = 2;
Helper.DrawToolbarButtons(ref toolBarRect, metaDatas);
onDrawToolbarLeft?.Invoke(this, ref toolBarRect);
ToolbarHelper.DrawToolbarButtons(ref toolBarRect, metaDatas);
onDrawToolbarRight?.Invoke(this, ref toolBarRect);
ToolbarHelper.DrawSearchField(toolBarRect, metaDatas);
Helper.DrawSearchField(toolBarRect, metaDatas);
GUILayoutUtility.GetRect(0, 0); // Space(0)
GUI.enabled = enabled;
Helper.DrawSplitLine();
GUILayoutUtility.GetRect(0, 0); // Space(0)
GUI.enabled = enabled;
Helper.DrawSplitLine();
ToolbarHelper.DrawShaderPerformanceStats(metaDatas);
}
//-----------------------------------------------------------------------------
// Draw Properties
@ -147,7 +155,7 @@ namespace LWGUI
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition))
propStaticData.isExpanding = !propStaticData.isExpanding;
RevertableHelper.DrawRevertableProperty(revertButtonRect, prop, metaDatas, true);
Helper.DoPropertyContextMenus(rect, prop, metaDatas);
ContextMenuHelper.DoPropertyContextMenus(rect, prop, metaDatas);
}
private void DrawProperty(MaterialProperty prop)
@ -167,9 +175,10 @@ namespace LWGUI
var revertButtonRect = RevertableHelper.SplitRevertButtonRect(ref rect);
var enabled = GUI.enabled;
if (propStaticData.isReadOnly) GUI.enabled = false;
if (propStaticData.isReadOnly || !propDynamicData.isActive)
GUI.enabled = false;
Helper.BeginProperty(rect, prop, metaDatas);
Helper.DoPropertyContextMenus(rect, prop, metaDatas);
ContextMenuHelper.DoPropertyContextMenus(rect, prop, metaDatas);
RevertableHelper.FixGUIWidthMismatch(prop.GetPropertyType(), materialEditor);
if (propStaticData.isAdvancedHeaderProperty)
@ -229,5 +238,19 @@ namespace LWGUI
if (!hasChange) hasChange = true;
}
}
[MenuItem("CONTEXT/Material/Reimport Shader", false, 100)]
static void MenuItem_ReimportShader(MenuCommand command)
{
var mat = command.context as Material;
if (mat != null)
{
var path = AssetDatabase.GetAssetPath(mat.shader);
if (!string.IsNullOrEmpty(path))
{
AssetDatabase.ImportAsset(path);
}
}
}
}
}

View File

@ -1,212 +1,273 @@
// Copyright (c) Jason Ma
// Copyright (c) Jason Ma
// Per Shader > Per Material > Per Inspector
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using LWGUI.PerformanceMonitor;
namespace LWGUI
{
public class PersetDynamicData
{
public LwguiShaderPropertyPreset.Preset preset;
public MaterialProperty property;
public class PresetDynamicData
{
public LwguiShaderPropertyPreset.Preset preset;
public MaterialProperty property;
public PersetDynamicData(LwguiShaderPropertyPreset.Preset preset, MaterialProperty property)
{
this.preset = preset;
this.property = property;
}
}
public PresetDynamicData(LwguiShaderPropertyPreset.Preset preset, MaterialProperty property)
{
this.preset = preset;
this.property = property;
}
}
public class PropertyDynamicData
{
public MaterialProperty property;
public MaterialProperty defualtProperty; // Default values may be overridden by Preset
public class PropertyDynamicData
{
public MaterialProperty property;
public MaterialProperty defaultProperty; // Default values may be overridden by Preset
public string defaultValueDescription = string.Empty; // Description of the default values used in Tooltip
public bool hasModified = false; // Are properties modified in the material?
public bool hasChildrenModified = false; // Are Children properties modified in the material?
public bool hasRevertChanged = false; // Used to call property EndChangeCheck()
public bool isShowing = true; // ShowIf() result
public bool isAnimated = false; // Material Parameter Animation preview in Timeline is activated
}
public string defaultValueDescription = string.Empty; // Description of the default values used in Tooltip
public bool hasModified = false; // Are properties modified in the material?
public bool hasChildrenModified = false; // Are Children properties modified in the material?
public bool hasRevertChanged = false; // Used to call property EndChangeCheck()
public bool isShowing = true; // ShowIf() result
public bool isActive = true; // ActiveIf() result
public bool isAnimated = false; // Material Parameter Animation preview in Timeline is activated
}
/// <summary>
/// Contains Metadata that may be different for each Material.
/// </summary>
public class PerMaterialData
{
public Dictionary<string, PropertyDynamicData> propDynamicDatas = new Dictionary<string, PropertyDynamicData>();
public MaterialProperty[] props = null;
public Material material = null;
public Material defaultMaterialWithPresetOverride = null;
public MaterialProperty[] defaultPropertiesWithPresetOverride = null;
public List<PersetDynamicData> activePresetDatas = new List<PersetDynamicData>();
public int modifiedCount = 0;
public Dictionary<string, bool> cachedModifiedProperties = null;
public bool forceInit = true;
/// <summary>
/// Contains Metadata that may be different for each Material.
/// </summary>
public class PerMaterialData
{
public bool forceInit = true;
public Material material = null;
public MaterialProperty[] props = null;
public Dictionary<string, PropertyDynamicData> propDynamicDatas = new Dictionary<string, PropertyDynamicData>();
public PerMaterialData(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
{
Init(shader, material, editor, props, perShaderData);
}
public Material defaultMaterialWithPresetOverride = null;
public MaterialProperty[] defaultPropertiesWithPresetOverride = null;
public List<PresetDynamicData> activePresetDatas = new List<PresetDynamicData>();
public int modifiedCount = 0;
public Dictionary<string, bool> cachedModifiedProperties = null;
public void Init(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
{
forceInit = false;
// Performance Monitor
public List<string> activeKeywords = null;
public List<ShaderPerfData> shaderPerfDatas = null;
// Reset Datas
this.props = props;
this.material = material;
activePresetDatas.Clear();
propDynamicDatas.Clear();
modifiedCount = 0;
public PerMaterialData(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
{
Init(shader, material, editor, props, perShaderData);
}
// Get active presets
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var activePreset = propStaticData.presetDrawer?.GetActivePreset(prop, propStaticData.propertyPresetAsset);
if (activePreset != null
// Filter invisible preset properties
&& (propStaticData.showIfDatas.Count == 0
|| ShowIfDecorator.GetShowIfResultFromMaterial(propStaticData.showIfDatas, this.material)))
{
activePresetDatas.Add(new PersetDynamicData(activePreset, prop));
}
}
// Signature of the cached default material: which presets and which base material were used to build it.
// Compared each Init() to auto-detect preset switching, material.parent changes, etc.
private List<LwguiShaderPropertyPreset.Preset> _cachedActivePresets;
private Material _cachedDefaultMaterialSource;
{
// Apply presets to default material
defaultMaterialWithPresetOverride = UnityEngine.Object.Instantiate(
private bool IsDefaultMaterialCacheValid(Material baseMaterial)
{
if (defaultMaterialWithPresetOverride == null || _cachedActivePresets == null)
return false;
if (_cachedDefaultMaterialSource != baseMaterial)
return false;
if (_cachedActivePresets.Count != activePresetDatas.Count)
return false;
for (int i = 0; i < _cachedActivePresets.Count; i++)
{
if (_cachedActivePresets[i] != activePresetDatas[i].preset)
return false;
}
return true;
}
private void StoreDefaultMaterialCacheSignature(Material baseMaterial)
{
_cachedDefaultMaterialSource = baseMaterial;
if (_cachedActivePresets == null)
_cachedActivePresets = new List<LwguiShaderPropertyPreset.Preset>(activePresetDatas.Count);
else
_cachedActivePresets.Clear();
for (int i = 0; i < activePresetDatas.Count; i++)
_cachedActivePresets.Add(activePresetDatas[i].preset);
}
public void Init(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
{
forceInit = false;
// Reset Datas
this.props = props;
this.material = material;
activePresetDatas.Clear();
propDynamicDatas.Clear();
modifiedCount = 0;
// Get active presets
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var activePreset = propStaticData.presetDrawer?.GetActivePreset(prop, propStaticData.propertyPresetAsset);
if (activePreset != null
// Filter invisible preset properties
&& (propStaticData.showIfDatas.Count == 0
|| ShowIfDecorator.GetShowIfResultFromMaterial(propStaticData.showIfDatas, this.material)))
{
activePresetDatas.Add(new PresetDynamicData(activePreset, prop));
}
}
{
var baseMaterial =
#if UNITY_2022_1_OR_NEWER
material.parent
? material.parent
:
material.parent
? material.parent
:
#endif
perShaderData.defaultMaterial
);
perShaderData.defaultMaterial;
foreach (var activePresetData in activePresetDatas)
activePresetData.preset.ApplyToDefaultMaterial(defaultMaterialWithPresetOverride);
var cacheWasValid = IsDefaultMaterialCacheValid(baseMaterial);
if (!cacheWasValid)
{
defaultMaterialWithPresetOverride = UnityEngine.Object.Instantiate(baseMaterial);
defaultPropertiesWithPresetOverride = MaterialEditor.GetMaterialProperties(new[] { defaultMaterialWithPresetOverride });
Debug.Assert(defaultPropertiesWithPresetOverride.Length == props.Length);
foreach (var activePresetData in activePresetDatas)
activePresetData.preset.ApplyToDefaultMaterial(defaultMaterialWithPresetOverride);
// Init propDynamicDatas
for (int i = 0; i < props.Length; i++)
{
var hasModified = !Helper.PropertyValueEquals(props[i], defaultPropertiesWithPresetOverride[i]);
if (hasModified) modifiedCount++;
propDynamicDatas.Add(props[i].name, new PropertyDynamicData()
{
property = props[i],
defualtProperty = defaultPropertiesWithPresetOverride[i],
hasModified = hasModified
});
}
defaultPropertiesWithPresetOverride = MaterialEditor.GetMaterialProperties(new Object[] { defaultMaterialWithPresetOverride });
StoreDefaultMaterialCacheSignature(baseMaterial);
}
// Collect modification
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var propDynamicData = propDynamicDatas[prop.name];
Debug.Assert(defaultPropertiesWithPresetOverride.Length == props.Length);
// Extra Prop hasModified
foreach (var extraPropName in propStaticData.extraPropNames)
propDynamicData.hasModified |= propDynamicDatas[extraPropName].hasModified;
// Init propDynamicDatas
for (int i = 0; i < props.Length; i++)
{
var hasModified = !Helper.PropertyValueEquals(props[i], defaultPropertiesWithPresetOverride[i]);
if (hasModified) modifiedCount++;
propDynamicDatas.Add(props[i].name, new PropertyDynamicData()
{
property = props[i],
defaultProperty = defaultPropertiesWithPresetOverride[i],
hasModified = hasModified
});
}
// Override parent hasChildrenModified
if (propDynamicData.hasModified)
{
var parentPropData = propStaticData.parent;
if (parentPropData != null)
{
propDynamicDatas[parentPropData.name].hasChildrenModified = true;
if (parentPropData.parent != null)
propDynamicDatas[parentPropData.parent.name].hasChildrenModified = true;
}
}
}
}
// Collect modification
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var propDynamicData = propDynamicDatas[prop.name];
// Store "Show Modified Props Only" Caches
{
if (perShaderData.displayModeData.showOnlyModifiedGroups || perShaderData.displayModeData.showOnlyModifiedProperties)
{
if (cachedModifiedProperties == null)
{
cachedModifiedProperties = new Dictionary<string, bool>();
foreach (var propDynamicDataKWPair in propDynamicDatas)
{
if (propDynamicDataKWPair.Value.hasModified || propDynamicDataKWPair.Value.hasChildrenModified)
cachedModifiedProperties.Add(propDynamicDataKWPair.Key, true);
}
}
}
else
cachedModifiedProperties = null;
}
// Extra Prop hasModified
foreach (var extraPropName in propStaticData.extraPropNames)
propDynamicData.hasModified |= propDynamicDatas[extraPropName].hasModified;
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var propDynamicData = propDynamicDatas[prop.name];
// Override parent hasChildrenModified
if (propDynamicData.hasModified)
{
var parentPropData = propStaticData.parent;
if (parentPropData != null)
{
propDynamicDatas[parentPropData.name].hasChildrenModified = true;
if (parentPropData.parent != null)
propDynamicDatas[parentPropData.parent.name].hasChildrenModified = true;
}
}
}
}
// Get default value descriptions
propStaticData.baseDrawers?.ForEach(propertyDrawer => propertyDrawer.GetDefaultValueDescription(shader, prop, propDynamicData.defualtProperty, perShaderData, this));
if (string.IsNullOrEmpty(propDynamicData.defaultValueDescription))
propDynamicData.defaultValueDescription = RevertableHelper.GetPropertyDefaultValueText(propDynamicData.defualtProperty);
// Store "Show Modified Props Only" Caches
{
if (perShaderData.displayModeData.showOnlyModifiedGroups || perShaderData.displayModeData.showOnlyModifiedProperties)
{
if (cachedModifiedProperties == null)
{
cachedModifiedProperties = new Dictionary<string, bool>();
foreach (var propDynamicDataKWPair in propDynamicDatas)
{
if (propDynamicDataKWPair.Value.hasModified || propDynamicDataKWPair.Value.hasChildrenModified)
cachedModifiedProperties.Add(propDynamicDataKWPair.Key, true);
}
}
}
else
cachedModifiedProperties = null;
}
// Get ShowIf() results
ShowIfDecorator.GetShowIfResult(propStaticData, propDynamicData, this);
}
}
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var propDynamicData = propDynamicDatas[prop.name];
public void Update(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
{
if (forceInit)
{
Init(shader, material, editor, props, perShaderData);
}
else
{
foreach (var prop in props)
{
propDynamicDatas[prop.name].property = prop;
}
}
// Check animated
var renderer = editor.GetRendererForAnimationMode();
if (renderer != null)
{
forceInit = true;
foreach (var prop in props)
{
ReflectionHelper.MaterialAnimationUtility_OverridePropertyColor(prop, renderer, out var color);
if (color != Color.white)
propDynamicDatas[prop.name].isAnimated = true;
}
}
}
// Get default value descriptions
propStaticData.baseDrawers?.ForEach(propertyDrawer => propertyDrawer.GetDefaultValueDescription(shader, prop, propDynamicData.defaultProperty, perShaderData, this));
if (string.IsNullOrEmpty(propDynamicData.defaultValueDescription))
propDynamicData.defaultValueDescription = RevertableHelper.GetPropertyDefaultValueText(propDynamicData.defaultProperty);
public bool EndChangeCheck(string propName = null)
{
if (!string.IsNullOrEmpty(propName))
{
GUI.changed |= propDynamicDatas[propName].hasRevertChanged;
propDynamicDatas[propName].hasRevertChanged = false;
}
return EditorGUI.EndChangeCheck();
}
// Get ShowIf() results
ShowIfDecorator.GetShowIfResult(propStaticData, propDynamicData, this);
// Get ActiveIf() results
if (propStaticData.activeIfDatas.Count > 0)
propDynamicData.isActive = ShowIfDecorator.GetShowIfResultFromMaterial(propStaticData.activeIfDatas, this.material);
}
public PropertyDynamicData GetPropDynamicData(string propName)
{
propDynamicDatas.TryGetValue(propName, out var propDynamicData);
return propDynamicData;
}
}
// Get Shader Perf Stats
if (ToolbarHelper.IsDisplayShaderPerfStatsEnabled(perShaderData.shaderUID))
{
activeKeywords = ShaderPerfMonitor.GetMaterialAndGlobalAndUserOverrideActiveKeywords(material, perShaderData.shaderUID);
shaderPerfDatas = ShaderPerfMonitor.GetShaderVariantPerfDatas(shader, activeKeywords);
}
}
public void InvalidateDefaultMaterialCache()
{
_cachedActivePresets = null;
}
public void Update(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
{
if (forceInit)
{
Init(shader, material, editor, props, perShaderData);
}
else
{
foreach (var prop in props)
{
propDynamicDatas[prop.name].property = prop;
}
}
// Check animated
var renderer = editor.GetRendererForAnimationMode();
if (renderer != null)
{
forceInit = true;
foreach (var prop in props)
{
ReflectionHelper.MaterialAnimationUtility_OverridePropertyColor(prop, renderer, out var color);
if (color != Color.white)
propDynamicDatas[prop.name].isAnimated = true;
}
}
}
public bool EndChangeCheck(string propName = null)
{
if (!string.IsNullOrEmpty(propName))
{
GUI.changed |= propDynamicDatas[propName].hasRevertChanged;
propDynamicDatas[propName].hasRevertChanged = false;
}
return EditorGUI.EndChangeCheck();
}
public PropertyDynamicData GetPropDynamicData(string propName)
{
propDynamicDatas.TryGetValue(propName, out var propDynamicData);
return propDynamicData;
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma
// Copyright (c) Jason Ma
// Per Shader > Per Material > Per Inspector
using System;
@ -9,361 +9,369 @@ using UnityEngine;
namespace LWGUI
{
public enum SearchMode
{
Auto = 0, // Search by group first, and search by property when there are no results
Property = 1, // Search by property
Group = 2, // Search by group
Num = 3
}
public enum SearchMode
{
Auto = 0, // Search by group first, and search by property when there are no results
Property = 1, // Search by property
Group = 2, // Search by group
Num = 3
}
public class DisplayModeData
{
public bool showAllAdvancedProperties;
public bool showAllHiddenProperties;
public bool showOnlyModifiedProperties;
public bool showOnlyModifiedGroups;
public class DisplayModeData
{
public bool showAllAdvancedProperties;
public bool showAllHiddenProperties;
public bool showOnlyModifiedProperties;
public bool showOnlyModifiedGroups;
public int advancedCount;
public int hiddenCount;
public int advancedCount;
public int hiddenCount;
public bool IsDefaultDisplayMode()
{
return !(showAllAdvancedProperties || showAllHiddenProperties || showOnlyModifiedProperties || showOnlyModifiedGroups);
}
}
public bool IsDefaultDisplayMode() { return !(showAllAdvancedProperties || showAllHiddenProperties || showOnlyModifiedProperties || showOnlyModifiedGroups); }
}
/// <summary>
/// The static metadata of Material Property is only related to Shader.
/// </summary>
public partial class PropertyStaticData
{
public string name = string.Empty;
public string displayName = string.Empty; // Decoded displayName (Helpbox and Tooltip are encoded in displayName)
/// <summary>
/// The static metadata of Material Property is only related to Shader.
/// </summary>
public partial class PropertyStaticData
{
public string name = string.Empty;
public string displayName = string.Empty; // Decoded displayName (Helpbox and Tooltip are encoded in displayName)
// Structure
public string groupName = string.Empty; // [Group(groupName)] / [Sub(groupName)] / [Advanced(groupName)]
public bool isMain = false; // [Group]
public bool isAdvanced = false; // [Advanced]
public bool isAdvancedHeader = false; // the first [Advanced] in the same group
public bool isAdvancedHeaderProperty = false;
public string advancedHeaderString = string.Empty;
public PropertyStaticData parent = null;
public List<PropertyStaticData> children = new List<PropertyStaticData>();
// Structure
public string groupName = string.Empty; // [Group(groupName)] / [Sub(groupName)] / [Advanced(groupName)]
public bool isMain = false; // [Group]
public bool isAdvanced = false; // [Advanced]
public bool isAdvancedHeader = false; // the first [Advanced] in the same group
public bool isAdvancedHeaderProperty = false;
public string advancedHeaderString = string.Empty;
public PropertyStaticData parent = null;
public List<PropertyStaticData> children = new List<PropertyStaticData>();
// Visibility
public bool isSearchMatched = true; // Search filter result
public bool isExpanding = false; // Children are displayed only when expanded
public bool isReadOnly = false; // [ReadOnly]
public bool isHidden = false; // [Hidden]
public List<ShowIfDecorator.ShowIfData> showIfDatas = new List<ShowIfDecorator.ShowIfData>(); // [ShowIf()]
public List<ShowIfDecorator.ShowIfData> activeIfDatas = new List<ShowIfDecorator.ShowIfData>(); // [ActiveIf()]
public string conditionalDisplayKeyword = string.Empty; // [Group(groupName_conditionalDisplayKeyword)]
// Visibility
public bool isSearchMatched = true; // Search filter result
public bool isExpanding = false; // Children are displayed only when expanded
public bool isReadOnly = false; // [ReadOnly]
public bool isHidden = false; // [Hidden]
public List<ShowIfDecorator.ShowIfData> showIfDatas = new List<ShowIfDecorator.ShowIfData>(); // [ShowIf()]
public string conditionalDisplayKeyword = string.Empty; // [Group(groupName_conditionalDisplayKeyword)]
// Drawers
public IPresetDrawer presetDrawer = null;
public List<IBaseDrawer> baseDrawers = null;
// Drawers
public IPresetDrawer presetDrawer = null;
public List<IBaseDrawer> baseDrawers = null;
// Metadata
public List<string> extraPropNames = new List<string>(); // Other Props that have been associated
public string helpboxMessages = string.Empty;
public string tooltipMessages = string.Empty;
public LwguiShaderPropertyPreset propertyPresetAsset = null; // The Referenced Preset Asset
// Metadata
public List<string> extraPropNames = new List<string>(); // Other Props that have been associated
public string helpboxMessages = string.Empty;
public string tooltipMessages = string.Empty;
public LwguiShaderPropertyPreset propertyPresetAsset = null; // The Referenced Preset Asset
public void AddExtraProperty(string propName)
{
if (!extraPropNames.Contains(propName)) extraPropNames.Add(propName);
}
}
public void AddExtraProperty(string propName)
{
if (!extraPropNames.Contains(propName)) extraPropNames.Add(propName);
}
}
/// <summary>
/// All Shader static metadata can be determined after Shader is compiled and will not change.
/// </summary>
public class PerShaderData
{
public Dictionary<string, PropertyStaticData> propStaticDatas = new Dictionary<string, PropertyStaticData>();
public Shader shader = null;
public string shaderUID = string.Empty;
public DisplayModeData displayModeData = new DisplayModeData();
public SearchMode searchMode = SearchMode.Auto;
public string searchString = string.Empty;
// public List<string> favoriteproperties = new List<string>();
/// <summary>
/// All Shader static metadata can be determined after Shader is compiled and will not change.
/// </summary>
public class PerShaderData
{
public Dictionary<string, PropertyStaticData> propStaticDatas = new Dictionary<string, PropertyStaticData>();
public Shader shader = null;
public DisplayModeData displayModeData = new DisplayModeData();
public SearchMode searchMode = SearchMode.Auto;
public string searchString = string.Empty;
// public List<string> favoriteproperties = new List<string>();
// UnityEngine.Object may be destroyed when loading new scene, so must manually check null reference
private Material _defaultMaterial = null;
// UnityEngine.Object may be destroyed when loading new scene, so must manually check null reference
private Material _defaultMaterial = null;
public Material defaultMaterial
{
get
{
if (!_defaultMaterial && shader) _defaultMaterial = new Material(shader);
return _defaultMaterial;
}
}
public Material defaultMaterial
{
get
{
if (!_defaultMaterial && shader) _defaultMaterial = new Material(shader);
return _defaultMaterial;
}
}
public PerShaderData(Shader shader, MaterialProperty[] props)
{
this.shader = shader;
if (AssetDatabase.Contains(shader))
shaderUID = AssetDatabase.GetAssetPath(shader).Replace('/', '_').Replace('\\', '_');
else
shaderUID = shader.GetHashCode().ToString();
public PerShaderData(Shader shader, MaterialProperty[] props)
{
this.shader = shader;
// Get Property Static Data
foreach (var prop in props)
{
var propStaticData = new PropertyStaticData() { name = prop.name };
propStaticDatas[prop.name] = propStaticData;
// Get Property Static Data
foreach (var prop in props)
{
var propStaticData = new PropertyStaticData() { name = prop.name };
propStaticDatas[prop.name] = propStaticData;
// Get Drawers and Build Drawer StaticMetaData
bool hasDecodedStaticMetaData = false;
{
var drawer = ReflectionHelper.GetPropertyDrawer(shader, prop, out var decoratorDrawers);
// Get Drawers and Build Drawer StaticMetaData
bool hasDecodedStaticMetaData = false;
{
var drawer = ReflectionHelper.GetPropertyDrawer(shader, prop, out var decoratorDrawers);
if (drawer is IPresetDrawer)
propStaticData.presetDrawer = drawer as IPresetDrawer;
if (drawer is IPresetDrawer)
propStaticData.presetDrawer = drawer as IPresetDrawer;
var baseDrawer = drawer as IBaseDrawer;
if (baseDrawer != null)
{
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
hasDecodedStaticMetaData = true;
}
var baseDrawer = drawer as IBaseDrawer;
if (baseDrawer != null)
{
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
hasDecodedStaticMetaData = true;
}
decoratorDrawers?.ForEach(decoratorDrawer =>
{
baseDrawer = decoratorDrawer as IBaseDrawer;
if (baseDrawer != null)
{
if (propStaticData.baseDrawers == null)
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
else
propStaticData.baseDrawers.Add(baseDrawer);
decoratorDrawers?.ForEach(decoratorDrawer =>
{
baseDrawer = decoratorDrawer as IBaseDrawer;
if (baseDrawer != null)
{
if (propStaticData.baseDrawers == null)
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
else
propStaticData.baseDrawers.Add(baseDrawer);
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
}
});
}
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
}
});
}
if (!hasDecodedStaticMetaData)
DecodeMetaDataFromDisplayName(prop, propStaticData);
}
if (!hasDecodedStaticMetaData)
DecodeMetaDataFromDisplayName(prop, propStaticData);
}
// Check Data
foreach (var prop in props)
{
var propStaticData = propStaticDatas[prop.name];
propStaticData.extraPropNames.RemoveAll((extraPropName =>
string.IsNullOrEmpty(extraPropName) || !propStaticDatas.ContainsKey(extraPropName)));
}
// Check Data
foreach (var prop in props)
{
var propStaticData = propStaticDatas[prop.name];
propStaticData.extraPropNames.RemoveAll((extraPropName =>
string.IsNullOrEmpty(extraPropName) || !propStaticDatas.ContainsKey(extraPropName)));
}
// Build Property Structure
{
var groupToMainPropertyDic = new Dictionary<string, MaterialProperty>();
// Build Property Structure
{
var groupToMainPropertyDic = new Dictionary<string, MaterialProperty>();
// Collection Groups
foreach (var prop in props)
{
var propData = propStaticDatas[prop.name];
if (propData.isMain
&& !string.IsNullOrEmpty(propData.groupName)
&& !groupToMainPropertyDic.ContainsKey(propData.groupName))
groupToMainPropertyDic.Add(propData.groupName, prop);
}
// Collection Groups
foreach (var prop in props)
{
var propData = propStaticDatas[prop.name];
if (propData.isMain
&& !string.IsNullOrEmpty(propData.groupName)
&& !groupToMainPropertyDic.ContainsKey(propData.groupName))
groupToMainPropertyDic.Add(propData.groupName, prop);
}
// Register SubProps
foreach (var prop in props)
{
var propData = propStaticDatas[prop.name];
if (!propData.isMain
&& !string.IsNullOrEmpty(propData.groupName))
{
foreach (var groupName in groupToMainPropertyDic.Keys)
{
if (propData.groupName.StartsWith(groupName))
{
// Update Structure
var mainProp = groupToMainPropertyDic[groupName];
propData.parent = propStaticDatas[mainProp.name];
propStaticDatas[mainProp.name].children.Add(propData);
// Register SubProps
foreach (var prop in props)
{
var propData = propStaticDatas[prop.name];
if (!propData.isMain
&& !string.IsNullOrEmpty(propData.groupName))
{
foreach (var groupName in groupToMainPropertyDic.Keys)
{
if (propData.groupName.StartsWith(groupName))
{
// Update Structure
var mainProp = groupToMainPropertyDic[groupName];
propData.parent = propStaticDatas[mainProp.name];
propStaticDatas[mainProp.name].children.Add(propData);
// Split groupName and conditional display keyword
if (propData.groupName.Length > groupName.Length)
{
propData.conditionalDisplayKeyword =
propData.groupName.Substring(groupName.Length, propData.groupName.Length - groupName.Length).ToUpper();
propData.groupName = groupName;
}
break;
}
}
}
}
}
// Split groupName and conditional display keyword
if (propData.groupName.Length > groupName.Length)
{
propData.conditionalDisplayKeyword =
propData.groupName.Substring(groupName.Length, propData.groupName.Length - groupName.Length).ToUpper();
propData.groupName = groupName;
}
break;
}
}
}
}
}
// Build Display Mode Data
{
PropertyStaticData lastPropData = null;
PropertyStaticData lastHeaderPropData = null;
for (int i = 0; i < props.Length; i++)
{
var prop = props[i];
var propStaticData = propStaticDatas[prop.name];
// Build Display Mode Data
{
PropertyStaticData lastPropData = null;
PropertyStaticData lastHeaderPropData = null;
for (int i = 0; i < props.Length; i++)
{
var prop = props[i];
var propStaticData = propStaticDatas[prop.name];
// Counting
if (propStaticData.isHidden
|| (propStaticData.parent != null
&& (propStaticData.parent.isHidden
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isHidden))))
displayModeData.hiddenCount++;
if (propStaticData.isAdvanced
|| (propStaticData.parent != null
&& (propStaticData.parent.isAdvanced
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isAdvanced))))
displayModeData.advancedCount++;
// Counting
if (propStaticData.isHidden
|| (propStaticData.parent != null
&& (propStaticData.parent.isHidden
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isHidden))))
displayModeData.hiddenCount++;
if (propStaticData.isAdvanced
|| (propStaticData.parent != null
&& (propStaticData.parent.isAdvanced
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isAdvanced))))
displayModeData.advancedCount++;
// Build Advanced Structure
if (propStaticData.isAdvanced)
{
// If it is the first prop in a Advanced Block, set to Header
if (lastPropData == null
|| !lastPropData.isAdvanced
|| propStaticData.isAdvancedHeaderProperty
|| (!string.IsNullOrEmpty(propStaticData.advancedHeaderString)
&& propStaticData.advancedHeaderString != lastPropData.advancedHeaderString))
{
propStaticData.isAdvancedHeader = true;
lastHeaderPropData = propStaticData;
}
// Else set to child
else
{
propStaticData.parent = lastHeaderPropData;
lastHeaderPropData.children.Add(propStaticData);
}
}
// Build Advanced Structure
if (propStaticData.isAdvanced)
{
// If it is the first prop in a Advanced Block, set to Header
if (lastPropData == null
|| !lastPropData.isAdvanced
|| propStaticData.isAdvancedHeaderProperty
|| (!string.IsNullOrEmpty(propStaticData.advancedHeaderString)
&& propStaticData.advancedHeaderString != lastPropData.advancedHeaderString))
{
propStaticData.isAdvancedHeader = true;
lastHeaderPropData = propStaticData;
}
// Else set to child
else
{
propStaticData.parent = lastHeaderPropData;
lastHeaderPropData.children.Add(propStaticData);
}
}
lastPropData = propStaticData;
}
}
}
lastPropData = propStaticData;
}
}
}
public PropertyStaticData GetPropStaticData(string propName)
{
propStaticDatas.TryGetValue(propName, out var propStaticData);
return propStaticData;
}
public PropertyStaticData GetPropStaticData(string propName)
{
propStaticDatas.TryGetValue(propName, out var propStaticData);
return propStaticData;
}
private static readonly string _tooltipSplitter = "#";
private static readonly string _tooltipSplitter = "#";
private static readonly string _helpboxSplitter = "%";
private static readonly string _helpboxSplitter = "%";
public static void DecodeMetaDataFromDisplayName(MaterialProperty prop, PropertyStaticData propStaticData)
{
var tooltips = prop.displayName.Split(new String[] { _tooltipSplitter }, StringSplitOptions.None);
if (tooltips.Length > 1)
{
for (int i = 1; i <= tooltips.Length - 1; i++)
{
var str = tooltips[i];
var helpboxIndex = tooltips[i].IndexOf(_helpboxSplitter, StringComparison.Ordinal);
if (helpboxIndex > 0)
str = tooltips[i].Substring(0, helpboxIndex);
propStaticData.tooltipMessages += str + "\n";
}
}
public static void DecodeMetaDataFromDisplayName(MaterialProperty prop, PropertyStaticData propStaticData)
{
var tooltips = prop.displayName.Split(new String[] { _tooltipSplitter }, StringSplitOptions.None);
if (tooltips.Length > 1)
{
for (int i = 1; i <= tooltips.Length - 1; i++)
{
var str = tooltips[i];
var helpboxIndex = tooltips[i].IndexOf(_helpboxSplitter, StringComparison.Ordinal);
if (helpboxIndex > 0)
str = tooltips[i].Substring(0, helpboxIndex);
propStaticData.tooltipMessages += str + "\n";
}
}
var helpboxes = prop.displayName.Split(new String[] { _helpboxSplitter }, StringSplitOptions.None);
if (helpboxes.Length > 1)
{
for (int i = 1; i <= helpboxes.Length - 1; i++)
{
var str = helpboxes[i];
var tooltipIndex = helpboxes[i].IndexOf(_tooltipSplitter, StringComparison.Ordinal);
if (tooltipIndex > 0)
str = tooltips[i].Substring(0, tooltipIndex);
propStaticData.helpboxMessages += str + "\n";
}
}
var helpboxes = prop.displayName.Split(new String[] { _helpboxSplitter }, StringSplitOptions.None);
if (helpboxes.Length > 1)
{
for (int i = 1; i <= helpboxes.Length - 1; i++)
{
var str = helpboxes[i];
var tooltipIndex = helpboxes[i].IndexOf(_tooltipSplitter, StringComparison.Ordinal);
if (tooltipIndex > 0)
str = tooltips[i].Substring(0, tooltipIndex);
propStaticData.helpboxMessages += str + "\n";
}
}
if (propStaticData.helpboxMessages.EndsWith("\n"))
propStaticData.helpboxMessages = propStaticData.helpboxMessages.Substring(0, propStaticData.helpboxMessages.Length - 1);
if (propStaticData.helpboxMessages.EndsWith("\n"))
propStaticData.helpboxMessages = propStaticData.helpboxMessages.Substring(0, propStaticData.helpboxMessages.Length - 1);
propStaticData.displayName = prop.displayName.Split(new String[] { _tooltipSplitter, _helpboxSplitter }, StringSplitOptions.None)[0];
}
propStaticData.displayName = prop.displayName.Split(new String[] { _tooltipSplitter, _helpboxSplitter }, StringSplitOptions.None)[0];
}
public void UpdateSearchFilter()
{
var isSearchStringEmpty = string.IsNullOrEmpty(searchString);
var searchStringLower = searchString.ToLower();
var searchKeywords = searchStringLower.Split(' ', ',', ';', '|', '', ''); // Some possible separators
public void UpdateSearchFilter()
{
var isSearchStringEmpty = string.IsNullOrEmpty(searchString);
var searchStringLower = searchString.ToLower();
var searchKeywords = searchStringLower.Split(' ', ',', ';', '|', '', ''); // Some possible separators
// The First Search
foreach (var propStaticDataKWPair in propStaticDatas)
{
propStaticDataKWPair.Value.isSearchMatched = isSearchStringEmpty
? true
: IsWholeWordMatch(propStaticDataKWPair.Value.displayName, propStaticDataKWPair.Value.name, searchKeywords);
}
// The First Search
foreach (var propStaticDataKWPair in propStaticDatas)
{
propStaticDataKWPair.Value.isSearchMatched = isSearchStringEmpty
? true
: IsWholeWordMatch(propStaticDataKWPair.Value.displayName, propStaticDataKWPair.Value.name, searchKeywords);
}
// Further adjust visibility
if (!isSearchStringEmpty)
{
var searchModeTemp = searchMode;
// Auto: search by group first, and search by property when there are no results
if (searchModeTemp == SearchMode.Auto)
{
// if has no group
if (!propStaticDatas.Any((propStaticDataKWPair => propStaticDataKWPair.Value.isSearchMatched && propStaticDataKWPair.Value.isMain)))
searchModeTemp = SearchMode.Property;
else
searchModeTemp = SearchMode.Group;
}
// Further adjust visibility
if (!isSearchStringEmpty)
{
var searchModeTemp = searchMode;
// Auto: search by group first, and search by property when there are no results
if (searchModeTemp == SearchMode.Auto)
{
// if has no group
if (!propStaticDatas.Any((propStaticDataKWPair => propStaticDataKWPair.Value.isSearchMatched && propStaticDataKWPair.Value.isMain)))
searchModeTemp = SearchMode.Property;
else
searchModeTemp = SearchMode.Group;
}
// search by property
if (searchModeTemp == SearchMode.Property)
{
// when a SubProp is displayed, the MainProp is also displayed
foreach (var propStaticDataKWPair in propStaticDatas)
{
var propStaticData = propStaticDataKWPair.Value;
if (propStaticData.isMain
&& propStaticData.children.Any((childPropStaticData => propStaticDatas[childPropStaticData.name].isSearchMatched)))
propStaticDataKWPair.Value.isSearchMatched = true;
}
}
// search by group
else if (searchModeTemp == SearchMode.Group)
{
// when search by group, all SubProps should display with MainProp
foreach (var propStaticDataKWPair in propStaticDatas)
{
var propStaticData = propStaticDataKWPair.Value;
if (propStaticData.isMain)
foreach (var childPropStaticData in propStaticData.children)
propStaticDatas[childPropStaticData.name].isSearchMatched = propStaticData.isSearchMatched;
}
}
}
}
// search by property
if (searchModeTemp == SearchMode.Property)
{
// when a SubProp is displayed, the MainProp is also displayed
foreach (var propStaticDataKWPair in propStaticDatas)
{
var propStaticData = propStaticDataKWPair.Value;
if (propStaticData.isMain
&& propStaticData.children.Any((childPropStaticData => propStaticDatas[childPropStaticData.name].isSearchMatched)))
propStaticDataKWPair.Value.isSearchMatched = true;
}
}
// search by group
else if (searchModeTemp == SearchMode.Group)
{
// when search by group, all SubProps should display with MainProp
foreach (var propStaticDataKWPair in propStaticDatas)
{
var propStaticData = propStaticDataKWPair.Value;
if (propStaticData.isMain)
foreach (var childPropStaticData in propStaticData.children)
propStaticDatas[childPropStaticData.name].isSearchMatched = propStaticData.isSearchMatched;
}
}
}
}
private static bool IsWholeWordMatch(string displayName, string propertyName, string[] searchingKeywords)
{
bool contains = true;
displayName = displayName.ToLower();
var name = propertyName.ToLower();
private static bool IsWholeWordMatch(string displayName, string propertyName, string[] searchingKeywords)
{
bool contains = true;
displayName = displayName.ToLower();
var name = propertyName.ToLower();
foreach (var keyword in searchingKeywords)
{
var isMatch = false;
isMatch |= displayName.Contains(keyword);
isMatch |= name.Contains(keyword);
contains &= isMatch;
}
return contains;
}
foreach (var keyword in searchingKeywords)
{
var isMatch = false;
isMatch |= displayName.Contains(keyword);
isMatch |= name.Contains(keyword);
contains &= isMatch;
}
return contains;
}
public void ToggleShowAllAdvancedProperties()
{
foreach (var propStaticDataKWPair in propStaticDatas)
{
if (propStaticDataKWPair.Value.isAdvancedHeader)
propStaticDataKWPair.Value.isExpanding = displayModeData.showAllAdvancedProperties;
}
}
}
public void ToggleShowAllAdvancedProperties()
{
foreach (var propStaticDataKWPair in propStaticDatas)
{
if (propStaticDataKWPair.Value.isAdvancedHeader)
propStaticDataKWPair.Value.isExpanding = displayModeData.showAllAdvancedProperties;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 699d09d8caac43769acdfc186d37b21b
timeCreated: 1760615698

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c48ecf063064446097c4da1c6ee1c085
timeCreated: 1760615805

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f2edf52a23ac4bedb7dd9866bae7383a
timeCreated: 1760616152

View File

@ -0,0 +1,460 @@
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using LWGUI.PerformanceMonitor;
using Debug = UnityEngine.Debug;
namespace LWGUI.PerformanceMonitor.ShaderCompiler
{
public class ShaderCompilerDefaultFxc : IShaderCompiler
{
// Per-compiler stats structure moved here so FXC owns its output data shape.
public struct ShaderPerfStats
{
public float estimatedCost; // Estimated relative performance cost based on experience, not precise results.
public int sampleCount;
public int samplerCount;
public int registerCount;
public int interpolatorChannelCount;
public bool isValid;
}
private static ShaderCompilerDefaultFxc _instance;
public static ShaderCompilerDefaultFxc instance => _instance ??= new ShaderCompilerDefaultFxc();
#if UNITY_STANDALONE_WIN
public static bool isSupportCurrentPlatform => true;
#else
public static bool isSupportCurrentPlatform => false;
#endif
public static int priority => 0;
public string compilerName => "Default Fxc";
public ShaderCompilerPlatform api { get; set; } = ShaderCompilerPlatform.D3D;
public BuildTarget target { get; set; } = BuildTarget.StandaloneWindows64;
public GraphicsTier tier { get; set; } = (GraphicsTier)(-1);
public string GetCompiledShaderPath(ShaderPerfData shaderPerfData, string compiledShaderDirectory, string shaderTypeName)
=> Path.Combine(compiledShaderDirectory, $"Fxc_{api}_{target}_{shaderTypeName}.txt");
public string GetCompiledDxbcPath(ShaderPerfData shaderPerfData)
=> Path.Combine(shaderPerfData.compiledShaderDirectory, $"Fxc_{api}_{target}_{shaderPerfData.shaderTypeName}.dxbc");
public bool CompilePass(ShaderPerfData shaderPerfData, ShaderData.Pass pass, ShaderType shaderType, string[] keywords,
out string compiledShader)
{
compiledShader = string.Empty;
if (shaderPerfData == null || pass == null || keywords == null)
return false;
var compileInfo = pass.CompileVariant(shaderType, keywords, api, target, tier, true);
if (!compileInfo.Success)
return false;
// Write DXBC
var dxbcPath = GetCompiledDxbcPath(shaderPerfData);
IOHelper.WriteBinaryFile(dxbcPath, compileInfo.ShaderData);
// Disassemble With fxc.exe
return IOHelper.RunProcess(_fxcAbsPath, $"/dumpbin \"{dxbcPath}\"", out compiledShader);
}
public object AnalyzeShaderPerformance(ShaderPerfData shaderPerfData, string compiledShader)
=> ParseAsmStats(compiledShader);
public void DrawShaderPerformanceStatsHeader(LWGUIMetaDatas metaDatas)
{
EditorGUILayout.LabelField(" ", " Cost Samples Registers");
}
public void DrawShaderPerformanceStatsLine(LWGUIMetaDatas metaDatas, ShaderPerfData shaderPerfData)
{
EditorGUILayout.BeginHorizontal();
var statsObj = shaderPerfData.stats;
if (statsObj is ShaderPerfStats { isValid: true } stats)
{
var statsStr = $"{stats.estimatedCost,7:0.0} {stats.sampleCount,7:0} {stats.registerCount,8:0}";
EditorGUILayout.LabelField($"{shaderPerfData.passName} | {shaderPerfData.shaderTypeName}", statsStr, GUIStyles.label_monospace);
ToolbarHelper.DrawShaderPerformanceStatsLineButtons(shaderPerfData);
}
else
{
var status = shaderPerfData.isCompiledSuccessful ? "ANALYSIS FAILED" : "COMPILATION FAILED";
EditorGUILayout.LabelField($"{shaderPerfData.passName} | {shaderPerfData.shaderTypeName}", status);
}
EditorGUILayout.EndHorizontal();
}
private static string _cachedFxcPath;
private static string _fxcAbsPath
{
get
{
if (string.IsNullOrEmpty(_cachedFxcPath))
_cachedFxcPath = IOHelper.GetAbsPath(AssetDatabase.GUIDToAssetPath("994434336edc8a8469c9afcbb92c5936"));
if (string.IsNullOrEmpty(_cachedFxcPath) || !File.Exists(_cachedFxcPath))
Debug.LogError("LWGUI: Can not find fxc.exe!");
return _cachedFxcPath;
}
}
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/shader-model-5-assembly--directx-hlsl-
private static readonly Dictionary<string, float> _opcodeWeight = new()
{
// ALU
{ "add", 1.0f },
{ "and", 1.0f },
{ "bfi", 1.0f },
{ "bfrev", 1.0f },
{ "countbits", 1.0f },
{ "dadd", 4.0f },
{ "ddiv", 16.0f },
{ "deq", 4.0f },
{ "deriv_rtx_coarse", 1.0f },
{ "deriv_rtx_fine", 1.0f },
{ "deriv_rty_coarse", 1.0f },
{ "deriv_rty_fine", 1.0f },
{ "dfma", 4.0f },
{ "dge", 4.0f },
{ "div", 8.0f },
{ "dlt", 4.0f },
{ "dmax", 4.0f },
{ "dmin", 4.0f },
{ "dmov", 1.0f },
{ "dmovc", 1.0f },
{ "dmul", 4.0f },
{ "dne", 4.0f },
{ "dp2", 2.0f },
{ "dp3", 3.0f },
{ "dp4", 4.0f },
{ "drcp", 8.0f },
{ "eq", 1.0f },
{ "exp", 4.0f },
{ "f16tof32", 1.0f },
{ "f32tof16", 1.0f },
{ "firstbit", 1.0f },
{ "frc", 1.0f },
{ "ftod", 2.0f },
{ "ftoi", 1.0f },
{ "ftou", 1.0f },
{ "ge", 1.0f },
{ "iadd", 1.0f },
{ "ibfe", 1.0f },
{ "ieq", 1.0f },
{ "ige", 1.0f },
{ "ilt", 1.0f },
{ "imad", 1.0f },
{ "imin", 1.0f },
{ "imul", 1.0f },
{ "ine", 1.0f },
{ "ineg", 1.0f },
{ "ishl", 1.0f },
{ "ishr", 1.0f },
{ "itof", 1.0f },
{ "log", 4.0f },
{ "lt", 1.0f },
{ "mad", 1.0f },
{ "max", 1.0f },
{ "min", 1.0f },
{ "mov", 1.0f },
{ "movc", 1.0f },
{ "mul", 1.0f },
{ "ne", 1.0f },
{ "not", 1.0f },
{ "or", 1.0f },
{ "rcp", 4.0f },
{ "round_ne", 1.0f },
{ "round_ni", 1.0f },
{ "round_pi", 1.0f },
{ "round_z", 1.0f },
{ "rsq", 4.0f },
{ "sincos", 4.0f },
{ "sqrt", 4.0f },
{ "swapc", 1.0f },
{ "uaddc", 1.0f },
{ "ubfe", 1.0f },
{ "udiv", 8.0f },
{ "uge", 1.0f },
{ "ult", 1.0f },
{ "umad", 1.0f },
{ "umax", 1.0f },
{ "umin", 1.0f },
{ "umul", 1.0f },
{ "ushr", 1.0f },
{ "usubb", 1.0f },
{ "utof", 1.0f },
{ "xor", 1.0f },
// Non-ALU (ignored)
{ "atomic_and", 0f },
{ "atomic_cmp_store", 0f },
{ "atomic_iadd", 0f },
{ "atomic_imax", 0f },
{ "atomic_imin", 0f },
{ "atomic_or", 0f },
{ "atomic_umax", 0f },
{ "atomic_umin", 0f },
{ "atomic_xor", 0f },
{ "break", 0f },
{ "breakc", 0f },
{ "bufinfo", 0f },
{ "call", 0f },
{ "callc", 0f },
{ "case", 0f },
{ "continue", 0f },
{ "continuec", 0f },
{ "cut", 0f },
{ "cut_stream", 0f },
{ "dcl_constantbuffer", 0f },
{ "dcl_function_body", 0f },
{ "dcl_function_table", 0f },
{ "dcl_globalflags", 0f },
{ "dcl_hs_fork_phase_instance_count", 0f },
{ "dcl_hs_join_phase_instance_count", 0f },
{ "dcl_hs_max_tessfactor", 0f },
{ "dcl_immediateconstantbuffer", 0f },
{ "dcl_indexabletemp", 0f },
{ "dcl_indexrange", 0f },
{ "dcl_input", 0f },
{ "dcl_input vforkinstanceid", 0f },
{ "dcl_input vgsinstanceid", 0f },
{ "dcl_input vjoininstanceid", 0f },
{ "dcl_input voutputcontrolpointid", 0f },
{ "dcl_input vprim", 0f },
{ "dcl_input vthread", 0f },
{ "dcl_input_control_point_count", 0f },
{ "dcl_input_sv", 0f },
{ "dcl_inputprimitive", 0f },
{ "dcl_interface", 0f },
{ "dcl_interface_dynamicindexed", 0f },
{ "dcl_maxoutputvertexcount", 0f },
{ "dcl_output", 0f },
{ "dcl_output odepth", 0f },
{ "dcl_output omask", 0f },
{ "dcl_output_control_point_count", 0f },
{ "dcl_output_sgv", 0f },
{ "dcl_output_siv", 0f },
{ "dcl_outputtopology", 0f },
{ "dcl_resource", 0f },
{ "dcl_resource raw", 0f },
{ "dcl_resource structured", 0f },
{ "dcl_sampler", 0f },
{ "dcl_stream", 0f },
{ "dcl_temps", 0f },
{ "dcl_tessellator_domain", 0f },
{ "dcl_tessellator_output_primitive", 0f },
{ "dcl_tessellator_partitioning", 0f },
{ "dcl_tgsm_raw", 0f },
{ "dcl_tgsm_structured", 0f },
{ "dcl_thread_group", 0f },
{ "dcl_uav_raw", 0f },
{ "dcl_uav_structured", 0f },
{ "dcl_uav_typed", 0f },
{ "default", 0f },
{ "discard", 0f },
{ "else", 0f },
{ "emit", 0f },
{ "emit_stream", 0f },
{ "emitthencut", 0f },
{ "emitthencut_stream", 0f },
{ "endif", 0f },
{ "endloop", 0f },
{ "endswitch", 0f },
{ "fcall", 0f },
{ "gather4", 0f },
{ "gather4_c", 0f },
{ "gather4_po", 0f },
{ "gather4_po_c", 0f },
{ "hs_control_point_phase", 0f },
{ "hs_decls", 0f },
{ "hs_fork_phase", 0f },
{ "hs_join_phase", 0f },
{ "if", 0f },
{ "imm_atomic_alloc", 0f },
{ "imm_atomic_and", 0f },
{ "imm_atomic_cmp_exch", 0f },
{ "imm_atomic_consume", 0f },
{ "imm_atomic_exch", 0f },
{ "imm_atomic_iadd", 0f },
{ "imm_atomic_imax", 0f },
{ "imm_atomic_imin", 0f },
{ "imm_atomic_or", 0f },
{ "imm_atomic_umax", 0f },
{ "imm_atomic_umin", 0f },
{ "imm_atomic_xor", 0f },
{ "label", 0f },
{ "ld", 0f },
{ "ld_raw", 0f },
{ "ld_structured", 0f },
{ "ld_uav_typed", 0f },
{ "ld2dms", 0f },
{ "lod", 0f },
{ "loop", 0f },
{ "nop", 0f },
{ "resinfo", 0f },
{ "ret", 0f },
{ "retc", 0f },
{ "sample", 0f },
{ "sample_b", 0f },
{ "sample_c", 0f },
{ "sample_c_lz", 0f },
{ "sample_d", 0f },
{ "sample_l", 0f },
{ "sampleinfo", 0f },
{ "samplepos", 0f },
{ "store_raw", 0f },
{ "store_structured", 0f },
{ "store_uav_typed", 0f },
{ "switch", 0f },
{ "sync", 0f },
};
private static int IndexOfWhitespace(string s)
{
for (int i = 0; i < s.Length; i++)
{
var c = s[i];
if (char.IsWhiteSpace(c)) return i;
}
return -1;
}
private static string NormalizeOpcode(string opcode)
{
// normalize to lower, strip optional _sat suffix used in some ops like mul_sat/mov_sat
// opcode = opcode.ToLowerInvariant();
if (opcode.EndsWith("_sat", StringComparison.Ordinal))
opcode = opcode.Substring(0, opcode.Length - 3);
return opcode;
}
private static ShaderPerfStats ParseAsmStats(string asmText)
{
if (string.IsNullOrEmpty(asmText))
{
return new ShaderPerfStats();
}
var totalCost = 0f;
var sampleCount = 0;
var samplers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var registerCount = 0;
var interpChannels = 0;
using (var reader = new StringReader(asmText))
{
int lineIndex = -1;
while (reader.ReadLine() is { } line)
{
lineIndex++;
line = line.Trim();
if (lineIndex < 2) continue;
if (string.IsNullOrEmpty(line)) continue;
if (line.StartsWith("//")) continue;
// TODO: Statistical flow control and other special instructions
var firstSpace = IndexOfWhitespace(line);
// Skip headers like ps_#_# labels not considered
if (firstSpace <= 0) continue;
var opcode = line.Substring(0, firstSpace).Trim();
opcode = NormalizeOpcode(opcode);
if (string.IsNullOrWhiteSpace(opcode)) continue;
if (!char.IsLetter(opcode[0])) continue;
if (_opcodeWeight.TryGetValue(opcode, out var w))
{
totalCost += w;
}
else
{
Debug.LogWarning($"LWGUI: {typeof(ShaderCompilerDefaultFxc)}: Unknown opcode: {opcode}");
}
// Texture sampling stats
if (opcode.StartsWith("sample", StringComparison.Ordinal) || opcode.StartsWith("gather4", StringComparison.Ordinal))
{
sampleCount++;
}
else if (string.Equals(opcode, "ld", StringComparison.Ordinal))
{
// count ld reading from textures t#
if (line.Contains(", t")) sampleCount++;
}
// Sampler declarations
if (opcode == "dcl_sampler")
{
// e.g. dcl_sampler s1, mode_default
var idx = line.IndexOf('s');
if (idx >= 0)
{
var end = idx + 1;
while (end < line.Length && char.IsDigit(line[end])) end++;
if (end > idx)
{
var sid = line.Substring(idx, end - idx);
samplers.Add(sid);
}
}
}
// Temp registers
if (opcode == "dcl_temps")
{
// e.g. dcl_temps 27
var numStr = line.Substring(firstSpace).Trim();
int.TryParse(numStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out registerCount);
}
// Interpolator channels: dcl_input_ps lines only
if (opcode == "dcl_input_ps")
{
var dot = line.IndexOf('.');
if (dot >= 0)
{
int count = 0;
for (int i = dot + 1; i < line.Length; i++)
{
var c = line[i];
if (c == 'x' || c == 'y' || c == 'z' || c == 'w') count++;
else break;
}
interpChannels += count;
}
}
}
}
return new ShaderPerfStats
{
estimatedCost = totalCost,
sampleCount = sampleCount,
samplerCount = samplers.Count,
registerCount = registerCount,
interpolatorChannelCount = interpChannels,
isValid = true
};
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7b5553f7fbdc45d585e9ecd2c73d8539
timeCreated: 1760616203

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: 130ac8fd23814be49854157d7be9194e
ShaderIncludeImporter:
guid: 994434336edc8a8469c9afcbb92c5936
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@ -0,0 +1,51 @@
// Copyright (c) Jason Ma
using UnityEditor.Rendering;
using UnityEngine.Rendering;
using UnityEditor;
namespace LWGUI.PerformanceMonitor.ShaderCompiler
{
public interface IShaderCompiler
{
public static IShaderCompiler instance { get; }
public static bool isSupportCurrentPlatform => true;
public static int priority => 0;
public string compilerName { get; }
public ShaderCompilerPlatform api { get; set; }
public BuildTarget target { get; set; }
public GraphicsTier tier { get; set; }
/// <summary>
/// The path to the Shader compilation result stored in text.
/// </summary>
public string GetCompiledShaderPath(ShaderPerfData shaderPerfData, string compiledShaderDirectory, string shaderTypeName);
/// <summary>
/// Try to compile a pass variant. Returns true and outputs string on success.
/// </summary>
public bool CompilePass(ShaderPerfData shaderPerfData, ShaderData.Pass pass, ShaderType shaderType, string[] keywords,
out string compiledShader);
/// <summary>
/// Analyze a compiled shader (or readable asm) and return a compiler-specific stats object.
/// The returned object is opaque to the caller and will be stored in ShaderPerfData.stats.
/// Return null on failure.
/// </summary>
public object AnalyzeShaderPerformance(ShaderPerfData shaderPerfData, string compiledShader);
public void DrawShaderPerformanceStatsHeader(LWGUIMetaDatas metaDatas) { }
/// <summary>
/// Draw a single line (pass) of shader performance UI inside the toolbar area.
/// Compiler-specific UI (Find/Open buttons, label contents) should be implemented here.
/// </summary>
public void DrawShaderPerformanceStatsLine(LWGUIMetaDatas metaDatas, ShaderPerfData shaderPerfData);
public void DrawShaderPerformanceStatsFooter(LWGUIMetaDatas metaDatas) { }
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b7661dfeec673f340b66ac769fa42d18
guid: 8719df0f3ab95f449b01b7c46d9bf80d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bf6617caaa864ee6854c54d837967c6d
timeCreated: 1761656115

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e21bdb269db343c6b20d39dda87981e3
timeCreated: 1761656119

View File

@ -0,0 +1,54 @@
// Copyright (c) Jason Ma
using System;
namespace LWGUI.PerformanceMonitor.ShaderCompiler.Mali
{
[Serializable]
public struct DynamicValue
{
public enum ValueType
{
Unknown,
Null,
Float,
Int,
Bool,
}
public ValueType Type;
public int Int;
public float Float;
public bool Bool;
public DynamicValue(object value)
{
if (value is long l)
{
value = (int) l;
}
(Type, Int, Float, Bool) = value switch
{
int i => (ValueType.Int, i, 0, false),
float f => (ValueType.Float, 0, f, false),
bool b => (ValueType.Bool, 0, 0, b),
null => (ValueType.Null, 0, 0, false),
var _ => throw new ArgumentOutOfRangeException(nameof(value),
$"Invalid object type: {value?.GetType()}"
),
};
}
public override string ToString() =>
Type switch
{
ValueType.Unknown => throw new InvalidOperationException("Unknown value type"),
ValueType.Float => Float.ToString("F2"),
ValueType.Int => Int.ToString(),
ValueType.Bool => Bool.ToString(),
ValueType.Null => "N/A",
_ => throw new ArgumentOutOfRangeException(),
};
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 082ebd2742584b6dbbfc47422310b203
timeCreated: 1761656119

View File

@ -0,0 +1,66 @@
// Copyright (c) Jason Ma
// ReSharper disable InconsistentNaming
using System;
namespace LWGUI.PerformanceMonitor.ShaderCompiler.Mali
{
[Serializable]
internal class JsonMaliocOutput
{
public Schema schema;
public Shader[] shaders;
[Serializable]
public class Schema
{
public string name;
public int version;
}
[Serializable]
public class Shader
{
// Normal output fields
public ShaderProperty[] properties;
public ShaderVariant[] variants;
// Error output fields
public string[] errors;
public string[] warnings;
public string filename;
}
[Serializable]
public class ShaderProperty
{
public string display_name;
public string name;
public string value;
}
[Serializable]
public class ShaderVariant
{
public string name;
public ShaderVariantPerformance performance;
public ShaderProperty[] properties;
}
[Serializable]
public class ShaderVariantPerformance
{
public string[] pipelines;
public ShaderVariantCycles longest_path_cycles;
public ShaderVariantCycles shortest_path_cycles;
public ShaderVariantCycles total_cycles;
}
[Serializable]
public class ShaderVariantCycles
{
public string[] bound_pipelines;
public float[] cycle_count;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 97b5dd9724dd4e16b4cd3fade5b6b26e
timeCreated: 1761656120

View File

@ -0,0 +1,59 @@
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
namespace LWGUI.PerformanceMonitor.ShaderCompiler.Mali
{
[Serializable]
public class RuntimeMaliocShader
{
[Serializable]
public enum ShaderVariantPipelineType
{
Null,
Arithmetic,
LoadStore,
Varying,
Texture,
}
public bool HasErrors;
public List<string> Errors;
public List<string> Warnings;
public List<ShaderProperty> Properties;
public List<ShaderVariant> Variants;
[Serializable]
public class ShaderProperty
{
public enum Unit
{
None,
Percent,
}
public string Name;
public DynamicValue Value;
public Unit ValueUnit;
}
[Serializable]
public class ShaderVariant
{
public string Name;
public List<ShaderVariantPipelineType> Pipelines;
public ShaderPipelineCycles LongestPathCycles;
public ShaderPipelineCycles ShortestPathCycles;
public ShaderPipelineCycles TotalCycles;
public List<ShaderProperty> Properties;
}
[Serializable]
public class ShaderPipelineCycles
{
public List<float> PipelineCycles;
public ShaderVariantPipelineType BoundPipeline;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d803c9bc0cc4f1d9b50cb06b05aacb1
timeCreated: 1761656119

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24c5b23d58934748abe6deaa63777dd0
timeCreated: 1761656120

View File

@ -0,0 +1,139 @@
// Copyright (c) Jason Ma
using System;
using System.Globalization;
using System.Linq;
using UnityEngine;
namespace LWGUI.PerformanceMonitor.ShaderCompiler.Mali
{
public static class MaliocOutputParser
{
public static RuntimeMaliocShader Parse(string json)
{
if (json == null)
return null;
JsonMaliocOutput jsonModel;
try
{
jsonModel = JsonUtility.FromJson<JsonMaliocOutput>(json);
}
catch (Exception e)
{
Debug.LogError($"LWGUI: Failed to parse malioc json: {e.Message}");
return null;
}
if (jsonModel == null || jsonModel.shaders == null || jsonModel.shaders.Length != 1)
{
Debug.LogError("LWGUI: malioc json missing shaders or unexpected format.");
return null;
}
var shader = jsonModel.shaders[0];
// Check if this is an error response
bool isErrorResponse = jsonModel.schema?.name == "error";
if (isErrorResponse || (shader.errors != null && shader.errors.Length > 0))
{
return new RuntimeMaliocShader
{
HasErrors = true,
Errors = shader.errors?.ToList() ?? new System.Collections.Generic.List<string>(),
Warnings = shader.warnings?.ToList() ?? new System.Collections.Generic.List<string>(),
Properties = new System.Collections.Generic.List<RuntimeMaliocShader.ShaderProperty>(),
Variants = new System.Collections.Generic.List<RuntimeMaliocShader.ShaderVariant>(),
};
}
return new RuntimeMaliocShader
{
HasErrors = false,
Errors = new System.Collections.Generic.List<string>(),
Warnings = shader.warnings?.ToList() ?? new System.Collections.Generic.List<string>(),
Properties = shader.properties.Select(ConvertProperty).ToList(),
Variants = shader.variants.Select(variant =>
{
var pipelines = variant.performance.pipelines.Select(ParsePipelineType).ToList();
return new RuntimeMaliocShader.ShaderVariant
{
Name = variant.name,
Properties = variant.properties.Select(ConvertVariantProperty).ToList(),
Pipelines = pipelines,
LongestPathCycles =
ParseShaderPipelineCycles(variant.performance.longest_path_cycles),
ShortestPathCycles =
ParseShaderPipelineCycles(variant.performance.shortest_path_cycles),
TotalCycles = ParseShaderPipelineCycles(variant.performance.total_cycles),
};
}
).ToList(),
};
}
private static RuntimeMaliocShader.ShaderProperty ConvertProperty(JsonMaliocOutput.ShaderProperty property) =>
new() { Name = property.display_name, Value = new DynamicValue(ParseValue(property.value)) };
private static RuntimeMaliocShader.ShaderProperty
ConvertVariantProperty(JsonMaliocOutput.ShaderProperty property) =>
new()
{
Name = property.display_name, Value = new DynamicValue(ParseValue(property.value)),
ValueUnit = ParseValueUnit(property.name),
};
private static RuntimeMaliocShader.ShaderProperty.Unit ParseValueUnit(string name) =>
name switch
{
"thread_occupancy" => RuntimeMaliocShader.ShaderProperty.Unit.Percent,
"fp16_arithmetic" => RuntimeMaliocShader.ShaderProperty.Unit.Percent,
var _ => RuntimeMaliocShader.ShaderProperty.Unit.None,
};
private static RuntimeMaliocShader.ShaderVariantPipelineType ParsePipelineType(string text) =>
text switch
{
"arithmetic" => RuntimeMaliocShader.ShaderVariantPipelineType.Arithmetic,
"load_store" => RuntimeMaliocShader.ShaderVariantPipelineType.LoadStore,
"varying" => RuntimeMaliocShader.ShaderVariantPipelineType.Varying,
"texture" => RuntimeMaliocShader.ShaderVariantPipelineType.Texture,
"" => RuntimeMaliocShader.ShaderVariantPipelineType.Null,
null => RuntimeMaliocShader.ShaderVariantPipelineType.Null,
var _ => throw new ArgumentOutOfRangeException(nameof(text), text, "Invalid pipeline type."),
};
private static RuntimeMaliocShader.ShaderPipelineCycles ParseShaderPipelineCycles(
JsonMaliocOutput.ShaderVariantCycles cycles) =>
new()
{
PipelineCycles = (cycles.cycle_count ?? Array.Empty<float>()).Select(f => f).ToList(),
BoundPipeline = ParsePipelineType(cycles.bound_pipelines?.First()),
};
private static object ParseValue(string value)
{
if (string.IsNullOrEmpty(value))
return null;
var s = value.Trim();
if (s.Equals("null", StringComparison.OrdinalIgnoreCase))
return null;
// Try int
if (int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i))
return i;
// Try float
if (float.TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var f))
return f;
// Try bool
if (bool.TryParse(s, out var b))
return b;
// Fallback: return string
return s;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5b9ef44e450a4f9e81fc0f0d35afc38a
timeCreated: 1761656120

View File

@ -0,0 +1,178 @@
// Copyright (c) Jason Ma
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor.Rendering;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEditor;
using LWGUI.PerformanceMonitor.ShaderCompiler.Mali;
namespace LWGUI.PerformanceMonitor.ShaderCompiler
{
public class ShaderCompilerMali : IShaderCompiler
{
private static ShaderCompilerMali _instance;
public static ShaderCompilerMali instance => _instance ??= new ShaderCompilerMali();
public ShaderCompilerPlatform api { get; set; } = ShaderCompilerPlatform.GLES3x;
public BuildTarget target { get; set; } = BuildTarget.Android;
public GraphicsTier tier { get; set; } = (GraphicsTier)(-1);
public string compilerName => "Malioc";
private static int _isSupportCurrentPlatform = -1;
public static bool isSupportCurrentPlatform
{
get
{
if (_isSupportCurrentPlatform == -1)
{
if (!IOHelper.RunCMD("malioc --list", out var output) || string.IsNullOrWhiteSpace(output) || !output.Contains("Compiler"))
_isSupportCurrentPlatform = 0;
else
_isSupportCurrentPlatform = 1;
}
return _isSupportCurrentPlatform == 1;
}
}
public static int priority => 10;
public string GetCompiledShaderPath(ShaderPerfData shaderPerfData, string compiledShaderDirectory, string shaderTypeName)
{
string ext;
switch (shaderPerfData.shaderType)
{
// https://developer.arm.com/documentation/101863/8-8/Using-Mali-Offline-Compiler/Compiling-OpenGL-ES-shaders
case ShaderType.Vertex: ext = ".vert"; break;
case ShaderType.Fragment: ext = ".frag"; break;
case ShaderType.Geometry: ext = ".geom"; break;
default: return null;
}
return Path.Combine(compiledShaderDirectory, $"Mali_{api}_{target}_{shaderTypeName}{ext}");
}
public string GetMaliJsonOutputPath(ShaderPerfData shaderPerfData)
=> Path.Combine(shaderPerfData.compiledShaderDirectory, $"Mali_{api}_{target}_{shaderPerfData.shaderTypeName}.json");
public bool CompilePass(ShaderPerfData shaderPerfData, ShaderData.Pass pass, ShaderType shaderType, string[] keywords,
out string compiledShader)
{
compiledShader = string.Empty;
if (shaderPerfData == null || pass == null || keywords == null)
return false;
var compileInfo = pass.CompileVariant(shaderType, keywords, api, target, tier, true);
if (!compileInfo.Success)
return false;
compiledShader = Encoding.UTF8.GetString(compileInfo.ShaderData);
// Fix Mali Compiler Errors
compiledShader = compiledShader.Replace("#version 300 es", "#version 320 es");
compiledShader = compiledShader.Replace("#version 310 es", "#version 320 es");
IOHelper.WriteTextFile(shaderPerfData.compiledShaderPath, compiledShader);
return !string.IsNullOrWhiteSpace(compiledShader);
}
public object AnalyzeShaderPerformance(ShaderPerfData shaderPerfData, string compiledShader)
{
var jsonPath = GetMaliJsonOutputPath(shaderPerfData);
string jsonString;
if (!File.Exists(jsonPath))
{
// https://developer.arm.com/documentation/101863/8-8/Using-Mali-Offline-Compiler/Compiling-OpenGL-ES-shaders
IOHelper.RunCMD($"malioc --core Mali-G76 --format json \"{shaderPerfData.compiledShaderPath}\"", out jsonString);
IOHelper.WriteTextFile(jsonPath, jsonString);
}
else
{
jsonString = IOHelper.ReadTextFile(jsonPath);
}
if (IOHelper.ExistAndNotEmpty(jsonPath))
{
return MaliocOutputParser.Parse(jsonString);
}
return null;
}
public void DrawShaderPerformanceStatsHeader(LWGUIMetaDatas metaDatas)
{
EditorGUILayout.LabelField(" ", "Arithmetic Load/Store Varying Texture");
}
// https://developer.arm.com/documentation/101863/8-8/Using-Mali-Offline-Compiler/Performance-analysis
public void DrawShaderPerformanceStatsLine(LWGUIMetaDatas metaDatas, ShaderPerfData shaderPerfData)
{
EditorGUILayout.BeginHorizontal();
if (shaderPerfData.stats is RuntimeMaliocShader stats)
{
if (stats.Variants is { Count: > 0 } && stats.Variants[0].Pipelines is { Count: > 0 })
{
var variant = stats.Variants[0];
var cycles = Enumerable.Repeat(0.0f, variant.Pipelines.Count).ToList();
for (int i = 0; i < variant.Pipelines.Count; i++)
{
cycles[i] = Mathf.Max(Mathf.Max(variant.ShortestPathCycles.PipelineCycles[i],
variant.LongestPathCycles.PipelineCycles[i]),
variant.TotalCycles.PipelineCycles[i]);
}
// https://developer.arm.com/documentation/101863/8-8/Using-Mali-Offline-Compiler/Performance-analysis/Performance-table
float arithmeticCycle = 0;
float loadStoreCycle = 0;
float varyingCycle = 0;
float textureCycle = 0;
for (int i = 0; i < variant.Pipelines.Count; i++)
{
switch (variant.Pipelines[i])
{
case RuntimeMaliocShader.ShaderVariantPipelineType.Arithmetic: arithmeticCycle = cycles[i]; break;
case RuntimeMaliocShader.ShaderVariantPipelineType.LoadStore: loadStoreCycle = cycles[i]; break;
case RuntimeMaliocShader.ShaderVariantPipelineType.Varying: varyingCycle = cycles[i]; break;
case RuntimeMaliocShader.ShaderVariantPipelineType.Texture: textureCycle = cycles[i]; break;
}
}
var statsStr = $"{arithmeticCycle,8:0.0} {loadStoreCycle,9:0.0} {varyingCycle,7:0.0} {textureCycle,6:0.0}";
EditorGUILayout.LabelField($"{shaderPerfData.passName} | {shaderPerfData.shaderTypeName}", statsStr, GUIStyles.label_monospace);
ToolbarHelper.DrawShaderPerformanceStatsLineButtons(shaderPerfData);
if (GUILayout.Button("Json", GUILayout.MaxWidth(40)))
IOHelper.OpenFile(GetMaliJsonOutputPath(shaderPerfData));
}
else
{
EditorGUILayout.LabelField($"{shaderPerfData.passName} | {shaderPerfData.shaderTypeName}", "ANALYSIS FAILED");
}
if (stats.HasErrors)
{
var errorMsg = stats.Errors.Count > 0 ? stats.Errors[0] : "Unknown Error";
Debug.LogError($"LWGUI: {shaderPerfData.passName} | {shaderPerfData.shaderTypeName} Error:\n{errorMsg}");
}
}
else
{
var status = shaderPerfData.isCompiledSuccessful ? "ANALYSIS FAILED" : "COMPILATION FAILED";
EditorGUILayout.LabelField($"{shaderPerfData.passName} | {shaderPerfData.shaderTypeName}", status);
}
EditorGUILayout.EndHorizontal();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 40365616bf6b4d40bb751e848dba6105
timeCreated: 1760616221

View File

@ -0,0 +1,27 @@
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
using UnityEditor.Rendering;
namespace LWGUI.PerformanceMonitor
{
public class ShaderPerfData
{
public int subshaderIndex = -1;
public int passIndex = -1;
public string passName = string.Empty;
public ShaderType shaderType;
public string hash = string.Empty;
public bool isCompiledSuccessful = false;
// Path
public string shaderTypeName = string.Empty;
public string compiledShaderDirectory = string.Empty;
public string compiledShaderPath = string.Empty;
// Opaque compiler-specific stats.
// Compiler implementations should populate this with their own stats object (or null if unavailable).
public object stats;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6adc39ef525045bb86847fb8e69ddbaf
timeCreated: 1760615788

View File

@ -0,0 +1,253 @@
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using LWGUI.PerformanceMonitor.ShaderCompiler;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using Debug = UnityEngine.Debug;
namespace LWGUI.PerformanceMonitor
{
public static class ShaderPerfMonitor
{
#region Global Settings
public static GraphicsTier graphicsTier = (GraphicsTier)(-1);
public static ShaderCompilerPlatform shaderCompilerPlatform = ShaderCompilerPlatform.D3D;
public static BuildTarget buildTarget = BuildTarget.StandaloneWindows64;
#endregion
public static List<string> GetMaterialAndGlobalAndUserOverrideActiveKeywords(Material material, string shaderUID)
{
var output = new List<string>();
foreach (var localKeyword in material.enabledKeywords)
{
if (ToolbarHelper.IsUserKeywordOverride(shaderUID, localKeyword.name))
{
if (ToolbarHelper.IsUserKeywordEnabled(shaderUID, localKeyword.name))
output.Add(localKeyword.name);
}
else
{
output.Add(localKeyword.name);
}
}
foreach (var keyword in material.shader.keywordSpace.keywords)
{
if (ToolbarHelper.IsUserKeywordOverride(shaderUID, keyword.name))
{
if (ToolbarHelper.IsUserKeywordEnabled(shaderUID, keyword.name))
output.Add(keyword.name);
}
else
{
if (!keyword.isValid)
continue;
// Is a global keyword?
if (keyword.isOverridable && Shader.IsKeywordEnabled(keyword.name))
output.Add(keyword.name);
}
}
return output.Distinct().ToList();
}
public static string ComputeShaderVariantHash(List<string> keywords, BuildTarget target, ShaderCompilerPlatform platform, GraphicsTier tier)
{
var sorted = keywords?.OrderBy(k => k) ?? Enumerable.Empty<string>();
var key = string.Join(";", sorted) + $"|{target}|{platform}|{tier}";
using var md5 = MD5.Create();
var bytes = Encoding.UTF8.GetBytes(key);
var hash = md5.ComputeHash(bytes);
return string.Concat(hash.Select(b => b.ToString("x2")));
}
public static List<ShaderPerfData> GetShaderVariantPerfDatas(Shader shader, List<string> keywords)
{
var output = new List<ShaderPerfData>();
var shaderData = ShaderUtil.GetShaderData(shader);
var subshader = shaderData.ActiveSubshader;
IShaderCompiler compiler = GetActiveCompiler();
for (int i = 0; i < subshader.PassCount; i++)
{
var pass = subshader.GetPass(i);
for (int j = 0; j < (int)ShaderType.Count; j++)
{
var shaderType = (ShaderType)j;
if (!pass.HasShaderStage(shaderType))
continue;
// Collect input data
var hash = ComputeShaderVariantHash(keywords, buildTarget, shaderCompilerPlatform, graphicsTier);
var shaderPerfData = new ShaderPerfData
{
subshaderIndex = shaderData.ActiveSubshaderIndex,
passIndex = i,
passName = IOHelper.GetValidFileName(pass.Name),
shaderType = shaderType,
hash = hash,
};
shaderPerfData.shaderTypeName = shaderPerfData.shaderType.ToString();
shaderPerfData.compiledShaderDirectory = IOHelper.GetCompiledShaderVariantCacheDirectory(shader, shaderPerfData);
if (compiler != null)
{
shaderPerfData.compiledShaderPath = compiler.GetCompiledShaderPath(shaderPerfData, shaderPerfData.compiledShaderDirectory, shaderPerfData.shaderTypeName);
if (!string.IsNullOrEmpty(shaderPerfData.compiledShaderPath))
{
// Compile and create cache
shaderPerfData.isCompiledSuccessful = true;
string compiledShader;
if (!File.Exists(shaderPerfData.compiledShaderPath))
{
shaderPerfData.isCompiledSuccessful = compiler.CompilePass(shaderPerfData, pass, shaderType, keywords.ToArray(),
out compiledShader);
IOHelper.WriteTextFile(shaderPerfData.compiledShaderPath, compiledShader);
}
else
{
compiledShader = IOHelper.ReadTextFile(shaderPerfData.compiledShaderPath);
}
shaderPerfData.isCompiledSuccessful &= IOHelper.ExistAndNotEmpty(shaderPerfData.compiledShaderPath);
// Analyze performance
if (shaderPerfData.isCompiledSuccessful)
{
shaderPerfData.stats = compiler.AnalyzeShaderPerformance(shaderPerfData, compiledShader);
if (shaderPerfData.stats == null)
Debug.LogError($"LWGUI: Failed to Analyze Shader: {shader.name} | Subshader: {shaderPerfData.subshaderIndex} | Pass: {shaderPerfData.passName} | Stage: {shaderType}\n" +
$"Keywords: \n{string.Join('\n', keywords)}");
}
else
{
Debug.LogError($"LWGUI: Failed to Compile Shader: {shader.name} | Subshader: {shaderPerfData.subshaderIndex} | Pass: {shaderPerfData.passName} | Stage: {shaderType}\n" +
$"Keywords: \n{string.Join('\n', keywords)}");
}
}
else
{
Debug.LogError("LWGUI: Unable to get the compiled Shader path!");
break;
}
}
output.Add(shaderPerfData);
}
}
return output;
}
public static void ClearShaderPerfCache(Shader shader)
{
IOHelper.ClearShaderPerfCache(shader);
}
private static IShaderCompiler _cachedActiveCompiler;
private static readonly object _compilerLock = new object();
public static IShaderCompiler GetActiveCompiler()
{
if (_cachedActiveCompiler != null)
return _cachedActiveCompiler;
lock (_compilerLock)
{
if (_cachedActiveCompiler != null)
return _cachedActiveCompiler;
var assembly = Assembly.GetExecutingAssembly();
var compilerTypes = assembly.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract && typeof(IShaderCompiler).IsAssignableFrom(t))
.ToList();
Type bestType = null;
int bestPriority = int.MinValue;
foreach (var compilerType in compilerTypes)
{
try
{
var isSupportProp = compilerType.GetProperty("isSupportCurrentPlatform",
BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (isSupportProp == null)
continue;
bool isSupported = (bool)isSupportProp.GetValue(null);
if (!isSupported)
continue;
var priorityProp = compilerType.GetProperty("priority",
BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
int priority = 0;
if (priorityProp != null)
{
try { priority = (int)priorityProp.GetValue(null); } catch { priority = 0; }
}
if (bestType == null || priority > bestPriority)
{
bestType = compilerType;
bestPriority = priority;
}
}
catch (Exception e)
{
Debug.LogError($"LWGUI: Error inspecting compiler type {compilerType.Name}: {e.Message}");
}
}
if (bestType != null)
{
try
{
var instanceProp = bestType.GetProperty("instance",
BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (instanceProp != null)
{
var instance = instanceProp.GetValue(null) as IShaderCompiler;
if (instance != null)
{
_cachedActiveCompiler = instance;
return instance;
}
}
}
catch (Exception e)
{
Debug.LogError($"LWGUI: Error getting compiler instance of type {bestType.Name}: {e.Message}");
}
}
Debug.LogWarning("LWGUI: No supported shader compiler found!");
return null;
}
}
public static void ResetActiveCompiler()
{
lock (_compilerLock)
{
_cachedActiveCompiler = null;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 626fb2fbefe34f578241fed5232ed380
timeCreated: 1760615736

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma
// Copyright (c) Jason Ma
using System;
using UnityEngine;

View File

@ -1,4 +1,5 @@
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
using System.IO;
@ -7,44 +8,186 @@ using LWGUI.LwguiGradientEditor;
using LWGUI.Runtime.LwguiGradient;
using UnityEngine;
using UnityEditor;
using UnityEngine.Serialization;
namespace LWGUI
{
[CreateAssetMenu(fileName = "LWGUI_RampAtlas.asset", menuName = "LWGUI/Ramp Atlas", order = 84)]
public class LwguiRampAtlas : ScriptableObject
public interface IRamp
{
[Serializable]
public class Ramp
string Name { get; set; }
ColorSpace ColorSpace { get; set; }
LwguiGradient.ChannelMask ChannelMask { get; set; }
LwguiGradient.GradientTimeRange TimeRange { get; set; }
/// <summary>
/// Ramp contains at least one Gradient.
/// You can add more custom Gradients by overriding the virtual functions in LwguiRampAtlas.
/// </summary>
LwguiGradient Gradient { get; set; }
/// <summary>
/// Get All Gradients.
/// </summary>
LwguiGradient[] GetGradients();
/// <summary>
/// Fill pixels for this Ramp into the output array.
/// </summary>
/// <param name="outputPixels">The output pixel array to fill</param>
/// <param name="currentIndex">Current index in the output array, will be updated after filling</param>
/// <param name="width">Width of each row in pixels</param>
void GetPixelsForAtlas(ref Color[] outputPixels, ref int currentIndex, int width);
/// <summary>
/// Get preview textures for this Ramp in Ramp Selector Window.
/// Returns multiple textures if the Ramp contains multiple gradients.
/// </summary>
/// <param name="width">Width of the preview texture</param>
/// <returns>Array of preview textures</returns>
Texture2D[] GetPreviewTexturesForRampSelector(int width);
/// <summary>
/// Copy properties from another Ramp.
/// </summary>
/// <param name="source">The source Ramp to copy from</param>
void CopyFrom(IRamp source);
}
[Serializable]
public class Ramp : IRamp
{
public string name = "New Ramp";
public ColorSpace colorSpace = ColorSpace.Gamma;
public LwguiGradient.ChannelMask channelMask = LwguiGradient.ChannelMask.All;
public LwguiGradient.GradientTimeRange timeRange = LwguiGradient.GradientTimeRange.One;
public LwguiGradient gradient = LwguiGradient.white;
// IRamp interface implementation using explicit properties that wrap the fields
string IRamp.Name { get => name; set => name = value; }
ColorSpace IRamp.ColorSpace { get => colorSpace; set => colorSpace = value; }
LwguiGradient.ChannelMask IRamp.ChannelMask { get => channelMask; set => channelMask = value; }
LwguiGradient.GradientTimeRange IRamp.TimeRange { get => timeRange; set => timeRange = value; }
LwguiGradient IRamp.Gradient { get => gradient; set => gradient = value; }
/// <summary>
/// The number of rows this Ramp type occupies in the Ramp Atlas Texture.
/// This is a static value bound to the type. Derived classes can hide this with 'new static'.
/// </summary>
public static int RowCount => 1;
public virtual LwguiGradient[] GetGradients() => new[] { gradient };
public virtual void GetPixelsForAtlas(ref Color[] outputPixels, ref int currentIndex, int width)
{
public string name = "New Ramp";
public LwguiGradient gradient = LwguiGradient.white;
public ColorSpace colorSpace = ColorSpace.Gamma;
public LwguiGradient.ChannelMask channelMask = LwguiGradient.ChannelMask.All;
public LwguiGradient.GradientTimeRange timeRange = LwguiGradient.GradientTimeRange.One;
var gradients = GetGradients();
for (int i = 0; i < gradients.Length; i++)
{
gradients[i]?.GetPixels(ref outputPixels, ref currentIndex, width, 1, channelMask);
}
}
public const string RampAtlasSOExtensionName = "asset";
public virtual Texture2D[] GetPreviewTexturesForRampSelector(int width)
{
var gradients = GetGradients();
var textures = new Texture2D[gradients.Length];
for (int i = 0; i < gradients.Length; i++)
{
textures[i] = gradients[i]?.GetPreviewRampTexture(width, 1, colorSpace, channelMask);
}
return textures;
}
public virtual void CopyFrom(IRamp source)
{
if (source == null) return;
name = source.Name;
colorSpace = source.ColorSpace;
channelMask = source.ChannelMask;
timeRange = source.TimeRange;
gradient = new LwguiGradient(source.Gradient);
var gradients = GetGradients();
for (int i = 1; i < gradients.Length; i++)
{
if (gradients[i] == null)
continue;
gradients[i].DeepCopyFrom(source.Gradient);
}
}
}
[CreateAssetMenu(fileName = "LWGUI_RampAtlas.asset", menuName = "LWGUI/Ramp Atlas", order = 84)]
public partial class LwguiRampAtlas : ScriptableObject
{
public const string RampAtlasSOExtensionName = "asset";
public const string RampAtlasTextureExtensionName = "tga";
public int rampAtlasWidth = 256;
public int rampAtlasHeight = 4;
public bool rampAtlasSRGB = true;
public int rampAtlasWidth = 256;
public int rampAtlasHeight = 4;
public bool rampAtlasSRGB = true;
private string _rampAtlasSOPath = string.Empty;
private string _rampAtlasTexturePath = string.Empty;
[SerializeField] private bool _saveTextureToggle;
[SerializeField] private List<Ramp> _ramps = new List<Ramp>();
[NonSerialized] public Texture2D rampAtlasTexture = null;
[SerializeField] private List<Ramp> _ramps = new List<Ramp>();
public List<Ramp> ramps
{
get => _ramps ?? new List<Ramp>();
set => _ramps = value ?? new List<Ramp>();
#region Ramp Operate
/// <summary>
/// Access the list of Ramps as IRamp interface. This property uses covariance (IReadOnlyList),
/// allowing derived classes to directly return their strongly-typed lists without type conversion.
/// Override this property in derived classes to return a custom Ramp list.
/// </summary>
public virtual IReadOnlyList<IRamp> Ramps => _ramps ??= new List<Ramp>();
public virtual int RowCountPerRamp => Ramp.RowCount;
public int RampCount => Ramps.Count;
public int TotalRowCount => RampCount * RowCountPerRamp;
public virtual IRamp CreateRamp(IRamp srcRamp = null)
{
var newRamp = new Ramp();
if (srcRamp != null)
newRamp.CopyFrom(srcRamp);
return newRamp;
}
[SerializeField] private bool _saveTextureToggle;
private string _rampAtlasSOPath = string.Empty;
private string _rampAtlasTexturePath = string.Empty;
public virtual IRamp AddRamp(IRamp srcRamp = null)
{
_ramps ??= new List<Ramp>();
var newRamp = CreateRamp(srcRamp);
_ramps.Add(newRamp as Ramp);
return newRamp;
}
public virtual IRamp GetRamp(int index)
{
if (index < RampCount && index >= 0)
{
return Ramps[index];
}
return null;
}
public virtual void ClearRamps()
{
_ramps?.Clear();
}
public void CheckRampRowCount()
{
if (TotalRowCount > rampAtlasHeight)
Debug.LogError($"LWGUI: Ramp Atlas does NOT have enough height ({rampAtlasHeight} < {TotalRowCount}):\n{_rampAtlasSOPath}");
}
#endregion
public void InitData()
{
@ -59,12 +202,16 @@ namespace LWGUI
{
if (!AssetDatabase.Contains(this))
return false;
// Try to load
rampAtlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(_rampAtlasTexturePath);
// Create
if (!rampAtlasTexture)
if (!rampAtlasTexture
|| rampAtlasTexture.width != rampAtlasWidth
|| rampAtlasTexture.height != rampAtlasHeight
|| rampAtlasTexture.isDataSRGB != rampAtlasSRGB
)
{
CreateRampAtlasTexture();
rampAtlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(_rampAtlasTexturePath);
@ -72,69 +219,51 @@ namespace LWGUI
if (!rampAtlasTexture)
{
Debug.LogError($"LWGUI: Can NOT create a Ramp Atlas Texture at path: { _rampAtlasTexturePath }");
Debug.LogError($"LWGUI: Can NOT create a Ramp Atlas Texture at path: {_rampAtlasTexturePath}");
return false;
}
CheckRampRowCount();
return true;
}
public Color[] GetPixels()
public virtual Color[] GetPixels()
{
Color[] pixels = Enumerable.Repeat(Color.white, rampAtlasWidth * rampAtlasHeight).ToArray();
var pixels = Enumerable.Repeat(Color.white, rampAtlasWidth * rampAtlasHeight).ToArray();
int currentIndex = 0;
foreach (var ramp in ramps)
foreach (var ramp in Ramps)
{
ramp.gradient.GetPixels(ref pixels, ref currentIndex, rampAtlasWidth, 1, ramp.channelMask);
ramp?.GetPixelsForAtlas(ref pixels, ref currentIndex, rampAtlasWidth);
}
return pixels;
}
public Texture2D[] GetTexture2Ds(LwguiGradient.ChannelMask channelMask = LwguiGradient.ChannelMask.All)
{
Texture2D[] textures = new Texture2D[ramps.Count];
for (int i = 0; i < ramps.Count; i++)
{
var ramp = ramps[i];
textures[i] = Instantiate(ramp.gradient?.GetPreviewRampTexture(rampAtlasWidth, 1, ramp.colorSpace, ramp.channelMask & channelMask));
textures[i].name = ramp.name;
}
return textures;
}
public Ramp GetRamp(int index)
{
if (index < ramps.Count && index >= 0)
{
return ramps[index] ?? new Ramp();
}
return null;
}
public void CreateRampAtlasTexture()
{
var rampAtlasTexture = new Texture2D(rampAtlasWidth, rampAtlasHeight, TextureFormat.RGBA32, false, !rampAtlasSRGB);
rampAtlasTexture.SetPixels(GetPixels());
rampAtlasTexture.wrapMode = TextureWrapMode.Clamp;
rampAtlasTexture.name = Path.GetFileName(_rampAtlasTexturePath);
rampAtlasTexture.Apply();
var rampAtlas = new Texture2D(rampAtlasWidth, rampAtlasHeight, TextureFormat.RGBA32, false, !rampAtlasSRGB);
rampAtlas.SetPixels(GetPixels());
rampAtlas.wrapMode = TextureWrapMode.Clamp;
rampAtlas.name = Path.GetFileName(_rampAtlasTexturePath);
rampAtlas.Apply();
SaveTexture(rampAtlasTexture);
SaveTexture(rampAtlas, checkoutAndForceWrite:true);
AssetDatabase.ImportAsset(_rampAtlasTexturePath);
RampHelper.SetRampTextureImporter(_rampAtlasTexturePath, true, !rampAtlasSRGB, EditorJsonUtility.ToJson(this));
}
public void SaveTexture(Texture2D rampAtlasTexture = null, string targetRelativePath = null, bool checkoutAndForceWrite = false)
public void SaveTexture(Texture2D rampAtlas = null, string targetRelativePath = null, bool checkoutAndForceWrite = false)
{
targetRelativePath ??= _rampAtlasTexturePath;
rampAtlasTexture ??= this.rampAtlasTexture;
if (!rampAtlasTexture || string.IsNullOrEmpty(targetRelativePath))
rampAtlas ??= rampAtlasTexture;
if (!rampAtlas || string.IsNullOrEmpty(targetRelativePath))
return;
var absPath = Helper.ProjectPath + targetRelativePath;
CheckRampRowCount();
var absPath = IOHelper.GetAbsPath(targetRelativePath);
if (File.Exists(absPath))
{
var existRampTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(targetRelativePath);
@ -144,7 +273,7 @@ namespace LWGUI
{
if (!VersionControlHelper.Checkout(targetRelativePath))
{
Debug.LogError($"LWGUI: Can NOT write the Ramp Atlas Texture to path: { absPath }");
Debug.LogError($"LWGUI: Can NOT write the Ramp Atlas Texture to path: {absPath}");
return;
}
}
@ -157,10 +286,10 @@ namespace LWGUI
try
{
File.WriteAllBytes(absPath, rampAtlasTexture.EncodeToTGA());
File.WriteAllBytes(absPath, rampAtlas.EncodeToTGA());
SaveTextureUserData(targetRelativePath);
Debug.Log($"LWGUI: Saved the Ramp Atlas Texture at path: { absPath }");
Debug.Log($"LWGUI: Saved the Ramp Atlas Texture at path: {absPath}");
}
catch (Exception e)
{
@ -191,58 +320,26 @@ namespace LWGUI
{
if (!rampAtlasTexture)
return;
LwguiGradientWindow.RegisterSerializedObjectUndo(this);
rampAtlasTexture.Reinitialize(rampAtlasWidth, rampAtlasHeight);
rampAtlasTexture.SetPixels(GetPixels());
rampAtlasTexture.Apply();
}
public void DiscardChanges()
{
var importer = AssetImporter.GetAtPath(_rampAtlasTexturePath);
if (!importer)
return;
EditorJsonUtility.FromJsonOverwrite(importer.userData, this);
InitData();
AssetDatabase.ImportAsset(_rampAtlasTexturePath, ImportAssetOptions.ForceUpdate);
LoadTexture();
EditorUtility.ClearDirty(this);
}
public void ConvertColorSpace(ColorSpace targetColorSpace)
{
foreach (var ramp in ramps)
{
if (ramp.colorSpace != targetColorSpace)
{
ramp.colorSpace = targetColorSpace;
ramp.gradient.ConvertColorSpaceWithoutCopy(
targetColorSpace != ColorSpace.Gamma
? ColorSpace.Linear
: ColorSpace.Gamma);
}
}
rampAtlasSRGB = targetColorSpace == ColorSpace.Gamma;
RampHelper.SetRampTextureImporter(_rampAtlasTexturePath, true, !rampAtlasSRGB, EditorJsonUtility.ToJson(this));
UpdateTexturePixels();
SaveTexture();
}
[ContextMenu("Convert Gamma To Linear")]
public void ConvertGammaToLinear()
{
ConvertColorSpace(ColorSpace.Linear);
}
[ContextMenu("Convert Linear To Gamma")]
public void ConvertLinearToGamma()
{
ConvertColorSpace(ColorSpace.Gamma);
}
private void OnEnable()
{
InitData();
@ -253,18 +350,21 @@ namespace LWGUI
{
// Skip at the end of compilation
if (Event.current == null
// Skip when editing Text Field
|| EditorGUIUtility.editingTextField)
// Skip when editing Text Field
|| EditorGUIUtility.editingTextField)
return;
InitData();
if (!LoadTexture())
return;
UpdateTexturePixels();
SaveTexture();
SaveTexture(checkoutAndForceWrite:_saveTextureToggle);
_saveTextureToggle = false;
}
#region Static
public static Texture LoadRampAtlasTexture(LwguiRampAtlas rampAtlasSO)
{
@ -272,11 +372,11 @@ namespace LWGUI
{
return null;
}
var soPath = Path.ChangeExtension(AssetDatabase.GetAssetPath(rampAtlasSO), RampAtlasTextureExtensionName);
return AssetDatabase.LoadAssetAtPath<Texture>(soPath);
}
public static LwguiRampAtlas LoadRampAtlasSO(Texture texture)
{
if (!texture || !AssetDatabase.Contains(texture))
@ -287,17 +387,17 @@ namespace LWGUI
var soPath = Path.ChangeExtension(AssetDatabase.GetAssetPath(texture), RampAtlasSOExtensionName);
return AssetDatabase.LoadAssetAtPath<LwguiRampAtlas>(soPath);
}
public static LwguiRampAtlas CreateRampAtlasSO(MaterialProperty rampAtlasProp, LWGUIMetaDatas metaDatas)
public static LwguiRampAtlas CreateRampAtlasSO(MaterialProperty rampAtlasProp, LWGUIMetaDatas metaDatas, Type rampAtlasType = null)
{
if (rampAtlasProp == null || metaDatas == null)
return null;
var shader = metaDatas.GetShader();
// Get default ramps
RampAtlasDrawer targetRampAtlasDrawer = null;
List<(int defaultIndex, RampAtlasIndexerDrawer indexerDrawer)> defaultRampAtlasIndexerDrawers = new ();
List<(int defaultIndex, RampAtlasIndexerDrawer indexerDrawer)> defaultRampAtlasIndexerDrawers = new();
// Unity Bug: The cache of MaterialPropertyHandler must be cleared first, otherwise the default value cannot be obtained correctly.
ReflectionHelper.InvalidatePropertyCache(shader);
for (int i = 0; i < metaDatas.perMaterialData.defaultPropertiesWithPresetOverride.Length; i++)
@ -309,19 +409,25 @@ namespace LWGUI
if (drawer is RampAtlasDrawer rampAtlasDrawer && prop.name == rampAtlasProp.name)
targetRampAtlasDrawer = rampAtlasDrawer;
if (drawer is RampAtlasIndexerDrawer rampAtlasIndexerDrawer && rampAtlasIndexerDrawer.rampAtlasPropName == rampAtlasProp.name)
defaultRampAtlasIndexerDrawers.Add(((int)prop.GetNumericValue(), rampAtlasIndexerDrawer));
}
if (targetRampAtlasDrawer == null)
{
Debug.LogError($"LWGUI: Can NOT find RampAtlasDrawer { rampAtlasProp.name } in Shader { shader }");
Debug.LogError($"LWGUI: Can NOT find RampAtlasDrawer {rampAtlasProp.name} in Shader {shader}");
return null;
}
// Init Ramp Atlas with custom type or default type
rampAtlasType ??= typeof(LwguiRampAtlas);
var newRampAtlasSO = ScriptableObject.CreateInstance(rampAtlasType) as LwguiRampAtlas;
if (newRampAtlasSO == null)
{
Debug.LogError($"LWGUI: Failed to create RampAtlas of type '{rampAtlasType.Name}'");
return null;
}
// Init Ramp Atlas
var newRampAtlasSO = ScriptableObject.CreateInstance<LwguiRampAtlas>();
newRampAtlasSO.name = targetRampAtlasDrawer.defaultFileName;
newRampAtlasSO.rampAtlasWidth = targetRampAtlasDrawer.defaultAtlasWidth;
newRampAtlasSO.rampAtlasHeight = targetRampAtlasDrawer.defaultAtlasHeight;
@ -335,36 +441,50 @@ namespace LWGUI
var maxIndex = defaultRampAtlasIndexerDrawers.Max((tuple => tuple.defaultIndex));
for (int i = 0; i < maxIndex + 1; i++)
{
newRampAtlasSO.ramps.Add(new LwguiRampAtlas.Ramp());
if (newRampAtlasSO.ramps.Count >= newRampAtlasSO.rampAtlasHeight)
newRampAtlasSO.AddRamp();
if (newRampAtlasSO.TotalRowCount > newRampAtlasSO.rampAtlasHeight)
newRampAtlasSO.rampAtlasHeight *= 2;
}
// Set Ramps Default Value
for (int i = 0; i < defaultRampAtlasIndexerDrawers.Count; i++)
{
var defaultRampAtlasIndexerDrawer = defaultRampAtlasIndexerDrawers[i];
var ramp = newRampAtlasSO.ramps[defaultRampAtlasIndexerDrawer.defaultIndex];
var ramp = newRampAtlasSO.GetRamp(defaultRampAtlasIndexerDrawer.defaultIndex);
var drawer = defaultRampAtlasIndexerDrawer.indexerDrawer;
ramp.name = drawer.defaultRampName;
ramp.colorSpace = drawer.colorSpace;
ramp.channelMask = drawer.viewChannelMask;
ramp.timeRange = drawer.timeRange;
ramp.Name = drawer.defaultRampName;
ramp.ColorSpace = drawer.colorSpace;
ramp.ChannelMask = drawer.viewChannelMask;
ramp.TimeRange = drawer.timeRange;
}
}
return SaveRampAtlasSOToAsset(newRampAtlasSO, targetRampAtlasDrawer.rootPath, targetRampAtlasDrawer.defaultFileName);
}
public static LwguiRampAtlas CloneRampAtlasSO(LwguiRampAtlas rampAtlasSO)
public static LwguiRampAtlas CloneRampAtlasSO(LwguiRampAtlas rampAtlasSO, Type targetType = null)
{
if (!rampAtlasSO)
return null;
var newRampAtlasSO = Instantiate(rampAtlasSO);
var rootPath = Path.GetDirectoryName(rampAtlasSO._rampAtlasSOPath);
var defaultFileName = Path.GetFileName(rampAtlasSO._rampAtlasSOPath);
LwguiRampAtlas newRampAtlasSO;
// If target type is specified and different from source type, use ConvertToType
if (targetType != null && targetType != rampAtlasSO.GetType())
{
// Use ConvertToType for type conversion
newRampAtlasSO = ConvertToType(rampAtlasSO, targetType, false);
return newRampAtlasSO;
}
else
{
// Same type, use Instantiate for direct clone
newRampAtlasSO = Instantiate(rampAtlasSO);
}
if (SaveRampAtlasSOToAsset(newRampAtlasSO, rootPath, defaultFileName))
{
newRampAtlasSO.InitData();
@ -374,7 +494,7 @@ namespace LWGUI
return null;
}
public static LwguiRampAtlas SaveRampAtlasSOToAsset(LwguiRampAtlas rampAtlasSO, string rootPath, string defaultFileName)
{
if (!rampAtlasSO)
@ -387,15 +507,15 @@ namespace LWGUI
// TODO: Warning:
// PropertiesGUI() is being called recursively. If you want to render the default gui for shader properties then call PropertiesDefaultGUI() instead
var absPath = EditorUtility.SaveFilePanel("Create a Ramp Atlas SO", rootPath, defaultFileName, "asset");
if (absPath.StartsWith(Helper.ProjectPath))
if (absPath.StartsWith(IOHelper.ProjectPath))
{
createdFileRelativePath = absPath.Replace(Helper.ProjectPath, string.Empty);
createdFileRelativePath = IOHelper.GetRelativePath(absPath);
break;
}
else if (absPath != string.Empty)
{
var retry = EditorUtility.DisplayDialog("Invalid Path", "Please select the subdirectory of '" + Helper.ProjectPath + "'", "Retry", "Cancel");
var retry = EditorUtility.DisplayDialog("Invalid Path", $"Please select the subdirectory of '{IOHelper.ProjectPath}'", "Retry", "Cancel");
if (!retry) break;
}
else
@ -403,7 +523,7 @@ namespace LWGUI
break;
}
}
if (!string.IsNullOrEmpty(createdFileRelativePath))
{
AssetDatabase.CreateAsset(rampAtlasSO, createdFileRelativePath);
@ -411,8 +531,126 @@ namespace LWGUI
rampAtlasSO.LoadTexture();
return rampAtlasSO;
}
return null;
}
#endregion
#region Context Menu
public void ConvertColorSpace(ColorSpace targetColorSpace)
{
foreach (var ramp in Ramps)
{
if (ramp.ColorSpace != targetColorSpace)
{
ramp.ColorSpace = targetColorSpace;
foreach (var gradient in ramp.GetGradients())
{
gradient?.ConvertColorSpaceWithoutCopy(
targetColorSpace != ColorSpace.Gamma
? ColorSpace.Linear
: ColorSpace.Gamma);
}
}
}
rampAtlasSRGB = targetColorSpace == ColorSpace.Gamma;
RampHelper.SetRampTextureImporter(_rampAtlasTexturePath, true, !rampAtlasSRGB, EditorJsonUtility.ToJson(this));
UpdateTexturePixels();
SaveTexture(checkoutAndForceWrite:true);
}
[ContextMenu("Convert Gamma To Linear")]
public void ConvertGammaToLinear()
{
ConvertColorSpace(ColorSpace.Linear);
}
[ContextMenu("Convert Linear To Gamma")]
public void ConvertLinearToGamma()
{
ConvertColorSpace(ColorSpace.Gamma);
}
#endregion
#region Conversion Utilities
/// <summary>
/// Convert an existing LwguiRampAtlas asset to a custom derived type.
/// The new asset will be created at the same location with a suffix.
/// For custom Ramp types with additional Gradients, the extra Gradients will be copied from the default Gradient.
/// </summary>
public static LwguiRampAtlas ConvertToType(LwguiRampAtlas source, Type targetType, bool saveToAsset = true, string suffix = "_Converted")
{
if (source == null)
{
Debug.LogError("LWGUI: Source RampAtlas is null");
return null;
}
if (targetType == null || !typeof(LwguiRampAtlas).IsAssignableFrom(targetType))
{
Debug.LogError($"LWGUI: Target type must be derived from LwguiRampAtlas");
return null;
}
// Create new instance of target type
var newAtlas = ScriptableObject.CreateInstance(targetType) as LwguiRampAtlas;
if (newAtlas == null)
{
Debug.LogError($"LWGUI: Failed to create instance of type '{targetType.Name}'");
return null;
}
// Copy basic properties
newAtlas.rampAtlasWidth = source.rampAtlasWidth;
newAtlas.rampAtlasHeight = source.rampAtlasHeight;
newAtlas.rampAtlasSRGB = source.rampAtlasSRGB;
// Convert each Ramp using CopyFrom interface
foreach (var sourceRamp in source.Ramps)
{
if (sourceRamp == null) continue;
var newRamp = newAtlas.AddRamp();
newRamp?.CopyFrom(sourceRamp);
}
// Adjust height if needed
while (newAtlas.TotalRowCount > newAtlas.rampAtlasHeight)
{
newAtlas.rampAtlasHeight *= 2;
}
if (!saveToAsset)
return newAtlas;
// Save as new asset
var sourcePath = AssetDatabase.GetAssetPath(source);
if (string.IsNullOrEmpty(sourcePath))
{
Debug.LogWarning("LWGUI: Source RampAtlas is not a saved asset");
return newAtlas;
}
var directory = Path.GetDirectoryName(sourcePath);
var fileName = Path.GetFileNameWithoutExtension(sourcePath);
var newPath = Path.Combine(directory, fileName + suffix + "." + RampAtlasSOExtensionName);
newPath = AssetDatabase.GenerateUniqueAssetPath(newPath);
var result = SaveRampAtlasSOToAsset(newAtlas, directory, Path.GetFileNameWithoutExtension(newPath));
if (result != null)
Debug.Log($"LWGUI: Successfully converted RampAtlas to '{targetType.Name}' at: {newPath}");
else
Debug.LogError($"LWGUI: Conversion of RampAtlas to '{targetType.Name}' failed at: {newPath}");
return result;
}
#endregion
}
}
}

View File

@ -283,18 +283,25 @@ namespace LWGUI
if (presets == null)
return null;
if (index < presets.Count)
{
if (index < presets.Count && index >= 0)
return presets[index];
}
else
{
Debug.LogError($"LWGUI: Index ({ index }) is out of range when accessing PresetFile: { name }");
return null;
}
Debug.LogError($"LWGUI: Index ({ index }) is out of range when accessing PresetFile: { name }");
return null;
}
public Preset GetPreset(float index) => GetPreset((int)index);
public Preset TryGetPreset(int index)
{
if (presets == null)
return null;
if (index < presets.Count && index >= 0)
return presets[index];
return null;
}
public Preset TryGetPreset(float floatIndex) => TryGetPreset(Mathf.CeilToInt(floatIndex));
private void OnValidate()
{

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 799ea78c6ec34670ac45e915a967b2cc
timeCreated: 1769096897

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 721b8d7aa96ce3243ad6e988775df5ee
guid: 0087db252163ae340b69ec11fcd1e28a
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -0,0 +1,109 @@
// Copyright (c) Jason Ma
using System;
using LWGUI.Timeline;
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Create a Folding Group
///
/// group: group name (Default: Property Name)
/// keyword: keyword used for toggle, "_" = ignore, none or "__" = Property Name + "_ON", always Upper (Default: none)
/// default Folding State: "on" or "off" (Default: off)
/// default Toggle Displayed: "on" or "off" (Default: on)
/// preset File Name: "Shader Property Preset" asset name, see Preset() for detail (Default: none)
/// Target Property Type: Float, express Toggle value
/// </summary>
public class MainDrawer : MaterialPropertyDrawer, IBaseDrawer, IPresetDrawer
{
protected LWGUIMetaDatas metaDatas;
private static readonly float _height = 28f;
private bool _isFolding;
private string _group;
private string _keyword;
private bool _defaultFoldingState;
private bool _defaultToggleDisplayed;
private string _presetFileName;
public MainDrawer() : this(String.Empty) { }
public MainDrawer(string group) : this(group, String.Empty) { }
public MainDrawer(string group, string keyword) : this(group, keyword, "off") { }
public MainDrawer(string group, string keyword, string defaultFoldingState) : this(group, keyword, defaultFoldingState, "on") { }
public MainDrawer(string group, string keyword, string defaultFoldingState, string defaultToggleDisplayed) : this(group, keyword, defaultFoldingState, defaultToggleDisplayed, String.Empty) { }
public MainDrawer(string group, string keyword, string defaultFoldingState, string defaultToggleDisplayed, string presetFileName)
{
this._group = group;
this._keyword = keyword;
this._defaultFoldingState = Helper.StringToBool(defaultFoldingState);
this._defaultToggleDisplayed = Helper.StringToBool(defaultToggleDisplayed);
this._presetFileName = presetFileName;
}
public virtual void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.groupName = _group;
inoutPropertyStaticData.isMain = true;
inoutPropertyStaticData.isExpanding = _defaultFoldingState;
PerShaderData.DecodeMetaDataFromDisplayName(inProp, inoutPropertyStaticData);
PresetDrawer.SetPresetAssetToStaticData(inoutPropertyStaticData, _presetFileName);
}
public virtual void GetDefaultValueDescription(Shader inShader, MaterialProperty inProp, MaterialProperty inDefaultProp, PerShaderData inPerShaderData, PerMaterialData inoutPerMaterialData)
{
inoutPerMaterialData.propDynamicDatas[inProp.name].defaultValueDescription = inDefaultProp.floatValue > 0 ? "On" : "Off";
}
public string GetPresetFileName() => _presetFileName;
public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
metaDatas = Helper.GetLWGUIMetadatas(editor);
var showMixedValue = EditorGUI.showMixedValue;
EditorGUI.showMixedValue = prop.hasMixedValue;
EditorGUI.BeginChangeCheck();
bool toggleResult = Helper.DrawFoldout(position, ref metaDatas.GetPropStaticData(prop).isExpanding, !Helper.Approximately(prop.floatValue, 0), _defaultToggleDisplayed, label);
if (Helper.EndChangeCheck(metaDatas, prop))
{
prop.floatValue = toggleResult ? 1.0f : 0.0f;
var keyword = Helper.GetKeywordName(_keyword, prop.name);
Helper.SetShaderKeywordEnabled(editor.targets, keyword, toggleResult);
PresetHelper.GetPresetAsset(_presetFileName)?.TryGetPreset(prop.floatValue)?.ApplyToEditingMaterial(editor, metaDatas.perMaterialData);
TimelineHelper.SetKeywordToggleToTimeline(prop, editor, keyword);
}
EditorGUI.showMixedValue = showMixedValue;
}
// Call in custom shader gui
public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor)
{
return _height;
}
// Call when create/edit/undo materials.
// Used to set material settings such as Keywords that need to be kept synchronized with the value forever.
// DO NOT modify other properties here!!! Otherwise, manually modified values will be overwritten.
public override void Apply(MaterialProperty prop)
{
base.Apply(prop);
if (!prop.hasMixedValue && VersionControlHelper.IsWriteable(prop.targets))
{
Helper.SetShaderKeywordEnabled(prop.targets, Helper.GetKeywordName(_keyword, prop.name), prop.floatValue > 0f);
PresetDrawer.ApplyPresetWithoutPropertyChanges(_presetFileName, prop);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80bd09bde334c2c439f7374bb2ba821c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,74 @@
// Copyright (c) Jason Ma
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace LWGUI
{
/// <summary>
/// Draw a property with default style in the folding group
///
/// group: parent group name (Default: none)
/// Target Property Type: Any
/// </summary>
public class SubDrawer : MaterialPropertyDrawer, IBaseDrawer
{
public string group = String.Empty;
public LWGUIMetaDatas metaDatas;
public SubDrawer() { }
public SubDrawer(string group)
{
this.group = group;
}
protected virtual bool IsMatchPropType(MaterialProperty property) { return true; }
protected virtual float GetVisibleHeight(MaterialProperty prop)
{
var height = MaterialEditor.GetDefaultPropertyHeight(prop);
return prop.GetPropertyType() == ShaderPropertyType.Vector ? EditorGUIUtility.singleLineHeight : height;
}
public virtual void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.groupName = group;
PerShaderData.DecodeMetaDataFromDisplayName(inProp, inoutPropertyStaticData);
}
public virtual void GetDefaultValueDescription(Shader inShader, MaterialProperty inProp, MaterialProperty inDefaultProp, PerShaderData inPerShaderData, PerMaterialData inoutPerMaterialData) { }
public virtual void GetCustomContextMenus(GenericMenu menu, Rect rect, MaterialProperty prop, LWGUIMetaDatas metaDatas) { }
public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
metaDatas = Helper.GetLWGUIMetadatas(editor);
if (IsMatchPropType(prop))
{
DrawProp(position, prop, label, editor);
}
else
{
Debug.LogWarning("LWGUI: Property:'" + prop.name + "' Type:'" + prop.GetPropertyType() + "' mismatch!");
editor.DefaultShaderProperty(position, prop, label.text);
}
}
public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor)
{
return GetVisibleHeight(prop);
}
// Draws a custom style property
public virtual void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
RevertableHelper.FixGUIWidthMismatch(prop.GetPropertyType(), editor);
editor.DefaultShaderPropertyInternal(position, prop, label);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1018092e809fcf448a004c21f19c56cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 734309d3fd8d31246bde14f3278d4ee3
ShaderIncludeImporter:
guid: d88e9b9124470694db30ecb3c7da26c2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5e5aaa8816648ca4eaed701110c637d4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Display a Helpbox on the property
/// You can also use "%Text" in DisplayName to add Helpbox that supports Multi-Language.
///
/// message: a single-line string to display, support up to 4 ','. (Default: Newline)
/// </summary>
public class HelpboxDecorator : TooltipDecorator
{
private string _message;
#region
public HelpboxDecorator() : this(string.Empty) { }
public HelpboxDecorator(string message) { this._message = message; }
public HelpboxDecorator(string s1, string s2) : this(s1 + ", " + s2) { }
public HelpboxDecorator(string s1, string s2, string s3) : this(s1 + ", " + s2 + ", " + s3) { }
public HelpboxDecorator(string s1, string s2, string s3, string s4) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4) { }
public HelpboxDecorator(string s1, string s2, string s3, string s4, string s5) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4 + ", " + s5) { }
#endregion
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.helpboxMessages += _message + "\n";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c60e1921fd5b285489b55fa3650c40a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Set the property to read-only.
/// </summary>
public class ReadOnlyDecorator : SubDrawer
{
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.isReadOnly = true;
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e86854765f20f184185f804b18f4bd1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
// Copyright (c) Jason Ma
namespace LWGUI
{
/// <summary>
/// Similar to Title()
///
/// group: parent group name (Default: none)
/// header: string to display, "SpaceLine" or "_" = none (Default: none)
/// height: line height (Default: 22)
/// </summary>
public class SubTitleDecorator : TitleDecorator
{
public SubTitleDecorator(string group, string header) : base(group, header, DefaultHeight) { }
public SubTitleDecorator(string group, string header, float height) : base(group, header, height) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59dc6bb7a17b4da4c9ea5d75aed10b7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
// Copyright (c) Jason Ma
using System;
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Similar to Header()
///
/// group: parent group name (Default: none)
/// header: string to display, "SpaceLine" or "_" = none (Default: none)
/// height: line height (Default: 22)
/// </summary>
public class TitleDecorator : SubDrawer
{
private string _header;
private float _height;
public static readonly float DefaultHeight = EditorGUIUtility.singleLineHeight + 6f;
protected override float GetVisibleHeight(MaterialProperty prop) { return _height; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData) { }
public TitleDecorator(string header) : this("_", header, DefaultHeight) { }
public TitleDecorator(string header, float height) : this("_", header, height) { }
public TitleDecorator(string group, string header) : this(group, header, DefaultHeight) { }
public TitleDecorator(string group, string header, float height)
{
this.group = group;
this._header = header == "SpaceLine" || header == "_" ? String.Empty : header;
this._height = height;
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
position = EditorGUI.IndentedRect(position);
GUI.Label(position, _header, GUIStyles.title);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09b3118ef6d65ec49bdd423f20f09f09
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Tooltip, describes the details of the property. (Default: property.name and property default value)
/// You can also use "#Text" in DisplayName to add Tooltip that supports Multi-Language.
///
/// tooltip: a single-line string to display, support up to 4 ','. (Default: Newline)
/// </summary>
public class TooltipDecorator : SubDrawer
{
private string _tooltip;
#region
public TooltipDecorator() : this(string.Empty) { }
public TooltipDecorator(string tooltip) { this._tooltip = tooltip; }
public TooltipDecorator(string s1, string s2) : this(s1 + ", " + s2) { }
public TooltipDecorator(string s1, string s2, string s3) : this(s1 + ", " + s2 + ", " + s3) { }
public TooltipDecorator(string s1, string s2, string s3, string s4) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4) { }
public TooltipDecorator(string s1, string s2, string s3, string s4, string s5) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4 + ", " + s5) { }
#endregion
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.tooltipMessages += _tooltip + "\n";
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5934a4eb43f5b3b48b5396a2cb653f76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 921ffaf8b8533bf42983cf1189a4ad45
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Control whether the property can be edited based on multiple conditions.
///
/// logicalOperator: And | Or (Default: And).
/// propName: Target Property Name used for comparison.
/// compareFunction: Less (L) | Equal (E) | LessEqual (LEqual / LE) | Greater (G) | NotEqual (NEqual / NE) | GreaterEqual (GEqual / GE).
/// value: Target Property Value used for comparison.
/// </summary>
public class ActiveIfDecorator : SubDrawer
{
public ShowIfDecorator.ShowIfData activeIfData = new();
public ActiveIfDecorator(string propName, string comparisonMethod, float value) : this("And", propName, comparisonMethod, value) { }
public ActiveIfDecorator(string logicalOperator, string propName, string compareFunction, float value)
{
activeIfData.logicalOperator = logicalOperator.ToLower() == "or" ? ShowIfDecorator.LogicalOperator.Or : ShowIfDecorator.LogicalOperator.And;
activeIfData.targetPropertyName = propName;
activeIfData.compareFunction = ShowIfDecorator.ParseCompareFunction(compareFunction);
activeIfData.value = value;
}
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.activeIfDatas.Add(activeIfData);
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 224088b56f7a2814ba7520292da26cf8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Similar to HideInInspector(), the difference is that Hidden() can be unhidden through the Display Mode button.
/// </summary>
public class HiddenDecorator : SubDrawer
{
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.isHidden = true;
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b730963b9bced7f418f78f2502b39607
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,170 @@
// Copyright (c) Jason Ma
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace LWGUI
{
/// <summary>
/// Control the show or hide of a single or a group of properties based on multiple conditions.
///
/// logicalOperator: And | Or (Default: And).
/// propName: Target Property Name used for comparison.
/// compareFunction: Less (L) | Equal (E) | LessEqual (LEqual / LE) | Greater (G) | NotEqual (NEqual / NE) | GreaterEqual (GEqual / GE).
/// value: Target Property Value used for comparison.
/// </summary>
public class ShowIfDecorator : SubDrawer
{
public enum LogicalOperator
{
And,
Or
}
public class ShowIfData
{
public LogicalOperator logicalOperator = LogicalOperator.And;
public string targetPropertyName = string.Empty;
public CompareFunction compareFunction = CompareFunction.Equal;
public float value = 0;
}
public ShowIfData showIfData = new();
private static readonly Dictionary<string, string> _compareFunctionLUT = new()
{
{ "Less", "Less" },
{ "L", "Less" },
{ "Equal", "Equal" },
{ "E", "Equal" },
{ "LessEqual", "LessEqual" },
{ "LEqual", "LessEqual" },
{ "LE", "LessEqual" },
{ "Greater", "Greater" },
{ "G", "Greater" },
{ "NotEqual", "NotEqual" },
{ "NEqual", "NotEqual" },
{ "NE", "NotEqual" },
{ "GreaterEqual", "GreaterEqual" },
{ "GEqual", "GreaterEqual" },
{ "GE", "GreaterEqual" },
};
public static CompareFunction ParseCompareFunction(string compareFunction)
{
if (!_compareFunctionLUT.TryGetValue(compareFunction, out var compareFunctionName)
|| !Enum.IsDefined(typeof(CompareFunction), compareFunctionName))
{
Debug.LogError("LWGUI: Invalid compareFunction: '"
+ compareFunction
+ "', Must be one of the following: Less (L) | Equal (E) | LessEqual (LEqual / LE) | Greater (G) | NotEqual (NEqual / NE) | GreaterEqual (GEqual / GE).");
return CompareFunction.Equal;
}
return (CompareFunction)Enum.Parse(typeof(CompareFunction), compareFunctionName);
}
public ShowIfDecorator(string propName, string comparisonMethod, float value) : this("And", propName, comparisonMethod, value) { }
public ShowIfDecorator(string logicalOperator, string propName, string compareFunction, float value)
{
showIfData.logicalOperator = logicalOperator.ToLower() == "or" ? LogicalOperator.Or : LogicalOperator.And;
showIfData.targetPropertyName = propName;
showIfData.compareFunction = ParseCompareFunction(compareFunction);
showIfData.value = value;
}
private static void Compare(ShowIfData showIfData, float targetValue, ref bool result)
{
bool compareResult;
switch (showIfData.compareFunction)
{
case CompareFunction.Less:
compareResult = targetValue < showIfData.value;
break;
case CompareFunction.LessEqual:
compareResult = targetValue <= showIfData.value;
break;
case CompareFunction.Greater:
compareResult = targetValue > showIfData.value;
break;
case CompareFunction.NotEqual:
compareResult = targetValue != showIfData.value;
break;
case CompareFunction.GreaterEqual:
compareResult = targetValue >= showIfData.value;
break;
default:
compareResult = targetValue == showIfData.value;
break;
}
switch (showIfData.logicalOperator)
{
case LogicalOperator.And:
result &= compareResult;
break;
case LogicalOperator.Or:
result |= compareResult;
break;
}
}
public static bool GetShowIfResultToFilterDrawerApplying(MaterialProperty prop)
{
var material = prop.targets[0] as Material;
var showIfDatas = new List<ShowIfData>();
{
var drawer = ReflectionHelper.GetPropertyDrawer(material.shader, prop, out var decoratorDrawers);
if (decoratorDrawers != null && decoratorDrawers.Count > 0)
{
foreach (ShowIfDecorator showIfDecorator in decoratorDrawers.Where(drawer => drawer is ShowIfDecorator))
{
showIfDatas.Add(showIfDecorator.showIfData);
}
}
else
{
return true;
}
}
return GetShowIfResultFromMaterial(showIfDatas, material);
}
public static bool GetShowIfResultFromMaterial(List<ShowIfData> showIfDatas, Material material)
{
bool result = true;
foreach (var showIfData in showIfDatas)
{
var targetValue = material.GetFloat(showIfData.targetPropertyName);
Compare(showIfData, targetValue, ref result);
}
return result;
}
public static void GetShowIfResult(PropertyStaticData propStaticData, PropertyDynamicData propDynamicData, PerMaterialData perMaterialData)
{
foreach (var showIfData in propStaticData.showIfDatas)
{
var targetValue = perMaterialData.propDynamicDatas[showIfData.targetPropertyName].property.floatValue;
Compare(showIfData, targetValue, ref propDynamicData.isShowing);
}
}
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.showIfDatas.Add(showIfData);
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f391774ca908ee42abfa5b95dea3b8f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 39a1eac873eb3a04a8bd4ad4805745b9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,71 @@
// Copyright (c) Jason Ma
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace LWGUI
{
/// <summary>
/// Cooperate with Toggle to switch certain Passes.
///
/// lightModeName(s): Light Mode in Shader Pass (https://docs.unity3d.com/2017.4/Documentation/Manual/SL-PassTags.html)
/// </summary>
public class PassSwitchDecorator : SubDrawer
{
private string[] _lightModeNames;
#region
public PassSwitchDecorator(string lightModeName1)
: this(new[] { lightModeName1 }) { }
public PassSwitchDecorator(string lightModeName1, string lightModeName2)
: this(new[] { lightModeName1, lightModeName2 }) { }
public PassSwitchDecorator(string lightModeName1, string lightModeName2, string lightModeName3)
: this(new[] { lightModeName1, lightModeName2, lightModeName3 }) { }
public PassSwitchDecorator(string lightModeName1, string lightModeName2, string lightModeName3, string lightModeName4)
: this(new[] { lightModeName1, lightModeName2, lightModeName3, lightModeName4 }) { }
public PassSwitchDecorator(string lightModeName1, string lightModeName2, string lightModeName3, string lightModeName4, string lightModeName5)
: this(new[] { lightModeName1, lightModeName2, lightModeName3, lightModeName4, lightModeName5 }) { }
public PassSwitchDecorator(string lightModeName1, string lightModeName2, string lightModeName3, string lightModeName4, string lightModeName5, string lightModeName6)
: this(new[] { lightModeName1, lightModeName2, lightModeName3, lightModeName4, lightModeName5, lightModeName6 }) { }
public PassSwitchDecorator(string[] lightModeNames) { _lightModeNames = lightModeNames.Select((s => s.ToUpper())).ToArray(); }
#endregion
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
protected override bool IsMatchPropType(MaterialProperty property)
{
return property.GetPropertyType() == ShaderPropertyType.Float
|| property.GetPropertyType() == ShaderPropertyType.Int;
}
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData) { }
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor)
{
if (!prop.hasMixedValue && VersionControlHelper.IsWriteable(prop.targets))
Helper.SetShaderPassEnabled(prop.targets, _lightModeNames, prop.floatValue > 0);
}
public override void Apply(MaterialProperty prop)
{
base.Apply(prop);
if (!prop.hasMixedValue && VersionControlHelper.IsWriteable(prop.targets))
{
if (ShowIfDecorator.GetShowIfResultToFilterDrawerApplying(prop))
Helper.SetShaderPassEnabled(prop.targets, _lightModeNames, prop.floatValue > 0);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ce1e6b3dcc10bf4d904506b5427dd62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a3d4d8d42a6cdcf4280e9f094d31280c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Collapse the current Property into an Advanced Block.
/// Specify the Header String to create a new Advanced Block.
/// All Properties using Advanced() will be collapsed into the nearest Advanced Block.
///
/// headerString: The title of the Advanced Block. Default: "Advanced"
/// </summary>
public class AdvancedDecorator : SubDrawer
{
private string headerString;
public AdvancedDecorator() : this(string.Empty) { }
public AdvancedDecorator(string headerString)
{
this.headerString = headerString;
}
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.isAdvanced = true;
inoutPropertyStaticData.advancedHeaderString = headerString;
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f94593a3791d98342bcf7e2ce8a434ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,24 @@
// Copyright (c) Jason Ma
using UnityEditor;
using UnityEngine;
namespace LWGUI
{
/// <summary>
/// Create an Advanced Block using the current Property as the Header.
/// </summary>
public class AdvancedHeaderPropertyDecorator : SubDrawer
{
protected override float GetVisibleHeight(MaterialProperty prop) { return 0; }
public override void BuildStaticMetaData(Shader inShader, MaterialProperty inProp, MaterialProperty[] inProps, PropertyStaticData inoutPropertyStaticData)
{
inoutPropertyStaticData.isAdvanced = true;
inoutPropertyStaticData.isAdvancedHeader = true;
inoutPropertyStaticData.isAdvancedHeaderProperty = true;
}
public override void DrawProp(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eda92ad2828dbbf4b8ad1ceec6a203bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f91db697c82843841ac35226be10a332
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More