mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-20 14:00:55 +02:00
Budget Cuts Ultimate asks for a 2048x2048 cube swapchain (faces=6); the Frame's runtime has no cube layers and refuses it (-2), OVRPlugin carries on with no images and crashes in ovrp_EndFrame4 (memset). cube_standin (default on, GLES) serves it as a GL cube map in the game's context and drops its layers. Triage cube-swapchain-refused (supersedes unity-render-crash/native-crash). Verified headless: the game runs on at ~70 fps.
163 lines
8.4 KiB
C
163 lines
8.4 KiB
C
// SPDX-License-Identifier: GPL-3.0-only
|
|
// Included by frame_adapter.c (after flip_vk.c). Per-game diagnostics for "one eye looks wrong" reports
|
|
// (eye_debug=1; everything also goes to framebridge.log in the app's external files folder):
|
|
// * every frame's projection layer: each eye's submitted pose vs. a fresh xrLocateViews at the frame's display time
|
|
// (game-side pose errors), the left/right eye relation (eye distance, relative rotation: must stay constant),
|
|
// and how long before xrEndFrame each eye's image was released (images handed over too early/late);
|
|
// summarised every 5 s, the first frames in full.
|
|
// * release_wait: before an image is released, wait for the app's GPU work on the session's queue (Vulkan) or
|
|
// glFinish (GLES), for games that release images whose rendering hasn't finished. 2 = only after the first 60 s
|
|
// of frames, so one headset session compares both (log "release_wait now ON").
|
|
|
|
#define EYE_TRACKED 16
|
|
|
|
static struct { XrSwapchain handle; int64_t released_ns; } eye_released[EYE_TRACKED];
|
|
static pthread_mutex_t eye_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static int64_t eye_first_frame_ns;
|
|
|
|
static int release_wait_active(void) {
|
|
if (release_wait == 1) return 1;
|
|
if (release_wait != 2 || !eye_first_frame_ns) return 0;
|
|
static int announced;
|
|
int on = monotonic_ns() - eye_first_frame_ns > 60000000000ll;
|
|
if (on && !announced++) LOG("eye_debug: release_wait now ON (A/B: the first 60 s ran without it)");
|
|
return on;
|
|
}
|
|
|
|
static void eye_wait_for_gpu(void) {
|
|
if (vk.device) {
|
|
static PFN_vkQueueWaitIdle wait_idle;
|
|
if (!wait_idle && vk_ready()) wait_idle = (PFN_vkQueueWaitIdle)vk.GetDeviceProcAddr(vk.device, "vkQueueWaitIdle");
|
|
if (wait_idle && vk.queue) wait_idle(vk.queue);
|
|
} else {
|
|
static void (*finish)(void);
|
|
if (!finish) finish = (void (*)(void))dlsym(RTLD_DEFAULT, "glFinish");
|
|
if (finish) finish();
|
|
}
|
|
}
|
|
|
|
static XRAPI_ATTR XrResult XRAPI_CALL eye_hook_xrReleaseSwapchainImage(XrSwapchain swapchain,
|
|
const XrSwapchainImageReleaseInfo *info) {
|
|
PFN_xrReleaseSwapchainImage fn = (PFN_xrReleaseSwapchainImage)lookup(active_instance, "xrReleaseSwapchainImage");
|
|
if (!fn) return XR_ERROR_FUNCTION_UNSUPPORTED;
|
|
if (is_standin(swapchain)) return XR_SUCCESS;
|
|
if (release_wait_active()) eye_wait_for_gpu();
|
|
XrResult result = fn(swapchain, info);
|
|
if (eye_debug) {
|
|
int64_t now = monotonic_ns();
|
|
pthread_mutex_lock(&eye_lock);
|
|
int slot = -1;
|
|
for (int i = 0; i < EYE_TRACKED && slot < 0; ++i)
|
|
if (eye_released[i].handle == swapchain) slot = i;
|
|
for (int i = 0; i < EYE_TRACKED && slot < 0; ++i)
|
|
if (!eye_released[i].handle) slot = i;
|
|
if (slot < 0) slot = (int)(now % EYE_TRACKED);
|
|
eye_released[slot].handle = swapchain;
|
|
eye_released[slot].released_ns = now;
|
|
pthread_mutex_unlock(&eye_lock);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int64_t eye_release_age_ns(XrSwapchain swapchain, int64_t now) {
|
|
int64_t age = -1;
|
|
pthread_mutex_lock(&eye_lock);
|
|
for (int i = 0; i < EYE_TRACKED; ++i)
|
|
if (eye_released[i].handle == swapchain) age = now - eye_released[i].released_ns;
|
|
pthread_mutex_unlock(&eye_lock);
|
|
return age;
|
|
}
|
|
|
|
static float eye_angle_deg(XrQuaternionf a, XrQuaternionf b) {
|
|
float d = fabsf(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
|
|
return d >= 1.0f ? 0.0f : 2.0f * acosf(d) * 57.29578f;
|
|
}
|
|
|
|
static float eye_dist_mm(XrVector3f a, XrVector3f b) {
|
|
float x = a.x - b.x, y = a.y - b.y, z = a.z - b.z;
|
|
return sqrtf(x * x + y * y + z * z) * 1000.0f;
|
|
}
|
|
|
|
static void eye_debug_end_frame(XrSession session, const XrFrameEndInfo *info) {
|
|
if (!info) return;
|
|
const XrCompositionLayerProjection *proj = NULL;
|
|
for (uint32_t i = 0; i < info->layerCount && !proj; ++i)
|
|
if (info->layers[i] && info->layers[i]->type == XR_TYPE_COMPOSITION_LAYER_PROJECTION &&
|
|
((const XrCompositionLayerProjection *)info->layers[i])->viewCount == 2)
|
|
proj = (const XrCompositionLayerProjection *)info->layers[i];
|
|
if (!proj) return;
|
|
int64_t now = monotonic_ns();
|
|
if (!eye_first_frame_ns) eye_first_frame_ns = now;
|
|
static struct {
|
|
int frames, located;
|
|
float pos_err[2], rot_err[2], pos_err_max[2], rot_err_max[2]; // submitted vs. fresh, per eye
|
|
float ipd_min, ipd_max, rel_rot_max, step_max[2]; // eye relation; frame-to-frame jump per eye
|
|
float age_min[2], age_max[2]; // ms from release to xrEndFrame
|
|
int64_t window;
|
|
} st;
|
|
static XrPosef last[2];
|
|
static int have_last;
|
|
static int detailed;
|
|
if (!st.window) { st.window = now; st.ipd_min = 1e9f; st.age_min[0] = st.age_min[1] = 1e9f; }
|
|
// a fresh location of the same views at the frame's display time, in the layer's space
|
|
XrView fresh[2] = {{XR_TYPE_VIEW, NULL}, {XR_TYPE_VIEW, NULL}};
|
|
XrViewState vs = {XR_TYPE_VIEW_STATE, NULL};
|
|
uint32_t n = 0;
|
|
PFN_xrLocateViews locate = (PFN_xrLocateViews)lookup(active_instance, "xrLocateViews");
|
|
XrViewLocateInfo li = {XR_TYPE_VIEW_LOCATE_INFO, NULL, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, info->displayTime,
|
|
proj->space};
|
|
int located = locate && XR_SUCCEEDED(locate(session, &li, &vs, 2, &n, fresh)) && n == 2 &&
|
|
(vs.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT);
|
|
const XrCompositionLayerProjectionView *v = proj->views;
|
|
float ipd = eye_dist_mm(v[0].pose.position, v[1].pose.position);
|
|
float rel = eye_angle_deg(v[0].pose.orientation, v[1].pose.orientation);
|
|
++st.frames;
|
|
if (ipd < st.ipd_min) st.ipd_min = ipd;
|
|
if (ipd > st.ipd_max) st.ipd_max = ipd;
|
|
if (rel > st.rel_rot_max) st.rel_rot_max = rel;
|
|
for (int e = 0; e < 2; ++e) {
|
|
int64_t age = eye_release_age_ns(v[e].subImage.swapchain, now);
|
|
float ms = age < 0 ? -1.0f : age / 1e6f;
|
|
if (ms < st.age_min[e]) st.age_min[e] = ms;
|
|
if (ms > st.age_max[e]) st.age_max[e] = ms;
|
|
if (located) {
|
|
float pe = eye_dist_mm(v[e].pose.position, fresh[e].pose.position);
|
|
float re = eye_angle_deg(v[e].pose.orientation, fresh[e].pose.orientation);
|
|
st.pos_err[e] += pe;
|
|
st.rot_err[e] += re;
|
|
if (pe > st.pos_err_max[e]) st.pos_err_max[e] = pe;
|
|
if (re > st.rot_err_max[e]) st.rot_err_max[e] = re;
|
|
}
|
|
if (have_last) {
|
|
float step = eye_angle_deg(last[e].orientation, v[e].pose.orientation);
|
|
if (step > st.step_max[e]) st.step_max[e] = step;
|
|
}
|
|
last[e] = v[e].pose;
|
|
}
|
|
have_last = 1;
|
|
if (located) ++st.located;
|
|
if (detailed < 4) {
|
|
++detailed;
|
|
for (int e = 0; e < 2; ++e)
|
|
LOG("eye_debug: eye %d swapchain=%p rect=%d,%d %dx%d array=%u pos=%.4f,%.4f,%.4f rot=%.4f,%.4f,%.4f,%.4f "
|
|
"fov=%.3f/%.3f/%.3f/%.3f fresh pos=%.4f,%.4f,%.4f", e, (void *)(uintptr_t)v[e].subImage.swapchain,
|
|
v[e].subImage.imageRect.offset.x, v[e].subImage.imageRect.offset.y, v[e].subImage.imageRect.extent.width,
|
|
v[e].subImage.imageRect.extent.height, v[e].subImage.imageArrayIndex, v[e].pose.position.x,
|
|
v[e].pose.position.y, v[e].pose.position.z, v[e].pose.orientation.x, v[e].pose.orientation.y,
|
|
v[e].pose.orientation.z, v[e].pose.orientation.w, v[e].fov.angleLeft, v[e].fov.angleRight,
|
|
v[e].fov.angleUp, v[e].fov.angleDown, fresh[e].pose.position.x, fresh[e].pose.position.y,
|
|
fresh[e].pose.position.z);
|
|
}
|
|
if (now - st.window > 5000000000ll) {
|
|
int l = st.located ? st.located : 1;
|
|
LOG("eye_debug: %d frames | eye distance %.1f-%.1f mm, eye-to-eye rotation max %.2f deg | vs fresh pose: "
|
|
"L avg %.1f mm/%.2f deg max %.1f mm/%.2f deg, R avg %.1f mm/%.2f deg max %.1f mm/%.2f deg (%d located) | "
|
|
"largest frame step L %.2f R %.2f deg | release->end L %.1f-%.1f ms R %.1f-%.1f ms | release_wait %s",
|
|
st.frames, st.ipd_min, st.ipd_max, st.rel_rot_max, st.pos_err[0] / l, st.rot_err[0] / l, st.pos_err_max[0],
|
|
st.rot_err_max[0], st.pos_err[1] / l, st.rot_err[1] / l, st.pos_err_max[1], st.rot_err_max[1], st.located,
|
|
st.step_max[0], st.step_max[1], st.age_min[0], st.age_max[0], st.age_min[1], st.age_max[1],
|
|
release_wait_active() ? "on" : "off");
|
|
memset(&st, 0, sizeof(st));
|
|
}
|
|
}
|