mirror of
https://github.com/goodpuppies/petplay.git
synced 2026-10-28 12:00:54 +01:00
Render thread (panorama, nice 0, load ~27 on 16 cores): - Per-run UI panel slots reused across eyes, one per-frame panel data texture, and cached struct bytes for shader uniforms and draw matrices: renderRaythreeFrame 4.16 -> 2.27 ms. - The worker skips publishing scenes whose fingerprint (overlaySceneFingerprint) matches the last one sent, with a 1 s keepalive. Anchored items now keep bit-identical local matrices while nothing moves: built from the local transform chain, or held through rounding noise where uikit derives matrixWorld from layout. Decode on the render thread 1.78 -> 0.04 ms/frame when idle; render thread busy 6.4 -> 4.2 ms/frame. Diagnostics: a frame timeline probe with schedstat in overlayRenderOwner, CPU profiling with an interval, dropLateFrames, renderThreadNice, split presenter finish/wait/publish, a native WaitFrameSync phase probe and a compositor frame-timing probe. asyncFrameWait, projectionPacing and deferDecode are projection experiments that did not help. Also includes the other agent's pending overlay scene buffer, wire and ovrdriver lab work. Agent: Claude Code 2.1.296; model claude-opus-5-5; thread 941be8c7-aca1-4664-b284-7cc0eed69e21
85 lines
4.0 KiB
C++
85 lines
4.0 KiB
C++
// Records IVRCompositor::GetFrameTimings for every compositor frame over a run, as a background
|
|
// app (no overlay, no rendering). Shows whether frames PetPlay misses line up with compositor
|
|
// drops, mispresents or reprojection, i.e. whether SteamVR itself stalled.
|
|
//
|
|
// Build: c++ -std=c++17 -O2 native/compositorTimingProbe/probe.cpp -ldl -o probe
|
|
// Run: probe [seconds=20] [csv-path]
|
|
// Columns follow Compositor_FrameTiming; vsync_counter is GetTimeSinceLastVsync's counter at
|
|
// the poll that first saw the frame, to map frame indices onto PetPlay's timeline.
|
|
#include "../../submodules/OpenVR_TS_Bindings_Deno/CTest/openvr_capi.h"
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <dlfcn.h>
|
|
#include <map>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
using InitFn = intptr_t (*)(EVRInitError *, EVRApplicationType);
|
|
using ShutdownFn = void (*)();
|
|
using GetInterfaceFn = intptr_t (*)(const char *, EVRInitError *);
|
|
|
|
int main(int argc, char **argv) {
|
|
double seconds = argc > 1 ? std::atof(argv[1]) : 20;
|
|
const char *csv_path = argc > 2 ? argv[2] : nullptr;
|
|
const char *lib_path = std::getenv("PETPLAY_OPENVR_LIBRARY");
|
|
void *lib = dlopen(lib_path ? lib_path : "/usr/lib/libopenvr_api.so", RTLD_NOW);
|
|
if (!lib) {
|
|
std::fprintf(stderr, "dlopen: %s\n", dlerror());
|
|
return 1;
|
|
}
|
|
auto init = reinterpret_cast<InitFn>(dlsym(lib, "VR_InitInternal"));
|
|
auto shutdown = reinterpret_cast<ShutdownFn>(dlsym(lib, "VR_ShutdownInternal"));
|
|
auto get = reinterpret_cast<GetInterfaceFn>(dlsym(lib, "VR_GetGenericInterface"));
|
|
EVRInitError error = EVRInitError_VRInitError_None;
|
|
init(&error, EVRApplicationType_VRApplication_Background);
|
|
if (error) {
|
|
std::fprintf(stderr, "VR_InitInternal error %d\n", error);
|
|
return 1;
|
|
}
|
|
auto system = reinterpret_cast<VR_IVRSystem_FnTable *>(
|
|
get((std::string("FnTable:") + IVRSystem_Version).c_str(), &error));
|
|
auto compositor = reinterpret_cast<VR_IVRCompositor_FnTable *>(
|
|
get((std::string("FnTable:") + IVRCompositor_Version).c_str(), &error));
|
|
if (!system || !compositor || error) {
|
|
std::fprintf(stderr, "interface error %d\n", error);
|
|
return 1;
|
|
}
|
|
struct Row { Compositor_FrameTiming t; uint64_t vsync; };
|
|
std::map<uint32_t, Row> frames;
|
|
Compositor_FrameTiming batch[64];
|
|
auto end = std::chrono::steady_clock::now() + std::chrono::duration<double>(seconds);
|
|
while (std::chrono::steady_clock::now() < end) {
|
|
for (auto &b : batch) b.m_nSize = sizeof(Compositor_FrameTiming);
|
|
uint32_t n = compositor->GetFrameTimings(batch, 64);
|
|
float since = 0;
|
|
uint64_t counter = 0;
|
|
system->GetTimeSinceLastVsync(&since, &counter);
|
|
// Timings for a frame keep filling in after it first appears; keep the latest copy.
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
auto it = frames.find(batch[i].m_nFrameIndex);
|
|
if (it == frames.end()) frames[batch[i].m_nFrameIndex] = {batch[i], counter};
|
|
else it->second.t = batch[i];
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
shutdown();
|
|
|
|
FILE *f = csv_path ? std::fopen(csv_path, "w") : stdout;
|
|
std::fprintf(f, "frame,vsync_counter,system_s,presents,mispresented,dropped,reprojection,"
|
|
"compositor_cpu_ms,compositor_gpu_ms,compositor_idle_ms,update_start_ms,"
|
|
"update_end_ms,render_start_ms,client_interval_ms,vsyncs_ready,vsyncs_to_view\n");
|
|
for (auto &[k, r] : frames) {
|
|
auto &t = r.t;
|
|
std::fprintf(f, "%u,%llu,%.6f,%u,%u,%u,%u,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%u,%u\n", k,
|
|
(unsigned long long)r.vsync, t.m_flSystemTimeInSeconds, t.m_nNumFramePresents,
|
|
t.m_nNumMisPresented, t.m_nNumDroppedFrames, t.m_nReprojectionFlags,
|
|
t.m_flCompositorRenderCpuMs, t.m_flCompositorRenderGpuMs,
|
|
t.m_flCompositorIdleCpuMs, t.m_flCompositorUpdateStartMs,
|
|
t.m_flCompositorUpdateEndMs, t.m_flCompositorRenderStartMs,
|
|
t.m_flClientFrameIntervalMs, t.m_nNumVSyncsReadyForUse, t.m_nNumVSyncsToFirstView);
|
|
}
|
|
if (csv_path) std::fclose(f);
|
|
return 0;
|
|
}
|