View Issue Details

IDProjectCategoryView StatusLast Update
0003909NoesisGUIUnitypublic2025-05-15 02:07
ReporterDaxten Assigned Tojsantos  
PrioritynormalSeveritymajor 
Status resolvedResolutionfixed 
Product Version3.2.7 
Target Version3.2.8Fixed in Version3.2.8 
Summary0003909: Unity 6 - Issues with HDR in HDPR
Description

Issue discussed here in the forum: https://www.noesisengine.com/forums/viewtopic.php?p=17981#p17981

Using the Noesis View + Custom Pass Volume with "After Post Process" renders the UI extremly dark in HDR / HDPR.
Using "Before Post Process" renders the colors correctly, but it gets a heavy jitter effect when moving the camera.

PlatformWindows

Relationships

related to 0004168 assignedjsantos Dark rendering when using HDR capable monitor 

Activities

jsantos

jsantos

2025-05-15 01:54

manager   ~0010658

I've been working on improving HDRP integration. In Unity 6, all our samples stopped working, and it became necessary to manually create a Custom Volume Pass. I've resolved that issue, and creating this volume is no longer required.

Now, in HDRP, the Noesis view exposes two injection points (similar to what we do in URP):

  1. Before Post Processing – This isn't particularly useful, as any kind of TAA will introduce jitter to the UI. Still, it might be helpful in some cases.
  2. After Post Processing – This is the default option and likely the most useful one.

he issue with extremely dark rendering (as shown in the post) is related to using HDR Display Output on a monitor that supports HDR. Since I don't have a monitor with those capabilities, I'll create a separate ticket for this problem and mark this one as resolved.

If you'd like to test, I'm attaching the latest changes.

Noesis.NoesisGUI.Editor.asmdef (1,014 bytes)   
{
    "name": "Noesis.NoesisGUI.Editor",
    "rootNamespace": "",
    "references": [
        "Noesis.NoesisGUI",
        "Unity.InputSystem",
        "Unity.RenderPipelines.Universal.Runtime",
        "Unity.RenderPipelines.HighDefinition.Runtime"
    ],
    "includePlatforms": [
        "Editor"
    ],
    "excludePlatforms": [],
    "allowUnsafeCode": false,
    "overrideReferences": false,
    "precompiledReferences": [],
    "autoReferenced": true,
    "defineConstraints": [],
    "versionDefines": [
        {
            "name": "com.unity.ugui",
            "expression": "1.0.0",
            "define": "ENABLE_UGUI_PACKAGE"
        },
        {
            "name": "com.unity.render-pipelines.universal",
            "expression": "7.2.1",
            "define": "ENABLE_URP_PACKAGE"
        },
        {
            "name": "com.unity.render-pipelines.high-definition",
            "expression": "12.1.1",
            "define": "ENABLE_HDRP_PACKAGE"
        }
    ],
    "noEngineReferences": false
}
Noesis.NoesisGUI.Editor.asmdef (1,014 bytes)   
NoesisViewEditor.cs (16,948 bytes)   
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<b>" + stats.FrameTime.ToString("#,##0.00", format) + "</b>");
        right.AppendLine("<b>" + stats.UpdateTime.ToString("#,##0.00", format) + "</b>");
        right.AppendLine("<b>" + stats.RenderTime.ToString("#,##0.00", format) + "</b>");
        right.AppendLine();
        right.AppendLine("<b>" + stats.Triangles + "</b>");
        right.AppendLine("<b>" + stats.Draws + "</b>");
        right.AppendLine("<b>" + stats.Batches + "</b>");
        right.AppendLine("<b>" + stats.Tessellations + "</b>");
        right.AppendLine("<b>" + String.Format("{0:F0}", stats.GeometrySize / 1024) + "</b>");
        right.AppendLine("<b>" + stats.Flushes + "</b>");
        right.AppendLine();
        right.AppendLine("<b>" + stats.Masks + "</b>");
        right.AppendLine("<b>" + stats.Opacities + "</b>");
        right.AppendLine("<b>" + stats.RenderTargetSwitches + "</b>");
        right.AppendLine();
        right.AppendLine("<b>" + stats.UploadedRamps + "</b>");
        right.AppendLine("<b>" + stats.RasterizedGlyphs + "</b>");
        right.AppendLine("<b>" + stats.DiscardedGlyphTiles + "</b>");

        _previewTextStyle.alignment = TextAnchor.UpperRight;
        GUI.Label(new Rect(rect_.x + 15, rect_.y + 5, 220, 500), right.ToString(), _previewTextStyle);
    }
}
NoesisViewEditor.cs (16,948 bytes)   
NoesisView.cs (72,652 bytes)   
#define IGNORE_ISHITTESTVISIBLE_FALSE_ELEMENTS

using UnityEngine;
using Noesis;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.Rendering;
using UnityEngine.Profiling;
using UnityEngine.XR;

#if ENABLE_URP_PACKAGE
using UnityEngine.Rendering.Universal;
#endif

#if ENABLE_URP_PACKAGE_RENDER_GRAPH
using UnityEngine.Rendering.RenderGraphModule;
#endif

#if ENABLE_HDRP_PACKAGE
using UnityEngine.Rendering.HighDefinition;
#endif

using LoadAction = UnityEngine.Rendering.RenderBufferLoadAction;
using StoreAction = UnityEngine.Rendering.RenderBufferStoreAction;

//[ExecuteInEditMode]
[AddComponentMenu("NoesisGUI/Noesis View")]
[HelpURL("https://www.noesisengine.com/docs")]
[DisallowMultipleComponent]
public class NoesisView: MonoBehaviour, ISerializationCallbackReceiver
{
    #region Public properties

    /// <summary>
    /// User interface definition XAML
    /// </summary>
    public NoesisXaml Xaml
    {
        set { this._xaml = value; }
        get { return this._xaml; }
    }

    /// <summary>
    /// The texture to render this View into
    /// </summary>
    public RenderTexture Texture
    {
        set { this._texture = value; }
        get { return this._texture; }
    }

    /// <summary>
    /// 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
    /// </summary>
    public float TessellationMaxPixelError
    {
        set
        {
            if (_uiView != null)
            {
                _uiView.SetTessellationMaxPixelError(value);
            }

            this._tessellationMaxPixelError = value;
        }

        get
        {
            if (_uiView != null)
            {
                return _uiView.GetTessellationMaxPixelError().Error;
            }

            return this._tessellationMaxPixelError; 
        }
    }

    /// <summary>
    /// Bit flags used for debug rendering purposes.
    /// </summary>
    public RenderFlags RenderFlags
    {
        set
        {
            if (_uiView != null)
            {
                _uiView.SetFlags(value);
            }

            this._renderFlags = value;
        }
        get
        {
            if (_uiView != null)
            {
                return _uiView.GetFlags();
            }

            return this._renderFlags;
        }
    }

    /// <summary>
    /// When enabled, the UI is positioned in the world among other objects in the Scene
    /// </summary>
    public bool WorldSpace
    {
        get { return (RenderFlags & RenderFlags.DepthTesting) > 0; }
    }

    /// <summary>
    /// The projection matrix set to the view is used for determining the visual impact of nodes
    /// in the offscreen phase. The stereo matrices used for rendering in VR are slightly different.
    /// To account for this difference, it is recommended to apply a scale using this property.
    /// For non-VR this must be always 1. For VR, we recommend a value between 2 and 3.
    /// </summary>
    public float StereoOffscreenScaleFactor
    {
        set { this._stereoScale = value; }
        get { return this._stereoScale; }
    }

    /// <summary>
    /// When enabled, the view is scaled by the actual DPI of the screen or physical device running the application
    /// </summary>
    public bool DPIScale
    {
        set { this._dpiScale = value; }
        get { return this._dpiScale; }
    }

    /// <summary>
    /// 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. If not rendering
    /// to texture, this property is ignored. Use the property 'NeedsRendering' instead.
    /// </summary>
    public bool ContinuousRendering
    {
        set { this._continuousRendering = value; }
        get { return this._continuousRendering; }
    }

    /// <summary>
    /// When enabled, the view must be explicitly updated by calling 'ExternalUpdate()'.
    /// By default, the view is automatically updated during LateUpdate.
    /// </summary>
    public bool EnableExternalUpdate
    {
        set { this._enableExternalUpdate = value; }
        get { return this._enableExternalUpdate; }
    }

    /// <summary>
    /// After updating the view, this flag indicates if the GUI needs to be repainted.
    /// This flag can be used on manually painted cameras to optimize performance and save battery.
    /// </summary>
    public bool NeedsRendering
    {
        set { this._needsRendering = value; }
        get { return this._needsRendering; }
    }

    /// <summary>
    /// Enables keyboard input management.
    /// </summary>
    public bool EnableKeyboard
    {
        set { this._enableKeyboard = value; }
        get { return this._enableKeyboard; }
    }

    /// <summary>
    /// Enables mouse input management.
    /// </summary>
    public bool EnableMouse
    {
        set { this._enableMouse = value; }
        get { return this._enableMouse; }
    }

    /// <summary>
    /// Enables touch input management.
    /// </summary>
    public bool EnableTouch
    {
        set { this._enableTouch = value; }
        get { return this._enableTouch; }
    }

    /// <summary>
    /// Enables actions input management.
    /// </summary>
    public bool EnableActions
    {
        get { return _enableActions; }
        set
        {
            if (_enableActions != value)
            {
                _enableActions = value;
                ReloadActions(_actions, _actionMap, _actionsBound);
            }
        }
    }

    /// <summary>
    /// Input System actions.
    /// </summary>
    public UnityEngine.InputSystem.InputActionAsset Actions
    {
        get { return _actions; }
        set
        {
            if (_actions != value)
            {
                ReloadActions(value, _actionMap, _actionsBound);
            }
        }
    }

    /// <summary>
    /// Set of actions being used by this view and enabled by default.
    /// </summary>
    public string ActionMap
    {
        get { return _actionMap; }
        set
        {
            if (_actionMap != value)
            {
                ReloadActions(_actions, value, _actionsBound);
            }
        }
    }

    /// <summary>
    /// The initial delay (in seconds) between an initial button action and a repeated action.
    /// </summary>
    public float ActionsRepeatDelay
    {
        set { this._actionsRepeatDelay = value; }
        get { return this._actionsRepeatDelay; }
    }

    /// <summary>
    /// The speed (in seconds) that the button action repeats itself once repeating.
    /// </summary>
    public float ActionsRepeatRate
    {
        set { this._actionsRepeatRate = value; }
        get { return this._actionsRepeatRate; }
    }

    /// <summary>
    /// Transform representing the real world origin for tracking devices
    /// </summary>
    public UnityEngine.Transform XRTrackingOrigin
    {
        set { this._xrTrackingOrigin = value; }
        get { return this._xrTrackingOrigin; }
    }

  #if ENABLE_URP_PACKAGE
    /// <summary>
    /// Controls when the UI render pass executes
    /// </summary>
    public RenderPassEvent RenderPassEvent
    {
        set { this._renderPassEvent = value; }
        get { return this._renderPassEvent; }
    }
  #endif

  #if ENABLE_HDRP_PACKAGE
    /// <summary>
    /// Controls when the UI render pass executes
    /// </summary>
    public CustomPassInjectionPoint InjectionPoint
    {
        set { this._injectionPoint = value; }
        get { return this._injectionPoint; }
    }
  #endif

    /// <summary>
    /// Emulate touch input with mouse.
    /// </summary>
    public bool EmulateTouch
    {
        set
        {
            if (_uiView != null)
            {
                _uiView.SetEmulateTouch(value);
            }

            this._emulateTouch = value;
        }
        get { return this._emulateTouch; }
    }

    /// <summary>
    /// When enabled, UI is updated using Time.realtimeSinceStartup.
    /// </summary>
    public bool UseRealTimeClock
    {
        set { this._useRealTimeClock = value; }
        get { return this._useRealTimeClock; }
    }

    /// <summary>
    /// When enabled, then stencil buffer is cleared before rendering this view
    /// </summary>
    public bool ClearStencil
    {
        set { this._clearStencil = value; }
        get { return this._clearStencil; }
    }

    /// <summary>
    /// Gets the root of the loaded Xaml.
    /// </summary>
    /// <returns>Root element.</returns>
    public FrameworkElement Content
    {
        get { return _uiView != null ? _uiView.Content : null; }
    }

    /// <summary>
    /// Indicates if this component is rendering UI to a RenderTexture.
    /// </summary>
    /// <returns></returns>
    public bool IsRenderToTexture()
    {
        return !gameObject.TryGetComponent(out Camera _);
    }

    #endregion

    #region Public events

    #region Render
    public event RenderingEventHandler Rendering
    {
        add
        {
            if (_uiView != null)
            {
                _uiView.Rendering += value;
            }
        }
        remove
        {
            if (_uiView != null)
            {
                _uiView.Rendering -= value;
            }
        }
    }

    public ViewStats GetStats()
    {
        if (_uiView != null)
        {
            return _uiView.GetStats();
        }

        return new ViewStats();
    }
    #endregion

    #region Keyboard input events
    /// <summary>
    /// Notifies Renderer that a key was pressed.
    /// </summary>
    /// <param name="key">Key identifier.</param>
    public bool KeyDown(Noesis.Key key)
    {
        if (_uiView != null)
        {
            return _uiView.KeyDown(key);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer that a key was released.
    /// </summary>
    /// <param name="key">Key identifier.</param>
    public bool KeyUp(Noesis.Key key)
    {
        if (_uiView != null)
        {
            return _uiView.KeyUp(key);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer that a key was translated to the corresponding character.
    /// </summary>
    /// <param name="ch">Unicode character value.</param>
    public bool Char(uint ch)
    {
        if (_uiView != null)
        {
            return _uiView.Char(ch);
        }

        return false;
    }
    #endregion

    #region Mouse input events
    /// <summary>
    /// Notifies Renderer that mouse was moved. The mouse position is specified in renderer
    /// surface pixel coordinates.
    /// </summary>
    /// <param name="x">Mouse x-coordinate.</param>
    /// <param name="y">Mouse y-coordinate.</param>
    public bool MouseMove(int x, int y)
    {
        if (_uiView != null)
        {
            return _uiView.MouseMove(x, y);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer that a mouse button was pressed. The mouse position is specified in
    /// renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Mouse x-coordinate.</param>
    /// <param name="y">Mouse y-coordinate.</param>
    /// <param name="button">Indicates which button was pressed.</param>
    public bool MouseButtonDown(int x, int y, Noesis.MouseButton button)
    {
        if (_uiView != null)
        {
            return _uiView.MouseButtonDown(x, y, button);
        }

        return false;
    }

    /// Notifies Renderer that a mouse button was released. The mouse position is specified in
    /// renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Mouse x-coordinate.</param>
    /// <param name="y">Mouse y-coordinate.</param>
    /// <param name="button">Indicates which button was released.</param>
    public bool MouseButtonUp(int x, int y, Noesis.MouseButton button)
    {
        if (_uiView != null)
        {
            return _uiView.MouseButtonUp(x, y, button);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer of a mouse button double click. The mouse position is specified in
    /// renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Mouse x-coordinate.</param>
    /// <param name="y">Mouse y-coordinate.</param>
    /// <param name="button">Indicates which button was pressed.</param>
    public bool MouseDoubleClick(int x, int y, Noesis.MouseButton button)
    {
        if (_uiView != null)
        {
            return _uiView.MouseDoubleClick(x, y, button);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer that mouse wheel was rotated. The mouse position is specified in
    /// renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Mouse x-coordinate.</param>
    /// <param name="y">Mouse y-coordinate.</param>
    /// <param name="wheelRotation">Indicates the amount mouse wheel has changed.</param>
    public bool MouseWheel(int x, int y, int wheelRotation)
    {
        if (_uiView != null)
        {
            return _uiView.MouseWheel(x, y, wheelRotation);
        }

        return false;
    }
    #endregion

    #region Touch input events
    /// <summary>
    /// Notifies Renderer that a finger is moving on the screen. The finger position is
    /// specified in renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Finger x-coordinate.</param>
    /// <param name="y">Finger y-coordinate.</param>
    /// <param name="touchId">Finger identifier.</param>
    public bool TouchMove(int x, int y, uint touchId)
    {
        if (_uiView != null)
        {
            return _uiView.TouchMove(x, y, touchId);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer that a finger touches the screen. The finger position is
    /// specified in renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Finger x-coordinate.</param>
    /// <param name="y">Finger y-coordinate.</param>
    /// <param name="touchId">Finger identifier.</param>
    public bool TouchDown(int x, int y, uint touchId)
    {
        if (_uiView != null)
        {
            return _uiView.TouchDown(x, y, touchId);
        }

        return false;
    }

    /// <summary>
    /// Notifies Renderer that a finger is raised off the screen. The finger position is
    /// specified in renderer surface pixel coordinates.
    /// </summary>
    /// <param name="x">Finger x-coordinate.</param>
    /// <param name="y">Finger y-coordinate.</param>
    /// <param name="touchId">Finger identifier.</param>
    public bool TouchUp(int x, int y, uint touchId)
    {
        if (_uiView != null)
        {
            return _uiView.TouchUp(x, y, touchId);
        }

        return false;
    }
    #endregion

    #endregion

    #region Public methods

    /// <summary>
    /// Loads the user interface specified in the XAML property
    /// </summary>
    public void LoadXaml(bool force)
    {
        if (force)
        {
            DestroyView();
        }

        if (_xaml != null && _uiView == null)
        {
            object obj = _xaml.Load();

            if (obj != null)
            {
                FrameworkElement content = obj as FrameworkElement;

                if (content != null)
                {
                    CreateView(content);
                }
                else
                {
                    Debug.LogError($"{_xaml.uri}: Root type '{obj.GetType().Name}' does not inherit from 'FrameworkElement'");
                }
            }
        }
    }

    #endregion

    #region Private members

    #region MonoBehavior component messages

    /// <summary>
    /// Called once when component is attached to GameObject for the first time
    /// </summary>
    void Reset()
    {
        _isPPAAEnabled = true;
        _tessellationMaxPixelError = Noesis.TessellationMaxPixelError.MediumQuality.Error;
        _renderFlags = 0;
        _dpiScale = true;
        _continuousRendering = gameObject.TryGetComponent(out Camera _);
        _enableExternalUpdate = false;
        _enableKeyboard = true;
        _enableMouse = true;
        _enableTouch = true;
        _enableActions = false;
        _emulateTouch = false;
        _useRealTimeClock = false;
        _clearStencil = false;
        _actionMap = "Gamepad";
        _actionsRepeatDelay = 0.5f;
        _actionsRepeatRate = 0.1f;
      #if ENABLE_URP_PACKAGE
        _renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
      #endif
      #if ENABLE_HDRP_PACKAGE
        _injectionPoint = CustomPassInjectionPoint.AfterPostProcess;
      #endif
    }

    void Start()
    {
        // https://forum.unity.com/threads/gc-collect-in-guiutility-begingui.642229/
        // Avoid OnGUI GC Allocations
        useGUILayout = false;

      #if !ENABLE_INPUT_SYSTEM
        if (_enableActions)
        {
            Debug.LogWarning("Actions enabled requires 'Active Input Handling' set to 'New' or 'Both' in Player Settings");
            _enableActions = false;
        }
      #endif
    }

    private void ReloadActions(UnityEngine.InputSystem.InputActionAsset actions, string actionMap,
        bool actionsBound)
    {
        if (actionsBound)
        {
            UnbindActions();
        }

        _actions = actions;
        _actionMap = actionMap;

        if (actionsBound)
        {
            BindActions();
        }
    }

    private bool _actionMapEnabled = false;
    private bool _actionsBound = false;

    private void BindActions()
    {
        var actionMap = _actions?.FindActionMap(_actionMap ?? "");

        if (actionMap != null)
        {
            if (_enableActions && !actionMap.enabled)
            {
                actionMap.Enable();
                _actionMapEnabled = true;
            }

            _upAction = actionMap.FindAction("Up");
            _downAction = actionMap.FindAction("Down");
            _leftAction = actionMap.FindAction("Left");
            _rightAction = actionMap.FindAction("Right");

            _acceptAction = actionMap.FindAction("Accept");
            _cancelAction = actionMap.FindAction("Cancel");

            _menuAction = actionMap.FindAction("Menu");
            _viewAction = actionMap.FindAction("View");

            _pageLeftAction = actionMap.FindAction("PageLeft");
            _pageRightAction = actionMap.FindAction("PageRight");
            _pageUpAction = actionMap.FindAction("PageUp");
            _pageDownAction = actionMap.FindAction("PageDown");
            _scrollAction = actionMap.FindAction("Scroll");

            _trackedPositionAction = actionMap.FindAction("TrackedPosition");
            _trackedRotationAction = actionMap.FindAction("TrackedRotation");
            _trackedTriggerAction = actionMap.FindAction("TrackedTrigger");
        }

        _actionsBound = true;
    }

    private void UnbindActions()
    {
        var actionMap = _actions?.FindActionMap(_actionMap ?? "");

        if (actionMap != null)
        {
            if (_actionMapEnabled)
            {
                actionMap.Disable();
                _actionMapEnabled = false;
            }

            _upAction = default;
            _downAction = default;
            _leftAction = default;
            _rightAction = default;

            _acceptAction = default;
            _cancelAction = default;

            _menuAction = default;
            _viewAction = default;

            _pageLeftAction = default;
            _pageRightAction = default;
            _pageUpAction = default;
            _pageDownAction = default;
            _scrollAction = default;

            _trackedPositionAction = default;
            _trackedRotationAction = default;
            _trackedTriggerAction = default;
        }

        _actionsBound = false;
    }

    private CommandBuffer _commands;

    private void EnsureCommandBuffer()
    {
        if (_commands == null)
        {
            _commands = new CommandBuffer();
        }
    }

    void Awake()
    {
        EnsureCommandBuffer();
    }

    private Camera _camera;

    void OnEnable()
    {
        EnsureCommandBuffer();
        TryGetComponent<Camera>(out _camera);

      #if !ENABLE_LEGACY_INPUT_MANAGER
        if (UnityEngine.InputSystem.Touchscreen.current != null)
        {
            UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport.Enable();
        }
      #endif

        BindActions();
        LoadXaml(false);

        Camera.onPreRender += PreRender;

      #if ENABLE_URP_PACKAGE || ENABLE_HDRP_PACKAGE
        RenderPipelineManager.beginCameraRendering += BeginCameraRendering;
        RenderPipelineManager.endCameraRendering += EndCameraRendering;

        #if ENABLE_URP_PACKAGE
          _scriptableRenderPass = new NoesisScriptableRenderPass(this);
        #endif
      #endif
    }

    void OnDisable()
    {
      #if !ENABLE_LEGACY_INPUT_MANAGER
        if (UnityEngine.InputSystem.Touchscreen.current != null)
        {
            UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport.Disable();
        }
      #endif

        UnbindActions();

        Camera.onPreRender -= PreRender;

      #if ENABLE_URP_PACKAGE || ENABLE_HDRP_PACKAGE
        RenderPipelineManager.beginCameraRendering -= BeginCameraRendering;
        RenderPipelineManager.endCameraRendering -= EndCameraRendering;
      #endif
    }

    private static class Profiling
    {
        public static readonly CustomSampler UpdateSampler = CustomSampler.Create("Noesis.Update");
        public static readonly string RegisterView = "Noesis.RegisterView";
        public static readonly string UnregisterView = "Noesis.UnregisterView";
        public static readonly string UpdateRenderTree = "Noesis.UpdateRenderTree";
        public static readonly string RenderOffScreen = "Noesis.RenderOffscreen";
        public static readonly string RenderOnScreen = "Noesis.RenderOnscreen";
        public static readonly string RenderTexture = "Noesis.RenderTexture";
    }

#region Universal Render Pipeline
#if ENABLE_URP_PACKAGE
    private class NoesisScriptableRenderPass: ScriptableRenderPass
    {
        public NoesisScriptableRenderPass(NoesisView view)
        {
            _view = view;
        }

      #if ENABLE_URP_PACKAGE_RENDER_GRAPH
        private class PassData
        {
            public bool flipY;
            public bool clearStencil;
            public NoesisView view;
            public UniversalCameraData cameraData;
        }

        ProfilingSampler _profilingSampler = new ProfilingSampler("Noesis.RenderOnscreen");

        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            if (_view._uiView != null && _view._visible)
            {
                using (var builder = renderGraph.AddRasterRenderPass<PassData>("Noesis", out var passData, _profilingSampler))
                {
                    var resourceData = frameData.Get<UniversalResourceData>();
                    var cameraData = frameData.Get<UniversalCameraData>();

                    passData.view = _view;
                    passData.clearStencil = _view._clearStencil;
                    passData.flipY = !IsGL() && !resourceData.isActiveTargetBackBuffer;
                    passData.cameraData = cameraData;

                    // Ensure that Unity does not cull this pass
                    builder.AllowPassCulling(false);

                    // These attachments are implicit; setting them appears to be unnecessary
                    //builder.SetRenderAttachment(resourceData.activeColorTexture, 0, AccessFlags.WriteAll);
                    //builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture, AccessFlags.ReadWrite);

                    builder.SetRenderFunc((PassData passData, RasterGraphContext context) =>
                    {
                       #if ENABLE_VR && ENABLE_XR_MODULE
                        if (passData.cameraData.xrRendering)
                        {
                            var cameraData = passData.cameraData;
                            var camera = passData.view._camera;
                            var width = camera.pixelWidth;
                            var height = camera.pixelHeight;

                          #if ENABLE_URP_PACKAGE_VR
                            // CameraData.xr available in URP 14.0+
                            if (cameraData.xr.singlePassEnabled)
                          #else
                            // https://forum.unity.com/threads/detect-single-pass-stereo-on-android-at-runtime.509304/
                            if (XRSettings.eyeTextureDesc.vrUsage == VRTextureUsage.TwoEyes)
                          #endif
                            {
                                Matrix4x4 viewMatrix0 = cameraData.GetViewMatrix(0);
                                Matrix4x4 projectionMatrix0 = cameraData.GetProjectionMatrix(0);
                                Noesis.Matrix4 viewProj0 = NoesisMatrix(viewMatrix0, projectionMatrix0, width, height);

                                Matrix4x4 viewMatrix1 = cameraData.GetViewMatrix(1);
                                Matrix4x4 projectionMatrix1 = cameraData.GetProjectionMatrix(1);
                                Noesis.Matrix4 viewProj1 = NoesisMatrix(viewMatrix1, projectionMatrix1, width, height);

                                NoesisRenderer.RenderOnscreen_(passData.view._uiView, CameraMatrix(camera), viewProj0, viewProj1,
                                    passData.flipY, context.cmd, true, passData.clearStencil);
                            }
                            else
                            {
                                Matrix4x4 viewMatrix = cameraData.GetViewMatrix(0);
                                Matrix4x4 projectionMatrix = cameraData.GetProjectionMatrix(0);
                                Noesis.Matrix4 viewProj = NoesisMatrix(viewMatrix, projectionMatrix, width, height);
                                NoesisRenderer.RenderOnscreen_(passData.view._uiView, viewProj, passData.flipY, context.cmd,
                                    true, passData.clearStencil);
                            }
                        }
                        else
                       #endif
                        {
                            NoesisRenderer.RenderOnscreen_(passData.view._uiView, passData.flipY, context.cmd, true,
                                passData.clearStencil);
                        }
                    });
                }
            }
        }
      #else
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (_view._uiView != null && _view._visible)
            {
                bool flipY = !IsGL() && !IsBackbuffer(_view._camera);
                _view._commands.name = Profiling.RenderOnScreen;

              #if ENABLE_VR && ENABLE_XR_MODULE
                if (renderingData.cameraData.xrRendering)
                {
                    var cameraData = renderingData.cameraData;
                    var camera = _view._camera;
                    var width = camera.pixelWidth;
                    var height = camera.pixelHeight;

                  #if ENABLE_URP_PACKAGE_VR
                    // CameraData.xr available in URP 14.0+
                    if (cameraData.xr.singlePassEnabled)
                  #else
                    // https://forum.unity.com/threads/detect-single-pass-stereo-on-android-at-runtime.509304/
                    if (XRSettings.eyeTextureDesc.vrUsage == VRTextureUsage.TwoEyes)
                  #endif
                    {
                        Matrix4x4 viewMatrix0 = cameraData.GetViewMatrix(0);
                        Matrix4x4 projectionMatrix0 = cameraData.GetProjectionMatrix(0);
                        Noesis.Matrix4 viewProj0 = NoesisMatrix(viewMatrix0, projectionMatrix0, width, height);

                        Matrix4x4 viewMatrix1 = cameraData.GetViewMatrix(1);
                        Matrix4x4 projectionMatrix1 = cameraData.GetProjectionMatrix(1);
                        Noesis.Matrix4 viewProj1 = NoesisMatrix(viewMatrix1, projectionMatrix1, width, height);

                        NoesisRenderer.RenderOnscreen(_view._uiView, CameraMatrix(camera), viewProj0, viewProj1,
                            flipY, _view._commands, true, _view._clearStencil);
                    }
                    else
                    {
                        Matrix4x4 viewMatrix = cameraData.GetViewMatrix(0);
                        Matrix4x4 projectionMatrix = cameraData.GetProjectionMatrix(0);
                        Noesis.Matrix4 viewProj = NoesisMatrix(viewMatrix, projectionMatrix, width, height);
                        NoesisRenderer.RenderOnscreen(_view._uiView, viewProj, flipY, _view._commands, true, _view._clearStencil);
                    }
                }
                else
              #endif
                {
                    NoesisRenderer.RenderOnscreen(_view._uiView, flipY, _view._commands, true, _view._clearStencil);
                }

                context.ExecuteCommandBuffer(_view._commands);
                _view._commands.Clear();
            }
        }

        private bool IsBackbuffer(Camera camera)
        {
            var scriptableRenderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;

            #if UNITY_2022_1_OR_NEWER
                return camera.targetTexture == null && scriptableRenderer.cameraColorTargetHandle.rt == null;
            #else
                return camera.targetTexture == null && scriptableRenderer.cameraColorTarget == BuiltinRenderTextureType.CameraTarget;
            #endif
        }
      #endif

        NoesisView _view;
    }

    private NoesisScriptableRenderPass _scriptableRenderPass;

    private void RenderOffscreenUniversal(ScriptableRenderContext context)
    {
        if (_uiView != null && _visible)
        {
            _commands.name = Profiling.RenderOffScreen;
            NoesisRenderer.RenderOffscreen(_uiView, _commands, true);
            context.ExecuteCommandBuffer(_commands);
            _commands.Clear();
        }
    }

    private void BeginCameraRendering(ScriptableRenderContext context, Camera camera)
    {
        var cameraData = camera.GetUniversalAdditionalCameraData();

        // To avoid inefficient changes of render target, stacked cameras must render
        // their offscreen phase before the base camera is started
        if (cameraData.renderType == CameraRenderType.Base)
        {
            if (_camera == camera)
            {
                RenderOffscreenUniversal(context);
            }
            else
            {
                foreach (var stackedCamera in cameraData.cameraStack)
                {
                    if (_camera == stackedCamera)
                    {
                        RenderOffscreenUniversal(context);
                        break;
                    }
                }
            }
        }

        if (_camera == camera)
        {
            _scriptableRenderPass.renderPassEvent = _renderPassEvent;
            cameraData.scriptableRenderer.EnqueuePass(_scriptableRenderPass);
        }
    }

    private void EndCameraRendering(ScriptableRenderContext context, Camera camera) {}
#endif
#endregion

#region High Definition Render Pipeline
#if ENABLE_HDRP_PACKAGE
  #if UNITY_EDITOR
    [UnityEditor.InitializeOnLoad] 
  #endif
    static class RegisterNoesisCustomPass
    {
        [HideInInspector]
        private class NoesisCustomPass: CustomPass
        {
            public CustomPassInjectionPoint InjectionPoint;

            protected override bool executeInSceneView { get { return false; } }

            protected override void Execute(CustomPassContext ctx) 
            {
                if (ctx.hdCamera.camera.TryGetComponent(out NoesisView view))
                {
                    if (InjectionPoint == view.InjectionPoint)
                    {
                        view.OnExecuteCustomPass(ctx.cmd);
                    }
                }
            }
        }

        static RegisterNoesisCustomPass() => RegisterCustomPasses();

        [RuntimeInitializeOnLoadMethod]
        static void RegisterCustomPasses()
        {
            RegisterCustomPass(CustomPassInjectionPoint.BeforePostProcess);
            RegisterCustomPass(CustomPassInjectionPoint.AfterPostProcess);
        }

        static void RegisterCustomPass(CustomPassInjectionPoint injectionPoint)
        {
            var pass = new NoesisCustomPass
            {
                clearFlags = ClearFlag.Stencil,
                targetColorBuffer = CustomPass.TargetBuffer.Camera,
                targetDepthBuffer = CustomPass.TargetBuffer.Camera,
                InjectionPoint = injectionPoint
            };

            CustomPassVolume.RegisterUniqueGlobalCustomPass(injectionPoint, pass);
        }
    }

    private void BeginCameraRendering(ScriptableRenderContext context, Camera camera)
    {
        if (_camera == camera)
        {
            if (_uiView != null && _visible)
            {
                _commands.name = Profiling.RenderOffScreen;
                NoesisRenderer.RenderOffscreen(_uiView, _commands, true);

                context.ExecuteCommandBuffer(_commands);
                _commands.Clear();
            }
        }
    }

    public static readonly CustomSampler _customgSampler = CustomSampler.Create("Noesis.RenderOnscreen", true);

    private void OnExecuteCustomPass(CommandBuffer commands)
    {
        if (_uiView != null && _visible)
        {
            // HDRP is always rendering to an intermediate texture
            bool flipY = !IsGL();

            commands.BeginSample(_customgSampler);
            NoesisRenderer.RenderOnscreen(_uiView, flipY, commands, true, _clearStencil);
            commands.EndSample(_customgSampler);
        }
    }

     private void EndCameraRendering(ScriptableRenderContext context, Camera camera) {}
#endif
#endregion

    void OnDestroy()
    {
        DestroyView();
    }

#if ENABLE_UGUI_PACKAGE
    UnityEngine.EventSystems.PointerEventData _pointerData;
#endif

    private UnityEngine.Vector2 ProjectPointer(float x, float y)
    {
        if (_camera != null)
        {
            return new UnityEngine.Vector2(x, UnityEngine.Screen.height - y);
        }
        else if (_texture != null)
        {
            // Project using texture coordinates

          #if ENABLE_UGUI_PACKAGE
            // First try with Unity UI RawImage objects
            UnityEngine.EventSystems.EventSystem eventSystem = UnityEngine.EventSystems.EventSystem.current;

            if (eventSystem != null && eventSystem.IsPointerOverGameObject())
            {
                UnityEngine.Vector2 pos = new UnityEngine.Vector2(x, y);

                if (_pointerData == null)
                {
                    _pointerData = new UnityEngine.EventSystems.PointerEventData(eventSystem)
                    {
                        pointerId = 0,
                        position = pos
                    };
                }
                else
                {
                    _pointerData.Reset();
                }

                _pointerData.delta = pos - _pointerData.position;
                _pointerData.position = pos;

                if (TryGetComponent(out RectTransform rect))
                {
                    if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rect,
                        _pointerData.position, _pointerData.pressEventCamera, out pos))
                    {
                        UnityEngine.Vector2 pivot = new UnityEngine.Vector2(
                            rect.pivot.x * rect.rect.width,
                            rect.pivot.y * rect.rect.height);

                        float texCoordX = (pos.x + pivot.x) / rect.rect.width;
                        float texCoordY = (pos.y + pivot.y) / rect.rect.height;

                        float localX = _texture.width * texCoordX;
                        float localY = _texture.height * (1.0f - texCoordY);
                        return new UnityEngine.Vector2(localX, localY);
                    }
                }
            }
          #endif

            // NOTE: A MeshCollider must be attached to the target to obtain valid
            // texture coordinates, otherwise Hit Testing won't work

            UnityEngine.Ray ray = UnityEngine.Camera.main.ScreenPointToRay(new UnityEngine.Vector3(x, y, 0));

            UnityEngine.RaycastHit hit;
            if (UnityEngine.Physics.Raycast(ray, out hit))
            {
                if (hit.collider.gameObject == gameObject)
                {
                    float localX = _texture.width * hit.textureCoord.x;
                    float localY = _texture.height * (1.0f - hit.textureCoord.y);
                    return new UnityEngine.Vector2(localX, localY);
                }
            }

            return new UnityEngine.Vector2(-1, -1);
        }

        return Vector2.zero;
    }

    private UnityEngine.Vector3 _mousePos;
    private int _activeDisplay = 0;

    private static bool HasMouse()
    {
      #if ENABLE_LEGACY_INPUT_MANAGER
        return Input.mousePresent;
      #else
        return UnityEngine.InputSystem.Mouse.current != null;
      #endif
    }

    private static bool IsCursorVisible()
    {
        return UnityEngine.Cursor.visible && UnityEngine.Cursor.lockState != CursorLockMode.Locked;
    }

    private Vector3 MousePosition()
    {
      #if ENABLE_LEGACY_INPUT_MANAGER
        Vector3 mousePosition = UnityEngine.Input.mousePosition;
      #else
        Vector3 mousePosition = UnityEngine.InputSystem.Mouse.current.position.ReadValue();
      #endif

        Vector3 p = Display.RelativeMouseAt(mousePosition);

        if (p == Vector3.zero)
        {
            return mousePosition;
        }

        _activeDisplay = (int)p.z;
        return p;
    }

    private void UpdateMouse()
    {
        if (HasMouse() && IsCursorVisible())
        {
            Vector3 mousePos = MousePosition();

            // mouse move
            if ((_camera == null || _activeDisplay == _camera.targetDisplay) && _mousePos != mousePos)
            {
                _mousePos = mousePos;

                UnityEngine.Vector2 mouse = ProjectPointer(_mousePos.x, _mousePos.y);
                _uiView.MouseMove((int)mouse.x, (int)mouse.y);
            }
        }
    }

    private void UpdateTouch()
    {
      #if ENABLE_LEGACY_INPUT_MANAGER
        for (int i = 0; i < UnityEngine.Input.touchCount; i++) 
        {
            UnityEngine.Touch touch = UnityEngine.Input.GetTouch(i);
            UnityEngine.Vector2 pos = ProjectPointer(touch.position.x, touch.position.y);
            UnityEngine.TouchPhase phase = touch.phase;

            if (phase == UnityEngine.TouchPhase.Began)
            {
                _uiView.TouchDown((int)pos.x, (int)pos.y, (uint)touch.fingerId);
            }
            else if (phase == UnityEngine.TouchPhase.Moved || phase == UnityEngine.TouchPhase.Stationary)
            {
                _uiView.TouchMove((int)pos.x, (int)pos.y, (uint)touch.fingerId);
            }
            else
            {
                _uiView.TouchUp((int)pos.x, (int)pos.y, (uint)touch.fingerId);
            }
        }
      #else
        if (UnityEngine.InputSystem.Touchscreen.current != null)
        {
            foreach (var touch in UnityEngine.InputSystem.EnhancedTouch.Touch.activeTouches)
            {
                UnityEngine.Vector2 pos = ProjectPointer(touch.screenPosition.x, touch.screenPosition.y);

                if (touch.began)
                {
                    _uiView.TouchDown((int)pos.x, (int)pos.y, (uint)touch.touchId);
                }
                else if (touch.ended)
                {
                    _uiView.TouchUp((int)pos.x, (int)pos.y, (uint)touch.touchId);
                }
                else
                {
                    _uiView.TouchMove((int)pos.x, (int)pos.y, (uint)touch.touchId);
                }
            }
        }
      #endif
    }

    [FlagsAttribute]
    enum ActionButtons
    {
         Up = 1,
         Down = 2,
         Left = 4,
         Right = 8,
         Accept = 16,
         Cancel = 32,
         Menu = 64,
         View = 128,
         PageUp = 256,
         PageDown = 512,
         PageLeft = 1024,
         PageRight = 2048
    }

    private struct ButtonState
    {
        public ActionButtons button;
        public Noesis.Key key;
        public float t;
    }

    private ButtonState[] _buttonStates = new ButtonState[]
    {
        new ButtonState { button = ActionButtons.Up, key = Key.GamepadUp },
        new ButtonState { button = ActionButtons.Down, key = Key.GamepadDown },
        new ButtonState { button = ActionButtons.Left, key = Key.GamepadLeft },
        new ButtonState { button = ActionButtons.Right, key = Key.GamepadRight },
        new ButtonState { button = ActionButtons.Accept, key = Key.GamepadAccept },
        new ButtonState { button = ActionButtons.Cancel, key = Key.GamepadCancel },
        new ButtonState { button = ActionButtons.Menu, key = Key.GamepadMenu},
        new ButtonState { button = ActionButtons.View, key = Key.GamepadView },
        new ButtonState { button = ActionButtons.PageUp, key = Key.GamepadPageUp },
        new ButtonState { button = ActionButtons.PageDown, key = Key.GamepadPageDown },
        new ButtonState { button = ActionButtons.PageLeft, key = Key.GamepadPageLeft },
        new ButtonState { button = ActionButtons.PageRight, key = Key.GamepadPageRight },
    };

    private ActionButtons _actionButtons = 0;

    private void UpdateActions(float t)
    {
        int x = int.MaxValue;
        int y = int.MaxValue;
        bool trackedPos = false;

        if (_trackedPositionAction?.activeControl != null && _trackedRotationAction?.activeControl != null)
        {
            Vector3 pos_ = _trackedPositionAction.ReadValue<Vector3>();
            Vector3 dir_ = _trackedRotationAction.ReadValue<Quaternion>() * Vector3.forward;

            if (_xrTrackingOrigin != null)
            {
                pos_ = _xrTrackingOrigin.TransformPoint(pos_);
                dir_ = _xrTrackingOrigin.TransformVector(dir_);
            }

            Point3D pos = new Point3D(pos_.x, pos_.y, pos_.z);
            Vector3D dir = new Vector3D(dir_.x, dir_.y, dir_.z);

            var captured = Content.Mouse.Captured;
            if (captured != null)
            {
                // Visuals capturing the mouse must always utilize its 3D plane for hit testing
                // https://www.noesisengine.com/bugs/view.php?id=2837
                if (VisualTreeHelper.IntersectPlane(captured, pos, dir, out Point3D worldPos))
                {
                    Noesis.Matrix4 mtx = CameraMatrix(_camera);
                    Noesis.Vector4 cameraPos = new Noesis.Vector4(worldPos.X, worldPos.Y, worldPos.Z, 1.0f) * mtx;

                    x = (int)(cameraPos.X / cameraPos.W);
                    y = (int)(cameraPos.Y / cameraPos.W);

                    trackedPos = true;
                }
            }
            else
            {
                Visual root = (Visual)VisualTreeHelper.GetRoot(_uiView.Content);
                var hit = VisualTreeHelper.HitTest3D(root, pos, dir);

                if (hit.VisualHit != null)
                {
                    Noesis.Matrix4 mtx = CameraMatrix(_camera);
                    Noesis.Vector4 cameraPos = new Noesis.Vector4(hit.WorldPos.X, hit.WorldPos.Y, hit.WorldPos.Z, 1.0f) * mtx;

                    x = (int)(cameraPos.X / cameraPos.W);
                    y = (int)(cameraPos.Y / cameraPos.W);

                    trackedPos = true;
                }
            }

            MouseMove(x, y);
        }

        if (_trackedTriggerAction != null)
        {
            if (_trackedTriggerAction.WasPressedThisFrame())
            {
                MouseButtonDown(x, y, Noesis.MouseButton.Left);
            }

            if (_trackedTriggerAction.WasReleasedThisFrame())
            {
                MouseButtonUp(x, y, Noesis.MouseButton.Left);
            }
        }

        if (_scrollAction != null)
        {
            Vector2 v = _scrollAction.ReadValue<Vector2>();

            if (trackedPos)
            {
                if (v.y != 0.0f) _uiView.Scroll(x, y, v.y);
                if (v.x != 0.0f) _uiView.HScroll(x, y, v.x);
            }
            else
            {
                if (v.y != 0.0f) _uiView.Scroll(v.y);
                if (v.x != 0.0f) _uiView.HScroll(v.x);
            }
        }

        ActionButtons actionButtons = 0;

        if (_upAction != null && _upAction.IsPressed()) actionButtons |= ActionButtons.Up;
        if (_downAction != null && _downAction.IsPressed()) actionButtons |= ActionButtons.Down;
        if (_leftAction != null && _leftAction.IsPressed()) actionButtons |= ActionButtons.Left;
        if (_rightAction != null && _rightAction.IsPressed()) actionButtons |= ActionButtons.Right;

        if (_acceptAction != null && _acceptAction.IsPressed()) actionButtons |= ActionButtons.Accept;
        if (_cancelAction != null && _cancelAction.IsPressed()) actionButtons |= ActionButtons.Cancel;

        if (_menuAction != null && _menuAction.IsPressed()) actionButtons |= ActionButtons.Menu;
        if (_viewAction != null && _viewAction.IsPressed()) actionButtons |= ActionButtons.View;

        if (_pageUpAction != null && _pageUpAction.IsPressed()) actionButtons |= ActionButtons.PageUp;
        if (_pageDownAction != null && _pageDownAction.IsPressed()) actionButtons |= ActionButtons.PageDown;
        if (_pageLeftAction != null && _pageLeftAction.IsPressed()) actionButtons |= ActionButtons.PageLeft;
        if (_pageRightAction != null && _pageRightAction.IsPressed()) actionButtons |= ActionButtons.PageRight;

        ActionButtons delta = actionButtons ^ _actionButtons;
        if (delta != 0 || actionButtons != 0)
        {
            for (int i = 0; i < _buttonStates.Length; i++)
            {
                if ((delta & _buttonStates[i].button) > 0)
                {
                    if ((actionButtons & _buttonStates[i].button) > 0)
                    {
                        _uiView.KeyDown(_buttonStates[i].key);
                        _buttonStates[i].t = t + _actionsRepeatDelay;
                    }
                    else
                    {
                        _uiView.KeyUp(_buttonStates[i].key);
                    }
                }
                else if ((actionButtons & _buttonStates[i].button) > 0)
                {
                    if (t >= _buttonStates[i].t)
                    {
                        _uiView.KeyDown(_buttonStates[i].key);
                        _buttonStates[i].t = t + _actionsRepeatRate;
                    }
                }
            }
        }

         _actionButtons = actionButtons;
    }

    private void UpdateInputs(float t)
    {
        if (_enableMouse)
        {
            UpdateMouse();
        }

        if (_enableTouch)
        {
            UpdateTouch();
        }

        if (_enableActions)
        {
            UpdateActions(t);
        }
    }

    private int _viewSizeX;
    private int _viewSizeY;
    private float _viewScale;
    private float _viewStereoScale = 1.0f;

    private void UpdateSize()
    {
        int sizeX = 0;
        int sizeY = 0;

        if (_camera != null)
        {
            sizeX = _camera.pixelWidth;
            sizeY = _camera.pixelHeight;
        }
        else if (_texture != null)
        {
            sizeX = _texture.width;
            sizeY = _texture.height;
        }

        if (sizeX != _viewSizeX || sizeY != _viewSizeY)
        {
            _uiView.SetSize(sizeX, sizeY);
            _viewSizeX = sizeX;
            _viewSizeY = sizeY;
        }

        float scale = (!WorldSpace && _dpiScale && Screen.dpi > 0.0f) ? Screen.dpi / 96.0f : 1.0f;

        if (scale != _viewScale)
        {
            _uiView.SetScale(scale);
            _viewScale = scale;
        }

        if (_stereoScale != _viewStereoScale)
        {
            _uiView.SetStereoOffscreenScaleFactor(_stereoScale);
            _viewStereoScale = _stereoScale;
        }
    }

    private bool _visible = true;

    void LateUpdate()
    {
        if (!_enableExternalUpdate)
        {
            UpdateInternal();
        }
    }

    public void ExternalUpdate()
    {
        Debug.Assert(_enableExternalUpdate, "Calling ExternalUpdate() with EnableExternalUpdate disabled", this);
        UpdateInternal();
    }

    private static Noesis.Matrix4 NoesisMatrix(Matrix4x4 viewMatrix, Matrix4x4 projectionMatrix, float w, float h)
    {
        float hw = 0.5f * w;
        float hh = 0.5f * h;

        Matrix4x4 noesisMatrix;

        if (SystemInfo.usesReversedZBuffer)
        {
            noesisMatrix = new Matrix4x4
            (
                new UnityEngine.Vector4(hw,  0,  0,    0),
                new UnityEngine.Vector4(0, -hh,  0,    0),
                new UnityEngine.Vector4(0,   0, -0.5f, 0),
                new UnityEngine.Vector4(hw, hh,  0.5f, 1)
            );
        }
        else
        {
            noesisMatrix = new Matrix4x4
            (
                new UnityEngine.Vector4(hw,  0, 0,    0),
                new UnityEngine.Vector4(0, -hh, 0,    0),
                new UnityEngine.Vector4(0,   0, 0.5f, 0),
                new UnityEngine.Vector4(hw, hh, 0.5f, 1)
            );
        }

        Matrix4x4 _ = noesisMatrix * projectionMatrix * viewMatrix;

        return new Matrix4
        (
            _.m00, _.m10, _.m20, _.m30,
            _.m01, _.m11, _.m21, _.m31,
            _.m02, _.m12, _.m22, _.m32,
            _.m03, _.m13, _.m23, _.m33
        );
    }

    private static Noesis.Matrix4 CameraMatrix(Camera camera)
    {
        return NoesisMatrix(camera.worldToCameraMatrix, camera.projectionMatrix, camera.pixelWidth, camera.pixelHeight);
    }

    private static Matrix4 CameraStereoMatrix(Camera camera, Camera.StereoscopicEye eye)
    {
        Matrix4x4 viewMatrix = camera.GetStereoViewMatrix(eye);
        Matrix4x4 projectionMatrix = camera.GetStereoProjectionMatrix(eye);

        return NoesisMatrix(viewMatrix, projectionMatrix, camera.pixelWidth, camera.pixelHeight);
    }

    private static Matrix4 CameraActiveStereoMatrix(Camera camera)
    {
        Camera.StereoscopicEye eye;

        if (camera.stereoActiveEye == Camera.MonoOrStereoscopicEye.Left)
        {
            eye = Camera.StereoscopicEye.Left;
        }
        else
        {
            Debug.Assert(camera.stereoActiveEye == Camera.MonoOrStereoscopicEye.Right);
            eye = Camera.StereoscopicEye.Right;
        }

        return CameraStereoMatrix(camera, eye);
    }

    private void UpdateInternal()
    {
        if (_uiView != null && _visible)
        {
            Profiling.UpdateSampler.Begin();

            if (_camera != null && WorldSpace)
            {
                _uiView.SetProjectionMatrix(CameraMatrix(_camera));
            }

            float t = Time.realtimeSinceStartup;

            UpdateSize();
            UpdateInputs(t);

            NoesisUnity.IME.Update(_uiView);
            NoesisUnity.TouchKeyboard.Update();

            Noesis_UnityUpdate();
            _needsRendering = _uiView.Update(_useRealTimeClock ? t : Time.time);

            Profiling.UpdateSampler.End();

            if (_needsRendering)
            {
                _commands.name = Profiling.UpdateRenderTree;
                NoesisRenderer.UpdateRenderTree(_uiView, _commands);

                Graphics.ExecuteCommandBuffer(_commands);
                _commands.Clear();
            }

            if (_camera == null && _texture != null)
            {
                if (_continuousRendering || _needsRendering)
                {
                    _commands.name = Profiling.RenderTexture;
                    NoesisRenderer.RenderOffscreen(_uiView, _commands, false);
                    _commands.SetRenderTarget(_texture, LoadAction.DontCare, StoreAction.Store, LoadAction.DontCare, StoreAction.DontCare);
                    _commands.ClearRenderTarget(true, true, UnityEngine.Color.clear, 0.0f);
                    NoesisRenderer.RenderOnscreen(_uiView, !IsGL(), _commands, false, _clearStencil);

                    Graphics.ExecuteCommandBuffer(_commands);
                    _commands.Clear();

                    GL.InvalidateState();
                    _texture.DiscardContents(false, true);
                }
            }
        }
    }

    void OnBecameInvisible()
    {
        if (_uiView != null && _texture != null)
        {
            _visible = false;
        }
    }

    void OnBecameVisible()
    {
        if (_uiView != null && _texture != null)
        {
            _visible = true;
        }
    }

    private void RenderOffscreen(CommandBuffer commands)
    {
        NoesisRenderer.RenderOffscreen(_uiView, _commands, false);
    }

    private void RenderOnScreen(bool flipY, CommandBuffer commands)
    {
        if (_camera != null && _camera.stereoEnabled)
        {
          #if (ENABLE_VR_MODULE && ENABLE_VR)
            if (XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.MultiPass)
            {
                NoesisRenderer.RenderOnscreen(_uiView, CameraActiveStereoMatrix(_camera),
                    flipY, commands, false, _clearStencil);
            }
            else
            {
                NoesisRenderer.RenderOnscreen(_uiView, CameraMatrix(_camera),
                    CameraStereoMatrix(_camera, Camera.StereoscopicEye.Left),
                    CameraStereoMatrix(_camera, Camera.StereoscopicEye.Right),
                    flipY, commands, false, _clearStencil);
            }
          #endif
        }
        else
        {
            NoesisRenderer.RenderOnscreen(_uiView, flipY, commands, false, _clearStencil);
        }
    }

    private bool _updatePending = true;

    private void PreRender(Camera cam)
    {
        if (_camera != null)
        {
            // In case there are several cameras rendering to the same texture (Camera Stacking),
            // the camera rendered first (less depth) is the one that must apply our offscreen phase
            // to avoid inefficient Load/Store in Tiled architectures
            if (_updatePending && cam.targetTexture == _camera.targetTexture && cam.depth <= _camera.depth)
            {
                if (_uiView != null && _visible)
                {
                    _commands.name = Profiling.RenderOffScreen;
                    RenderOffscreen(_commands);

                    Graphics.ExecuteCommandBuffer(_commands);
                    _commands.Clear();

                    GL.InvalidateState();
                    ForceRestoreCameraRenderTarget();
                }

                _updatePending = false;
            }
        }
    }

    private void ForceRestoreCameraRenderTarget()
    {
        // Unity should automatically restore the render target but sometimes (for example a scene without lights)
        // it doesn't. We use this hack to flush the active render target and force unity to set the camera RT afterward
        RenderTexture surface = RenderTexture.GetTemporary(1,1);
        Graphics.SetRenderTarget(surface);
        RenderTexture.ReleaseTemporary(surface);
    }

    private static bool IsGL()
    {
        var type = SystemInfo.graphicsDeviceType;

      #if UNITY_2023_1_OR_NEWER
        return type == GraphicsDeviceType.OpenGLES3 || type == GraphicsDeviceType.OpenGLCore;
      #else
        return type == GraphicsDeviceType.OpenGLES2 || type == GraphicsDeviceType.OpenGLES3
            || type == GraphicsDeviceType.OpenGLCore;
      #endif
    }

    private bool IsEyeTexture(RenderTexture texture)
    {
        // In VR the Swap Chain is named 'XR Texture[#]' (before Unity 2020 it was 'RTDeviceEyeTextureArray')
        return texture.name.StartsWith("XR Texture");
    }

    private bool FlipRender()
    {
        // In D3D when Unity is rendering to an intermediate texture instead of the back buffer, we need to vertically flip the output
        // Note that camera.activeTexture should only be checked from OnPostRender
        if (!IsGL())
        {
          #if ENABLE_VR && ENABLE_XR_MODULE
            return _camera.activeTexture != null && !IsEyeTexture(_camera.activeTexture);
          #else
            return _camera.activeTexture != null;
          #endif
        }

        return false;
    }

    private void OnPostRender()
    {
        if (_uiView != null && _visible)
        {
            _commands.name = Profiling.RenderOnScreen;
            RenderOnScreen(FlipRender(), _commands);

            Graphics.ExecuteCommandBuffer(_commands);
            _commands.Clear();

            GL.InvalidateState();
            _updatePending = true;
        }
    }

    private UnityEngine.EventModifiers _modifiers = 0;

    private void ProcessModifierKey(EventModifiers modifiers, EventModifiers delta, EventModifiers flag, Noesis.Key key)
    {
        if ((delta & flag) > 0)
        {
            if ((modifiers & flag) > 0)
            {
                _uiView.KeyDown(key);
            }
            else
            {
                _uiView.KeyUp(key);
            }
        }
    }

    private bool HitTest(float x, float y)
    {
        Visual root = (Visual)VisualTreeHelper.GetRoot(_uiView.Content);
        Point p = root.PointFromScreen(new Point(x, y));

        // Comment this define at the top of the file if you want to use the old behavior
      #if IGNORE_ISHITTESTVISIBLE_FALSE_ELEMENTS
        return Noesis_UnityHitTest(BaseComponent.getCPtr(root), ref p);
      #else
        return VisualTreeHelper.HitTest(root, p).VisualHit != null;
      #endif
    }

#if !UNITY_EDITOR && UNITY_STANDALONE_OSX
    private static int lastFrame;
    private static Noesis.Key lastKeyDown;
#endif

    private bool MouseEmulated()
    {
      #if ENABLE_LEGACY_INPUT_MANAGER
        return Input.simulateMouseWithTouches && Input.touchCount > 0;
      #else
        // Unfortunately, in the new InputSystem when the emulated mousedown is sent, the number of 
        // touches is zero. So for now, the only workaround is checking if there is any touch screen device.
        // In system when both mouse and touch are available, simulateMouseWithTouches must be disabled
        return Input.simulateMouseWithTouches && UnityEngine.InputSystem.Touchscreen.current != null;
      #endif
    }

    private void ProcessEvent(UnityEngine.Event ev, bool enableKeyboard, bool enableMouse)
    {
        // Process keyboard modifiers
        if (enableKeyboard)
        {
            EventModifiers delta = ev.modifiers ^ _modifiers;
            if (delta > 0)
            {
                _modifiers = ev.modifiers;

                ProcessModifierKey(ev.modifiers, delta, EventModifiers.Shift, Key.LeftShift);
                ProcessModifierKey(ev.modifiers, delta, EventModifiers.Control, Key.LeftCtrl);
                ProcessModifierKey(ev.modifiers, delta, EventModifiers.Command, Key.LeftCtrl);
                ProcessModifierKey(ev.modifiers, delta, EventModifiers.Alt, Key.LeftAlt);
            }
        }

        switch (ev.type)
        {
            case UnityEngine.EventType.MouseDown:
            {
                if (enableMouse && IsCursorVisible())
                {
                    UnityEngine.Vector2 mouse = ProjectPointer(ev.mousePosition.x, UnityEngine.Screen.height - ev.mousePosition.y);

                    if (HitTest(mouse.x, mouse.y))
                    {
                        ev.Use();
                    }

                    if (!MouseEmulated())
                    {
                        if (ev.clickCount == 1)
                        {
                            _uiView.MouseButtonDown((int)mouse.x, (int)mouse.y, (Noesis.MouseButton)ev.button);
                        }
                        else
                        {
                            _uiView.MouseDoubleClick((int)mouse.x, (int)mouse.y, (Noesis.MouseButton)ev.button);
                        }
                    }
                }
                break;
            }
            case UnityEngine.EventType.MouseUp:
            {
                if (enableMouse && IsCursorVisible())
                {
                    UnityEngine.Vector2 mouse = ProjectPointer(ev.mousePosition.x, UnityEngine.Screen.height - ev.mousePosition.y);

                    if (HitTest(mouse.x, mouse.y))
                    {
                        ev.Use();
                    }

                    if (!MouseEmulated())
                    {
                        _uiView.MouseButtonUp((int)mouse.x, (int)mouse.y, (Noesis.MouseButton)ev.button);
                    }
                }
                break;
            }
            case UnityEngine.EventType.ScrollWheel:
            {
                if (enableMouse && IsCursorVisible())
                {
                    UnityEngine.Vector2 mouse = ProjectPointer(ev.mousePosition.x, UnityEngine.Screen.height - ev.mousePosition.y);

                    if (ev.delta.y != 0.0f)
                    {
                        _uiView.MouseWheel((int)mouse.x, (int)mouse.y, -(int)(ev.delta.y * 40.0f));
                    }

                    if (ev.delta.x != 0.0f)
                    {
                        _uiView.MouseHWheel((int)mouse.x, (int)mouse.y, (int)(ev.delta.x * 40.0f));
                    }
                }
                break;
            }
            case UnityEngine.EventType.KeyDown:
            {
                if (enableKeyboard)
                {
                    // Don't process key when IME composition is being used
                    if (ev.keyCode != KeyCode.None && NoesisUnity.IME.compositionString == "")
                    {
                        Noesis.Key noesisKeyCode = NoesisKeyCodes.Convert(ev.keyCode);
                        if (noesisKeyCode != Noesis.Key.None)
                        {
                          #if !UNITY_EDITOR && UNITY_STANDALONE_OSX
                            // In OSX Standalone, CMD + key always sends two KeyDown events for the key.
                            // This seems to be a bug in Unity. 
                            if (!ev.command || lastFrame != Time.frameCount || lastKeyDown != noesisKeyCode)
                            {
                                lastFrame = Time.frameCount;
                                lastKeyDown = noesisKeyCode;
                          #endif
                                _uiView.KeyDown(noesisKeyCode);
                          #if !UNITY_EDITOR && UNITY_STANDALONE_OSX
                            }
                          #endif
                        }
                    }

                    if (ev.character != 0)
                    {
                        // Filter out character events when CTRL is down
                        bool isControl = (_modifiers & EventModifiers.Control) != 0 || (_modifiers & EventModifiers.Command) != 0;
                        bool isAlt = (_modifiers & EventModifiers.Alt) != 0;
                        bool filter = isControl && !isAlt;

                        if (!filter)
                        {
                          #if !UNITY_EDITOR && UNITY_STANDALONE_LINUX
                            // It seems that linux is sending KeySyms instead of Unicode points
                            // https://github.com/substack/node-keysym/blob/master/data/keysyms.txt
                            ev.character = NoesisKeyCodes.KeySymToUnicode(ev.character);
                          #endif
                            _uiView.Char((uint)ev.character);
                        }
                    }

                }
                break;
            }
            case UnityEngine.EventType.KeyUp:
            {
                // Don't process key when IME composition is being used
                if (enableKeyboard)
                {
                    if (ev.keyCode != KeyCode.None && NoesisUnity.IME.compositionString == "")
                    {
                        Noesis.Key noesisKeyCode = NoesisKeyCodes.Convert(ev.keyCode);
                        if (noesisKeyCode != Noesis.Key.None)
                        {
                            _uiView.KeyUp(noesisKeyCode);
                        }
                    }
                }
                break;
            }
        }
    }

    void OnGUI()
    {
        if (_uiView != null && (_camera == null || _activeDisplay == _camera.targetDisplay))
        {
            if (_camera)
            {
                UnityEngine.GUI.depth = -(int)_camera.depth;
            }

            ProcessEvent(UnityEngine.Event.current, _enableKeyboard, _enableMouse);
        }
    }

    void OnApplicationFocus(bool focused)
    {
        if (_uiView != null)
        {
            if (NoesisUnity.TouchKeyboard.keyboard == null)
            {
                if (focused)
                {
                    _uiView.Activate();
                }
                else
                {
                    _uiView.Deactivate();
                }
            }
        }
    }
#endregion

    private void CreateView(FrameworkElement content)
    {
        if (_uiView == null)
        {
            // Send settings for the internal device, created by the first view
            NoesisRenderer.SetRenderSettings();

            _viewSizeX = 0;
            _viewSizeY = 0;
            _viewScale = 1.0f;

            _uiView = new Noesis.View(content);
            _uiView.SetTessellationMaxPixelError(_tessellationMaxPixelError);
            _uiView.SetEmulateTouch(_emulateTouch);
            _uiView.SetFlags(_renderFlags);

            _commands.name = Profiling.RegisterView;
            NoesisRenderer.RegisterView(_uiView, _commands);
            Graphics.ExecuteCommandBuffer(_commands);
            _commands.Clear();

          #if UNITY_EDITOR
            UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += DestroyView;
          #endif
        }
    }

    private void DestroyView()
    {
        if (_uiView != null)
        {
            _commands.name = Profiling.UnregisterView;
            NoesisRenderer.UnregisterView(_uiView, _commands);
            Graphics.ExecuteCommandBuffer(_commands);
            _commands.Clear();

            _uiView = null;
        }
    }

    public void OnBeforeSerialize() {}

    public void OnAfterDeserialize()
    {
        // (3.0) PPAA flag is now in view render flags 
        if (_isPPAAEnabled)
        {
            _renderFlags |= RenderFlags.PPAA;
            _isPPAAEnabled = false;
        }
    }

    private Noesis.View _uiView;
    private bool _needsRendering = false;
    private float _stereoScale = 1.0f;

#region Serialized properties
    [SerializeField] private NoesisXaml _xaml;
    [SerializeField] private RenderTexture _texture;

    [SerializeField] private bool _isPPAAEnabled = true;
    [SerializeField] private float _tessellationMaxPixelError = Noesis.TessellationMaxPixelError.MediumQuality.Error;
    [SerializeField] private RenderFlags _renderFlags = 0;
    [SerializeField] private bool _dpiScale = true;
    [SerializeField] private bool _continuousRendering = true;
    [SerializeField] private bool _enableExternalUpdate = false;
    [SerializeField] private bool _enableKeyboard = true;
    [SerializeField] private bool _enableMouse = true;
    [SerializeField] private bool _enableTouch = true;
    [UnityEngine.Serialization.FormerlySerializedAs("_enableGamepad")]
    [SerializeField] private bool _enableActions = false;
    [SerializeField] private bool _emulateTouch = false;
    [SerializeField] private bool _useRealTimeClock = false;
    [SerializeField] private bool _clearStencil = false;

    [SerializeField] private UnityEngine.InputSystem.InputActionAsset _actions;
    [SerializeField] private string _actionMap = "Gamepad";

    [UnityEngine.Serialization.FormerlySerializedAs("_gamepadRepeatDelay")]
    [SerializeField] private float _actionsRepeatDelay = 0.5f;

    [UnityEngine.Serialization.FormerlySerializedAs("_gamepadRepeatRate")]
    [SerializeField] private float _actionsRepeatRate = 0.1f;

    [SerializeField] private UnityEngine.Transform _xrTrackingOrigin;

  #if ENABLE_URP_PACKAGE
    [SerializeField] private RenderPassEvent _renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
  #endif

  #if ENABLE_HDRP_PACKAGE
    [SerializeField] private CustomPassInjectionPoint _injectionPoint = CustomPassInjectionPoint.AfterPostProcess;
  #endif

    private UnityEngine.InputSystem.InputAction _upAction;
    private UnityEngine.InputSystem.InputAction _downAction;
    private UnityEngine.InputSystem.InputAction _leftAction;
    private UnityEngine.InputSystem.InputAction _rightAction;

    private UnityEngine.InputSystem.InputAction _acceptAction;
    private UnityEngine.InputSystem.InputAction _cancelAction;

    private UnityEngine.InputSystem.InputAction _menuAction;
    private UnityEngine.InputSystem.InputAction _viewAction;

    private UnityEngine.InputSystem.InputAction _pageLeftAction;
    private UnityEngine.InputSystem.InputAction _pageRightAction;
    private UnityEngine.InputSystem.InputAction _pageUpAction;
    private UnityEngine.InputSystem.InputAction _pageDownAction;
    private UnityEngine.InputSystem.InputAction _scrollAction;

    private UnityEngine.InputSystem.InputAction _trackedPositionAction;
    private UnityEngine.InputSystem.InputAction _trackedRotationAction;
    private UnityEngine.InputSystem.InputAction _trackedTriggerAction;
#endregion

#region Imports
    [DllImport(Library.Name)]
    private static extern void Noesis_UnityUpdate();

    [DllImport(Library.Name)]
    private static extern bool Noesis_UnityHitTest(HandleRef root, ref Point point);
#endregion

#endregion
}
NoesisView.cs (72,652 bytes)   
jsantos

jsantos

2025-05-15 02:02

manager   ~0010659

Related ticket 0004168

Issue History

Date Modified Username Field Change
2025-01-18 22:35 Daxten New Issue
2025-01-18 22:36 Daxten Description Updated
2025-01-20 18:05 jsantos Assigned To => jsantos
2025-01-20 18:05 jsantos Status new => assigned
2025-01-20 18:05 jsantos Target Version => 3.2.8
2025-05-15 01:54 jsantos Note Added: 0010658
2025-05-15 01:54 jsantos File Added: Noesis.NoesisGUI.Editor.asmdef
2025-05-15 01:54 jsantos File Added: NoesisViewEditor.cs
2025-05-15 01:54 jsantos File Added: NoesisView.cs
2025-05-15 02:01 jsantos Relationship added related to 0004168
2025-05-15 02:01 jsantos Status assigned => resolved
2025-05-15 02:01 jsantos Resolution open => fixed
2025-05-15 02:01 jsantos Fixed in Version => 3.2.8
2025-05-15 02:02 jsantos Note Added: 0010659
2025-05-15 02:07 jsantos Product Version 3.2 => 3.2.7
2025-10-10 13:29 jsantos Category Unity3D => Unity