using System;
using System.Text;
using System.Linq;
using UnityEditor;
using UnityEngine;
#if ENABLE_URP_PACKAGE
using UnityEngine.Rendering.Universal;
#endif
#if ENABLE_HDRP_PACKAGE
using UnityEngine.Rendering.HighDefinition;
#endif
[CustomEditor(typeof(NoesisView))]
public class NoesisViewEditor : Editor
{
enum RenderMode
{
None,
Wireframe,
Batches,
Overdraw
};
private RenderMode ToRenderMode(Noesis.RenderFlags renderFlags)
{
if ((renderFlags & Noesis.RenderFlags.Wireframe) > 0)
{
return RenderMode.Wireframe;
}
else if ((renderFlags & Noesis.RenderFlags.ColorBatches) > 0)
{
return RenderMode.Batches;
}
else if ((renderFlags & Noesis.RenderFlags.Overdraw) > 0)
{
return RenderMode.Overdraw;
}
else
{
return RenderMode.None;
}
}
private Noesis.RenderFlags ToRenderFlags(RenderMode renderMode)
{
if (renderMode == RenderMode.Wireframe)
{
return Noesis.RenderFlags.Wireframe;
}
else if (renderMode == RenderMode.Batches)
{
return Noesis.RenderFlags.ColorBatches;
}
else if (renderMode == RenderMode.Overdraw)
{
return Noesis.RenderFlags.Overdraw;
}
return 0;
}
private static readonly string[] ActionNames =
{
"Up",
"Down",
"Left",
"Right",
"Next",
"Prev",
"Accept",
"Cancel",
"Menu",
"View",
"PageLeft",
"PageRight",
"PageUp",
"PageDown",
"Scroll",
"TrackedPosition",
"TrackedRotation",
"TrackedTrigger"
};
private bool _showMatchedActions = false;
private GUIStyle _matchedActionStyle;
private GUIStyle _unmatchedActionStyle;
#if ENABLE_HDRP_PACKAGE
// Only these injection points are relevant
private enum FilteredCustomPassInjectionPoint
{
BeforePostProcess = CustomPassInjectionPoint.BeforePostProcess,
AfterPostProcess = CustomPassInjectionPoint.AfterPostProcess
}
#endif
public override void OnInspectorGUI()
{
NoesisView view = target as NoesisView;
Noesis.RenderFlags renderFlags = view.RenderFlags;
// Register changes in the component so scene can be saved, and Undo is also enabled
Undo.RecordObject(view, "Noesis View");
EditorGUILayout.LabelField(new GUIContent("Render Mode", "Views attached to camera objects work in 'Camera Overlay' mode. 'Render Texture' mode is enabled in all other cases"),
new GUIContent(view.IsRenderToTexture() ? "Render Texture" : "Camera Overlay"), EditorStyles.popup);
if (view.IsRenderToTexture())
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(new GUIContent("Target Texture", "The texture to render this View into"));
view.Texture = (RenderTexture)EditorGUILayout.ObjectField(view.Texture, typeof(RenderTexture), false);
EditorGUILayout.EndHorizontal();
view.ContinuousRendering = EditorGUILayout.Toggle(new GUIContent("Continuous Rendering", "When continuous rendering is disabled, rendering only happens when UI changes." +
" For performance purposes and to save battery this is the default mode when rendering to texture.\n\nThis flag is not available in 'Camera Overlay' mode and instead the property " +
"NoesisView.NeedsRendering must be used with a manually repainted camera."), view.ContinuousRendering);
}
else
{
#if ENABLE_URP_PACKAGE
view.RenderPassEvent = (RenderPassEvent)EditorGUILayout.EnumPopup(new GUIContent("Injection Point",
"Controls when the UI render pass executes"), view.RenderPassEvent);
#endif
#if ENABLE_HDRP_PACKAGE
view.InjectionPoint = (CustomPassInjectionPoint)EditorGUILayout.EnumPopup(new GUIContent("Injection Point",
"Controls when the UI render pass executes"), (FilteredCustomPassInjectionPoint)view.InjectionPoint);
#endif
bool depthTesting = EditorGUILayout.Toggle(new GUIContent("World Space UI",
"World Space UI is positioned in the world among other objects in the Scene"
), (renderFlags & Noesis.RenderFlags.DepthTesting) > 0);
renderFlags = (renderFlags & ~Noesis.RenderFlags.DepthTesting) | (depthTesting ? Noesis.RenderFlags.DepthTesting : 0);
}
EditorGUILayout.Space();
view.Xaml = (NoesisXaml)EditorGUILayout.ObjectField(new GUIContent("XAML", "User interface definition XAML"), view.Xaml, typeof(NoesisXaml), false);
EditorGUILayout.BeginHorizontal();
GUIContent[] options = { new GUIContent("Low Quality"), new GUIContent("Medium Quality"), new GUIContent("High Quality"), new GUIContent("Custom Quality") };
int[] values = { 0, 1, 2, 3};
float inMaxError = view.TessellationMaxPixelError;
int value = inMaxError == 0.7f ? 0 : inMaxError == 0.4f ? 1 : inMaxError == 0.2f ? 2 : 3;
value = EditorGUILayout.IntPopup(new GUIContent("Tessellation Pixel Error", "Tessellation curve tolerance in screen space. " +
"'Medium Quality' is usually fine for PPAA (non-multisampled) while 'High Quality' is the recommended pixel error if you are rendering to a 8x multisampled surface"),
value, options, values);
float outMaxError = value == 0 ? 0.7f : value == 1 ? 0.4f : value == 2 ? 0.2f: inMaxError;
view.TessellationMaxPixelError = Math.Max(0.01f, EditorGUILayout.FloatField(outMaxError, GUILayout.Width(64)));
EditorGUILayout.EndHorizontal();
RenderMode renderMode = (RenderMode)EditorGUILayout.EnumPopup(new GUIContent("Debug Render Flags",
"Enables debugging render flags." +
"\n\n- Wireframe: toggles wireframe mode when rendering triangles" +
"\n\n- ColorBatches: each batch submitted to the GPU is given a unique solid color" +
"\n\n- Overdraw: displays pixel overdraw using blending layers. Different colors are used for each type of triangles." +
"'Green' for normal ones, 'Red' for opacities and 'Blue' for clipping masks"
), ToRenderMode(renderFlags));
renderFlags = (renderFlags & ~(Noesis.RenderFlags.Wireframe | Noesis.RenderFlags.ColorBatches | Noesis.RenderFlags.Overdraw))
| ToRenderFlags(renderMode);
bool ppaa = EditorGUILayout.Toggle(new GUIContent("Enable PPAA",
"Per-Primitive Antialiasing extrudes the contours of the geometry and smooths them. " +
"It is a 'cheap' antialiasing algorithm useful when GPU MSAA is not enabled.\n\n" +
"Not recommended when using World Space UI or Transform3D."),
(renderFlags & Noesis.RenderFlags.PPAA) > 0);
renderFlags = (renderFlags & ~Noesis.RenderFlags.PPAA) | (ppaa ? Noesis.RenderFlags.PPAA : 0);
bool lcd = EditorGUILayout.Toggle(new GUIContent("Subpixel Rendering",
"Enables subpixel rendering compatible with LCD displays"),
(renderFlags & Noesis.RenderFlags.LCD) > 0);
renderFlags = (renderFlags & ~Noesis.RenderFlags.LCD) | (lcd ? Noesis.RenderFlags.LCD : 0);
view.RenderFlags = renderFlags;
view.DPIScale = EditorGUILayout.Toggle(new GUIContent("DPI Scale",
"When enabled, the view is scaled by the actual DPI of the screen or physical device running the application"),
view.DPIScale);
view.ClearStencil = EditorGUILayout.Toggle(new GUIContent("Clear Stencil Buffer",
"(Experimental)\nNoesis requires the stencil buffer to be cleared to zero at the start of each frame. " +
"If Unity is using the stencil buffer somewhere else, you must enable this option to force a clear. " +
"Note that enabling this can have a minor performance impact, so it should be left disabled if not necessary."),
view.ClearStencil);
EditorGUILayout.Space();
view.EnableExternalUpdate = EditorGUILayout.Toggle(new GUIContent("External Update",
"When enabled, the view must be explicitly updated by calling 'ExternalUpdate()'. " +
"By default, the view is automatically updated during LateUpdate"
), view.EnableExternalUpdate);
view.UseRealTimeClock = EditorGUILayout.Toggle(new GUIContent("Real Time Clock",
"Indicates if 'Time.realtimeSinceStartup' is used instead of 'Time.time' for animations"), view.UseRealTimeClock);
EditorGUILayout.Space();
view.EnableKeyboard = EditorGUILayout.Toggle(new GUIContent("Enable Keyboard",
"Indicates if keyboard input events are processed by this view"), view.EnableKeyboard);
view.EnableMouse = EditorGUILayout.Toggle(new GUIContent("Enable Mouse",
"Indicates if mouse input events are processed by this view"), view.EnableMouse);
EditorGUI.BeginDisabledGroup(!view.EnableMouse);
EditorGUI.indentLevel++;
view.EmulateTouch = EditorGUILayout.Toggle(new GUIContent("Emulate Touch",
"Indicates if touch input events are emulated by the Mouse"), view.EmulateTouch);
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
view.EnableTouch = EditorGUILayout.Toggle(new GUIContent("Enable Touch",
"Indicates if touch input events are processed by this view"), view.EnableTouch);
EditorGUILayout.Space();
view.EnableActions = EditorGUILayout.Toggle(new GUIContent("Enable Actions",
"Indicates if input system actions are processed by this view"), view.EnableActions);
EditorGUI.BeginDisabledGroup(!view.EnableActions);
view.XRTrackingOrigin = (UnityEngine.Transform)EditorGUILayout.ObjectField(new GUIContent("XR Tracking Origin",
"Transform representing the real world origin for tracking devices."),
view.XRTrackingOrigin, typeof(UnityEngine.Transform), true);
view.ActionsRepeatDelay = (float)EditorGUILayout.FloatField(new GUIContent("Actions Repeat Delay",
"The initial delay (in seconds) between an initial button action and a repeated action"), view.ActionsRepeatDelay);
view.ActionsRepeatRate = (float)EditorGUILayout.FloatField(new GUIContent("Actions Repeat Rate",
"The speed (in seconds) that the button action repeats itself once repeating"), view.ActionsRepeatRate);
view.Actions = (UnityEngine.InputSystem.InputActionAsset)EditorGUILayout.ObjectField(new GUIContent("Actions Asset",
"Asset that contains action maps"), view.Actions, typeof(UnityEngine.InputSystem.InputActionAsset), false);
var actionMaps = view.Actions ? view.Actions.actionMaps.Select(x => x.name).ToArray() : new string[0];
var actionMapIndex = EditorGUILayout.Popup(new GUIContent("Action Map",
"Set of actions being used by this view and enabled by default"), Array.IndexOf(actionMaps, view.ActionMap), actionMaps);
actionMapIndex = Math.Max(actionMapIndex, actionMaps.Length > 0 ? 0 : - 1);
view.ActionMap = actionMapIndex != -1 ? actionMaps[actionMapIndex] : view.ActionMap;
_showMatchedActions = EditorGUILayout.BeginFoldoutHeaderGroup(_showMatchedActions, "Matched Actions");
if (_showMatchedActions)
{
_matchedActionStyle = _matchedActionStyle ?? new GUIStyle(EditorStyles.textField);
_unmatchedActionStyle = _unmatchedActionStyle ?? new GUIStyle(EditorStyles.textField);
_unmatchedActionStyle.normal.textColor = Color.red;
foreach (var name in ActionNames)
{
string actionName = view.ActionMap + "/" + name;
var action = view.Actions?.FindAction(actionName);
if (action != null)
{
EditorGUILayout.LabelField(name, $"{action.actionMap?.name}/{action.name}", _matchedActionStyle);
}
else
{
EditorGUILayout.LabelField(name, "None", _unmatchedActionStyle);
}
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
#if !ENABLE_INPUT_SYSTEM
if (view.EnableActions)
{
EditorGUILayout.HelpBox("Actions requires 'Active Input Handling' set to " +
"'Input System Package (New)' or 'Both' in Player Settings", MessageType.Warning);
}
#endif
}
public override bool HasPreviewGUI()
{
return Application.isPlaying;
}
public override bool RequiresConstantRepaint()
{
return Application.isPlaying;
}
private GUIStyle _previewHeaderStyle;
private GUIStyle _previewTextStyle;
public override void OnPreviewGUI(Rect rect_, GUIStyle background)
{
NoesisView view = target as NoesisView;
Noesis.ViewStats stats = view.GetStats();
if (_previewHeaderStyle == null)
{
#if UNITY_2020_1_OR_NEWER
_previewHeaderStyle = new GUIStyle(EditorStyles.largeLabel)
#else
_previewHeaderStyle = new GUIStyle(EditorStyles.whiteLargeLabel)
#endif
{
alignment = TextAnchor.UpperLeft,
fontStyle = FontStyle.Bold
};
}
if (_previewTextStyle == null)
{
#if UNITY_2020_1_OR_NEWER
_previewTextStyle = new GUIStyle(EditorStyles.label)
#else
_previewTextStyle = new GUIStyle(EditorStyles.whiteLabel)
#endif
{
richText = true,
fontStyle = FontStyle.Normal
};
}
string uri = view.Xaml != null ? view.Xaml.uri : "No XAML selected";
GUI.Label(new Rect(rect_.x + 5, rect_.y + 5, rect_.width, rect_.height), uri, _previewHeaderStyle);
StringBuilder left = new StringBuilder();
left.AppendLine("\n\nFrame Time (ms)");
left.AppendLine("Update Time (ms)");
left.AppendLine("Render Time (ms)");
left.AppendLine();
left.AppendLine("Triangles");
left.AppendLine("Draws");
left.AppendLine("Batches");
left.AppendLine("Tessellations");
left.AppendLine("Geometry Size (kB)");
left.AppendLine("Flushes");
left.AppendLine();
left.AppendLine("Stencil Masks");
left.AppendLine("Opacity Groups");
left.AppendLine("RT Switches");
left.AppendLine();
left.AppendLine("Ramps Uploaded");
left.AppendLine("Rasterized Glyphs");
left.AppendLine("Discarded Glyph Tiles");
_previewTextStyle.alignment = TextAnchor.UpperLeft;
GUI.Label(new Rect(rect_.x + 15, rect_.y + 5, 220, 500), left.ToString(), _previewTextStyle);
var format = new System.Globalization.NumberFormatInfo { NumberDecimalSeparator = "." };
StringBuilder right = new StringBuilder();
right.AppendLine("\n\n" + stats.FrameTime.ToString("#,##0.00", format) + "");
right.AppendLine("" + stats.UpdateTime.ToString("#,##0.00", format) + "");
right.AppendLine("" + stats.RenderTime.ToString("#,##0.00", format) + "");
right.AppendLine();
right.AppendLine("" + stats.Triangles + "");
right.AppendLine("" + stats.Draws + "");
right.AppendLine("" + stats.Batches + "");
right.AppendLine("" + stats.Tessellations + "");
right.AppendLine("" + String.Format("{0:F0}", stats.GeometrySize / 1024) + "");
right.AppendLine("" + stats.Flushes + "");
right.AppendLine();
right.AppendLine("" + stats.Masks + "");
right.AppendLine("" + stats.Opacities + "");
right.AppendLine("" + stats.RenderTargetSwitches + "");
right.AppendLine();
right.AppendLine("" + stats.UploadedRamps + "");
right.AppendLine("" + stats.RasterizedGlyphs + "");
right.AppendLine("" + stats.DiscardedGlyphTiles + "");
_previewTextStyle.alignment = TextAnchor.UpperRight;
GUI.Label(new Rect(rect_.x + 15, rect_.y + 5, 220, 500), right.ToString(), _previewTextStyle);
}
}