diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2ba4cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +* + +# Include Github info. +!.github +!.gitignore +!LICENSE +!README.md + +# Include everything in Assets/Nessie/ +!Assets/ +!Assets/Nessie +!Assets/Nessie/** diff --git a/Assets/Nessie/ASE.meta b/Assets/Nessie/ASE.meta new file mode 100644 index 0000000..6849cde --- /dev/null +++ b/Assets/Nessie/ASE.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a92233f8c11b3144ba2083a9f1b5258f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Nessie/ASE/Editor.meta b/Assets/Nessie/ASE/Editor.meta new file mode 100644 index 0000000..4241218 --- /dev/null +++ b/Assets/Nessie/ASE/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cec643db523ef1c45be9b5ed9e43e3dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Nessie/ASE/Editor/ColorDebug.cs b/Assets/Nessie/ASE/Editor/ColorDebug.cs new file mode 100644 index 0000000..30219d9 --- /dev/null +++ b/Assets/Nessie/ASE/Editor/ColorDebug.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; +using HarmonyLib; +using AmplifyShaderEditor; + +namespace Nessie.ASE +{ + [InitializeOnLoad] + internal class ColorDebug + { + #region Private Fields + + private const string m_harmonyID = "Nessie.ASE.ColorDebugPatch"; + + private static bool m_useHotkey = true; + + private static Texture2D m_previewPixel; + + #endregion Private Fields + + #region Public Fields + + public static bool UseHotkey { get { return m_useHotkey; } set { m_useHotkey = value; } } + + public static Texture2D PreviewPixel + { + get + { + if (m_previewPixel == null) + m_previewPixel = new Texture2D( + 1, 1, + UnityEngine.Experimental.Rendering.GraphicsFormat.R32G32B32A32_SFloat, + UnityEngine.Experimental.Rendering.TextureCreationFlags.None); + + return m_previewPixel; + } + } + + #endregion Public Fields + + #region Injection Methods + + static ColorDebug() + { + AssemblyReloadEvents.afterAssemblyReload += PostAssemblyReload; + } + + static void PostAssemblyReload() + { + Harmony harmony = new Harmony(m_harmonyID); + + harmony.PatchAll(); + } + + [HarmonyPatch(typeof(ParentGraph), nameof(ParentGraph.Draw))] + static class PatchDrawPreview + { + static void Postfix(DrawInfo drawInfo, List ___m_nodes) + { + if (drawInfo.CurrentEventType == EventType.Repaint && (!UseHotkey || Event.current.control)) + { + ParentNode node = GetActiveNode(drawInfo, ___m_nodes); + if (node) + ShowColorTooltip(drawInfo, node); + } + } + } + + [HarmonyPatch(typeof(AmplifyShaderEditorWindow), nameof(AmplifyShaderEditorWindow.OnDisable))] + static class PatchDisable + { + static void Postfix() + { + if (m_previewPixel == null) + UnityEngine.Object.DestroyImmediate(PreviewPixel); + } + } + + #endregion Injection Methods + + #region ASE Methods + + static void ShowColorTooltip(DrawInfo drawInfo, ParentNode node) + { + bool isTextureNode = node.GetType().IsSubclassOf(typeof(TexturePropertyNode)); + bool drawPreview = isTextureNode ? ((TexturePropertyNode)node).IsValid : GetPrivateField(node, "m_drawPreview"); + Rect previewRect = GetPrivateField(node, "m_previewRect"); + if (previewRect.Contains(drawInfo.MousePosition) && (node.ShowPreview || node.ContainerGraph.ParentWindow.GlobalPreview) && drawPreview) + { + Vector2 mousePos = drawInfo.MousePosition; + Vector2 mouseRel = mousePos - new Vector2(previewRect.x, previewRect.y); + + // Push a single pixel from a RT into a Tex2D. + RenderTexture previousRT = RenderTexture.active; + RenderTexture.active = node.PreviewTexture; + PreviewPixel.ReadPixels(new Rect(mouseRel.x, mouseRel.y, 1, 1), 0, 0, false); + RenderTexture.active = previousRT; + + // Each channel is represented with a full 32-bit float. + byte[] bytes = PreviewPixel.GetRawTextureData(); + float colorR = BitConverter.ToSingle(bytes, 0); + float colorG = BitConverter.ToSingle(bytes, 4); + float colorB = BitConverter.ToSingle(bytes, 8); + float colorA = BitConverter.ToSingle(bytes, 12); + + string[] colorValues = new string[] + { + $"R: {colorR.ToString()}", + $"G: {colorG.ToString()}", + $"B: {colorB.ToString()}", + $"A: {colorA.ToString()}" + }; + + Array previewChannels = GetPrivateField(typeof(ParentNode), node, "m_previewChannels"); + int activeChannels = GetActiveChannels(node); + int usedChannels = 0; + string labelText = ""; + for (int i = 0; i < activeChannels; i++) + { + if ((bool)previewChannels.GetValue(i)) + labelText += usedChannels++ >= 1 ? $"\n{colorValues[i]}" : colorValues[i]; + } + if (usedChannels == 0) return; + + GUIStyle labelStyle = new GUIStyle(UIUtils.Textfield); + labelStyle.padding.left += 2; + labelStyle.padding.right += 2; + labelStyle.padding.top += 2; + labelStyle.padding.bottom += 2; + Vector2 rectSize = labelStyle.CalcSize(new GUIContent(labelText)); + Rect labelRect = new Rect(mousePos.x + EditorGUIUtility.singleLineHeight / 1.5f, mousePos.y + EditorGUIUtility.singleLineHeight / 1.5f, rectSize.x + 1, rectSize.y); + + GUI.Label(labelRect, labelText, labelStyle); + } + } + + static ParentNode GetActiveNode(DrawInfo drawInfo, List nodes) + { + int nodeCount = nodes.Count; + for (int i = nodeCount - 1; i >= 0; i--) + { + ParentNode node = nodes[i]; + if (node.IsVisible && !node.IsMoving) + { + if (node.GlobalPosition.Contains(drawInfo.MousePosition)) return node; + } + } + + return null; + } + + static int GetActiveChannels(ParentNode node) + { + switch (node.OutputPorts[0].DataType) + { + case WirePortDataType.FLOAT: + return 1; + case WirePortDataType.FLOAT2: + return 2; + case WirePortDataType.COLOR: + case WirePortDataType.FLOAT4: + case WirePortDataType.SAMPLER1D: + case WirePortDataType.SAMPLER2D: + case WirePortDataType.SAMPLER3D: + case WirePortDataType.SAMPLERCUBE: + case WirePortDataType.SAMPLER2DARRAY: + return 4; + default: + return 3; + } + } + + #endregion ASE Methods + + #region Reflections + + static T GetPrivateField(object obj, string fieldName) + { + BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; + Type type = obj.GetType(); + FieldInfo finfo = type.GetField(fieldName, bindingFlags); + + return (T)finfo.GetValue(obj); + } + + static T GetPrivateField(Type type, object obj, string fieldName) + { + BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; + FieldInfo finfo = type.GetField(fieldName, bindingFlags); + + return (T)finfo.GetValue(obj); + } + + #endregion Reflections + } +} diff --git a/Assets/Nessie/ASE/Editor/ColorDebug.cs.meta b/Assets/Nessie/ASE/Editor/ColorDebug.cs.meta new file mode 100644 index 0000000..787ac66 --- /dev/null +++ b/Assets/Nessie/ASE/Editor/ColorDebug.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7619b1e0eb9cc1428e50ecf4a6755bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Nessie/ASE/Plugins.meta b/Assets/Nessie/ASE/Plugins.meta new file mode 100644 index 0000000..5e0e222 --- /dev/null +++ b/Assets/Nessie/ASE/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a78bf36e2046dc04388729b631e3100b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Nessie/ASE/Plugins/0Harmony.dll b/Assets/Nessie/ASE/Plugins/0Harmony.dll new file mode 100644 index 0000000..8b342da Binary files /dev/null and b/Assets/Nessie/ASE/Plugins/0Harmony.dll differ diff --git a/Assets/Nessie/ASE/Plugins/0Harmony.dll.meta b/Assets/Nessie/ASE/Plugins/0Harmony.dll.meta new file mode 100644 index 0000000..f000769 --- /dev/null +++ b/Assets/Nessie/ASE/Plugins/0Harmony.dll.meta @@ -0,0 +1,76 @@ +fileFormatVersion: 2 +guid: 591f88af67b6f494f8ebb2418cac6133 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f11bea3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Nestorboy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a4fcf6 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# ASE Color Debug +A custom Amplify Shader Editor feature which enables the ability to display the color values of a given pixel from a node preview. + +### Description +Whilst hovering your mouse over a node preview, you can hold down Control and a tiny display next to the mouse will show up. +The color debugger will only display the color channels which are enabled on the node preview. +Each channel is displayed and represented using a full 32-bit float, it also includes negatives, NaNs, and infinities. + +### Requirements +- [Amplify Shader Editor](http://amplify.pt/unity/amplify-shader-editor/) \ No newline at end of file