diff --git a/Dotnet/AppApi/AppApi.cs b/Dotnet/AppApi/AppApi.cs index e960e356..978eff2f 100644 --- a/Dotnet/AppApi/AppApi.cs +++ b/Dotnet/AppApi/AppApi.cs @@ -208,17 +208,17 @@ namespace VRCX public void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) { - VRCXVR.Instance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand); + Program.VRCXVRInstance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand); } public void RefreshVR() { - VRCXVR.Instance.Restart(); + Program.VRCXVRInstance.Restart(); } public void RestartVR() { - VRCXVR.Instance.Restart(); + Program.VRCXVRInstance.Restart(); } public void SetZoom(double zoomLevel) @@ -355,18 +355,12 @@ namespace VRCX public void ExecuteVrFeedFunction(string function, string json) { - if (VRCXVR._wristOverlay == null) return; - if (VRCXVR._wristOverlay.IsLoading) - VRCXVR.Instance.Restart(); - VRCXVR._wristOverlay.ExecuteScriptAsync($"$app.{function}", json); + Program.VRCXVRInstance.ExecuteVrFeedFunction(function, json); } public void ExecuteVrOverlayFunction(string function, string json) { - if (VRCXVR._hmdOverlay == null) return; - if (VRCXVR._hmdOverlay.IsLoading) - VRCXVR.Instance.Restart(); - VRCXVR._hmdOverlay.ExecuteScriptAsync($"$app.{function}", json); + Program.VRCXVRInstance.ExecuteVrOverlayFunction(function, json); } /// diff --git a/Dotnet/AppApi/AppApiVr.cs b/Dotnet/AppApi/AppApiVr.cs index 361233be..c2051365 100644 --- a/Dotnet/AppApi/AppApiVr.cs +++ b/Dotnet/AppApi/AppApiVr.cs @@ -47,7 +47,7 @@ namespace VRCX /// An array of arrays containing information about the connected VR devices. public string[][] GetVRDevices() { - return VRCXVR.Instance.GetDevices(); + return Program.VRCXVRInstance.GetDevices(); } /// diff --git a/Dotnet/AppApi/GameHandler.cs b/Dotnet/AppApi/GameHandler.cs index 0d711688..0a399559 100644 --- a/Dotnet/AppApi/GameHandler.cs +++ b/Dotnet/AppApi/GameHandler.cs @@ -37,7 +37,7 @@ namespace VRCX isSteamVRRunning = true; } - var isHmdAfk = VRCXVR.Instance.IsHmdAfk; + var isHmdAfk = Program.VRCXVRInstance.IsHmdAfk; // TODO: fix this throwing an exception for being called before the browser is ready. somehow it gets past the checks if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) diff --git a/Dotnet/Overlay/OffScreenBrowserLegacy.cs b/Dotnet/Overlay/OffScreenBrowserLegacy.cs new file mode 100644 index 00000000..cd17ce01 --- /dev/null +++ b/Dotnet/Overlay/OffScreenBrowserLegacy.cs @@ -0,0 +1,206 @@ +// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors. +// All rights reserved. +// +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +using CefSharp; +using CefSharp.Enums; +using CefSharp.OffScreen; +using CefSharp.Structs; +using SharpDX.Direct3D11; +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Range = CefSharp.Structs.Range; + +namespace VRCX +{ + public class OffScreenBrowserLegacy : ChromiumWebBrowser, IRenderHandler + { + private readonly ReaderWriterLockSlim _paintBufferLock; + private GCHandle _paintBuffer; + private int _width; + private int _height; + + public OffScreenBrowserLegacy(string address, int width, int height) + : base( + address, + new BrowserSettings() + { + DefaultEncoding = "UTF-8" + } + ) + { + _paintBufferLock = new ReaderWriterLockSlim(); + + Size = new System.Drawing.Size(width, height); + RenderHandler = this; + + JavascriptBindings.ApplyVrJavascriptBindings(JavascriptObjectRepository); + } + + public new void Dispose() + { + RenderHandler = null; + base.Dispose(); + + _paintBufferLock.EnterWriteLock(); + try + { + if (_paintBuffer.IsAllocated == true) + { + _paintBuffer.Free(); + } + } + finally + { + _paintBufferLock.ExitWriteLock(); + } + + _paintBufferLock.Dispose(); + } + + public void RenderToTexture(Texture2D texture) + { + // Safeguard against uninitialized texture + if (texture == null) + return; + + _paintBufferLock.EnterReadLock(); + try + { + if (_width > 0 && + _height > 0) + { + var context = texture.Device.ImmediateContext; + var dataBox = context.MapSubresource( + texture, + 0, + MapMode.WriteDiscard, + MapFlags.None + ); + if (dataBox.IsEmpty == false) + { + var sourcePtr = _paintBuffer.AddrOfPinnedObject(); + var destinationPtr = dataBox.DataPointer; + var pitch = _width * 4; + var rowPitch = dataBox.RowPitch; + if (pitch == rowPitch) + { + WinApi.RtlCopyMemory( + destinationPtr, + sourcePtr, + (uint)(_width * _height * 4) + ); + } + else + { + for (var y = _height; y > 0; --y) + { + WinApi.RtlCopyMemory( + destinationPtr, + sourcePtr, + (uint)pitch + ); + sourcePtr += pitch; + destinationPtr += rowPitch; + } + } + } + context.UnmapSubresource(texture, 0); + } + } + finally + { + _paintBufferLock.ExitReadLock(); + } + } + + ScreenInfo? IRenderHandler.GetScreenInfo() + { + return null; + } + + bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) + { + screenX = viewX; + screenY = viewY; + return false; + } + + Rect IRenderHandler.GetViewRect() + { + return new Rect(0, 0, Size.Width, Size.Height); + } + + void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, AcceleratedPaintInfo paintInfo) + { + // NOT USED + } + + void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) + { + } + + void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) + { + } + + void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height) + { + if (type == PaintElementType.View) + { + _paintBufferLock.EnterWriteLock(); + try + { + if (_width != width || + _height != height) + { + _width = width; + _height = height; + if (_paintBuffer.IsAllocated == true) + { + _paintBuffer.Free(); + } + _paintBuffer = GCHandle.Alloc( + new byte[_width * _height * 4], + GCHandleType.Pinned + ); + } + + WinApi.RtlCopyMemory( + _paintBuffer.AddrOfPinnedObject(), + buffer, + (uint)(width * height * 4) + ); + } + finally + { + _paintBufferLock.ExitWriteLock(); + } + } + } + + void IRenderHandler.OnPopupShow(bool show) + { + } + + void IRenderHandler.OnPopupSize(Rect rect) + { + } + + void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) + { + } + + bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) + { + return false; + } + + void IRenderHandler.UpdateDragCursor(DragOperationsMask operation) + { + } + } +} diff --git a/Dotnet/Overlay/VRCXVR.cs b/Dotnet/Overlay/VRCXVR.cs index aa53640b..d063f2e0 100644 --- a/Dotnet/Overlay/VRCXVR.cs +++ b/Dotnet/Overlay/VRCXVR.cs @@ -23,9 +23,9 @@ using Device4 = SharpDX.Direct3D11.Device4; namespace VRCX { - public class VRCXVR + public class VRCXVR : VRCXVRInterface { - public static VRCXVR Instance; + public static VRCXVRInterface Instance; private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private static readonly float[] _rotation = { 0f, 0f, 0f }; private static readonly float[] _translation = { 0f, 0f, 0f }; @@ -33,8 +33,8 @@ namespace VRCX private static readonly float[] _translationRight = { 7f / 100f, -5f / 100f, 6f / 100f }; private static readonly float[] _rotationLeft = { 90f * (float)(Math.PI / 180f), 90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; private static readonly float[] _rotationRight = { -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; - public static OffScreenBrowser _wristOverlay; - public static OffScreenBrowser _hmdOverlay; + private static OffScreenBrowser _wristOverlay; + private static OffScreenBrowser _hmdOverlay; private readonly List _deviceList; private readonly ReaderWriterLockSlim _deviceListLock; private bool _active; @@ -74,12 +74,12 @@ namespace VRCX // NOTE // 메모리 릭 때문에 미리 생성해놓고 계속 사용함 - internal void Init() + public override void Init() { _thread.Start(); } - internal void Exit() + public override void Exit() { var thread = _thread; _thread = null; @@ -87,7 +87,7 @@ namespace VRCX thread?.Join(); } - public void Restart() + public override void Restart() { Exit(); Instance = new VRCXVR(); @@ -327,7 +327,7 @@ namespace VRCX _device?.Dispose(); } - public void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) + public override void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) { _active = active; _hmdOverlayActive = hmdOverlay; @@ -352,13 +352,13 @@ namespace VRCX _wristOverlayWasActive = _wristOverlayActive; } - public void Refresh() + public override void Refresh() { _wristOverlay.Reload(); _hmdOverlay.Reload(); } - public string[][] GetDevices() + public override string[][] GetDevices() { _deviceListLock.EnterReadLock(); try @@ -371,7 +371,7 @@ namespace VRCX } } - internal void UpdateDevices(CVRSystem system, ref uint overlayIndex) + private void UpdateDevices(CVRSystem system, ref uint overlayIndex) { _deviceListLock.EnterWriteLock(); try @@ -833,5 +833,21 @@ namespace VRCX return err; } + + public override void ExecuteVrFeedFunction(string function, string json) + { + if (_wristOverlay == null) return; + if (_wristOverlay.IsLoading) + Restart(); + _wristOverlay.ExecuteScriptAsync($"$app.{function}", json); + } + + public override void ExecuteVrOverlayFunction(string function, string json) + { + if (_hmdOverlay == null) return; + if (_hmdOverlay.IsLoading) + Restart(); + _hmdOverlay.ExecuteScriptAsync($"$app.{function}", json); + } } } \ No newline at end of file diff --git a/Dotnet/Overlay/VRCXVRInterface.cs b/Dotnet/Overlay/VRCXVRInterface.cs new file mode 100644 index 00000000..fadb789d --- /dev/null +++ b/Dotnet/Overlay/VRCXVRInterface.cs @@ -0,0 +1,15 @@ +namespace VRCX; + +public abstract class VRCXVRInterface +{ + public bool IsHmdAfk; + + public abstract void Init(); + public abstract void Exit(); + public abstract void Refresh(); + public abstract void Restart(); + public abstract void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand); + public abstract string[][] GetDevices(); + public abstract void ExecuteVrFeedFunction(string function, string json); + public abstract void ExecuteVrOverlayFunction(string function, string json); +} \ No newline at end of file diff --git a/Dotnet/Overlay/VRCXVRLegacy.cs b/Dotnet/Overlay/VRCXVRLegacy.cs new file mode 100644 index 00000000..fb1b888b --- /dev/null +++ b/Dotnet/Overlay/VRCXVRLegacy.cs @@ -0,0 +1,781 @@ +// Copyright(c) 2019-2022 pypy, Natsumi and individual contributors. +// All rights reserved. +// +// This work is licensed under the terms of the MIT license. +// For a copy, see . + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using CefSharp; +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using Valve.VR; +using Device = SharpDX.Direct3D11.Device; + +namespace VRCX +{ + public class VRCXVRLegacy : VRCXVRInterface + { + public static VRCXVRInterface Instance; + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private static readonly float[] _rotation = { 0f, 0f, 0f }; + private static readonly float[] _translation = { 0f, 0f, 0f }; + private static readonly float[] _translationLeft = { -7f / 100f, -5f / 100f, 6f / 100f }; + private static readonly float[] _translationRight = { 7f / 100f, -5f / 100f, 6f / 100f }; + private static readonly float[] _rotationLeft = { 90f * (float)(Math.PI / 180f), 90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; + private static readonly float[] _rotationRight = { -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; + private static OffScreenBrowserLegacy _wristOverlay; + private static OffScreenBrowserLegacy _hmdOverlay; + private readonly List _deviceList; + private readonly ReaderWriterLockSlim _deviceListLock; + private bool _active; + private Device _device; + private bool _hmdOverlayActive; + private bool _menuButton; + private int _overlayHand; + private Texture2D _texture1; + private Texture2D _texture2; + private Thread _thread; + private bool _wristOverlayActive; + private DateTime _nextOverlayUpdate; + public bool IsHmdAfk { get; private set; } + + static VRCXVRLegacy() + { + Instance = new VRCXVRLegacy(); + } + + public VRCXVRLegacy() + { + _deviceListLock = new ReaderWriterLockSlim(); + _deviceList = new List(); + _thread = new Thread(ThreadLoop) + { + IsBackground = true + }; + } + + // NOTE + // 메모리 릭 때문에 미리 생성해놓고 계속 사용함 + public override void Init() + { + _thread.Start(); + } + + public override void Exit() + { + var thread = _thread; + _thread = null; + thread?.Interrupt(); + thread?.Join(); + } + + public override void Restart() + { + Exit(); + Instance = new VRCXVRLegacy(); + Instance.Init(); + MainForm.Instance.Browser.ExecuteScriptAsync("console.log('VRCXVR Restarted');"); + } + + private void SetupTextures() + { + Factory f = new Factory1(); + _device = new Device(f.GetAdapter(OpenVR.System.GetD3D9AdapterIndex()), + DeviceCreationFlags.SingleThreaded | DeviceCreationFlags.BgraSupport); + + _texture1?.Dispose(); + _texture1 = new Texture2D( + _device, + new Texture2DDescription + { + Width = 512, + Height = 512, + MipLevels = 1, + ArraySize = 1, + Format = Format.B8G8R8A8_UNorm, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Dynamic, + BindFlags = BindFlags.ShaderResource, + CpuAccessFlags = CpuAccessFlags.Write + } + ); + + _texture2?.Dispose(); + _texture2 = new Texture2D( + _device, + new Texture2DDescription + { + Width = 1024, + Height = 1024, + MipLevels = 1, + ArraySize = 1, + Format = Format.B8G8R8A8_UNorm, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Dynamic, + BindFlags = BindFlags.ShaderResource, + CpuAccessFlags = CpuAccessFlags.Write + } + ); + } + + private void ThreadLoop() + { + var active = false; + var e = new VREvent_t(); + var nextInit = DateTime.MinValue; + var nextDeviceUpdate = DateTime.MinValue; + _nextOverlayUpdate = DateTime.MinValue; + var overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid; + var overlayVisible1 = false; + var overlayVisible2 = false; + var dashboardHandle = 0UL; + var overlayHandle1 = 0UL; + var overlayHandle2 = 0UL; + + _wristOverlay = new OffScreenBrowserLegacy( + "file://vrcx/vr.html?1", + 512, + 512 + ); + + _hmdOverlay = new OffScreenBrowserLegacy( + "file://vrcx/vr.html?2", + 1024, + 1024 + ); + + while (_thread != null) + { + if (_wristOverlayActive) + _wristOverlay.RenderToTexture(_texture1); + if (_hmdOverlayActive) + _hmdOverlay.RenderToTexture(_texture2); + try + { + Thread.Sleep(32); + } + catch (ThreadInterruptedException) + { + } + + if (_active) + { + var system = OpenVR.System; + if (system == null) + { + if (DateTime.UtcNow.CompareTo(nextInit) <= 0) + { + continue; + } + + var _err = EVRInitError.None; + system = OpenVR.Init(ref _err, EVRApplicationType.VRApplication_Background); + nextInit = DateTime.UtcNow.AddSeconds(5); + if (system == null) + { + continue; + } + + active = true; + SetupTextures(); + } + + while (system.PollNextEvent(ref e, (uint)Marshal.SizeOf(e))) + { + var type = (EVREventType)e.eventType; + if (type == EVREventType.VREvent_Quit) + { + active = false; + IsHmdAfk = false; + OpenVR.Shutdown(); + nextInit = DateTime.UtcNow.AddSeconds(10); + system = null; + break; + } + } + + if (system != null) + { + if (DateTime.UtcNow.CompareTo(nextDeviceUpdate) >= 0) + { + overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid; + UpdateDevices(system, ref overlayIndex); + if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid) + { + _nextOverlayUpdate = DateTime.UtcNow.AddSeconds(10); + } + + nextDeviceUpdate = DateTime.UtcNow.AddSeconds(0.1); + } + + var overlay = OpenVR.Overlay; + if (overlay != null) + { + var dashboardVisible = overlay.IsDashboardVisible(); + var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible); + if (err != EVROverlayError.None && + dashboardHandle != 0) + { + overlay.DestroyOverlay(dashboardHandle); + dashboardHandle = 0; + logger.Error(err); + } + + err = ProcessOverlay1(overlay, ref overlayHandle1, ref overlayVisible1, dashboardVisible, overlayIndex); + if (err != EVROverlayError.None && + overlayHandle1 != 0) + { + overlay.DestroyOverlay(overlayHandle1); + overlayHandle1 = 0; + logger.Error(err); + } + + err = ProcessOverlay2(overlay, ref overlayHandle2, ref overlayVisible2, dashboardVisible); + if (err != EVROverlayError.None && + overlayHandle2 != 0) + { + overlay.DestroyOverlay(overlayHandle2); + overlayHandle2 = 0; + logger.Error(err); + } + } + } + } + else if (active) + { + active = false; + IsHmdAfk = false; + OpenVR.Shutdown(); + _deviceListLock.EnterWriteLock(); + try + { + _deviceList.Clear(); + } + finally + { + _deviceListLock.ExitWriteLock(); + } + } + } + + _hmdOverlay?.Dispose(); + _wristOverlay?.Dispose(); + _texture2?.Dispose(); + _texture1?.Dispose(); + _device?.Dispose(); + } + + public override void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) + { + _active = active; + _hmdOverlayActive = hmdOverlay; + _wristOverlayActive = wristOverlay; + _menuButton = menuButton; + _overlayHand = overlayHand; + } + + public override void Refresh() + { + _wristOverlay.Reload(); + _hmdOverlay.Reload(); + } + + public override string[][] GetDevices() + { + _deviceListLock.EnterReadLock(); + try + { + return _deviceList.ToArray(); + } + finally + { + _deviceListLock.ExitReadLock(); + } + } + + private void UpdateDevices(CVRSystem system, ref uint overlayIndex) + { + _deviceListLock.EnterWriteLock(); + try + { + _deviceList.Clear(); + } + finally + { + _deviceListLock.ExitWriteLock(); + } + + var sb = new StringBuilder(256); + var state = new VRControllerState_t(); + var poses = new TrackedDevicePose_t[OpenVR.k_unMaxTrackedDeviceCount]; + system.GetDeviceToAbsoluteTrackingPose(ETrackingUniverseOrigin.TrackingUniverseStanding, 0, poses); + for (var i = 0u; i < OpenVR.k_unMaxTrackedDeviceCount; ++i) + { + var devClass = system.GetTrackedDeviceClass(i); + switch (devClass) + { + case ETrackedDeviceClass.HMD: + var success = system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)); + if (!success) + break; // this fails while SteamVR overlay is open + + var prox = state.ulButtonPressed & (1UL << ((int)EVRButtonId.k_EButton_ProximitySensor)); + var isHmdAfk = prox == 0; + if (isHmdAfk != IsHmdAfk) + { + IsHmdAfk = isHmdAfk; + AppApi.Instance.CheckGameRunning(); + } + + var headsetErr = ETrackedPropertyError.TrackedProp_Success; + var headsetBatteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref headsetErr); + if (headsetErr != ETrackedPropertyError.TrackedProp_Success) + { + // Headset has no battery, skip displaying it + break; + } + + var headset = new[] + { + "headset", + system.IsTrackedDeviceConnected(i) + ? "connected" + : "disconnected", + // Currently neither VD or SteamLink report charging state + "discharging", + (headsetBatteryPercentage * 100).ToString(), + poses[i].eTrackingResult.ToString() + }; + _deviceListLock.EnterWriteLock(); + try + { + _deviceList.Add(headset); + } + finally + { + _deviceListLock.ExitWriteLock(); + } + + break; + case ETrackedDeviceClass.Controller: + case ETrackedDeviceClass.GenericTracker: + case ETrackedDeviceClass.TrackingReference: + { + var err = ETrackedPropertyError.TrackedProp_Success; + var batteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref err); + if (err != ETrackedPropertyError.TrackedProp_Success) + { + batteryPercentage = 1f; + } + + err = ETrackedPropertyError.TrackedProp_Success; + var isCharging = system.GetBoolTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool, ref err); + if (err != ETrackedPropertyError.TrackedProp_Success) + { + isCharging = false; + } + + sb.Clear(); + system.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_TrackingSystemName_String, sb, (uint)sb.Capacity, ref err); + var isOculus = sb.ToString().IndexOf("oculus", StringComparison.OrdinalIgnoreCase) >= 0; + // Oculus : B/Y, Bit 1, Mask 2 + // Oculus : A/X, Bit 7, Mask 128 + // Vive : Menu, Bit 1, Mask 2, + // Vive : Grip, Bit 2, Mask 4 + var role = system.GetControllerRoleForTrackedDeviceIndex(i); + if (role == ETrackedControllerRole.LeftHand || role == ETrackedControllerRole.RightHand) + { + if (_overlayHand == 0 || + (_overlayHand == 1 && role == ETrackedControllerRole.LeftHand) || + (_overlayHand == 2 && role == ETrackedControllerRole.RightHand)) + { + if (system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)) && + (state.ulButtonPressed & (_menuButton ? 2u : isOculus ? 128u : 4u)) != 0) + { + _nextOverlayUpdate = DateTime.MinValue; + if (role == ETrackedControllerRole.LeftHand) + { + Array.Copy(_translationLeft, _translation, 3); + Array.Copy(_rotationLeft, _rotation, 3); + } + else + { + Array.Copy(_translationRight, _translation, 3); + Array.Copy(_rotationRight, _rotation, 3); + } + + overlayIndex = i; + } + } + } + + var type = string.Empty; + if (devClass == ETrackedDeviceClass.Controller) + { + if (role == ETrackedControllerRole.LeftHand) + { + type = "leftController"; + } + else if (role == ETrackedControllerRole.RightHand) + { + type = "rightController"; + } + else + { + type = "controller"; + } + } + else if (devClass == ETrackedDeviceClass.GenericTracker) + { + type = "tracker"; + } + else if (devClass == ETrackedDeviceClass.TrackingReference) + { + type = "base"; + } + + var item = new[] + { + type, + system.IsTrackedDeviceConnected(i) + ? "connected" + : "disconnected", + isCharging + ? "charging" + : "discharging", + (batteryPercentage * 100).ToString(), + poses[i].eTrackingResult.ToString() + }; + _deviceListLock.EnterWriteLock(); + try + { + _deviceList.Add(item); + } + finally + { + _deviceListLock.ExitWriteLock(); + } + + break; + } + } + } + } + + internal EVROverlayError ProcessDashboard(CVROverlay overlay, ref ulong dashboardHandle, bool dashboardVisible) + { + var err = EVROverlayError.None; + + if (dashboardHandle == 0) + { + err = overlay.FindOverlay("VRCX", ref dashboardHandle); + if (err != EVROverlayError.None) + { + if (err != EVROverlayError.UnknownOverlay) + { + return err; + } + + ulong thumbnailHandle = 0; + err = overlay.CreateDashboardOverlay("VRCX", "VRCX", ref dashboardHandle, ref thumbnailHandle); + if (err != EVROverlayError.None) + { + return err; + } + + var iconPath = Path.Combine(Program.BaseDirectory, "VRCX.png"); + err = overlay.SetOverlayFromFile(thumbnailHandle, iconPath); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayWidthInMeters(dashboardHandle, 1.5f); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayInputMethod(dashboardHandle, VROverlayInputMethod.Mouse); + if (err != EVROverlayError.None) + { + return err; + } + } + } + + var e = new VREvent_t(); + + while (overlay.PollNextOverlayEvent(dashboardHandle, ref e, (uint)Marshal.SizeOf(e))) + { + var type = (EVREventType)e.eventType; + if (type == EVREventType.VREvent_MouseMove) + { + var m = e.data.mouse; + var s = _wristOverlay.Size; + _wristOverlay.GetBrowserHost().SendMouseMoveEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), false, CefEventFlags.None); + } + else if (type == EVREventType.VREvent_MouseButtonDown) + { + var m = e.data.mouse; + var s = _wristOverlay.Size; + _wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, false, 1, CefEventFlags.LeftMouseButton); + } + else if (type == EVREventType.VREvent_MouseButtonUp) + { + var m = e.data.mouse; + var s = _wristOverlay.Size; + _wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, true, 1, CefEventFlags.None); + } + } + + if (dashboardVisible) + { + var texture = new Texture_t + { + handle = _texture1.NativePointer + }; + err = overlay.SetOverlayTexture(dashboardHandle, ref texture); + if (err != EVROverlayError.None) + { + return err; + } + } + + return err; + } + + internal EVROverlayError ProcessOverlay1(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible, uint overlayIndex) + { + var err = EVROverlayError.None; + + if (overlayHandle == 0) + { + err = overlay.FindOverlay("VRCX1", ref overlayHandle); + if (err != EVROverlayError.None) + { + if (err != EVROverlayError.UnknownOverlay) + { + return err; + } + + overlayVisible = false; + err = overlay.CreateOverlay("VRCX1", "VRCX1", ref overlayHandle); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayAlpha(overlayHandle, 0.9f); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None); + if (err != EVROverlayError.None) + { + return err; + } + } + } + + if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid) + { + // http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices + // Scaling-Rotation-Translation + var m = Matrix.Scaling(0.25f); + m *= Matrix.RotationX(_rotation[0]); + m *= Matrix.RotationY(_rotation[1]); + m *= Matrix.RotationZ(_rotation[2]); + m *= Matrix.Translation(_translation[0], _translation[1], _translation[2]); + var hm34 = new HmdMatrix34_t + { + m0 = m.M11, + m1 = m.M21, + m2 = m.M31, + m3 = m.M41, + m4 = m.M12, + m5 = m.M22, + m6 = m.M32, + m7 = m.M42, + m8 = m.M13, + m9 = m.M23, + m10 = m.M33, + m11 = m.M43 + }; + err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, overlayIndex, ref hm34); + if (err != EVROverlayError.None) + { + return err; + } + } + + if (!dashboardVisible && + DateTime.UtcNow.CompareTo(_nextOverlayUpdate) <= 0) + { + var texture = new Texture_t + { + handle = _texture1.NativePointer + }; + err = overlay.SetOverlayTexture(overlayHandle, ref texture); + if (err != EVROverlayError.None) + { + return err; + } + + if (!overlayVisible) + { + err = overlay.ShowOverlay(overlayHandle); + if (err != EVROverlayError.None) + { + return err; + } + + overlayVisible = true; + } + } + else if (overlayVisible) + { + err = overlay.HideOverlay(overlayHandle); + if (err != EVROverlayError.None) + { + return err; + } + + overlayVisible = false; + } + + return err; + } + + internal EVROverlayError ProcessOverlay2(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible) + { + var err = EVROverlayError.None; + + if (overlayHandle == 0) + { + err = overlay.FindOverlay("VRCX2", ref overlayHandle); + if (err != EVROverlayError.None) + { + if (err != EVROverlayError.UnknownOverlay) + { + return err; + } + + overlayVisible = false; + err = overlay.CreateOverlay("VRCX2", "VRCX2", ref overlayHandle); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayAlpha(overlayHandle, 0.9f); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f); + if (err != EVROverlayError.None) + { + return err; + } + + err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None); + if (err != EVROverlayError.None) + { + return err; + } + + var m = Matrix.Scaling(1f); + m *= Matrix.Translation(0, -0.3f, -1.5f); + var hm34 = new HmdMatrix34_t + { + m0 = m.M11, + m1 = m.M21, + m2 = m.M31, + m3 = m.M41, + m4 = m.M12, + m5 = m.M22, + m6 = m.M32, + m7 = m.M42, + m8 = m.M13, + m9 = m.M23, + m10 = m.M33, + m11 = m.M43 + }; + err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, OpenVR.k_unTrackedDeviceIndex_Hmd, ref hm34); + if (err != EVROverlayError.None) + { + return err; + } + } + } + + if (!dashboardVisible) + { + var texture = new Texture_t + { + handle = _texture2.NativePointer + }; + err = overlay.SetOverlayTexture(overlayHandle, ref texture); + if (err != EVROverlayError.None) + { + return err; + } + + if (!overlayVisible) + { + err = overlay.ShowOverlay(overlayHandle); + if (err != EVROverlayError.None) + { + return err; + } + + overlayVisible = true; + } + } + else if (overlayVisible) + { + err = overlay.HideOverlay(overlayHandle); + if (err != EVROverlayError.None) + { + return err; + } + + overlayVisible = false; + } + + return err; + } + + public override void ExecuteVrFeedFunction(string function, string json) + { + if (_wristOverlay == null) return; + if (_wristOverlay.IsLoading) + Restart(); + _wristOverlay.ExecuteScriptAsync($"$app.{function}", json); + } + + public override void ExecuteVrOverlayFunction(string function, string json) + { + if (_hmdOverlay == null) return; + if (_hmdOverlay.IsLoading) + Restart(); + _hmdOverlay.ExecuteScriptAsync($"$app.{function}", json); + } + } +} \ No newline at end of file diff --git a/Dotnet/Overlay/VRForm.cs b/Dotnet/Overlay/VRForm.cs index fe1f74ab..e49d4ff6 100644 --- a/Dotnet/Overlay/VRForm.cs +++ b/Dotnet/Overlay/VRForm.cs @@ -56,7 +56,7 @@ namespace VRCX { _browser1.ExecuteScriptAsync("location.reload()"); _browser2.ExecuteScriptAsync("location.reload()"); - VRCXVR.Instance.Refresh(); + Program.VRCXVRInstance.Refresh(); } private void button_devtools_Click(object sender, System.EventArgs e) diff --git a/Dotnet/Program.cs b/Dotnet/Program.cs index 39980f41..225faece 100644 --- a/Dotnet/Program.cs +++ b/Dotnet/Program.cs @@ -22,6 +22,7 @@ namespace VRCX public static string Version { get; private set; } public static bool LaunchDebug; private static readonly NLog.Logger logger = NLog.LogManager.GetLogger("VRCX"); + public static VRCXVRInterface VRCXVRInstance { get; private set; } private static void SetProgramDirectories() { @@ -187,14 +188,19 @@ namespace VRCX WebApi.Instance.Init(); LogWatcher.Instance.Init(); AutoAppLaunchManager.Instance.Init(); - CefService.Instance.Init(); IPCServer.Instance.Init(); - VRCXVR.Instance.Init(); + + if (VRCXStorage.Instance.Get("VRCX_DisableVrOverlayGpuAcceleration") == "true") + VRCXVRInstance = new VRCXVRLegacy(); + else + VRCXVRInstance = new VRCXVR(); + VRCXVRInstance.Init(); + Application.Run(new MainForm()); logger.Info("{0} Exiting...", Version); WebApi.Instance.SaveCookies(); - VRCXVR.Instance.Exit(); + VRCXVRInstance.Exit(); CefService.Instance.Exit(); AutoAppLaunchManager.Instance.Exit(); diff --git a/html/src/app.js b/html/src/app.js index ea69b634..d808ee39 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -8172,9 +8172,17 @@ speechSynthesis.getVoices(); if ((await VRCXStorage.Get('VRCX_DisableGpuAcceleration')) === '') { await VRCXStorage.Set('VRCX_DisableGpuAcceleration', 'false'); } + if ( + (await VRCXStorage.Get('VRCX_DisableVrOverlayGpuAcceleration')) === '' + ) { + await VRCXStorage.Set('VRCX_DisableVrOverlayGpuAcceleration', 'false'); + } $app.data.proxyServer = await VRCXStorage.Get('VRCX_ProxyServer'); $app.data.disableGpuAcceleration = (await VRCXStorage.Get('VRCX_DisableGpuAcceleration')) === 'true'; + $app.data.disableVrOverlayGpuAcceleration = + (await VRCXStorage.Get('VRCX_DisableVrOverlayGpuAcceleration')) === + 'true'; $app.data.disableWorldDatabase = (await VRCXStorage.Get('VRCX_DisableWorldDatabase')) === 'true'; $app.methods.saveVRCXWindowOption = async function () { @@ -8199,6 +8207,10 @@ speechSynthesis.getVoices(); 'VRCX_DisableGpuAcceleration', this.disableGpuAcceleration.toString() ); + VRCXStorage.Set( + 'VRCX_DisableVrOverlayGpuAcceleration', + this.disableVrOverlayGpuAcceleration.toString() + ); AppApi.SetStartup(this.isStartAtWindowsStartup); }; $app.data.photonEventOverlay = await configRepository.getBool( diff --git a/html/src/localization/en/en.json b/html/src/localization/en/en.json index 02d133bb..8deabfe9 100644 --- a/html/src/localization/en/en.json +++ b/html/src/localization/en/en.json @@ -227,6 +227,8 @@ "tray": "Minimize to tray when closing", "disable_gpu_acceleration": "Disable GPU Acceleration", "disable_gpu_acceleration_tooltip": "Only change this option if you know what you're doing, breaks SteamVR overlay, may fix issues with UI, requires restarting VRCX", + "disable_vr_overlay_gpu_acceleration": "Disable VR Overlay GPU Acceleration", + "disable_vr_overlay_gpu_acceleration_tooltip": "VR Overlay GPU acceleration can cause VRCX to crash or cause VRAM memory leaks, requires restarting VRCX", "proxy": "Proxy settings" }, "favorites": { diff --git a/html/src/mixins/tabs/settings.pug b/html/src/mixins/tabs/settings.pug index 36c654ad..662f3b69 100644 --- a/html/src/mixins/tabs/settings.pug +++ b/html/src/mixins/tabs/settings.pug @@ -88,6 +88,11 @@ mixin settingsTab() el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.general.application.disable_gpu_acceleration_tooltip')") i.el-icon-warning el-switch(v-model="disableGpuAcceleration" @change="saveVRCXWindowOption") + div.options-container-item + span.name(style="min-width:225px") {{ $t('view.settings.general.application.disable_vr_overlay_gpu_acceleration') }} + el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.general.application.disable_gpu_acceleration_tooltip')") + i.el-icon-warning + el-switch(v-model="disableVrOverlayGpuAcceleration" @change="saveVRCXWindowOption") div.options-container-item el-button(size="small" icon="el-icon-connection" @click="promptProxySettings()") {{ $t("view.settings.general.application.proxy") }} //- General | Favorite