mirror of
https://github.com/Toastconcern/FrameStudio.git
synced 2026-10-28 20:03:07 +01:00
Frame Home runs on SteamVR on a Windows PC (tested on SteamVR's virtual headset)
- vr_compat.h: GetProjectionMatrix / GetEyeToHeadTransform called with Microsoft's member ABI on MinGW builds (SteamVR is built with MSVC; the hidden return pointer goes after this): the first frame crashed there. - When SteamVR asks it to quit (a game, room setup), Frame Home leaves at once instead of waiting for a load (SteamVR killed it after 5 s). - The panel keeps SteamVR's home app running on a PC: SteamVR doesn't start it again after it stopped on its own. - Logs on Windows: the environment and the panel always write logs/frame-home.log and logs/frame-switcher.log (SteamVR gives them a hidden console); the panel logs to a file on the headset too now. A crash writes the exception and the stack's return addresses there. - Tested on SteamVR's null driver: Frame Home started by SteamVR as its home app, the panel with it, SteamVR's background made from the home, a crash recovered in seconds, 0.79 ms GPU at 1976x1976 per eye with 4x MSAA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1863e495d2
commit
4d058a9e7a
4 files changed
+89
-7
No files matched your search
+30
-1
@@ -66,7 +66,9 @@ inline std::filesystem::path selfPath() {
|
||||
const DWORD n = GetModuleFileNameW(nullptr, buf, static_cast<DWORD>(std::size(buf)));
|
||||
return n ? std::filesystem::path(buf) : std::filesystem::path();
|
||||
}
|
||||
inline bool stderrIsTerminal() { return _isatty(_fileno(stderr)) != 0; }
|
||||
// Is somebody reading stderr? On Windows a program SteamVR starts gets a hidden console (or NUL), indistinguishable
|
||||
// from a terminal: the modes SteamVR runs (the environment, the panel) always log to their files there.
|
||||
inline bool stderrIsTerminal() { return false; }
|
||||
inline long peakRamMB() {
|
||||
PROCESS_MEMORY_COUNTERS pmc = {};
|
||||
return GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof pmc) ? static_cast<long>(pmc.PeakWorkingSetSize / (1024 * 1024)) : 0;
|
||||
@@ -266,6 +268,33 @@ inline int run(const std::vector<std::string>& args, int nice, bool wait) {
|
||||
|
||||
inline void sleepMs(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); }
|
||||
|
||||
// A crash, said in the log (stderr): the exception, where, and the return addresses on the stack (each an address in
|
||||
// this program: tools/symbolize or `nm` maps them to functions). Windows only: Linux has its own (coredumps, the
|
||||
// headset's journal).
|
||||
#ifdef _WIN32
|
||||
inline LONG WINAPI logCrash(EXCEPTION_POINTERS* ep) {
|
||||
const HMODULE self = GetModuleHandleW(nullptr);
|
||||
const uintptr_t base = reinterpret_cast<uintptr_t>(self);
|
||||
std::fprintf(stderr, "[crash] exception 0x%08lx at 0x%llx (this program loads at 0x%llx)\n", ep->ExceptionRecord->ExceptionCode,
|
||||
static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(ep->ExceptionRecord->ExceptionAddress)), static_cast<unsigned long long>(base));
|
||||
const uintptr_t* sp = reinterpret_cast<const uintptr_t*>(ep->ContextRecord->Rsp);
|
||||
MODULEINFO mi = {};
|
||||
GetModuleInformation(GetCurrentProcess(), self, &mi, sizeof mi);
|
||||
const uintptr_t end = base + mi.SizeOfImage;
|
||||
int shown = 0;
|
||||
for (int k = 0; k < 2048 && shown < 24; ++k) { // words on the stack that point into this program's code
|
||||
uintptr_t v = 0;
|
||||
if (!ReadProcessMemory(GetCurrentProcess(), sp + k, &v, sizeof v, nullptr)) break;
|
||||
if (v > base && v < end) { std::fprintf(stderr, "[crash] stack+%d: +0x%llx\n", k * 8, static_cast<unsigned long long>(v - base)); ++shown; }
|
||||
}
|
||||
std::fflush(stderr);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
inline void logCrashes() { SetUnhandledExceptionFilter(logCrash); }
|
||||
#else
|
||||
inline void logCrashes() {}
|
||||
#endif
|
||||
|
||||
// Is SteamVR running (its server)? Asked before connecting as a utility: on Windows, connecting starts SteamVR.
|
||||
inline bool steamVrRunning() {
|
||||
#ifdef _WIN32
|
||||
|
||||
+15
-5
@@ -24,6 +24,7 @@
|
||||
#include "thermal.h"
|
||||
#include "fenv.h"
|
||||
#include "home_platform.h"
|
||||
#include "vr_compat.h"
|
||||
#include "stb_image_write.h"
|
||||
|
||||
#include <openvr.h>
|
||||
@@ -935,7 +936,7 @@ VkPhysicalDevice pickVrDevice(VkInstance instance) {
|
||||
// widen > 1: a wider view than the lens shows (system mode: margin for the compositor's rotation reprojection).
|
||||
Mat4 eyeProjection(vr::EVREye eye, float widen = 1.0f) {
|
||||
// Tangents from OpenVR's own projection (GL style: row 3 = [0 0 -1 0], y up).
|
||||
vr::HmdMatrix44_t p = vr::VRSystem()->GetProjectionMatrix(eye, kNear, 100.0f);
|
||||
vr::HmdMatrix44_t p = vrcompat::projectionMatrix(eye, kNear, 100.0f);
|
||||
const float l = (p.m[0][2] - 1) / p.m[0][0], r = (p.m[0][2] + 1) / p.m[0][0];
|
||||
const float b = (p.m[1][2] - 1) / p.m[1][1], t = (p.m[1][2] + 1) / p.m[1][1];
|
||||
return projection(l * widen, r * widen, b * widen, t * widen);
|
||||
@@ -1154,8 +1155,8 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
float lastHeadR[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
double widenSum = 0;
|
||||
uint64_t widenN = 0;
|
||||
const Mat4 eyeToHead[2] = {fromHmd34(vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left)),
|
||||
fromHmd34(vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Right))};
|
||||
const Mat4 eyeToHead[2] = {fromHmd34(vrcompat::eyeToHead(vr::Eye_Left)),
|
||||
fromHmd34(vrcompat::eyeToHead(vr::Eye_Right))};
|
||||
auto start = std::chrono::steady_clock::now(); // the shader clock (Animations off holds it)
|
||||
const auto loopEpoch = start;
|
||||
vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];
|
||||
@@ -1180,7 +1181,7 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
float gazeSmooth[2][2] = {{-1, -1}, {-1, -1}}; // pixels: restarted when the resolution changes
|
||||
uint32_t gazeW = 0;
|
||||
uint64_t gpuSeen = 0;
|
||||
bool restart = false, lostDevice = false;
|
||||
bool restart = false, lostDevice = false, quitAsked = false;
|
||||
float animTime = 0, animLength = 60;
|
||||
auto animTick = std::chrono::steady_clock::now();
|
||||
SystemPacer pacer; // system mode: frames timed to the compositor's
|
||||
@@ -1250,7 +1251,7 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
while (running) {
|
||||
vr::VREvent_t ev;
|
||||
while (vr::VRSystem()->PollNextEvent(&ev, sizeof(ev)))
|
||||
if (ev.eventType == vr::VREvent_Quit) { vr::VRSystem()->AcknowledgeQuit_Exiting(); running = false; }
|
||||
if (ev.eventType == vr::VREvent_Quit) { vr::VRSystem()->AcknowledgeQuit_Exiting(); running = false; quitAsked = true; }
|
||||
if (!running) break;
|
||||
|
||||
// A parsed home goes to the GPU a few milliseconds a frame (system mode: once faded to black), so frames keep
|
||||
@@ -1798,6 +1799,14 @@ int runVr(const std::vector<std::string>& apks, bool system = false) {
|
||||
if (++frames == 1) LOG("first frame submitted");
|
||||
if (frames % 5400 == 0) LOG("%llu frames", static_cast<unsigned long long>(frames)); // alive (about once a minute)
|
||||
}
|
||||
if (quitAsked && !restart) { // SteamVR wants the scene for a game (or room setup) now, and kills a home that takes
|
||||
// more than 5 s: off SteamVR at once, no waiting for a load under way; the system frees
|
||||
// the rest.
|
||||
vr::VR_Shutdown();
|
||||
LOG("quit: SteamVR asked for the scene");
|
||||
std::fflush(stderr);
|
||||
std::_Exit(0);
|
||||
}
|
||||
if (worker.joinable()) worker.join();
|
||||
if (soundJob.joinable()) soundJob.join();
|
||||
publisher.finish();
|
||||
@@ -1974,6 +1983,7 @@ int runInstall(bool install) {
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
homeos::logCrashes();
|
||||
#ifdef FH_USE_VOLK
|
||||
if (volkInitialize() != VK_SUCCESS) { std::fprintf(stderr, "frame-home: no Vulkan on this system\n"); return 1; }
|
||||
#endif
|
||||
|
||||
+14
-1
@@ -1489,6 +1489,11 @@ void prepareEnvironments() {
|
||||
}
|
||||
|
||||
int runPanel() {
|
||||
if (!homeos::stderrIsTerminal()) { // started by SteamVR: messages go to logs/frame-switcher.log
|
||||
std::error_code ec;
|
||||
fs::create_directories(rootDir() / "logs", ec);
|
||||
if (homeos::reopenStderr(rootDir() / "logs" / "frame-switcher.log", "w")) setvbuf(stderr, nullptr, _IOLBF, 0);
|
||||
}
|
||||
signal(SIGINT, [](int) { g_quit = 1; });
|
||||
signal(SIGTERM, [](int) { g_quit = 1; });
|
||||
vr::EVRInitError ierr = vr::VRInitError_None;
|
||||
@@ -1585,7 +1590,15 @@ int runPanel() {
|
||||
bool adopted = false; // systemPid isn't our child (a panel before this one started it)
|
||||
auto lastSpawn = std::chrono::steady_clock::now() - std::chrono::minutes(1);
|
||||
auto keepSystem = [&](std::chrono::steady_clock::time_point now) {
|
||||
if (!systemEnv) return; // SteamVR runs Frame Home itself (its home app)
|
||||
if (!systemEnv) { // SteamVR's home app: SteamVR starts it, but not again after it stopped on its own (a crash):
|
||||
// when nothing else is in VR, have it started (at most every 5 s)
|
||||
if (!homeInstalled || now - lastSpawn < std::chrono::seconds(5) || !homeos::runningCopies("frame-home").empty()) return;
|
||||
if (vr::VRApplications()->GetSceneApplicationState() != vr::EVRSceneApplicationState_None) return; // a game, or one starting / quitting
|
||||
lastSpawn = now;
|
||||
const vr::EVRApplicationError le = vr::VRApplications()->LaunchApplication("openvr.tool.steamvr_environments");
|
||||
LOG("Frame Home wasn't running: started it (%s)", vr::VRApplications()->GetApplicationsErrorNameFromEnum(le));
|
||||
return;
|
||||
}
|
||||
#ifndef _WIN32
|
||||
if (systemPid > 0 && adopted) {
|
||||
if (kill(systemPid, 0) == 0) return;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
// The OpenVR calls that return a struct, made the way SteamVR expects when Frame Home is built for Windows with
|
||||
// MinGW (GCC or Clang). SteamVR is built with Microsoft's compiler, whose C++ ABI passes a member function's hidden
|
||||
// return pointer after `this`; MinGW's (the Itanium ABI) passes it before. Called through the C++ interface, these
|
||||
// jumped to garbage (a crash in the first frame). Here they go through the interface's vtable with Microsoft's order.
|
||||
// Everywhere else (Linux, the Steam Frame, an MSVC build): the plain calls.
|
||||
#include <openvr.h>
|
||||
|
||||
namespace vrcompat {
|
||||
|
||||
#if defined(_WIN32) && !defined(_MSC_VER)
|
||||
// slot: the method's place in the interface's vtable, as openvr.h declares it for the version asked for (IVRSystem_026).
|
||||
template <typename R, typename... A> R msvcMember(void* object, int slot, A... args) {
|
||||
R result;
|
||||
using Fn = R* (*)(void*, R*, A...); // x64: this, the return pointer, then the arguments, as Microsoft's members take them
|
||||
(*reinterpret_cast<Fn* const*>(object))[slot](object, &result, args...);
|
||||
return result;
|
||||
}
|
||||
inline vr::HmdMatrix44_t projectionMatrix(vr::EVREye eye, float nearZ, float farZ) {
|
||||
return msvcMember<vr::HmdMatrix44_t>(vr::VRSystem(), 1, eye, nearZ, farZ); // IVRSystem::GetProjectionMatrix
|
||||
}
|
||||
inline vr::HmdMatrix34_t eyeToHead(vr::EVREye eye) {
|
||||
return msvcMember<vr::HmdMatrix34_t>(vr::VRSystem(), 5, eye); // IVRSystem::GetEyeToHeadTransform
|
||||
}
|
||||
#else
|
||||
inline vr::HmdMatrix44_t projectionMatrix(vr::EVREye eye, float nearZ, float farZ) { return vr::VRSystem()->GetProjectionMatrix(eye, nearZ, farZ); }
|
||||
inline vr::HmdMatrix34_t eyeToHead(vr::EVREye eye) { return vr::VRSystem()->GetEyeToHeadTransform(eye); }
|
||||
#endif
|
||||
|
||||
} // namespace vrcompat
|
||||
Reference in new issue
Block a user