mirror of
https://github.com/lbgos/rift-s-linux.git
synced 2026-10-21 19:01:13 +02:00
208 lines
11 KiB
C++
208 lines
11 KiB
C++
#include "openvr.h"
|
|
#include "headset_preview.hpp"
|
|
#include <dlfcn.h>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Background client for SteamVR state and native dashboard actions.
|
|
namespace {
|
|
void *library = nullptr;
|
|
vr::IVRSystem *vr_system = nullptr;
|
|
vr::IVRCompositor *compositor = nullptr;
|
|
vr::IVRSettings *settings = nullptr;
|
|
vr::IVRChaperone *chaperone = nullptr;
|
|
vr::IVRChaperoneSetup *setup = nullptr;
|
|
vr::IVROverlay *overlay = nullptr;
|
|
vr::IVROverlayView *overlay_view = nullptr;
|
|
vr::IVRTrackedCamera *camera = nullptr;
|
|
vr::IVRExtendedDisplay *display = nullptr;
|
|
vr::TrackedCameraHandle_t camera_handle = 0;
|
|
using Clock = std::chrono::steady_clock;
|
|
Clock::time_point previous_time;
|
|
uint32_t previous_sequence = 0;
|
|
bool previous_frame = false;
|
|
std::string output;
|
|
using GetInterface = void *(*)(const char *, vr::EVRInitError *);
|
|
template<typename T> T *get(GetInterface fn, const char *version) {
|
|
vr::EVRInitError error = vr::VRInitError_None;
|
|
return static_cast<T *>(fn(version, &error));
|
|
}
|
|
bool connect(const char *path) {
|
|
if (vr_system) return true;
|
|
if (!library) library = dlopen(path, RTLD_NOW | RTLD_LOCAL);
|
|
if (!library) return false;
|
|
auto init = reinterpret_cast<uint32_t (*)(vr::EVRInitError *, vr::EVRApplicationType)>(dlsym(library, "VR_InitInternal"));
|
|
auto generic = reinterpret_cast<GetInterface>(dlsym(library, "VR_GetGenericInterface"));
|
|
if (!init || !generic) return false;
|
|
vr::EVRInitError error = vr::VRInitError_None;
|
|
init(&error, vr::VRApplication_Background);
|
|
if (error != vr::VRInitError_None) return false;
|
|
vr_system = get<vr::IVRSystem>(generic, vr::IVRSystem_Version);
|
|
compositor = get<vr::IVRCompositor>(generic, vr::IVRCompositor_Version);
|
|
settings = get<vr::IVRSettings>(generic, vr::IVRSettings_Version);
|
|
chaperone = get<vr::IVRChaperone>(generic, vr::IVRChaperone_Version);
|
|
setup = get<vr::IVRChaperoneSetup>(generic, vr::IVRChaperoneSetup_Version);
|
|
overlay = get<vr::IVROverlay>(generic, vr::IVROverlay_Version);
|
|
overlay_view = get<vr::IVROverlayView>(generic, vr::IVROverlayView_Version);
|
|
camera = get<vr::IVRTrackedCamera>(generic, vr::IVRTrackedCamera_Version);
|
|
display = get<vr::IVRExtendedDisplay>(generic, vr::IVRExtendedDisplay_Version);
|
|
return vr_system;
|
|
}
|
|
template<typename T> void number(std::ostream &o, T v, bool valid = true) {
|
|
if (valid && std::isfinite(static_cast<double>(v))) o << v; else o << "null";
|
|
}
|
|
void points(std::ostream &o, const vr::HmdVector3_t *p, unsigned count) {
|
|
o << '[';
|
|
for (unsigned i=0; i<count; ++i) {
|
|
if (i) o << ',';
|
|
o << '['; number(o,p[i].v[0]); o << ','; number(o,p[i].v[1]); o << ','; number(o,p[i].v[2]); o << ']';
|
|
}
|
|
o << ']';
|
|
}
|
|
}
|
|
|
|
extern "C" const char *rifts_snapshot(const char *path) {
|
|
if (!connect(path)) return "{}";
|
|
std::ostringstream o;
|
|
vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount]{};
|
|
vr_system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0, poses, vr::k_unMaxTrackedDeviceCount);
|
|
o << "{\"devices\":[";
|
|
vr::TrackedDeviceIndex_t ids[] = {0,
|
|
vr_system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand),
|
|
vr_system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand)};
|
|
// Sleeping controllers can lose their assigned role. The driver retains
|
|
// its role hint, so their real disconnected/untracked state stays visible.
|
|
if(ids[1]==vr::k_unTrackedDeviceIndexInvalid || ids[2]==vr::k_unTrackedDeviceIndexInvalid) {
|
|
for(uint32_t index=1;index<vr::k_unMaxTrackedDeviceCount;++index) {
|
|
if(vr_system->GetTrackedDeviceClass(index)!=vr::TrackedDeviceClass_Controller) continue;
|
|
vr::ETrackedPropertyError error;
|
|
int role=vr_system->GetInt32TrackedDeviceProperty(index,vr::Prop_ControllerRoleHint_Int32,&error);
|
|
if(error==vr::TrackedProp_Success && (role==vr::TrackedControllerRole_LeftHand || role==vr::TrackedControllerRole_RightHand)) {
|
|
unsigned slot=role==vr::TrackedControllerRole_LeftHand ? 1:2;
|
|
if(ids[slot]==vr::k_unTrackedDeviceIndexInvalid) ids[slot]=index;
|
|
}
|
|
}
|
|
}
|
|
for (unsigned i=0;i<3;++i) {
|
|
if(i) o << ',';
|
|
auto id=ids[i];
|
|
bool valid=id<vr::k_unMaxTrackedDeviceCount;
|
|
const char *tracking=nullptr;
|
|
if (valid && vr_system->GetTrackedDeviceClass(id) == (i ? vr::TrackedDeviceClass_Controller : vr::TrackedDeviceClass_HMD)) {
|
|
const auto &pose=poses[id];
|
|
if (!pose.bDeviceIsConnected) tracking="Disconnected";
|
|
else if (!pose.bPoseIsValid) tracking="Untracked";
|
|
else if (pose.eTrackingResult==vr::TrackingResult_Running_OK) tracking="6DoF";
|
|
else if (pose.eTrackingResult==vr::TrackingResult_Fallback_RotationOnly) tracking="3DoF";
|
|
else tracking="Limited";
|
|
}
|
|
o << "{\"tracking\":";
|
|
if(tracking) o << '"' << tracking << '"'; else o << "null";
|
|
vr::ETrackedPropertyError err=vr::TrackedProp_UnknownProperty;
|
|
float battery=valid ? vr_system->GetFloatTrackedDeviceProperty(id,vr::Prop_DeviceBatteryPercentage_Float,&err) : 0;
|
|
o << ",\"battery\":"; number(o,battery,err==vr::TrackedProp_Success && battery>=0 && battery<=1); o << '}';
|
|
}
|
|
o << "],\"refresh\":";
|
|
vr::ETrackedPropertyError err;
|
|
float refresh=vr_system->GetFloatTrackedDeviceProperty(0,vr::Prop_DisplayFrequency_Float,&err);
|
|
number(o,refresh,err==vr::TrackedProp_Success);
|
|
o << ",\"display\":";
|
|
int width=vr_system->GetInt32TrackedDeviceProperty(0,vr::Prop_DisplayMCImageWidth_Int32,&err);
|
|
bool has_width=err==vr::TrackedProp_Success && width>0;
|
|
int height=vr_system->GetInt32TrackedDeviceProperty(0,vr::Prop_DisplayMCImageHeight_Int32,&err);
|
|
if(has_width && err==vr::TrackedProp_Success && height>0) o << '[' << width << ',' << height << ']';
|
|
else if(display) {
|
|
int32_t x=0,y=0; uint32_t w=0,h=0; display->GetWindowBounds(&x,&y,&w,&h);
|
|
if(w && h) o << '[' << w << ',' << h << ']'; else o << "null";
|
|
} else o << "null";
|
|
vr::Compositor_FrameTiming timing{}; timing.m_nSize=sizeof(timing);
|
|
bool timed=compositor && compositor->GetFrameTiming(&timing);
|
|
o << ",\"frameTime\":"; number(o,timing.m_flTotalRenderGpuMs,timed);
|
|
o << ",\"dropped\":"; number(o,timing.m_nNumDroppedFrames,timed);
|
|
float ipd=vr_system->GetFloatTrackedDeviceProperty(0,vr::Prop_UserIpdMeters_Float,&err);
|
|
o << ",\"ipd\":"; number(o,ipd*1000,err==vr::TrackedProp_Success);
|
|
o << ",\"proximity\":";
|
|
vr::VRControllerState_t state{};
|
|
if(vr_system->GetControllerState(0,&state,sizeof(state))) o << ((state.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ProximitySensor)) ? "true":"false"); else o << "null";
|
|
o << ",\"idle\":" << (poses[0].bPoseIsValid && std::abs(poses[0].vVelocity.v[0])+std::abs(poses[0].vVelocity.v[1])+std::abs(poses[0].vVelocity.v[2])<0.02 && std::abs(poses[0].vAngularVelocity.v[0])+std::abs(poses[0].vAngularVelocity.v[1])+std::abs(poses[0].vAngularVelocity.v[2])<0.05 ? "true":"false");
|
|
vr::HmdQuad_t rect{};
|
|
o << ",\"rect\":";
|
|
if(chaperone && chaperone->GetPlayAreaRect(&rect)) points(o,rect.vCorners,4); else o << "null";
|
|
o << ",\"walls\":[";
|
|
uint32_t count=0;
|
|
if(setup) setup->GetLiveCollisionBoundsInfo(nullptr,&count);
|
|
if(setup && count<4096) {
|
|
std::vector<vr::HmdQuad_t> walls(count);
|
|
if(count && setup->GetLiveCollisionBoundsInfo(walls.data(),&count))
|
|
for(uint32_t i=0;i<count;++i) { if(i) o << ','; points(o,walls[i].vCorners,4); }
|
|
}
|
|
o << "],\"floor\":";
|
|
auto m=vr_system->GetRawZeroPoseToStandingAbsoluteTrackingPose();
|
|
number(o,-(m.m[0][1]*m.m[0][3]+m.m[1][1]*m.m[1][3]+m.m[2][1]*m.m[2][3]),chaperone && chaperone->GetCalibrationState()==vr::ChaperoneCalibrationState_OK);
|
|
o << ",\"head\":";
|
|
if(poses[0].bPoseIsValid) o << '[' << poses[0].mDeviceToAbsoluteTracking.m[0][3] << ',' << poses[0].mDeviceToAbsoluteTracking.m[2][3] << ']'; else o << "null";
|
|
bool has_camera=false;
|
|
if(camera) camera->HasCamera(0,&has_camera);
|
|
vr::CameraVideoStreamFrameHeader_t frame{};
|
|
bool fresh=false; double rate=0;
|
|
if(has_camera && !camera_handle) camera->AcquireVideoStreamingService(0,&camera_handle);
|
|
if(camera_handle && camera->GetVideoStreamFrameBuffer(camera_handle,vr::VRTrackedCameraFrameType_Undistorted,nullptr,0,&frame,sizeof(frame))==vr::VRTrackedCameraError_None) {
|
|
auto now=Clock::now();
|
|
double seconds=std::chrono::duration<double>(now-previous_time).count();
|
|
fresh=previous_frame && frame.nFrameSequence!=previous_sequence;
|
|
if(fresh && seconds>0 && frame.nFrameSequence>previous_sequence) rate=(frame.nFrameSequence-previous_sequence)/seconds;
|
|
previous_sequence=frame.nFrameSequence; previous_time=now; previous_frame=true;
|
|
} else previous_frame=false;
|
|
o << ",\"cameraExport\":" << (has_camera ? "true":"false") << ",\"cameraActive\":" << (fresh ? "true":"false") << ",\"cameraRate\":"; number(o,rate,fresh && rate>0 && rate<100);
|
|
o << ",\"mirrorOnStartup\":";
|
|
vr::EVRSettingsError mirror_error = vr::VRSettingsError_None;
|
|
bool mirror = settings && settings->GetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_MirrorViewVisibility_Bool, &mirror_error);
|
|
if(settings && mirror_error == vr::VRSettingsError_None) o << (mirror ? "true" : "false"); else o << "null";
|
|
o << '}'; output=o.str(); return output.c_str();
|
|
}
|
|
|
|
// vrmonitor reads this public preference at startup, not when it changes.
|
|
extern "C" int rifts_mirror_preference(int enabled) {
|
|
if(!settings) return -1;
|
|
vr::EVRSettingsError error = vr::VRSettingsError_None;
|
|
if(enabled >= 0) settings->SetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_MirrorViewVisibility_Bool, enabled != 0, &error);
|
|
if(error != vr::VRSettingsError_None) return -1;
|
|
bool value = settings->GetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_MirrorViewVisibility_Bool, &error);
|
|
return error == vr::VRSettingsError_None ? int(value) : -1;
|
|
}
|
|
|
|
extern "C" int rifts_action(int action) {
|
|
if(!vr_system) return 0;
|
|
if(action==2 && overlay) { overlay->ShowDashboard(""); return 1; }
|
|
if(action==3 && chaperone) { chaperone->ResetZeroPose(vr::TrackingUniverseStanding); return 1; }
|
|
return 0;
|
|
}
|
|
extern "C" void rifts_shutdown() {
|
|
rifts_headset_preview_shutdown();
|
|
overlay_view = nullptr;
|
|
if(camera_handle && camera) camera->ReleaseVideoStreamingService(camera_handle);
|
|
camera_handle=0; camera=nullptr; previous_frame=false;
|
|
if(library) { auto shutdown=reinterpret_cast<void (*)()>(dlsym(library,"VR_ShutdownInternal")); if(shutdown) shutdown(); dlclose(library); }
|
|
library=nullptr; vr_system=nullptr; compositor=nullptr; settings=nullptr; chaperone=nullptr; setup=nullptr; overlay=nullptr; display=nullptr;
|
|
}
|
|
|
|
// OpenVR lock held. Drains this client's event queue and reports VREvent_Quit.
|
|
// vrserver sends it to every client when it quits (also when vrmonitor dies)
|
|
// and kills clients still connected ~5 s later, so the caller must disconnect.
|
|
extern "C" bool rifts_quit_requested() {
|
|
if(!vr_system) return false;
|
|
vr::VREvent_t event;
|
|
bool quit=false;
|
|
while(vr_system->PollNextEvent(&event, sizeof event)) quit |= event.eventType==vr::VREvent_Quit;
|
|
return quit;
|
|
}
|
|
|
|
// Preview thread, OpenVR lock held. Uses the connection the snapshot poll
|
|
// keeps; never connects or spawns anything itself.
|
|
extern "C" int64_t rifts_preview_step() {
|
|
return rifts_headset_preview_step(vr_system, overlay, overlay_view, compositor);
|
|
}
|