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 // Mapping from original materials to cloned materials
Dictionary<Material, Material> materialMap = new Dictionary<Material, Material>(); Dictionary<Material, Material> materialMap = new Dictionary<Material, Material>();
List<string> createdMaterialAssetPaths = new List<string>();
foreach (Material mat in materialsSet) foreach (Material mat in materialsSet)
{ {
@ -118,6 +119,7 @@ namespace NiloToon.NiloToonURP
AssetDatabase.CreateAsset(matClone, newMatPath); AssetDatabase.CreateAsset(matClone, newMatPath);
Debug.Log("Created Material Asset: " + newMatPath); Debug.Log("Created Material Asset: " + newMatPath);
materialMap[mat] = matClone; materialMap[mat] = matClone;
createdMaterialAssetPaths.Add(newMatPath);
} }
catch (System.Exception e) catch (System.Exception e)
{ {
@ -131,6 +133,18 @@ namespace NiloToon.NiloToonURP
// Instantiate the selected prefab // Instantiate the selected prefab
GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(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}"); 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.LogError($"Failed to save prefab variant at path: {variantPath}");
Debug.LogException(e); Debug.LogException(e);
GameObject.DestroyImmediate(instance); GameObject.DestroyImmediate(instance);
CleanupGeneratedMaterialAssets(createdMaterialAssetPaths);
return; return;
} }
@ -177,7 +192,10 @@ namespace NiloToon.NiloToonURP
} }
// Run auto setup for the prefab variant // Run auto setup for the prefab variant
NiloToonEditorPerCharacterRenderControllerCustomEditor.AutoSetupCharacterGameObject(prefabVariantContents); NiloToonEditorPerCharacterRenderControllerCustomEditor.AutoSetupCharacterGameObject(
prefabVariantContents,
shouldPromptMaterialEditConfirmation: false,
shouldEditMaterialWhenConfirmationSkipped: true);
// Save and unload prefab variant // Save and unload prefab variant
PrefabUtility.SaveAsPrefabAsset(prefabVariantContents, variantPath); PrefabUtility.SaveAsPrefabAsset(prefabVariantContents, variantPath);
@ -187,6 +205,17 @@ namespace NiloToon.NiloToonURP
EditorUtility.DisplayDialog("Success", "Prefab variant created with cloned materials.", "OK"); 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("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)] [MenuItem("Assets/NiloToon/[Prefab] Create NiloToon Prefab Variant and Materials", priority = 1100 + 0, validate = true)]
public static bool ValidateCreatePrefabVariantAndCloneMaterials() public static bool ValidateCreatePrefabVariantAndCloneMaterials()
@ -270,5 +299,17 @@ namespace NiloToon.NiloToonURP
AssetDatabase.Refresh(); 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) 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>(); var charScript = gameobject.GetComponent<NiloToonPerCharacterRenderController>();
if (!charScript) if (!charScript)
@ -175,9 +183,20 @@ namespace NiloToon.NiloToonURP
charScript = gameobject.AddComponent<NiloToonPerCharacterRenderController>(); charScript = gameobject.AddComponent<NiloToonPerCharacterRenderController>();
} }
AutoSetupCharacterGameObject(charScript); AutoSetupCharacterGameObject(
charScript,
shouldPromptMaterialEditConfirmation,
shouldEditMaterialWhenConfirmationSkipped);
} }
public static void AutoSetupCharacterGameObject(NiloToonPerCharacterRenderController perCharScript) public static void AutoSetupCharacterGameObject(NiloToonPerCharacterRenderController perCharScript)
{
AutoSetupCharacterGameObject(perCharScript, shouldPromptMaterialEditConfirmation: true);
}
public static void AutoSetupCharacterGameObject(
NiloToonPerCharacterRenderController perCharScript,
bool shouldPromptMaterialEditConfirmation,
bool shouldEditMaterialWhenConfirmationSkipped = true)
{ {
if (!perCharScript) if (!perCharScript)
{ {
@ -188,14 +207,17 @@ namespace NiloToon.NiloToonURP
} }
perCharScript.RefillAllRenderers(); perCharScript.RefillAllRenderers();
bool shouldEditMaterial = EditorUtility.DisplayDialog( bool shouldEditMaterial = ResolveShouldEditMaterialDecision(
$"Auto setup character - {perCharScript.gameObject.name}", shouldPromptMaterialEditConfirmation,
"Set up NiloToon's script on this character completed.\n\n" + shouldEditMaterialWhenConfirmationSkipped,
"Do you want to convert the materials to NiloToon also?\n" + () => EditorUtility.DisplayDialog(
"(convert from lilToon/MToon/UniUnlit/URP Lit/URP Unlit to NiloToon_Character)", $"Auto setup character - {perCharScript.gameObject.name}",
"Yes, convert the materials to NiloToon.", "Set up NiloToon's script on this character completed.\n\n" +
"No, DO NOT edit any materials." "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) if (shouldEditMaterial)
{ {
@ -255,6 +277,19 @@ namespace NiloToon.NiloToonURP
//Debug.Log($"NiloToon: Auto setup {perCharScript.gameObject.name} completed."); //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) 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 System.IO;
// using UnityEditor; // using UnityEditor;
// using UnityEditor.Build; // using UnityEditor.Build;

View File

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

View File

@ -1,4 +1,4 @@
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
namespace LWGUI.CustomGUISample 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 #region Misc
public static readonly string ProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
public static bool IsPropertyHideInInspector(MaterialProperty prop) public static bool IsPropertyHideInInspector(MaterialProperty prop)
{ {
return (prop.GetPropertyFlags() & ShaderPropertyFlags.HideInInspector) != 0; 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) public static void SetShaderKeywordEnabled(Object[] materials, string keywordName, bool isEnable)
{ {
if (string.IsNullOrEmpty(keywordName) || string.IsNullOrEmpty(keywordName)) return; if (string.IsNullOrEmpty(keywordName) || string.IsNullOrEmpty(keywordName)) return;
@ -90,6 +72,7 @@ namespace LWGUI
{ {
material.SetShaderPassEnabled(lightModeNames[i], enabled); 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 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) public static void BeginProperty(Rect rect, MaterialProperty property, LWGUIMetaDatas metaDatas)
{ {
#if UNITY_2022_1_OR_NEWER #if UNITY_2022_1_OR_NEWER
@ -148,6 +124,43 @@ namespace LWGUI
#endregion #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 #region Math
public const double Float_Epsilon = 1e-10; public const double Float_Epsilon = 1e-10;
@ -178,60 +191,6 @@ namespace LWGUI
#endregion #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 #region Draw GUI for Drawers
// TODO: use Reflection // TODO: use Reflection
@ -285,7 +244,7 @@ namespace LWGUI
GUI.enabled = true; GUI.enabled = true;
var guiColor = GUI.backgroundColor; var guiColor = GUI.backgroundColor;
GUI.backgroundColor = isFolding ? Color.white : new Color(0.85f, 0.85f, 0.85f); 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; isFolding = !isFolding;
GUI.changed = false; GUI.changed = false;
@ -349,10 +308,10 @@ namespace LWGUI
{ {
var content = new GUIContent(helpboxStr, _helpboxIcon); var content = new GUIContent(helpboxStr, _helpboxIcon);
var textWidth = GetCurrentPropertyLayoutWidth(); 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)); var helpboxRect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(true, textHeight));
helpboxRect.xMax -= RevertableHelper.revertButtonWidth; helpboxRect.xMax -= RevertableHelper.revertButtonWidth;
GUI.Label(helpboxRect, content, guiStyle_Helpbox); GUI.Label(helpboxRect, content, GUIStyles.helpbox);
// EditorGUI.HelpBox(helpboxRect, helpboxStr, MessageType.Info); // EditorGUI.HelpBox(helpboxRect, helpboxStr, MessageType.Info);
} }
} }
@ -372,610 +331,20 @@ namespace LWGUI
logoRect.xMin += w * 0.5f - _logo.width * 0.5f; logoRect.xMin += w * 0.5f - _logo.width * 0.5f;
logoRect.xMax -= 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"); Application.OpenURL("https://github.com/JasonMa0012/LWGUI");
} }
} }
#endregion #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 #region Importer
// https://docs.unity3d.com/ScriptReference/TextureImporter.GetPlatformTextureSettings.html // 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 #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;
using System.Collections.Generic; using System.Collections.Generic;
@ -14,7 +14,6 @@ namespace LWGUI
public PerMaterialData perMaterialData; public PerMaterialData perMaterialData;
public PerInspectorData perInspectorData; public PerInspectorData perInspectorData;
#region Get Prop Data #region Get Prop Data
public PropertyStaticData GetPropStaticData(string propName) => perShaderData?.GetPropStaticData(propName); public PropertyStaticData GetPropStaticData(string propName) => perShaderData?.GetPropStaticData(propName);
@ -27,7 +26,7 @@ namespace LWGUI
public MaterialProperty GetProperty(string propName) => GetPropDynamicData(propName)?.property; 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 #endregion
@ -47,6 +46,8 @@ namespace LWGUI
public Material GetMaterial() => perMaterialData.material; public Material GetMaterial() => perMaterialData.material;
public Shader GetShader() => perShaderData.shader; public Shader GetShader() => perShaderData.shader;
public string GetShaderUID() => perShaderData.shaderUID;
public MaterialEditor GetMaterialEditor() => perInspectorData.materialEditor; public MaterialEditor GetMaterialEditor() => perInspectorData.materialEditor;
} }

View File

@ -53,14 +53,18 @@ namespace LWGUI
if (!_loadedPresets.ContainsKey(presetFileName) || !_loadedPresets[presetFileName]) 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 null;
} }
return _loadedPresets[presetFileName]; 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) public static void ApplyPresetsInMaterial(Material material)
{ {
var props = MaterialEditor.GetMaterialProperties(new UnityEngine.Object[] { material }); var props = MaterialEditor.GetMaterialProperties(new UnityEngine.Object[] { material });
@ -69,11 +73,10 @@ namespace LWGUI
var drawer = ReflectionHelper.GetPropertyDrawer(material.shader, prop, out _); var drawer = ReflectionHelper.GetPropertyDrawer(material.shader, prop, out _);
// Apply active preset // 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)); var activePreset = presetDrawer.GetActivePreset(prop, GetPresetAsset(presetDrawer.GetPresetFileName()));
if (activePreset != null) activePreset?.ApplyToDefaultMaterial(material);
activePreset.ApplyToDefaultMaterial(material);
} }
} }
UnityEditorExtension.ApplyMaterialPropertyAndDecoratorDrawers(material); UnityEditorExtension.ApplyMaterialPropertyAndDecoratorDrawers(material);

View File

@ -1,12 +1,10 @@
// Copyright (c) Jason Ma // Copyright (c) Jason Ma
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using LWGUI.LwguiGradientEditor; using LWGUI.LwguiGradientEditor;
using LWGUI.Runtime.LwguiGradient; using LWGUI.Runtime.LwguiGradient;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object;
namespace LWGUI namespace LWGUI
{ {
@ -14,10 +12,13 @@ namespace LWGUI
{ {
#region RampEditor #region RampEditor
private static readonly GUIContent _iconAdd = new GUIContent(EditorGUIUtility.IconContent("d_Toolbar Plus").image, "Add"), private const string _iconCloneGUID = "9cdef444d18d2ce4abb6bbc4fed4d109";
_iconEdit = new GUIContent(EditorGUIUtility.IconContent("editicon.sml").image, "Edit"),
_iconDiscard = new GUIContent(EditorGUIUtility.IconContent("d_TreeEditor.Refresh").image, "Discard"), private static readonly GUIContent _iconAdd = new (EditorGUIUtility.IconContent("d_Toolbar Plus").image, "Add"),
_iconSave = new GUIContent(EditorGUIUtility.IconContent("SaveActive").image, "Save"); _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( public static void RampEditor(
Rect buttonRect, Rect buttonRect,
@ -29,6 +30,7 @@ namespace LWGUI
out bool hasChange, out bool hasChange,
out bool doEditWhenNoGradient, out bool doEditWhenNoGradient,
out bool doRegisterUndo, out bool doRegisterUndo,
out bool doClone,
out bool doCreate, out bool doCreate,
out bool doSave, out bool doSave,
out bool doDiscard, out bool doDiscard,
@ -38,11 +40,12 @@ namespace LWGUI
var hasNoGradient = gradient == null; var hasNoGradient = gradient == null;
var _doEditWhenNoGradient = false; var _doEditWhenNoGradient = false;
var doOpenWindow = 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 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 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 cloneRect = 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 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 // Edit button event
hasChange = false; hasChange = false;
@ -76,6 +79,8 @@ namespace LWGUI
} }
doEditWhenNoGradient = _doEditWhenNoGradient; doEditWhenNoGradient = _doEditWhenNoGradient;
// Clone button
doClone = GUI.Button(cloneRect, _iconClone);
// Create button // Create button
doCreate = GUI.Button(addRect, _iconAdd); doCreate = GUI.Button(addRect, _iconAdd);
@ -143,9 +148,8 @@ namespace LWGUI
// Save texture to disk // Save texture to disk
if (doSaveToDisk) if (doSaveToDisk)
{ {
var systemPath = Helper.ProjectPath + path;
VersionControlHelper.Checkout(path); VersionControlHelper.Checkout(path);
File.WriteAllBytes(systemPath, texture2D.EncodeToPNG()); File.WriteAllBytes(IOHelper.GetAbsPath(path), texture2D.EncodeToPNG());
assetImporter.SaveAndReimport(); assetImporter.SaveAndReimport();
} }
} }
@ -190,15 +194,14 @@ namespace LWGUI
return subJSONs[0] != subJSONs[1]; 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 ramp = gradient.GetPreviewRampTexture(width, height, ColorSpace.Linear);
var png = ramp.EncodeToPNG(); var png = ramp.EncodeToPNG();
var systemPath = Helper.ProjectPath + unityPath; File.WriteAllBytes(IOHelper.GetAbsPath(unityPath), png);
File.WriteAllBytes(systemPath, png);
AssetDatabase.ImportAsset(unityPath); AssetDatabase.ImportAsset(unityPath);
SetRampTextureImporter(unityPath, true, isLinear, EncodeGradientToJSON(gradient, gradient)); 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 #endregion
} }
public class RampSelectorWindow : EditorWindow public class RampSelectorWindow : EditorWindow
{ {
public delegate void SwitchRampMapCallback(MaterialProperty prop, Texture2D newRampMap, int index); 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 Texture2D[] _rampMaps;
private Vector2 _scrollPosition; private Vector2 _scrollPosition;
private MaterialProperty _prop; private MaterialProperty _prop;
private SwitchRampCallback _switchRampEvent;
private SwitchRampMapCallback _switchRampMapEvent; 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) 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.titleContent = new GUIContent("Ramp Selector");
window.minSize = new Vector2(400, 500); window.minSize = new Vector2(400, 500);
window._rampMaps = rampMaps; window._rampMaps = rampMaps;
@ -298,6 +308,62 @@ namespace LWGUI
} }
private void OnGUI() 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(); EditorGUILayout.BeginVertical();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
@ -305,26 +371,26 @@ namespace LWGUI
for (int i = 0; i < _rampMaps.Length; i++) for (int i = 0; i < _rampMaps.Length; i++)
{ {
var rampMap = _rampMaps[i]; var rampMap = _rampMaps[i];
EditorGUILayout.BeginHorizontal(); if (rampMap == null) continue;
if (rampMap != null)
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 }"); _switchRampMapEvent(_prop, rampMap, i);
var rect = EditorGUILayout.GetControlRect(); LwguiGradientWindow.CloseWindow();
var buttonWidth = Mathf.Min(300f, Mathf.Max(GUI.skin.button.CalcSize(guiContent).x, rect.width * 0.35f)); Close();
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);
} }
EditorGUILayout.EndHorizontal();
GUILayout.Space(RowSpacing);
} }
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma // Copyright (c) Jason Ma
using System; using System;
using System.Linq; using System.Linq;
@ -136,11 +136,24 @@ namespace LWGUI
if (isHeader) 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); var perMaterialData = metaDatas.perMaterialData;
foreach (var childChildStaticData in childStaticData.children) var freshProps = MaterialEditor.GetMaterialProperties(
DoRevertProperty(metaDatas.GetProperty(childChildStaticData.name), metaDatas); 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; 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) private static void DoRevertProperty(MaterialProperty prop, LWGUIMetaDatas metaDatas)
{ {
var propDynamicData = metaDatas.GetPropDynamicData(prop.name); var propDynamicData = metaDatas.GetPropDynamicData(prop.name);
propDynamicData.hasRevertChanged = true; propDynamicData.hasRevertChanged = true;
SetPropertyToDefault(propDynamicData.defualtProperty, prop); SetPropertyToDefault(propDynamicData.defaultProperty, prop);
foreach (var extraPropName in metaDatas.GetPropStaticData(prop.name).extraPropNames) foreach (var extraPropName in metaDatas.GetPropStaticData(prop.name).extraPropNames)
{ {
var extraPropDynamicData = metaDatas.GetPropDynamicData(extraPropName); var extraPropDynamicData = metaDatas.GetPropDynamicData(extraPropName);
extraPropDynamicData.hasRevertChanged = true; 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 fileFormatVersion: 2
guid: 72488141525eaa8499e65e52755cb6d0 guid: 72488141525eaa8499e65e52755cb6d0
TextureImporter: TextureImporter:
fileIDToRecycleName: {} internalIDToNameTable: []
externalObjects: {} externalObjects: {}
serializedVersion: 4 serializedVersion: 13
mipmaps: mipmaps:
mipMapMode: 0 mipMapMode: 0
enableMipMap: 0 enableMipMap: 0
sRGBTexture: 0 sRGBTexture: 1
linearTexture: 0 linearTexture: 0
fadeOut: 0 fadeOut: 0
borderMipMap: 0 borderMipMap: 0
@ -20,7 +20,12 @@ TextureImporter:
externalNormalMap: 0 externalNormalMap: 0
heightScale: 0.25 heightScale: 0.25
normalMapFilter: 0 normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0 isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0 grayScaleToAlpha: 0
generateCubemap: 6 generateCubemap: 6
cubemapConvolution: 0 cubemapConvolution: 0
@ -51,21 +56,32 @@ TextureImporter:
spriteTessellationDetail: -1 spriteTessellationDetail: -1
textureType: 2 textureType: 2
textureShape: 1 textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0 maxTextureSizeSet: 0
compressionQualitySet: 0 compressionQualitySet: 0
textureFormatSet: 0 textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings: platformSettings:
- buildTarget: DefaultTexturePlatform - serializedVersion: 3
maxTextureSize: 2048 buildTarget: DefaultTexturePlatform
resizeAlgorithm: 0 maxTextureSize: 128
textureFormat: -1 resizeAlgorithm: 0
textureCompression: 1 textureFormat: -1
compressionQuality: 50 textureCompression: 1
crunchedCompression: 0 compressionQuality: 50
allowsAlphaSplitting: 0 crunchedCompression: 0
overridden: 0 allowsAlphaSplitting: 0
androidETC2FallbackOverride: 0 overridden: 0
- buildTarget: Standalone ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048 maxTextureSize: 2048
resizeAlgorithm: 0 resizeAlgorithm: 0
textureFormat: -1 textureFormat: -1
@ -74,13 +90,25 @@ TextureImporter:
crunchedCompression: 0 crunchedCompression: 0
allowsAlphaSplitting: 0 allowsAlphaSplitting: 0
overridden: 0 overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0 androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet: spriteSheet:
serializedVersion: 2 serializedVersion: 2
sprites: [] sprites: []
outline: [] outline: []
physicsShape: [] physicsShape: []
spritePackingTag: bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) Jason Ma // Copyright (c) Jason Ma
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -7,6 +7,7 @@ using UnityEngine.Rendering;
namespace LWGUI namespace LWGUI
{ {
public delegate void LWGUICustomGUIEvent(LWGUI lwgui); public delegate void LWGUICustomGUIEvent(LWGUI lwgui);
public delegate void LWGUIToolbarExtensionEvent(LWGUI lwgui, ref Rect toolBarRect);
public class LWGUI : ShaderGUI public class LWGUI : ShaderGUI
{ {
@ -15,6 +16,8 @@ namespace LWGUI
public static LWGUICustomGUIEvent onDrawCustomHeader; public static LWGUICustomGUIEvent onDrawCustomHeader;
public static LWGUICustomGUIEvent onDrawCustomFooter; public static LWGUICustomGUIEvent onDrawCustomFooter;
public static LWGUIToolbarExtensionEvent onDrawToolbarLeft;
public static LWGUIToolbarExtensionEvent onDrawToolbarRight;
/// <summary> /// <summary>
/// Called when switch to a new Material Window, each window has a LWGUI instance /// Called when switch to a new Material Window, each window has a LWGUI instance
@ -44,19 +47,24 @@ namespace LWGUI
onDrawCustomHeader(this); onDrawCustomHeader(this);
// Toolbar // Toolbar
bool enabled = GUI.enabled; {
GUI.enabled = true; bool enabled = GUI.enabled;
var toolBarRect = EditorGUILayout.GetControlRect(); GUI.enabled = true;
toolBarRect.xMin = 2; 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;
GUILayoutUtility.GetRect(0, 0); // Space(0) Helper.DrawSplitLine();
GUI.enabled = enabled;
Helper.DrawSplitLine();
ToolbarHelper.DrawShaderPerformanceStats(metaDatas);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Draw Properties // Draw Properties
@ -147,7 +155,7 @@ namespace LWGUI
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition)) if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition))
propStaticData.isExpanding = !propStaticData.isExpanding; propStaticData.isExpanding = !propStaticData.isExpanding;
RevertableHelper.DrawRevertableProperty(revertButtonRect, prop, metaDatas, true); RevertableHelper.DrawRevertableProperty(revertButtonRect, prop, metaDatas, true);
Helper.DoPropertyContextMenus(rect, prop, metaDatas); ContextMenuHelper.DoPropertyContextMenus(rect, prop, metaDatas);
} }
private void DrawProperty(MaterialProperty prop) private void DrawProperty(MaterialProperty prop)
@ -167,9 +175,10 @@ namespace LWGUI
var revertButtonRect = RevertableHelper.SplitRevertButtonRect(ref rect); var revertButtonRect = RevertableHelper.SplitRevertButtonRect(ref rect);
var enabled = GUI.enabled; var enabled = GUI.enabled;
if (propStaticData.isReadOnly) GUI.enabled = false; if (propStaticData.isReadOnly || !propDynamicData.isActive)
GUI.enabled = false;
Helper.BeginProperty(rect, prop, metaDatas); Helper.BeginProperty(rect, prop, metaDatas);
Helper.DoPropertyContextMenus(rect, prop, metaDatas); ContextMenuHelper.DoPropertyContextMenus(rect, prop, metaDatas);
RevertableHelper.FixGUIWidthMismatch(prop.GetPropertyType(), materialEditor); RevertableHelper.FixGUIWidthMismatch(prop.GetPropertyType(), materialEditor);
if (propStaticData.isAdvancedHeaderProperty) if (propStaticData.isAdvancedHeaderProperty)
@ -229,5 +238,19 @@ namespace LWGUI
if (!hasChange) hasChange = true; 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 // Per Shader > Per Material > Per Inspector
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using LWGUI.PerformanceMonitor;
namespace LWGUI namespace LWGUI
{ {
public class PersetDynamicData public class PresetDynamicData
{ {
public LwguiShaderPropertyPreset.Preset preset; public LwguiShaderPropertyPreset.Preset preset;
public MaterialProperty property; public MaterialProperty property;
public PersetDynamicData(LwguiShaderPropertyPreset.Preset preset, MaterialProperty property) public PresetDynamicData(LwguiShaderPropertyPreset.Preset preset, MaterialProperty property)
{ {
this.preset = preset; this.preset = preset;
this.property = property; this.property = property;
} }
} }
public class PropertyDynamicData public class PropertyDynamicData
{ {
public MaterialProperty property; public MaterialProperty property;
public MaterialProperty defualtProperty; // Default values may be overridden by Preset public MaterialProperty defaultProperty; // Default values may be overridden by Preset
public string defaultValueDescription = string.Empty; // Description of the default values used in Tooltip 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 hasModified = false; // Are properties modified in the material?
public bool hasChildrenModified = false; // Are Children 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 hasRevertChanged = false; // Used to call property EndChangeCheck()
public bool isShowing = true; // ShowIf() result public bool isShowing = true; // ShowIf() result
public bool isAnimated = false; // Material Parameter Animation preview in Timeline is activated public bool isActive = true; // ActiveIf() result
} public bool isAnimated = false; // Material Parameter Animation preview in Timeline is activated
}
/// <summary> /// <summary>
/// Contains Metadata that may be different for each Material. /// Contains Metadata that may be different for each Material.
/// </summary> /// </summary>
public class PerMaterialData public class PerMaterialData
{ {
public Dictionary<string, PropertyDynamicData> propDynamicDatas = new Dictionary<string, PropertyDynamicData>(); public bool forceInit = true;
public MaterialProperty[] props = null; public Material material = null;
public Material material = null; public MaterialProperty[] props = null;
public Material defaultMaterialWithPresetOverride = null; public Dictionary<string, PropertyDynamicData> propDynamicDatas = new Dictionary<string, PropertyDynamicData>();
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;
public PerMaterialData(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData) public Material defaultMaterialWithPresetOverride = null;
{ public MaterialProperty[] defaultPropertiesWithPresetOverride = null;
Init(shader, material, editor, props, perShaderData); 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) // Performance Monitor
{ public List<string> activeKeywords = null;
forceInit = false; public List<ShaderPerfData> shaderPerfDatas = null;
// Reset Datas public PerMaterialData(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData)
this.props = props; {
this.material = material; Init(shader, material, editor, props, perShaderData);
activePresetDatas.Clear(); }
propDynamicDatas.Clear();
modifiedCount = 0;
// Get active presets // Signature of the cached default material: which presets and which base material were used to build it.
foreach (var prop in props) // Compared each Init() to auto-detect preset switching, material.parent changes, etc.
{ private List<LwguiShaderPropertyPreset.Preset> _cachedActivePresets;
var propStaticData = perShaderData.propStaticDatas[prop.name]; private Material _cachedDefaultMaterialSource;
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));
}
}
{ private bool IsDefaultMaterialCacheValid(Material baseMaterial)
// Apply presets to default material {
defaultMaterialWithPresetOverride = UnityEngine.Object.Instantiate( 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 #if UNITY_2022_1_OR_NEWER
material.parent material.parent
? material.parent ? material.parent
: :
#endif #endif
perShaderData.defaultMaterial perShaderData.defaultMaterial;
);
foreach (var activePresetData in activePresetDatas) var cacheWasValid = IsDefaultMaterialCacheValid(baseMaterial);
activePresetData.preset.ApplyToDefaultMaterial(defaultMaterialWithPresetOverride); if (!cacheWasValid)
{
defaultMaterialWithPresetOverride = UnityEngine.Object.Instantiate(baseMaterial);
defaultPropertiesWithPresetOverride = MaterialEditor.GetMaterialProperties(new[] { defaultMaterialWithPresetOverride }); foreach (var activePresetData in activePresetDatas)
Debug.Assert(defaultPropertiesWithPresetOverride.Length == props.Length); activePresetData.preset.ApplyToDefaultMaterial(defaultMaterialWithPresetOverride);
// Init propDynamicDatas defaultPropertiesWithPresetOverride = MaterialEditor.GetMaterialProperties(new Object[] { defaultMaterialWithPresetOverride });
for (int i = 0; i < props.Length; i++) StoreDefaultMaterialCacheSignature(baseMaterial);
{ }
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
});
}
// Collect modification Debug.Assert(defaultPropertiesWithPresetOverride.Length == props.Length);
foreach (var prop in props)
{
var propStaticData = perShaderData.propStaticDatas[prop.name];
var propDynamicData = propDynamicDatas[prop.name];
// Extra Prop hasModified // Init propDynamicDatas
foreach (var extraPropName in propStaticData.extraPropNames) for (int i = 0; i < props.Length; i++)
propDynamicData.hasModified |= propDynamicDatas[extraPropName].hasModified; {
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 // Collect modification
if (propDynamicData.hasModified) foreach (var prop in props)
{ {
var parentPropData = propStaticData.parent; var propStaticData = perShaderData.propStaticDatas[prop.name];
if (parentPropData != null) var propDynamicData = propDynamicDatas[prop.name];
{
propDynamicDatas[parentPropData.name].hasChildrenModified = true;
if (parentPropData.parent != null)
propDynamicDatas[parentPropData.parent.name].hasChildrenModified = true;
}
}
}
}
// Store "Show Modified Props Only" Caches // Extra Prop hasModified
{ foreach (var extraPropName in propStaticData.extraPropNames)
if (perShaderData.displayModeData.showOnlyModifiedGroups || perShaderData.displayModeData.showOnlyModifiedProperties) propDynamicData.hasModified |= propDynamicDatas[extraPropName].hasModified;
{
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;
}
foreach (var prop in props) // Override parent hasChildrenModified
{ if (propDynamicData.hasModified)
var propStaticData = perShaderData.propStaticDatas[prop.name]; {
var propDynamicData = propDynamicDatas[prop.name]; 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 // Store "Show Modified Props Only" Caches
propStaticData.baseDrawers?.ForEach(propertyDrawer => propertyDrawer.GetDefaultValueDescription(shader, prop, propDynamicData.defualtProperty, perShaderData, this)); {
if (string.IsNullOrEmpty(propDynamicData.defaultValueDescription)) if (perShaderData.displayModeData.showOnlyModifiedGroups || perShaderData.displayModeData.showOnlyModifiedProperties)
propDynamicData.defaultValueDescription = RevertableHelper.GetPropertyDefaultValueText(propDynamicData.defualtProperty); {
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 foreach (var prop in props)
ShowIfDecorator.GetShowIfResult(propStaticData, propDynamicData, this); {
} var propStaticData = perShaderData.propStaticDatas[prop.name];
} var propDynamicData = propDynamicDatas[prop.name];
public void Update(Shader shader, Material material, MaterialEditor editor, MaterialProperty[] props, PerShaderData perShaderData) // Get default value descriptions
{ propStaticData.baseDrawers?.ForEach(propertyDrawer => propertyDrawer.GetDefaultValueDescription(shader, prop, propDynamicData.defaultProperty, perShaderData, this));
if (forceInit) if (string.IsNullOrEmpty(propDynamicData.defaultValueDescription))
{ propDynamicData.defaultValueDescription = RevertableHelper.GetPropertyDefaultValueText(propDynamicData.defaultProperty);
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) // Get ShowIf() results
{ ShowIfDecorator.GetShowIfResult(propStaticData, propDynamicData, this);
if (!string.IsNullOrEmpty(propName))
{ // Get ActiveIf() results
GUI.changed |= propDynamicDatas[propName].hasRevertChanged; if (propStaticData.activeIfDatas.Count > 0)
propDynamicDatas[propName].hasRevertChanged = false; propDynamicData.isActive = ShowIfDecorator.GetShowIfResultFromMaterial(propStaticData.activeIfDatas, this.material);
} }
return EditorGUI.EndChangeCheck();
}
public PropertyDynamicData GetPropDynamicData(string propName) // Get Shader Perf Stats
{ if (ToolbarHelper.IsDisplayShaderPerfStatsEnabled(perShaderData.shaderUID))
propDynamicDatas.TryGetValue(propName, out var propDynamicData); {
return propDynamicData; 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 // Per Shader > Per Material > Per Inspector
using System; using System;
@ -9,361 +9,369 @@ using UnityEngine;
namespace LWGUI 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 public class DisplayModeData
{ {
Auto = 0, // Search by group first, and search by property when there are no results public bool showAllAdvancedProperties;
Property = 1, // Search by property public bool showAllHiddenProperties;
Group = 2, // Search by group public bool showOnlyModifiedProperties;
Num = 3 public bool showOnlyModifiedGroups;
}
public class DisplayModeData public int advancedCount;
{ public int hiddenCount;
public bool showAllAdvancedProperties;
public bool showAllHiddenProperties;
public bool showOnlyModifiedProperties;
public bool showOnlyModifiedGroups;
public int advancedCount; public bool IsDefaultDisplayMode()
public int hiddenCount; {
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> // Structure
/// The static metadata of Material Property is only related to Shader. public string groupName = string.Empty; // [Group(groupName)] / [Sub(groupName)] / [Advanced(groupName)]
/// </summary> public bool isMain = false; // [Group]
public partial class PropertyStaticData public bool isAdvanced = false; // [Advanced]
{ public bool isAdvancedHeader = false; // the first [Advanced] in the same group
public string name = string.Empty; public bool isAdvancedHeaderProperty = false;
public string displayName = string.Empty; // Decoded displayName (Helpbox and Tooltip are encoded in displayName) public string advancedHeaderString = string.Empty;
public PropertyStaticData parent = null;
public List<PropertyStaticData> children = new List<PropertyStaticData>();
// Structure // Visibility
public string groupName = string.Empty; // [Group(groupName)] / [Sub(groupName)] / [Advanced(groupName)] public bool isSearchMatched = true; // Search filter result
public bool isMain = false; // [Group] public bool isExpanding = false; // Children are displayed only when expanded
public bool isAdvanced = false; // [Advanced] public bool isReadOnly = false; // [ReadOnly]
public bool isAdvancedHeader = false; // the first [Advanced] in the same group public bool isHidden = false; // [Hidden]
public bool isAdvancedHeaderProperty = false; public List<ShowIfDecorator.ShowIfData> showIfDatas = new List<ShowIfDecorator.ShowIfData>(); // [ShowIf()]
public string advancedHeaderString = string.Empty; public List<ShowIfDecorator.ShowIfData> activeIfDatas = new List<ShowIfDecorator.ShowIfData>(); // [ActiveIf()]
public PropertyStaticData parent = null; public string conditionalDisplayKeyword = string.Empty; // [Group(groupName_conditionalDisplayKeyword)]
public List<PropertyStaticData> children = new List<PropertyStaticData>();
// Visibility // Drawers
public bool isSearchMatched = true; // Search filter result public IPresetDrawer presetDrawer = null;
public bool isExpanding = false; // Children are displayed only when expanded public List<IBaseDrawer> baseDrawers = null;
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 // Metadata
public IPresetDrawer presetDrawer = null; public List<string> extraPropNames = new List<string>(); // Other Props that have been associated
public List<IBaseDrawer> baseDrawers = null; public string helpboxMessages = string.Empty;
public string tooltipMessages = string.Empty;
public LwguiShaderPropertyPreset propertyPresetAsset = null; // The Referenced Preset Asset
// Metadata public void AddExtraProperty(string propName)
public List<string> extraPropNames = new List<string>(); // Other Props that have been associated {
public string helpboxMessages = string.Empty; if (!extraPropNames.Contains(propName)) extraPropNames.Add(propName);
public string tooltipMessages = string.Empty; }
public LwguiShaderPropertyPreset propertyPresetAsset = null; // The Referenced Preset Asset }
public void AddExtraProperty(string propName) /// <summary>
{ /// All Shader static metadata can be determined after Shader is compiled and will not change.
if (!extraPropNames.Contains(propName)) extraPropNames.Add(propName); /// </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> // UnityEngine.Object may be destroyed when loading new scene, so must manually check null reference
/// All Shader static metadata can be determined after Shader is compiled and will not change. private Material _defaultMaterial = null;
/// </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 public Material defaultMaterial
private Material _defaultMaterial = null; {
get
{
if (!_defaultMaterial && shader) _defaultMaterial = new Material(shader);
return _defaultMaterial;
}
}
public Material defaultMaterial public PerShaderData(Shader shader, MaterialProperty[] props)
{ {
get this.shader = shader;
{
if (!_defaultMaterial && shader) _defaultMaterial = new Material(shader); if (AssetDatabase.Contains(shader))
return _defaultMaterial; shaderUID = AssetDatabase.GetAssetPath(shader).Replace('/', '_').Replace('\\', '_');
} else
} shaderUID = shader.GetHashCode().ToString();
public PerShaderData(Shader shader, MaterialProperty[] props) // Get Property Static Data
{ foreach (var prop in props)
this.shader = shader; {
var propStaticData = new PropertyStaticData() { name = prop.name };
propStaticDatas[prop.name] = propStaticData;
// Get Property Static Data // Get Drawers and Build Drawer StaticMetaData
foreach (var prop in props) bool hasDecodedStaticMetaData = false;
{ {
var propStaticData = new PropertyStaticData() { name = prop.name }; var drawer = ReflectionHelper.GetPropertyDrawer(shader, prop, out var decoratorDrawers);
propStaticDatas[prop.name] = propStaticData;
// Get Drawers and Build Drawer StaticMetaData if (drawer is IPresetDrawer)
bool hasDecodedStaticMetaData = false; propStaticData.presetDrawer = drawer as IPresetDrawer;
{
var drawer = ReflectionHelper.GetPropertyDrawer(shader, prop, out var decoratorDrawers);
if (drawer is IPresetDrawer) var baseDrawer = drawer as IBaseDrawer;
propStaticData.presetDrawer = drawer as IPresetDrawer; if (baseDrawer != null)
{
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
hasDecodedStaticMetaData = true;
}
var baseDrawer = drawer as IBaseDrawer; decoratorDrawers?.ForEach(decoratorDrawer =>
if (baseDrawer != null) {
{ baseDrawer = decoratorDrawer as IBaseDrawer;
propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer }; if (baseDrawer != null)
baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData); {
hasDecodedStaticMetaData = true; if (propStaticData.baseDrawers == null)
} propStaticData.baseDrawers = new List<IBaseDrawer>() { baseDrawer };
else
propStaticData.baseDrawers.Add(baseDrawer);
decoratorDrawers?.ForEach(decoratorDrawer => baseDrawer.BuildStaticMetaData(shader, prop, props, propStaticData);
{ }
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); if (!hasDecodedStaticMetaData)
} DecodeMetaDataFromDisplayName(prop, propStaticData);
}); }
}
if (!hasDecodedStaticMetaData) // Check Data
DecodeMetaDataFromDisplayName(prop, propStaticData); foreach (var prop in props)
} {
var propStaticData = propStaticDatas[prop.name];
propStaticData.extraPropNames.RemoveAll((extraPropName =>
string.IsNullOrEmpty(extraPropName) || !propStaticDatas.ContainsKey(extraPropName)));
}
// Check Data // Build Property Structure
foreach (var prop in props) {
{ var groupToMainPropertyDic = new Dictionary<string, MaterialProperty>();
var propStaticData = propStaticDatas[prop.name];
propStaticData.extraPropNames.RemoveAll((extraPropName =>
string.IsNullOrEmpty(extraPropName) || !propStaticDatas.ContainsKey(extraPropName)));
}
// Build Property Structure // Collection Groups
{ foreach (var prop in props)
var groupToMainPropertyDic = new Dictionary<string, MaterialProperty>(); {
var propData = propStaticDatas[prop.name];
if (propData.isMain
&& !string.IsNullOrEmpty(propData.groupName)
&& !groupToMainPropertyDic.ContainsKey(propData.groupName))
groupToMainPropertyDic.Add(propData.groupName, prop);
}
// Collection Groups // Register SubProps
foreach (var prop in props) foreach (var prop in props)
{ {
var propData = propStaticDatas[prop.name]; var propData = propStaticDatas[prop.name];
if (propData.isMain if (!propData.isMain
&& !string.IsNullOrEmpty(propData.groupName) && !string.IsNullOrEmpty(propData.groupName))
&& !groupToMainPropertyDic.ContainsKey(propData.groupName)) {
groupToMainPropertyDic.Add(propData.groupName, prop); 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 // Split groupName and conditional display keyword
foreach (var prop in props) if (propData.groupName.Length > groupName.Length)
{ {
var propData = propStaticDatas[prop.name]; propData.conditionalDisplayKeyword =
if (!propData.isMain propData.groupName.Substring(groupName.Length, propData.groupName.Length - groupName.Length).ToUpper();
&& !string.IsNullOrEmpty(propData.groupName)) propData.groupName = groupName;
{ }
foreach (var groupName in groupToMainPropertyDic.Keys) break;
{ }
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 // Build Display Mode Data
if (propData.groupName.Length > groupName.Length) {
{ PropertyStaticData lastPropData = null;
propData.conditionalDisplayKeyword = PropertyStaticData lastHeaderPropData = null;
propData.groupName.Substring(groupName.Length, propData.groupName.Length - groupName.Length).ToUpper(); for (int i = 0; i < props.Length; i++)
propData.groupName = groupName; {
} var prop = props[i];
break; var propStaticData = propStaticDatas[prop.name];
}
}
}
}
}
// Build Display Mode Data // Counting
{ if (propStaticData.isHidden
PropertyStaticData lastPropData = null; || (propStaticData.parent != null
PropertyStaticData lastHeaderPropData = null; && (propStaticData.parent.isHidden
for (int i = 0; i < props.Length; i++) || (propStaticData.parent.parent != null && propStaticData.parent.parent.isHidden))))
{ displayModeData.hiddenCount++;
var prop = props[i]; if (propStaticData.isAdvanced
var propStaticData = propStaticDatas[prop.name]; || (propStaticData.parent != null
&& (propStaticData.parent.isAdvanced
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isAdvanced))))
displayModeData.advancedCount++;
// Counting // Build Advanced Structure
if (propStaticData.isHidden if (propStaticData.isAdvanced)
|| (propStaticData.parent != null {
&& (propStaticData.parent.isHidden // If it is the first prop in a Advanced Block, set to Header
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isHidden)))) if (lastPropData == null
displayModeData.hiddenCount++; || !lastPropData.isAdvanced
if (propStaticData.isAdvanced || propStaticData.isAdvancedHeaderProperty
|| (propStaticData.parent != null || (!string.IsNullOrEmpty(propStaticData.advancedHeaderString)
&& (propStaticData.parent.isAdvanced && propStaticData.advancedHeaderString != lastPropData.advancedHeaderString))
|| (propStaticData.parent.parent != null && propStaticData.parent.parent.isAdvanced)))) {
displayModeData.advancedCount++; propStaticData.isAdvancedHeader = true;
lastHeaderPropData = propStaticData;
}
// Else set to child
else
{
propStaticData.parent = lastHeaderPropData;
lastHeaderPropData.children.Add(propStaticData);
}
}
// Build Advanced Structure lastPropData = propStaticData;
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; public PropertyStaticData GetPropStaticData(string propName)
} {
} propStaticDatas.TryGetValue(propName, out var propStaticData);
} return propStaticData;
}
public PropertyStaticData GetPropStaticData(string propName) private static readonly string _tooltipSplitter = "#";
{
propStaticDatas.TryGetValue(propName, out var propStaticData);
return propStaticData;
}
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 helpboxes = prop.displayName.Split(new String[] { _helpboxSplitter }, StringSplitOptions.None);
{ if (helpboxes.Length > 1)
var tooltips = prop.displayName.Split(new String[] { _tooltipSplitter }, StringSplitOptions.None); {
if (tooltips.Length > 1) for (int i = 1; i <= helpboxes.Length - 1; i++)
{ {
for (int i = 1; i <= tooltips.Length - 1; i++) var str = helpboxes[i];
{ var tooltipIndex = helpboxes[i].IndexOf(_tooltipSplitter, StringComparison.Ordinal);
var str = tooltips[i]; if (tooltipIndex > 0)
var helpboxIndex = tooltips[i].IndexOf(_helpboxSplitter, StringComparison.Ordinal); str = tooltips[i].Substring(0, tooltipIndex);
if (helpboxIndex > 0) propStaticData.helpboxMessages += str + "\n";
str = tooltips[i].Substring(0, helpboxIndex); }
propStaticData.tooltipMessages += str + "\n"; }
}
}
var helpboxes = prop.displayName.Split(new String[] { _helpboxSplitter }, StringSplitOptions.None); if (propStaticData.helpboxMessages.EndsWith("\n"))
if (helpboxes.Length > 1) propStaticData.helpboxMessages = propStaticData.helpboxMessages.Substring(0, propStaticData.helpboxMessages.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.displayName = prop.displayName.Split(new String[] { _tooltipSplitter, _helpboxSplitter }, StringSplitOptions.None)[0];
propStaticData.helpboxMessages = propStaticData.helpboxMessages.Substring(0, propStaticData.helpboxMessages.Length - 1); }
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() // The First Search
{ foreach (var propStaticDataKWPair in propStaticDatas)
var isSearchStringEmpty = string.IsNullOrEmpty(searchString); {
var searchStringLower = searchString.ToLower(); propStaticDataKWPair.Value.isSearchMatched = isSearchStringEmpty
var searchKeywords = searchStringLower.Split(' ', ',', ';', '|', '', ''); // Some possible separators ? true
: IsWholeWordMatch(propStaticDataKWPair.Value.displayName, propStaticDataKWPair.Value.name, searchKeywords);
}
// The First Search // Further adjust visibility
foreach (var propStaticDataKWPair in propStaticDatas) if (!isSearchStringEmpty)
{ {
propStaticDataKWPair.Value.isSearchMatched = isSearchStringEmpty var searchModeTemp = searchMode;
? true // Auto: search by group first, and search by property when there are no results
: IsWholeWordMatch(propStaticDataKWPair.Value.displayName, propStaticDataKWPair.Value.name, searchKeywords); 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 // search by property
if (!isSearchStringEmpty) if (searchModeTemp == SearchMode.Property)
{ {
var searchModeTemp = searchMode; // when a SubProp is displayed, the MainProp is also displayed
// Auto: search by group first, and search by property when there are no results foreach (var propStaticDataKWPair in propStaticDatas)
if (searchModeTemp == SearchMode.Auto) {
{ var propStaticData = propStaticDataKWPair.Value;
// if has no group if (propStaticData.isMain
if (!propStaticDatas.Any((propStaticDataKWPair => propStaticDataKWPair.Value.isSearchMatched && propStaticDataKWPair.Value.isMain))) && propStaticData.children.Any((childPropStaticData => propStaticDatas[childPropStaticData.name].isSearchMatched)))
searchModeTemp = SearchMode.Property; propStaticDataKWPair.Value.isSearchMatched = true;
else }
searchModeTemp = SearchMode.Group; }
} // 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 private static bool IsWholeWordMatch(string displayName, string propertyName, string[] searchingKeywords)
if (searchModeTemp == SearchMode.Property) {
{ bool contains = true;
// when a SubProp is displayed, the MainProp is also displayed displayName = displayName.ToLower();
foreach (var propStaticDataKWPair in propStaticDatas) var name = propertyName.ToLower();
{
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) foreach (var keyword in searchingKeywords)
{ {
bool contains = true; var isMatch = false;
displayName = displayName.ToLower(); isMatch |= displayName.Contains(keyword);
var name = propertyName.ToLower(); isMatch |= name.Contains(keyword);
contains &= isMatch;
}
return contains;
}
foreach (var keyword in searchingKeywords) public void ToggleShowAllAdvancedProperties()
{ {
var isMatch = false; foreach (var propStaticDataKWPair in propStaticDatas)
isMatch |= displayName.Contains(keyword); {
isMatch |= name.Contains(keyword); if (propStaticDataKWPair.Value.isAdvancedHeader)
contains &= isMatch; propStaticDataKWPair.Value.isExpanding = displayModeData.showAllAdvancedProperties;
} }
return contains; }
} }
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 fileFormatVersion: 2
guid: 130ac8fd23814be49854157d7be9194e guid: 994434336edc8a8469c9afcbb92c5936
ShaderIncludeImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: 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 fileFormatVersion: 2
guid: b7661dfeec673f340b66ac769fa42d18 guid: 8719df0f3ab95f449b01b7c46d9bf80d
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 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 System;
using UnityEngine; using UnityEngine;

View File

@ -1,4 +1,5 @@
// Copyright (c) Jason Ma // Copyright (c) Jason Ma
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -7,44 +8,186 @@ using LWGUI.LwguiGradientEditor;
using LWGUI.Runtime.LwguiGradient; using LWGUI.Runtime.LwguiGradient;
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
using UnityEngine.Serialization;
namespace LWGUI namespace LWGUI
{ {
[CreateAssetMenu(fileName = "LWGUI_RampAtlas.asset", menuName = "LWGUI/Ramp Atlas", order = 84)] public interface IRamp
public class LwguiRampAtlas : ScriptableObject
{ {
[Serializable] string Name { get; set; }
public class Ramp 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"; var gradients = GetGradients();
public LwguiGradient gradient = LwguiGradient.white; for (int i = 0; i < gradients.Length; i++)
public ColorSpace colorSpace = ColorSpace.Gamma; {
public LwguiGradient.ChannelMask channelMask = LwguiGradient.ChannelMask.All; gradients[i]?.GetPixels(ref outputPixels, ref currentIndex, width, 1, channelMask);
public LwguiGradient.GradientTimeRange timeRange = LwguiGradient.GradientTimeRange.One; }
} }
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 const string RampAtlasTextureExtensionName = "tga";
public int rampAtlasWidth = 256;
public int rampAtlasHeight = 4;
public bool rampAtlasSRGB = true;
public int rampAtlasWidth = 256; private string _rampAtlasSOPath = string.Empty;
public int rampAtlasHeight = 4; private string _rampAtlasTexturePath = string.Empty;
public bool rampAtlasSRGB = true;
[SerializeField] private bool _saveTextureToggle;
[SerializeField] private List<Ramp> _ramps = new List<Ramp>();
[NonSerialized] public Texture2D rampAtlasTexture = null; [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; public virtual IRamp AddRamp(IRamp srcRamp = null)
private string _rampAtlasSOPath = string.Empty; {
private string _rampAtlasTexturePath = string.Empty; _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() public void InitData()
{ {
@ -59,12 +202,16 @@ namespace LWGUI
{ {
if (!AssetDatabase.Contains(this)) if (!AssetDatabase.Contains(this))
return false; return false;
// Try to load // Try to load
rampAtlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(_rampAtlasTexturePath); rampAtlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(_rampAtlasTexturePath);
// Create // Create
if (!rampAtlasTexture) if (!rampAtlasTexture
|| rampAtlasTexture.width != rampAtlasWidth
|| rampAtlasTexture.height != rampAtlasHeight
|| rampAtlasTexture.isDataSRGB != rampAtlasSRGB
)
{ {
CreateRampAtlasTexture(); CreateRampAtlasTexture();
rampAtlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(_rampAtlasTexturePath); rampAtlasTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(_rampAtlasTexturePath);
@ -72,69 +219,51 @@ namespace LWGUI
if (!rampAtlasTexture) 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; return false;
} }
CheckRampRowCount();
return true; 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; 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; 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() public void CreateRampAtlasTexture()
{ {
var rampAtlasTexture = new Texture2D(rampAtlasWidth, rampAtlasHeight, TextureFormat.RGBA32, false, !rampAtlasSRGB); var rampAtlas = new Texture2D(rampAtlasWidth, rampAtlasHeight, TextureFormat.RGBA32, false, !rampAtlasSRGB);
rampAtlasTexture.SetPixels(GetPixels()); rampAtlas.SetPixels(GetPixels());
rampAtlasTexture.wrapMode = TextureWrapMode.Clamp; rampAtlas.wrapMode = TextureWrapMode.Clamp;
rampAtlasTexture.name = Path.GetFileName(_rampAtlasTexturePath); rampAtlas.name = Path.GetFileName(_rampAtlasTexturePath);
rampAtlasTexture.Apply(); rampAtlas.Apply();
SaveTexture(rampAtlasTexture); SaveTexture(rampAtlas, checkoutAndForceWrite:true);
AssetDatabase.ImportAsset(_rampAtlasTexturePath); AssetDatabase.ImportAsset(_rampAtlasTexturePath);
RampHelper.SetRampTextureImporter(_rampAtlasTexturePath, true, !rampAtlasSRGB, EditorJsonUtility.ToJson(this)); 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; targetRelativePath ??= _rampAtlasTexturePath;
rampAtlasTexture ??= this.rampAtlasTexture; rampAtlas ??= rampAtlasTexture;
if (!rampAtlasTexture || string.IsNullOrEmpty(targetRelativePath)) if (!rampAtlas || string.IsNullOrEmpty(targetRelativePath))
return; return;
var absPath = Helper.ProjectPath + targetRelativePath; CheckRampRowCount();
var absPath = IOHelper.GetAbsPath(targetRelativePath);
if (File.Exists(absPath)) if (File.Exists(absPath))
{ {
var existRampTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(targetRelativePath); var existRampTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(targetRelativePath);
@ -144,7 +273,7 @@ namespace LWGUI
{ {
if (!VersionControlHelper.Checkout(targetRelativePath)) 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; return;
} }
} }
@ -157,10 +286,10 @@ namespace LWGUI
try try
{ {
File.WriteAllBytes(absPath, rampAtlasTexture.EncodeToTGA()); File.WriteAllBytes(absPath, rampAtlas.EncodeToTGA());
SaveTextureUserData(targetRelativePath); 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) catch (Exception e)
{ {
@ -191,58 +320,26 @@ namespace LWGUI
{ {
if (!rampAtlasTexture) if (!rampAtlasTexture)
return; return;
LwguiGradientWindow.RegisterSerializedObjectUndo(this); LwguiGradientWindow.RegisterSerializedObjectUndo(this);
rampAtlasTexture.Reinitialize(rampAtlasWidth, rampAtlasHeight); rampAtlasTexture.Reinitialize(rampAtlasWidth, rampAtlasHeight);
rampAtlasTexture.SetPixels(GetPixels()); rampAtlasTexture.SetPixels(GetPixels());
rampAtlasTexture.Apply(); rampAtlasTexture.Apply();
} }
public void DiscardChanges() public void DiscardChanges()
{ {
var importer = AssetImporter.GetAtPath(_rampAtlasTexturePath); var importer = AssetImporter.GetAtPath(_rampAtlasTexturePath);
if (!importer) if (!importer)
return; return;
EditorJsonUtility.FromJsonOverwrite(importer.userData, this); EditorJsonUtility.FromJsonOverwrite(importer.userData, this);
InitData(); InitData();
AssetDatabase.ImportAsset(_rampAtlasTexturePath, ImportAssetOptions.ForceUpdate); AssetDatabase.ImportAsset(_rampAtlasTexturePath, ImportAssetOptions.ForceUpdate);
LoadTexture(); LoadTexture();
EditorUtility.ClearDirty(this); 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() private void OnEnable()
{ {
InitData(); InitData();
@ -253,18 +350,21 @@ namespace LWGUI
{ {
// Skip at the end of compilation // Skip at the end of compilation
if (Event.current == null if (Event.current == null
// Skip when editing Text Field // Skip when editing Text Field
|| EditorGUIUtility.editingTextField) || EditorGUIUtility.editingTextField)
return; return;
InitData(); InitData();
if (!LoadTexture()) if (!LoadTexture())
return; return;
UpdateTexturePixels(); UpdateTexturePixels();
SaveTexture(); SaveTexture(checkoutAndForceWrite:_saveTextureToggle);
_saveTextureToggle = false;
} }
#region Static
public static Texture LoadRampAtlasTexture(LwguiRampAtlas rampAtlasSO) public static Texture LoadRampAtlasTexture(LwguiRampAtlas rampAtlasSO)
{ {
@ -272,11 +372,11 @@ namespace LWGUI
{ {
return null; return null;
} }
var soPath = Path.ChangeExtension(AssetDatabase.GetAssetPath(rampAtlasSO), RampAtlasTextureExtensionName); var soPath = Path.ChangeExtension(AssetDatabase.GetAssetPath(rampAtlasSO), RampAtlasTextureExtensionName);
return AssetDatabase.LoadAssetAtPath<Texture>(soPath); return AssetDatabase.LoadAssetAtPath<Texture>(soPath);
} }
public static LwguiRampAtlas LoadRampAtlasSO(Texture texture) public static LwguiRampAtlas LoadRampAtlasSO(Texture texture)
{ {
if (!texture || !AssetDatabase.Contains(texture)) if (!texture || !AssetDatabase.Contains(texture))
@ -287,17 +387,17 @@ namespace LWGUI
var soPath = Path.ChangeExtension(AssetDatabase.GetAssetPath(texture), RampAtlasSOExtensionName); var soPath = Path.ChangeExtension(AssetDatabase.GetAssetPath(texture), RampAtlasSOExtensionName);
return AssetDatabase.LoadAssetAtPath<LwguiRampAtlas>(soPath); 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) if (rampAtlasProp == null || metaDatas == null)
return null; return null;
var shader = metaDatas.GetShader(); var shader = metaDatas.GetShader();
// Get default ramps // Get default ramps
RampAtlasDrawer targetRampAtlasDrawer = null; 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. // Unity Bug: The cache of MaterialPropertyHandler must be cleared first, otherwise the default value cannot be obtained correctly.
ReflectionHelper.InvalidatePropertyCache(shader); ReflectionHelper.InvalidatePropertyCache(shader);
for (int i = 0; i < metaDatas.perMaterialData.defaultPropertiesWithPresetOverride.Length; i++) 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) if (drawer is RampAtlasDrawer rampAtlasDrawer && prop.name == rampAtlasProp.name)
targetRampAtlasDrawer = rampAtlasDrawer; targetRampAtlasDrawer = rampAtlasDrawer;
if (drawer is RampAtlasIndexerDrawer rampAtlasIndexerDrawer && rampAtlasIndexerDrawer.rampAtlasPropName == rampAtlasProp.name) if (drawer is RampAtlasIndexerDrawer rampAtlasIndexerDrawer && rampAtlasIndexerDrawer.rampAtlasPropName == rampAtlasProp.name)
defaultRampAtlasIndexerDrawers.Add(((int)prop.GetNumericValue(), rampAtlasIndexerDrawer)); defaultRampAtlasIndexerDrawers.Add(((int)prop.GetNumericValue(), rampAtlasIndexerDrawer));
} }
if (targetRampAtlasDrawer == null) 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; return null;
} }
// Init Ramp Atlas
var newRampAtlasSO = ScriptableObject.CreateInstance<LwguiRampAtlas>();
newRampAtlasSO.name = targetRampAtlasDrawer.defaultFileName; newRampAtlasSO.name = targetRampAtlasDrawer.defaultFileName;
newRampAtlasSO.rampAtlasWidth = targetRampAtlasDrawer.defaultAtlasWidth; newRampAtlasSO.rampAtlasWidth = targetRampAtlasDrawer.defaultAtlasWidth;
newRampAtlasSO.rampAtlasHeight = targetRampAtlasDrawer.defaultAtlasHeight; newRampAtlasSO.rampAtlasHeight = targetRampAtlasDrawer.defaultAtlasHeight;
@ -335,36 +441,50 @@ namespace LWGUI
var maxIndex = defaultRampAtlasIndexerDrawers.Max((tuple => tuple.defaultIndex)); var maxIndex = defaultRampAtlasIndexerDrawers.Max((tuple => tuple.defaultIndex));
for (int i = 0; i < maxIndex + 1; i++) for (int i = 0; i < maxIndex + 1; i++)
{ {
newRampAtlasSO.ramps.Add(new LwguiRampAtlas.Ramp()); newRampAtlasSO.AddRamp();
if (newRampAtlasSO.ramps.Count >= newRampAtlasSO.rampAtlasHeight) if (newRampAtlasSO.TotalRowCount > newRampAtlasSO.rampAtlasHeight)
newRampAtlasSO.rampAtlasHeight *= 2; newRampAtlasSO.rampAtlasHeight *= 2;
} }
// Set Ramps Default Value // Set Ramps Default Value
for (int i = 0; i < defaultRampAtlasIndexerDrawers.Count; i++) for (int i = 0; i < defaultRampAtlasIndexerDrawers.Count; i++)
{ {
var defaultRampAtlasIndexerDrawer = defaultRampAtlasIndexerDrawers[i]; var defaultRampAtlasIndexerDrawer = defaultRampAtlasIndexerDrawers[i];
var ramp = newRampAtlasSO.ramps[defaultRampAtlasIndexerDrawer.defaultIndex]; var ramp = newRampAtlasSO.GetRamp(defaultRampAtlasIndexerDrawer.defaultIndex);
var drawer = defaultRampAtlasIndexerDrawer.indexerDrawer; var drawer = defaultRampAtlasIndexerDrawer.indexerDrawer;
ramp.name = drawer.defaultRampName; ramp.Name = drawer.defaultRampName;
ramp.colorSpace = drawer.colorSpace; ramp.ColorSpace = drawer.colorSpace;
ramp.channelMask = drawer.viewChannelMask; ramp.ChannelMask = drawer.viewChannelMask;
ramp.timeRange = drawer.timeRange; ramp.TimeRange = drawer.timeRange;
} }
} }
return SaveRampAtlasSOToAsset(newRampAtlasSO, targetRampAtlasDrawer.rootPath, targetRampAtlasDrawer.defaultFileName); return SaveRampAtlasSOToAsset(newRampAtlasSO, targetRampAtlasDrawer.rootPath, targetRampAtlasDrawer.defaultFileName);
} }
public static LwguiRampAtlas CloneRampAtlasSO(LwguiRampAtlas rampAtlasSO) public static LwguiRampAtlas CloneRampAtlasSO(LwguiRampAtlas rampAtlasSO, Type targetType = null)
{ {
if (!rampAtlasSO) if (!rampAtlasSO)
return null; return null;
var newRampAtlasSO = Instantiate(rampAtlasSO);
var rootPath = Path.GetDirectoryName(rampAtlasSO._rampAtlasSOPath); var rootPath = Path.GetDirectoryName(rampAtlasSO._rampAtlasSOPath);
var defaultFileName = Path.GetFileName(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)) if (SaveRampAtlasSOToAsset(newRampAtlasSO, rootPath, defaultFileName))
{ {
newRampAtlasSO.InitData(); newRampAtlasSO.InitData();
@ -374,7 +494,7 @@ namespace LWGUI
return null; return null;
} }
public static LwguiRampAtlas SaveRampAtlasSOToAsset(LwguiRampAtlas rampAtlasSO, string rootPath, string defaultFileName) public static LwguiRampAtlas SaveRampAtlasSOToAsset(LwguiRampAtlas rampAtlasSO, string rootPath, string defaultFileName)
{ {
if (!rampAtlasSO) if (!rampAtlasSO)
@ -387,15 +507,15 @@ namespace LWGUI
// TODO: Warning: // TODO: Warning:
// PropertiesGUI() is being called recursively. If you want to render the default gui for shader properties then call PropertiesDefaultGUI() instead // 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"); 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; break;
} }
else if (absPath != string.Empty) 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; if (!retry) break;
} }
else else
@ -403,7 +523,7 @@ namespace LWGUI
break; break;
} }
} }
if (!string.IsNullOrEmpty(createdFileRelativePath)) if (!string.IsNullOrEmpty(createdFileRelativePath))
{ {
AssetDatabase.CreateAsset(rampAtlasSO, createdFileRelativePath); AssetDatabase.CreateAsset(rampAtlasSO, createdFileRelativePath);
@ -411,8 +531,126 @@ namespace LWGUI
rampAtlasSO.LoadTexture(); rampAtlasSO.LoadTexture();
return rampAtlasSO; return rampAtlasSO;
} }
return null; 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) if (presets == null)
return null; return null;
if (index < presets.Count) if (index < presets.Count && index >= 0)
{
return presets[index]; 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() private void OnValidate()
{ {

View File

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

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 721b8d7aa96ce3243ad6e988775df5ee guid: 0087db252163ae340b69ec11fcd1e28a
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} 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 fileFormatVersion: 2
guid: 734309d3fd8d31246bde14f3278d4ee3 guid: d88e9b9124470694db30ecb3c7da26c2
ShaderIncludeImporter: folderAsset: yes
DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: 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