#pragma once #include "app.h" #include "hmd.h" #include "log.h" #include #include #include #include #include #include namespace pp::platform::windows { [[nodiscard]] App* bound_app() noexcept; struct VrShellState final { std::mutex snapshot_mutex; VrSessionSnapshot session; std::jthread hmd_renderer; std::mutex hmd_render_mutex; std::condition_variable hmd_render_cv; std::unique_ptr vive; std::atomic vr_frames{0}; std::atomic_bool vr_running{false}; }; inline int current_vr_fps(const VrShellState& state) { return state.vr_frames.load(std::memory_order_relaxed); } inline VrSessionSnapshot read_vr_session_snapshot(VrShellState& state) { std::lock_guard lock(state.snapshot_mutex); return state.session; } inline void write_vr_session_snapshot( VrShellState& state, bool has_vr, bool vr_active, const VRController& primary_controller, const glm::mat4& vr_head) { std::lock_guard lock(state.snapshot_mutex); state.session.has_vr = has_vr; state.session.vr_active = vr_active; state.session.vr_controllers[0] = primary_controller; state.session.vr_head = vr_head; } inline void clear_vr_session_snapshot(VrShellState& state, bool has_vr) { std::lock_guard lock(state.snapshot_mutex); state.session.has_vr = has_vr; state.session.vr_active = false; state.session.vr_controllers[0] = {}; state.session.vr_head = glm::mat4(1.0f); } inline void request_stop_and_join_vr_thread(VrShellState& state) { if (state.hmd_renderer.joinable()) { state.hmd_renderer.request_stop(); state.hmd_renderer.join(); } } inline bool start_vr_shell(VrShellState& state, bool sandboxed, std::atomic& running) { if (sandboxed) return false; auto* app = bound_app(); state.vive = std::make_unique(); state.vive->on_draw = [app](const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose) { app->vr_draw(proj, view, pose); }; if (!state.vive->Initialize()) { state.vive.reset(); LOG("VR: failed to initialize vive"); return false; } if (state.hmd_renderer.joinable()) { state.hmd_renderer.request_stop(); state.hmd_renderer.join(); } state.hmd_renderer = std::jthread([app, &state, &running](std::stop_token stop_token) { if (!state.vive) return; BT_SetTerminate(); LOG("start hmd render thread"); clear_vr_session_snapshot(state, true); state.vr_running.store(true, std::memory_order_relaxed); state.vive->on_analog_button = std::bind(&App::vr_analog, app, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); state.vive->on_button = std::bind(&App::vr_digital, app, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); app->render_task([app] { app->vr_draw_ui(); }); const float target_tick_rate = 90; auto t0 = GetTickCount64(); float one_sec_timer = 0; int frames = 0; while (!stop_token.stop_requested() && state.vr_running.load(std::memory_order_relaxed) && running.load(std::memory_order_relaxed) == 1 && state.vive->Valid()) { std::unique_lock lock(state.hmd_render_mutex); auto t1 = GetTickCount64(); float dt = (float)(t1 - t0) / 1000.0f; one_sec_timer += dt; if (one_sec_timer >= 1.f) { one_sec_timer = 0; state.vr_frames.store(frames, std::memory_order_relaxed); frames = 0; } frames++; state.vive->Update(); write_vr_session_snapshot( state, true, state.vive->m_active, state.vive->m_controllers[0], state.vive->m_pose); app->vr_update(dt); if (state.vr_running.load(std::memory_order_relaxed) && state.vive->m_active) { app->render_task([&state] { state.vive->Draw(); }); } const int framerate = (1.f / target_tick_rate) * 1000; const int diff = framerate - (t1 - t0); (void)diff; //state.hmd_render_cv.wait_for(lock, std::chrono::milliseconds(diff)); t0 = t1; } clear_vr_session_snapshot(state, false); state.vr_running.store(false, std::memory_order_relaxed); state.vive->Terminate(); LOG("hmd renderer terminated"); }); return true; } inline void stop_vr_shell(VrShellState& state) { if (state.vive) { state.vr_running.store(false, std::memory_order_relaxed); request_stop_and_join_vr_thread(state); state.vive->Terminate(); state.vive.reset(); } } }