#include "openvr.h" #include "headset_preview.hpp" #include #include #include #include #include #include // 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 T *get(GetInterface fn, const char *version) { vr::EVRInitError error = vr::VRInitError_None; return static_cast(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(dlsym(library, "VR_InitInternal")); auto generic = reinterpret_cast(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(generic, vr::IVRSystem_Version); compositor = get(generic, vr::IVRCompositor_Version); settings = get(generic, vr::IVRSettings_Version); chaperone = get(generic, vr::IVRChaperone_Version); setup = get(generic, vr::IVRChaperoneSetup_Version); overlay = get(generic, vr::IVROverlay_Version); overlay_view = get(generic, vr::IVROverlayView_Version); camera = get(generic, vr::IVRTrackedCamera_Version); display = get(generic, vr::IVRExtendedDisplay_Version); return vr_system; } template void number(std::ostream &o, T v, bool valid = true) { if (valid && std::isfinite(static_cast(v))) o << v; else o << "null"; } void points(std::ostream &o, const vr::HmdVector3_t *p, unsigned count) { o << '['; for (unsigned i=0; iGetDeviceToAbsoluteTrackingPose(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;indexGetTrackedDeviceClass(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=idGetTrackedDeviceClass(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 walls(count); if(count && setup->GetLiveCollisionBoundsInfo(walls.data(),&count)) for(uint32_t i=0;iGetRawZeroPoseToStandingAbsoluteTrackingPose(); 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(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(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); }