mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-14 04:13:52 +02:00
* refactor: merge two overlay offScreenBrowser into one * Electron support for shared overlay * Separate overlay into its own process * fix: invalid overlay texture size * Handle duplicate processes * Remove logging --------- Co-authored-by: pa <maplenagisa@gmail.com> Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
858 lines
33 KiB
C#
858 lines
33 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using NLog;
|
|
using Valve.VR;
|
|
using System.Numerics;
|
|
using System.IO.MemoryMappedFiles;
|
|
|
|
namespace VRCX
|
|
{
|
|
public class VRCXVRElectron : VRCXVRInterface
|
|
{
|
|
public static VRCXVRInterface Instance;
|
|
private static readonly Logger logger = 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 readonly List<string[]> _deviceList;
|
|
private readonly ReaderWriterLockSlim _deviceListLock;
|
|
private bool _active;
|
|
private bool _menuButton;
|
|
private int _overlayHand;
|
|
private GLTextureWriter _overlayTextureWriter;
|
|
private Thread _thread;
|
|
private DateTime _nextOverlayUpdate;
|
|
|
|
private ulong _hmdOverlayHandle;
|
|
private bool _hmdOverlayActive;
|
|
private bool _hmdOverlayWasActive;
|
|
|
|
private ulong _wristOverlayHandle;
|
|
private bool _wristOverlayActive;
|
|
private bool _wristOverlayWasActive;
|
|
|
|
private const string OVERLAY_SHM_PATH = "/dev/shm/vrcx_overlay";
|
|
private const int WRIST_FRAME_WIDTH = 512;
|
|
private const int WRIST_FRAME_HEIGHT = 512;
|
|
private const int WRIST_FRAME_SIZE = WRIST_FRAME_WIDTH * WRIST_FRAME_HEIGHT * 4; // RGBA
|
|
private const int HMD_FRAME_WIDTH = 1024;
|
|
private const int HMD_FRAME_HEIGHT = 1024;
|
|
private const int HMD_FRAME_SIZE = HMD_FRAME_WIDTH * HMD_FRAME_HEIGHT * 4; // RGBA
|
|
|
|
private const int SHARED_FRAME_SIZE = SHARED_FRAME_WIDTH * SHARED_FRAME_HEIGHT * 4;
|
|
private const int SHARED_FRAME_WIDTH = 1024;
|
|
private const int SHARED_FRAME_HEIGHT = WRIST_FRAME_HEIGHT + HMD_FRAME_HEIGHT;
|
|
private byte[] frameBuffer = new byte[SHARED_FRAME_SIZE];
|
|
|
|
private MemoryMappedFile _overlayMMF;
|
|
private MemoryMappedViewAccessor _overlayAccessor;
|
|
private readonly ConcurrentQueue<KeyValuePair<string, string>> _overlayFunctionQueue = new ConcurrentQueue<KeyValuePair<string, string>>();
|
|
|
|
static VRCXVRElectron()
|
|
{
|
|
Instance = new VRCXVRElectron();
|
|
}
|
|
|
|
public VRCXVRElectron()
|
|
{
|
|
_deviceListLock = new ReaderWriterLockSlim();
|
|
_deviceList = new List<string[]>();
|
|
_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();
|
|
|
|
_overlayAccessor?.Dispose();
|
|
_overlayAccessor = null;
|
|
_overlayMMF?.Dispose();
|
|
_overlayMMF = null;
|
|
|
|
GLContextX11.Cleanup();
|
|
GLContextWayland.Cleanup();
|
|
}
|
|
|
|
public override void Restart()
|
|
{
|
|
Exit();
|
|
Instance = new VRCXVRElectron();
|
|
Instance.Init();
|
|
Program.VRCXVRInstance = Instance;
|
|
//MainForm.Instance.Browser.ExecuteScriptAsync("console.log('VRCXVR Restarted');");
|
|
}
|
|
|
|
private void SetupTextures()
|
|
{
|
|
bool contextInitialised = false;
|
|
|
|
// Check if we're running on Wayland
|
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WAYLAND_DISPLAY")) ||
|
|
Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower() == "wayland")
|
|
{
|
|
contextInitialised = GLContextWayland.Initialise();
|
|
}
|
|
else
|
|
{
|
|
contextInitialised = GLContextX11.Initialise();
|
|
}
|
|
|
|
if (!contextInitialised)
|
|
{
|
|
contextInitialised = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WAYLAND_DISPLAY")) ?
|
|
GLContextX11.Initialise() : GLContextWayland.Initialise();
|
|
}
|
|
|
|
if (!contextInitialised)
|
|
{
|
|
throw new Exception("Failed to initialise OpenGL context");
|
|
}
|
|
|
|
_overlayTextureWriter = new GLTextureWriter(SHARED_FRAME_WIDTH, SHARED_FRAME_HEIGHT);
|
|
_overlayTextureWriter.UpdateTexture();
|
|
}
|
|
|
|
private void UpgradeDevice()
|
|
{
|
|
|
|
}
|
|
|
|
public byte[] GetLatestOverlayFrame()
|
|
{
|
|
if (_overlayAccessor == null) return null;
|
|
byte ready = _overlayAccessor.ReadByte(0);
|
|
if (ready == 1)
|
|
{
|
|
_overlayAccessor.ReadArray(1, frameBuffer, 0, SHARED_FRAME_SIZE);
|
|
_overlayAccessor.Write(0, (byte)0); // reset flag
|
|
return frameBuffer;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static void FlipImageVertically(ref byte[] imageData, int width, int height)
|
|
{
|
|
int stride = width * 4; // 4 bytes per pixel (RGBA)
|
|
byte[] tempRow = new byte[stride];
|
|
|
|
for (int y = 0; y < height / 2; y++)
|
|
{
|
|
int topIndex = y * stride;
|
|
int bottomIndex = (height - 1 - y) * stride;
|
|
|
|
// Swap rows
|
|
Buffer.BlockCopy(imageData, topIndex, tempRow, 0, stride);
|
|
Buffer.BlockCopy(imageData, bottomIndex, imageData, topIndex, stride);
|
|
Buffer.BlockCopy(tempRow, 0, imageData, bottomIndex, stride);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
while (_thread != null)
|
|
{
|
|
try
|
|
{
|
|
if (_active)
|
|
Thread.Sleep(1);
|
|
else
|
|
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;
|
|
|
|
_wristOverlayHandle = 0;
|
|
_hmdOverlayHandle = 0;
|
|
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);
|
|
//}
|
|
|
|
if (_wristOverlayActive)
|
|
{
|
|
var err = ProcessOverlay1(overlay, ref _wristOverlayHandle, ref overlayVisible1,
|
|
dashboardVisible, overlayIndex);
|
|
if (err != EVROverlayError.None &&
|
|
_wristOverlayHandle != 0)
|
|
{
|
|
overlay.DestroyOverlay(_wristOverlayHandle);
|
|
_wristOverlayHandle = 0;
|
|
logger.Error(err);
|
|
}
|
|
}
|
|
|
|
if (_hmdOverlayActive)
|
|
{
|
|
var err = ProcessOverlay2(overlay, ref _hmdOverlayHandle, ref overlayVisible2,
|
|
dashboardVisible);
|
|
if (err != EVROverlayError.None &&
|
|
_hmdOverlayHandle != 0)
|
|
{
|
|
overlay.DestroyOverlay(_hmdOverlayHandle);
|
|
_hmdOverlayHandle = 0;
|
|
logger.Error(err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (active)
|
|
{
|
|
active = false;
|
|
IsHmdAfk = false;
|
|
OpenVR.Shutdown();
|
|
_deviceListLock.EnterWriteLock();
|
|
try
|
|
{
|
|
_deviceList.Clear();
|
|
}
|
|
finally
|
|
{
|
|
_deviceListLock.ExitWriteLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
_overlayAccessor?.Dispose();
|
|
_overlayAccessor = null;
|
|
_overlayMMF?.Dispose();
|
|
_overlayMMF = null;
|
|
|
|
GLContextX11.Cleanup();
|
|
GLContextWayland.Cleanup();
|
|
}
|
|
|
|
public override void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand)
|
|
{
|
|
_active = active;
|
|
_hmdOverlayActive = hmdOverlay;
|
|
_wristOverlayActive = wristOverlay;
|
|
_menuButton = menuButton;
|
|
_overlayHand = overlayHand;
|
|
|
|
if (_hmdOverlayActive != _hmdOverlayWasActive && _hmdOverlayHandle != 0)
|
|
{
|
|
OpenVR.Overlay.DestroyOverlay(_hmdOverlayHandle);
|
|
_hmdOverlayHandle = 0;
|
|
}
|
|
_hmdOverlayWasActive = _hmdOverlayActive;
|
|
|
|
if (_wristOverlayActive != _wristOverlayWasActive && _wristOverlayHandle != 0)
|
|
{
|
|
OpenVR.Overlay.DestroyOverlay(_wristOverlayHandle);
|
|
_wristOverlayHandle = 0;
|
|
}
|
|
_wristOverlayWasActive = _wristOverlayActive;
|
|
|
|
if (!_active || (!_hmdOverlayActive && !_wristOverlayActive))
|
|
{
|
|
_overlayAccessor?.Dispose();
|
|
_overlayAccessor = null;
|
|
_overlayMMF?.Dispose();
|
|
_overlayMMF = null;
|
|
GLContextX11.Cleanup();
|
|
GLContextWayland.Cleanup();
|
|
}
|
|
}
|
|
|
|
public override bool IsActive()
|
|
{
|
|
return _active;
|
|
}
|
|
|
|
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;
|
|
|
|
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.Join(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
_overlayMMF = MemoryMappedFile.CreateFromFile(OVERLAY_SHM_PATH, FileMode.Open, null, SHARED_FRAME_SIZE + 1);
|
|
_overlayAccessor = _overlayMMF.CreateViewAccessor();
|
|
}
|
|
}
|
|
|
|
if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid)
|
|
{
|
|
// http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices
|
|
// Scaling-Rotation-Translation
|
|
var m = Matrix4x4.CreateScale(0.25f);
|
|
m *= Matrix4x4.CreateRotationX(_rotation[0]);
|
|
m *= Matrix4x4.CreateRotationY(_rotation[1]);
|
|
m *= Matrix4x4.CreateRotationZ(_rotation[2]);
|
|
m *= Matrix4x4.CreateTranslation(new Vector3(_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)
|
|
{
|
|
if (_overlayTextureWriter != null)
|
|
{
|
|
byte[] imageData = GetLatestOverlayFrame();
|
|
if (imageData != null)
|
|
{
|
|
FlipImageVertically(ref imageData, SHARED_FRAME_WIDTH, SHARED_FRAME_HEIGHT);
|
|
_overlayTextureWriter.WriteImageToBuffer(imageData);
|
|
_overlayTextureWriter.UpdateTexture();
|
|
|
|
Texture_t texture = _overlayTextureWriter.AsTextureT();
|
|
var bounds = new VRTextureBounds_t
|
|
{
|
|
uMin = 0f,
|
|
uMax = 0.5f,
|
|
vMin = 0f,
|
|
vMax = (float)WRIST_FRAME_HEIGHT / SHARED_FRAME_HEIGHT
|
|
};
|
|
overlay.SetOverlayTextureBounds(overlayHandle, ref bounds);
|
|
err = OpenVR.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 = Matrix4x4.CreateScale(1f);
|
|
m *= Matrix4x4.CreateTranslation(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;
|
|
}
|
|
|
|
_overlayMMF = MemoryMappedFile.CreateFromFile(OVERLAY_SHM_PATH, FileMode.Open, null, SHARED_FRAME_SIZE + 1);
|
|
_overlayAccessor = _overlayMMF.CreateViewAccessor();
|
|
}
|
|
}
|
|
|
|
if (!dashboardVisible)
|
|
{
|
|
if (_overlayTextureWriter != null)
|
|
{
|
|
byte[] imageData = GetLatestOverlayFrame();
|
|
if (imageData != null)
|
|
{
|
|
FlipImageVertically(ref imageData, SHARED_FRAME_WIDTH, SHARED_FRAME_HEIGHT);
|
|
_overlayTextureWriter.WriteImageToBuffer(imageData);
|
|
_overlayTextureWriter.UpdateTexture();
|
|
|
|
Texture_t texture = _overlayTextureWriter.AsTextureT();
|
|
var bounds = new VRTextureBounds_t
|
|
{
|
|
uMin = 0f,
|
|
uMax = 1f,
|
|
vMin = (float)(SHARED_FRAME_HEIGHT - HMD_FRAME_HEIGHT) / SHARED_FRAME_HEIGHT,
|
|
vMax = 1f
|
|
};
|
|
overlay.SetOverlayTextureBounds(overlayHandle, ref bounds);
|
|
err = OpenVR.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 ConcurrentQueue<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue()
|
|
{
|
|
return _overlayFunctionQueue;
|
|
}
|
|
|
|
public override void ExecuteVrOverlayFunction(string function, string json)
|
|
{
|
|
//if (_hmdOverlaySocket == null || !_hmdOverlaySocket.Connected) return;
|
|
// if (_hmdOverlay.IsLoading)
|
|
// Restart();
|
|
|
|
_overlayFunctionQueue.Enqueue(new KeyValuePair<string, string>(function, json));
|
|
}
|
|
}
|
|
} |