#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 /// /// User interface definition XAML /// public NoesisXaml Xaml { set { this._xaml = value; } get { return this._xaml; } } /// /// The texture to render this View into /// public RenderTexture Texture { set { this._texture = value; } get { return this._texture; } } /// /// 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 /// public float TessellationMaxPixelError { set { if (_uiView != null) { _uiView.SetTessellationMaxPixelError(value); } this._tessellationMaxPixelError = value; } get { if (_uiView != null) { return _uiView.GetTessellationMaxPixelError().Error; } return this._tessellationMaxPixelError; } } /// /// Bit flags used for debug rendering purposes. /// public RenderFlags RenderFlags { set { if (_uiView != null) { _uiView.SetFlags(value); } this._renderFlags = value; } get { if (_uiView != null) { return _uiView.GetFlags(); } return this._renderFlags; } } /// /// When enabled, the UI is positioned in the world among other objects in the Scene /// public bool WorldSpace { get { return (RenderFlags & RenderFlags.DepthTesting) > 0; } } /// /// 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. /// public float StereoOffscreenScaleFactor { set { this._stereoScale = value; } get { return this._stereoScale; } } /// /// When enabled, the view is scaled by the actual DPI of the screen or physical device running the application /// public bool DPIScale { set { this._dpiScale = value; } get { return this._dpiScale; } } /// /// 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. /// public bool ContinuousRendering { set { this._continuousRendering = value; } get { return this._continuousRendering; } } /// /// When enabled, the view must be explicitly updated by calling 'ExternalUpdate()'. /// By default, the view is automatically updated during LateUpdate. /// public bool EnableExternalUpdate { set { this._enableExternalUpdate = value; } get { return this._enableExternalUpdate; } } /// /// 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. /// public bool NeedsRendering { set { this._needsRendering = value; } get { return this._needsRendering; } } /// /// Enables keyboard input management. /// public bool EnableKeyboard { set { this._enableKeyboard = value; } get { return this._enableKeyboard; } } /// /// Enables mouse input management. /// public bool EnableMouse { set { this._enableMouse = value; } get { return this._enableMouse; } } /// /// Enables touch input management. /// public bool EnableTouch { set { this._enableTouch = value; } get { return this._enableTouch; } } /// /// Enables actions input management. /// public bool EnableActions { get { return _enableActions; } set { if (_enableActions != value) { _enableActions = value; ReloadActions(_actions, _actionMap, _actionsBound); } } } /// /// Input System actions. /// public UnityEngine.InputSystem.InputActionAsset Actions { get { return _actions; } set { if (_actions != value) { ReloadActions(value, _actionMap, _actionsBound); } } } /// /// Set of actions being used by this view and enabled by default. /// public string ActionMap { get { return _actionMap; } set { if (_actionMap != value) { ReloadActions(_actions, value, _actionsBound); } } } /// /// The initial delay (in seconds) between an initial button action and a repeated action. /// public float ActionsRepeatDelay { set { this._actionsRepeatDelay = value; } get { return this._actionsRepeatDelay; } } /// /// The speed (in seconds) that the button action repeats itself once repeating. /// public float ActionsRepeatRate { set { this._actionsRepeatRate = value; } get { return this._actionsRepeatRate; } } /// /// Transform representing the real world origin for tracking devices /// public UnityEngine.Transform XRTrackingOrigin { set { this._xrTrackingOrigin = value; } get { return this._xrTrackingOrigin; } } #if ENABLE_URP_PACKAGE /// /// Controls when the UI render pass executes /// public RenderPassEvent RenderPassEvent { set { this._renderPassEvent = value; } get { return this._renderPassEvent; } } #endif #if ENABLE_HDRP_PACKAGE /// /// Controls when the UI render pass executes /// public CustomPassInjectionPoint InjectionPoint { set { this._injectionPoint = value; } get { return this._injectionPoint; } } #endif /// /// Emulate touch input with mouse. /// public bool EmulateTouch { set { if (_uiView != null) { _uiView.SetEmulateTouch(value); } this._emulateTouch = value; } get { return this._emulateTouch; } } /// /// When enabled, UI is updated using Time.realtimeSinceStartup. /// public bool UseRealTimeClock { set { this._useRealTimeClock = value; } get { return this._useRealTimeClock; } } /// /// When enabled, then stencil buffer is cleared before rendering this view /// public bool ClearStencil { set { this._clearStencil = value; } get { return this._clearStencil; } } /// /// Gets the root of the loaded Xaml. /// /// Root element. public FrameworkElement Content { get { return _uiView != null ? _uiView.Content : null; } } /// /// Indicates if this component is rendering UI to a RenderTexture. /// /// 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 /// /// Notifies Renderer that a key was pressed. /// /// Key identifier. public bool KeyDown(Noesis.Key key) { if (_uiView != null) { return _uiView.KeyDown(key); } return false; } /// /// Notifies Renderer that a key was released. /// /// Key identifier. public bool KeyUp(Noesis.Key key) { if (_uiView != null) { return _uiView.KeyUp(key); } return false; } /// /// Notifies Renderer that a key was translated to the corresponding character. /// /// Unicode character value. public bool Char(uint ch) { if (_uiView != null) { return _uiView.Char(ch); } return false; } #endregion #region Mouse input events /// /// Notifies Renderer that mouse was moved. The mouse position is specified in renderer /// surface pixel coordinates. /// /// Mouse x-coordinate. /// Mouse y-coordinate. public bool MouseMove(int x, int y) { if (_uiView != null) { return _uiView.MouseMove(x, y); } return false; } /// /// Notifies Renderer that a mouse button was pressed. The mouse position is specified in /// renderer surface pixel coordinates. /// /// Mouse x-coordinate. /// Mouse y-coordinate. /// Indicates which button was pressed. 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. /// /// Mouse x-coordinate. /// Mouse y-coordinate. /// Indicates which button was released. public bool MouseButtonUp(int x, int y, Noesis.MouseButton button) { if (_uiView != null) { return _uiView.MouseButtonUp(x, y, button); } return false; } /// /// Notifies Renderer of a mouse button double click. The mouse position is specified in /// renderer surface pixel coordinates. /// /// Mouse x-coordinate. /// Mouse y-coordinate. /// Indicates which button was pressed. public bool MouseDoubleClick(int x, int y, Noesis.MouseButton button) { if (_uiView != null) { return _uiView.MouseDoubleClick(x, y, button); } return false; } /// /// Notifies Renderer that mouse wheel was rotated. The mouse position is specified in /// renderer surface pixel coordinates. /// /// Mouse x-coordinate. /// Mouse y-coordinate. /// Indicates the amount mouse wheel has changed. 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 /// /// Notifies Renderer that a finger is moving on the screen. The finger position is /// specified in renderer surface pixel coordinates. /// /// Finger x-coordinate. /// Finger y-coordinate. /// Finger identifier. public bool TouchMove(int x, int y, uint touchId) { if (_uiView != null) { return _uiView.TouchMove(x, y, touchId); } return false; } /// /// Notifies Renderer that a finger touches the screen. The finger position is /// specified in renderer surface pixel coordinates. /// /// Finger x-coordinate. /// Finger y-coordinate. /// Finger identifier. public bool TouchDown(int x, int y, uint touchId) { if (_uiView != null) { return _uiView.TouchDown(x, y, touchId); } return false; } /// /// Notifies Renderer that a finger is raised off the screen. The finger position is /// specified in renderer surface pixel coordinates. /// /// Finger x-coordinate. /// Finger y-coordinate. /// Finger identifier. 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 /// /// Loads the user interface specified in the XAML property /// 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 /// /// Called once when component is attached to GameObject for the first time /// 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"); _context1Action = actionMap.FindAction("Context1"); _context2Action = actionMap.FindAction("Context2"); _context3Action = actionMap.FindAction("Context3"); _context4Action = actionMap.FindAction("Context4"); _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; _context1Action = default; _context2Action = default; _context3Action = default; _context4Action = 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(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 #if ENABLE_HDRP_PACKAGE EnableHDRPCustomPass(); #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; #if ENABLE_HDRP_PACKAGE DisableHDRPCustomPass(); #endif #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) { // The D3D12 RenderGraph implementation in Unity is still highly unstable. // Tested with Unity 6.0.49f1 and 6.1.3f1, both versions crash internally // when using 'AddRasterRenderPass'. Even with all Noesis-related code removed, // Unity crashes on a simple IssuePluginEvent call. Additionally, macOS Silicon // builds (iOS not tested) render a black screen when using UnsafePass. // For now, we must support both paths. UnsafePass is expected to be deprecated // soon, and this will be revisited accordingly. if (UnityEngine.SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D12) { using (var builder = renderGraph.AddUnsafePass("Noesis", out var passData, _profilingSampler)) { var resourceData = frameData.Get(); var cameraData = frameData.Get(); passData.view = _view; passData.clearStencil = _view._clearStencil; passData.flipY = SystemInfo.graphicsUVStartsAtTop && !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, UnsafeGraphContext 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 { using (var builder = renderGraph.AddRasterRenderPass("Noesis", out var passData, _profilingSampler)) { var resourceData = frameData.Get(); var cameraData = frameData.Get(); passData.view = _view; passData.clearStencil = _view._clearStencil; passData.flipY = SystemInfo.graphicsUVStartsAtTop && !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); } }); } } } } #endif #region Compatibility Mode (URP without Render Graph) #pragma warning disable CS0672 #pragma warning disable CS0618 public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (_view._uiView != null && _view._visible) { bool flipY = SystemInfo.graphicsUVStartsAtTop && !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 } #pragma warning restore CS0672 #pragma warning restore CS0618 #endregion 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 [HideInInspector] private class NoesisCustomPass: CustomPass { protected override bool executeInSceneView { get { return false; } } protected override void Execute(CustomPassContext ctx) { if (ctx.hdCamera.camera.TryGetComponent(out NoesisView view)) { bool flipY = SystemInfo.graphicsUVStartsAtTop && !IsBackbuffer(ctx.cameraColorBuffer); view.OnExecuteCustomPass(ctx.cmd, flipY); } } private static bool IsBackbuffer(RTHandle buffer) { var id = new RenderTargetIdentifier(buffer.nameID, 0, CubemapFace.Unknown, 0); return id == BuiltinRenderTextureType.CameraTarget; } } private CustomPassVolume _customPassVolume; private void EnableHDRPCustomPass() { if (_camera != null && _customPassVolume == null) { _customPassVolume = gameObject.AddComponent(); _customPassVolume.hideFlags = HideFlags.HideAndDontSave; _customPassVolume.isGlobal = false; _customPassVolume.targetCamera = _camera; _customPassVolume.injectionPoint = _injectionPoint; var pass = _customPassVolume.AddPassOfType(); pass.targetColorBuffer = CustomPass.TargetBuffer.Camera; pass.targetDepthBuffer = CustomPass.TargetBuffer.Camera; pass.clearFlags = ClearFlag.None; } } private void DisableHDRPCustomPass() { if (_customPassVolume != null) { Destroy(_customPassVolume); _customPassVolume = null; } } 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 _customSampler = CustomSampler.Create("Noesis.RenderOnscreen", true); private void OnExecuteCustomPass(CommandBuffer commands, bool flipY) { if (_uiView != null && _visible) { commands.BeginSample(_customSampler); NoesisRenderer.RenderOnscreen(_uiView, flipY, commands, true, _clearStencil); commands.EndSample(_customSampler); } } 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, Context1 = 4096, Context2 = 8192, Context3 = 16384, Context4 = 32768 } 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 }, new ButtonState { button = ActionButtons.Context1, key = Key.GamepadContext1 }, new ButtonState { button = ActionButtons.Context2, key = Key.GamepadContext2 }, new ButtonState { button = ActionButtons.Context3, key = Key.GamepadContext3 }, new ButtonState { button = ActionButtons.Context4, key = Key.GamepadContext4 } }; 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 dir_ = _trackedRotationAction.ReadValue() * 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(); 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; if (_context1Action != null && _context1Action.IsPressed()) actionButtons |= ActionButtons.Context1; if (_context2Action != null && _context2Action.IsPressed()) actionButtons |= ActionButtons.Context2; if (_context3Action != null && _context3Action.IsPressed()) actionButtons |= ActionButtons.Context3; if (_context4Action != null && _context4Action.IsPressed()) actionButtons |= ActionButtons.Context4; 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, SystemInfo.graphicsUVStartsAtTop, _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 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() { if (SystemInfo.graphicsUVStartsAtTop) { #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, with the Input System package active, 'Input.touchCount' is always zero, // so we can't detect emulated mouse events directly. Until we deprecate OnGUI events, // the workaround is to check whether a mouse device exists. If both mouse and touch devices // are available, this check won't be reliable. In that case, the current solution is to // disable one of these input methods in NoesisView // Note: Checking if 'InputSystem.Touchscreen.current' is null doesn't work because // recent Unity versions always report a Touchscreen device on PC, even when none exists. return UnityEngine.InputSystem.Mouse.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 _context1Action; private UnityEngine.InputSystem.InputAction _context2Action; private UnityEngine.InputSystem.InputAction _context3Action; private UnityEngine.InputSystem.InputAction _context4Action; 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 }