// panel_bench: feed-start -> photons latency benchmark. Lights the LEFT eye // solid white (right eye black) and prints millisecond wallclock milestones // so an external camera — watching the optic and a desktop wallclock in the // same shot — can measure when the panel actually emitted light relative to // when software started feeding it. // // panel_bench [seconds] cold-init mode (default). default 10, // HARD CAP 30 (OLED burn safety: a // full-white field must never linger). // // panel_bench cycle [n] [warm] [dwell] warm parked->wake mode. After a cold // init, run n park->dwell->wake cycles // (defaults 3 / 2s / 3s) and report the // WAKE_REQUEST -> DISPLAYS_ON latency of // each — the warm-wake baseline doze // must beat (cold path is the other // mode). Park = host POWER_OFF (scanout // stops, firmware drops panels with the // video signal, DM acquisition KEPT); // wake = SetDisplayMode + POWER_ON. // // Milestones (local wallclock, HH:MM:SS.mmm — same clock the on-screen // desktop timer shows, so camera frames correlate directly): // INIT_START — before DirectMode acquire / modeset / POWER_ON // INIT_DONE — acquire+modeset returned; present loop starts next // FIRST_DRAW — first frame's draw callback (its Present submits right after) // then one status line per second (fps + prox), and BENCH_END at teardown. // Cycle mode adds, per cycle: PARK_REQUEST / PARK_DONE, WAKE_REQUEST, and a // WARM_WAKE_LATENCY line (request -> firmware DISPLAYS_ON, ms; the camera // gives the true photon moment, paired off the WAKE_REQUEST wallclock stamp). // // Prox is reported but NOT acted on — panels are driven regardless of worn // state (bench rig: headset on a stool facing a camera, nobody wearing it). #include "device/mcu_prox.h" #include "present/nvapi_d3d12.h" #include #include #include #include #include #include #include #include namespace { std::atomic g_stop{false}; // Steady-clock ns of the last WAKE_REQUEST, or 0 when no wake is pending. The // director thread sets it just before requesting POWER_ON; the display-status // watcher reads it on the DISPLAYS_ON 0->1 edge to print WARM_WAKE_LATENCY, // then clears it. steady_clock (not wallclock) so the delta is monotonic. std::atomic g_wakeReqNs{0}; long long nowNs() { return std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count(); } BOOL WINAPI ctrlHandler(DWORD type) { if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT || type == CTRL_CLOSE_EVENT) { g_stop.store(true); return TRUE; } return FALSE; } void printStamp(const char* tag) { SYSTEMTIME st; GetLocalTime(&st); printf("%s %02u:%02u:%02u.%03u\n", tag, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); } } // namespace int main(int argc, char** argv) { setbuf(stdout, nullptr); setbuf(stderr, nullptr); // Mode select: // "cycle" -> warm PARKED->wake (host POWER_OFF, requestPanelPower) // "doze" -> warm DOZE->wake (firmware 'H'/'h', video stays up; fw >= 0.4.0) // else -> cold-init bench bool dozeMode = argc > 1 && strcmp(argv[1], "doze") == 0; bool cycleMode = (argc > 1 && strcmp(argv[1], "cycle") == 0) || dozeMode; int cycles = 3; double warmSec = 2.0, dwellSec = 3.0, seconds = 10.0; if (cycleMode) { if (argc > 2) cycles = atoi(argv[2]); if (argc > 3) warmSec = atof(argv[3]); if (argc > 4) dwellSec = atof(argv[4]); if (cycles < 1) cycles = 1; if (cycles > 10) cycles = 10; // bound total on-time if (warmSec < 0.5 || warmSec > 10.0) warmSec = 2.0; if (dwellSec < 0.5 || dwellSec > 20.0) dwellSec = 3.0; // Run-loop budget must outlast every cycle (the present loop's park branch // aborts at the deadline). Generous per-cycle slack covers park+wake waits. seconds = 5.0 + cycles * (warmSec + dwellSec + 4.0); } else { if (argc > 1) seconds = atof(argv[1]); if (seconds <= 0.0 || seconds > 30.0) seconds = 30.0; // hard cap (OLED burn) } SetConsoleCtrlHandler(ctrlHandler, TRUE); sauna::McuProx prox; bool haveProx = prox.start(); if (haveProx) printf("prox up: %s (distance %u)\n", prox.worn() ? "worn" : "away", prox.proxDistance()); else printf("prox unavailable (non-fatal)\n"); // Firmware gates panel emission on prox; bench rig is unworn, so disable // gating for the run and ALWAYS restore before exit ('p' persists until // '[' or power cycle). bool bypassed = haveProx && prox.setProxBypass(true); printf("prox bypass: %s\n", bypassed ? "ON ('p' sent)" : "FAILED"); // Firmware-truth watcher: stamp every display-status transition (50 ms // telemetry period bounds the resolution). DISPLAYS_ON going 0->1 is the // firmware-side "panels emitting" moment to pair with the camera's photons. std::thread dstat([&] { uint16_t prevDs = prox.displayStatus(); while (!g_stop.load()) { uint16_t ds = prox.displayStatus(); if (ds != prevDs) { SYSTEMTIME st; GetLocalTime(&st); printf("DSTAT_CHANGE 0x%04x -> 0x%04x [disp=%d proxOn=%d] " "%02u:%02u:%02u.%03u\n", prevDs, ds, (int)(ds & 1), (int)((ds >> 1) & 1), st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); // Warm-wake latency: firmware DISPLAYS_ON (bit0) rising while a wake is // pending = the panels-emitting moment. Telemetry period (50 ms) bounds // the resolution; the camera photon is the finer ground truth. if ((ds & 1) && !(prevDs & 1)) { long long req = g_wakeReqNs.exchange(0); if (req != 0) printf("WARM_WAKE_LATENCY %.1f ms (WAKE_REQUEST -> DISPLAYS_ON)\n", (nowNs() - req) / 1.0e6); } prevDs = ds; } Sleep(2); } }); sauna::NvapiPresenterConfig cfg; // S2 validated link config sauna::NvapiD3d12Presenter presenter(cfg); printStamp("INIT_START"); if (!presenter.init()) { g_stop.store(true); dstat.join(); return 3; } printStamp("INIT_DONE"); std::thread status([&] { sauna::PresentStats prev{}; while (!g_stop.load()) { std::this_thread::sleep_for(std::chrono::seconds(1)); auto ps = presenter.stats(); SYSTEMTIME st; GetLocalTime(&st); uint16_t ds = prox.displayStatus(); printf(" %02u:%02u:%02u.%03u fps=%5.1f prox=%s(%u) " "dstat=0x%04x[disp=%d proxOn=%d dsc=%d]%s\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, (double)(ps.frames - prev.frames), haveProx ? (prox.worn() ? "worn" : "away") : "n/a", haveProx ? prox.proxDistance() : 0, ds, (int)(ds & 1), (int)((ds >> 1) & 1), (int)((ds >> 3) & 1), ps.freeRunning ? " [FREE-RUN: vsync lost]" : ""); prev = ps; } }); // Cycle director (warm parked->wake mode): once panels are emitting, run the // park->dwell->wake cycles off the present thread. requestPanelPower is // thread-safe and the present loop acts on it at frame boundaries. Each wake // stamps WAKE_REQUEST (camera correlation) and arms g_wakeReqNs so the dstat // watcher prints WARM_WAKE_LATENCY on the firmware DISPLAYS_ON edge. auto interruptibleSleep = [&](double sec) { auto end = std::chrono::steady_clock::now() + std::chrono::duration(sec); while (!g_stop.load() && std::chrono::steady_clock::now() < end) Sleep(5); }; std::thread director; if (cycleMode) { director = std::thread([&] { // Firmware DISPLAYS_ON is the on/photon signal; with no prox telemetry // fall back to a fixed settle so the cycle still drives the camera. auto waitOn = [&] { if (haveProx) { while (!g_stop.load() && !prox.displaysOn()) Sleep(2); } else { interruptibleSleep(0.5); } }; waitOn(); // initial cold bring-up settle for (int i = 0; i < cycles && !g_stop.load(); ++i) { printf("CYCLE %d/%d\n", i + 1, cycles); interruptibleSleep(warmSec); // warm, panels on if (g_stop.load()) break; if (dozeMode) { // DOZE: firmware darkens the panels ('H'); the host KEEPS presenting, // video_enabled stays true, the link is never torn down. DISPLAYS_ON // falls as the firmware reaches the floor + OLED display-off. printStamp("DOZE_SLEEP_REQUEST"); prox.setDoze(true); while (!g_stop.load() && haveProx && prox.displaysOn()) Sleep(2); printStamp("DOZE_ASLEEP"); } else { // PARK: host POWER_OFF tears the pipeline down (the 4-6 s baseline). printStamp("PARK_REQUEST"); presenter.requestPanelPower(false); while (!g_stop.load() && !presenter.panelsOffNow()) Sleep(2); printStamp("PARK_DONE"); } interruptibleSleep(dwellSec); // asleep/parked dwell if (g_stop.load()) break; g_wakeReqNs.store(nowNs()); printStamp("WAKE_REQUEST"); if (dozeMode) prox.setDoze(false); // 'h' — firmware display-on + sweep up else presenter.requestPanelPower(true); // re-modeset + POWER_ON waitOn(); // wait photons } if (dozeMode && haveProx) prox.setDoze(false); // ensure not left dozed presenter.stop(); // cycles done -> end the run g_stop.store(true); }); } std::atomic firstDrawSeen{false}; presenter.run(seconds, [&](const sauna::FrameContext& fc) { if (!firstDrawSeen.exchange(true)) printStamp("FIRST_DRAW"); auto* list = static_cast(fc.cmdList); auto* rtv = static_cast(fc.rtv); LONG W = (LONG)fc.width, H = (LONG)fc.height, half = W / 2; float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; float black[4] = {0.0f, 0.0f, 0.0f, 1.0f}; D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H}; list->ClearRenderTargetView(*rtv, white, 1, &rl); list->ClearRenderTargetView(*rtv, black, 1, &rr); if (g_stop.load()) presenter.stop(); }); printStamp("BENCH_END"); g_stop.store(true); if (director.joinable()) director.join(); status.join(); dstat.join(); if (bypassed) printf("prox bypass restored: %s\n", prox.setProxBypass(false) ? "ok ('[' sent)" : "FAILED — power-cycle " "the headset or send '[' to clear"); prox.stop(); auto ps = presenter.stats(); printf("frames=%llu presentErrors=%llu freeRunEvents=%llu displayLost=%d\n", (unsigned long long)ps.frames, (unsigned long long)ps.presentErrors, (unsigned long long)ps.freeRunEvents, (int)ps.displayLost); return ps.displayLost ? 4 : 0; }