mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 01:00:05 +02:00
An independent reimplementation of Meta's libOVRPlugin ABI on top of OpenXR, so VrApi-era Meta Quest VR titles can run on non-Meta OpenXR runtimes (Monado, Steam Frame) instead of being locked to Meta hardware. Original code only — no Meta/Epic/Capcom binaries, headers, or assets. Includes a desktop harness that drives the shim against Monado headless. Scope/legal: interoperability; entitlement handling is out of scope. See README for the legal/scope section and docs/ for the research trail and design notes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6sFYGXZPsq3v7xtcDES6g
367 lines
19 KiB
C
367 lines
19 KiB
C
/* harness.c — desktop OpenXR harness for the OVRPlugin->OpenXR shim.
|
|
*
|
|
* Stands in for Resident Evil 4 VR (UE4 + OculusHMD) on a Linux desktop: it creates
|
|
* a Vulkan instance/device the way UE's VulkanRHI does, then drives the shim's ovrp_*
|
|
* entry points in the exact order UE calls them — PreInitialize3 -> Get*ExtensionsVk
|
|
* -> Initialize5 -> (per frame) Update3/WaitToBeginFrame/BeginFrame4/EndFrame4 -> a
|
|
* one-time CalculateEyeLayerDesc2/SetupLayer -> Shutdown2. The shim creates the real
|
|
* XrInstance/session/swapchains against whatever OpenXR runtime the loader selects
|
|
* (here: Monado's simulated HMD, headless via XRT_COMPOSITOR_NULL).
|
|
*
|
|
* This exercises the whole non-Android OpenXR path of the shim on a PC — no Quest, no
|
|
* libUE4 — so the Steam Frame / Monado / Lepton bring-up can be iterated on a laptop.
|
|
*
|
|
* It is NOT the game and ships nothing from Capcom/Epic/Meta: it only calls our own
|
|
* public ovrp_* ABI. Build: shim/build_host.sh. Run: tools/desktop-harness/run.sh. */
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
|
|
#include <vulkan/vulkan.h>
|
|
#include "ovrplugin_shim.h" /* shim public types/enums (ovrpLayerDesc, ovrpLayout, ...) */
|
|
|
|
/* ovrp_* the shim exports but doesn't declare in the public header — declare here so we
|
|
* call them with the verified ABI without depending on header completeness. */
|
|
extern ovrpResult ovrp_GetInstanceExtensionsVk(const char **outArray, int *inoutCount);
|
|
extern ovrpResult ovrp_GetDeviceExtensionsVk(const char **outArray, int *inoutCount);
|
|
extern ovrpResult ovrp_CalculateEyeLayerDesc2(ovrpLayout layout, float textureScale,
|
|
int mipLevels, int sampleCount, ovrpTextureFormat colorFormat,
|
|
ovrpTextureFormat depthFormat, int layerFlags, ovrpLayerDesc *out);
|
|
extern ovrpResult ovrp_SetupLayer(void *device, ovrpLayerDesc *desc, int *outLayerId);
|
|
extern ovrpResult ovrp_GetLayerTextureStageCount(int layerId, int *outCount);
|
|
extern ovrpResult ovrp_GetLayerTexture2(int layerId, int stage, int eyeId,
|
|
uint64_t *outColorTex, uint64_t *outDepthTex);
|
|
|
|
#define VKOK(call) do { VkResult _r = (call); if (_r != VK_SUCCESS) { \
|
|
fprintf(stderr, "[harness] FAIL %s = %d\n", #call, _r); exit(1); } } while (0)
|
|
#define LOG(...) do { fprintf(stderr, "[harness] " __VA_ARGS__); fputc('\n', stderr); } while (0)
|
|
|
|
static VkInstance g_inst;
|
|
static VkPhysicalDevice g_phys;
|
|
static VkDevice g_dev;
|
|
static VkQueue g_queue;
|
|
static uint32_t g_gfxFamily;
|
|
static VkCommandPool g_cmdPool;
|
|
|
|
/* CPU-side staging for the scene renderer (host-visible; both eye layers, RGBA8). */
|
|
static VkBuffer g_stageBuf;
|
|
static VkDeviceMemory g_stageMem;
|
|
static void *g_stagePtr;
|
|
static uint32_t g_stageW, g_stageH;
|
|
|
|
/* The ovrp_*ExtensionsVk getters report a count then fill a caller array of char*. */
|
|
static const char **query_exts(int forDevice, int *outCount) {
|
|
int n = 0;
|
|
ovrpResult r = forDevice ? ovrp_GetDeviceExtensionsVk(NULL, &n)
|
|
: ovrp_GetInstanceExtensionsVk(NULL, &n);
|
|
if (!OVRP_SUCCESS(r) || n <= 0) { *outCount = 0; return NULL; }
|
|
const char **arr = calloc((size_t)n, sizeof(char *));
|
|
int cap = n;
|
|
r = forDevice ? ovrp_GetDeviceExtensionsVk(arr, &cap)
|
|
: ovrp_GetInstanceExtensionsVk(arr, &cap);
|
|
if (!OVRP_SUCCESS(r)) { free(arr); *outCount = 0; return NULL; }
|
|
*outCount = n;
|
|
LOG("%s extensions required by runtime (%d):", forDevice ? "device" : "instance", n);
|
|
for (int i = 0; i < n; i++) LOG(" %s", arr[i]);
|
|
return arr;
|
|
}
|
|
|
|
static void make_vk_instance(void) {
|
|
int n = 0;
|
|
const char **exts = query_exts(0, &n); /* needs the XrInstance (PreInitialize3 done) */
|
|
VkApplicationInfo ai = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
|
|
ai.pApplicationName = "re4vr-shim-harness";
|
|
ai.apiVersion = VK_API_VERSION_1_1; /* UE/Quest Vulkan baseline */
|
|
VkInstanceCreateInfo ci = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
|
|
ci.pApplicationInfo = &ai;
|
|
ci.enabledExtensionCount = (uint32_t)n;
|
|
ci.ppEnabledExtensionNames = exts;
|
|
VKOK(vkCreateInstance(&ci, NULL, &g_inst));
|
|
free(exts);
|
|
LOG("VkInstance created");
|
|
}
|
|
|
|
static void pick_physical_and_device(void) {
|
|
uint32_t pc = 0;
|
|
VKOK(vkEnumeratePhysicalDevices(g_inst, &pc, NULL));
|
|
if (!pc) { LOG("no Vulkan physical devices"); exit(1); }
|
|
VkPhysicalDevice *pd = calloc(pc, sizeof(*pd));
|
|
VKOK(vkEnumeratePhysicalDevices(g_inst, &pc, pd));
|
|
g_phys = pd[0]; /* shim picks the runtime's preferred device internally; smoke test = [0] */
|
|
VkPhysicalDeviceProperties props;
|
|
vkGetPhysicalDeviceProperties(g_phys, &props);
|
|
LOG("physical device: %s", props.deviceName);
|
|
free(pd);
|
|
|
|
uint32_t qf = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(g_phys, &qf, NULL);
|
|
VkQueueFamilyProperties *qp = calloc(qf, sizeof(*qp));
|
|
vkGetPhysicalDeviceQueueFamilyProperties(g_phys, &qf, qp);
|
|
g_gfxFamily = UINT32_MAX;
|
|
for (uint32_t i = 0; i < qf; i++)
|
|
if (qp[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { g_gfxFamily = i; break; }
|
|
free(qp);
|
|
if (g_gfxFamily == UINT32_MAX) { LOG("no graphics queue family"); exit(1); }
|
|
|
|
int n = 0;
|
|
const char **exts = query_exts(1, &n);
|
|
float prio = 1.0f;
|
|
VkDeviceQueueCreateInfo qci = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
|
|
qci.queueFamilyIndex = g_gfxFamily;
|
|
qci.queueCount = 1;
|
|
qci.pQueuePriorities = &prio;
|
|
VkDeviceCreateInfo dci = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
|
|
dci.queueCreateInfoCount = 1;
|
|
dci.pQueueCreateInfos = &qci;
|
|
dci.enabledExtensionCount = (uint32_t)n;
|
|
dci.ppEnabledExtensionNames = exts;
|
|
VKOK(vkCreateDevice(g_phys, &dci, NULL, &g_dev));
|
|
free(exts);
|
|
vkGetDeviceQueue(g_dev, g_gfxFamily, 0, &g_queue);
|
|
|
|
VkCommandPoolCreateInfo pci = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
|
|
pci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
pci.queueFamilyIndex = g_gfxFamily;
|
|
VKOK(vkCreateCommandPool(g_dev, &pci, NULL, &g_cmdPool));
|
|
LOG("VkDevice + graphics queue (family %u) created", g_gfxFamily);
|
|
}
|
|
|
|
static uint32_t find_mem(uint32_t typeBits, VkMemoryPropertyFlags want) {
|
|
VkPhysicalDeviceMemoryProperties mp;
|
|
vkGetPhysicalDeviceMemoryProperties(g_phys, &mp);
|
|
for (uint32_t i = 0; i < mp.memoryTypeCount; i++)
|
|
if ((typeBits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & want) == want) return i;
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
/* Host-visible staging buffer big enough for both eye layers (RGBA8). Persistently mapped. */
|
|
static int make_staging(uint32_t w, uint32_t h) {
|
|
VkDeviceSize sz = (VkDeviceSize)w * h * 4u * 2u; /* 2 array layers */
|
|
VkBufferCreateInfo bci = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
|
bci.size = sz; bci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
|
bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
if (vkCreateBuffer(g_dev, &bci, NULL, &g_stageBuf) != VK_SUCCESS) return 0;
|
|
VkMemoryRequirements mr; vkGetBufferMemoryRequirements(g_dev, g_stageBuf, &mr);
|
|
uint32_t mt = find_mem(mr.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
|
if (mt == UINT32_MAX) return 0;
|
|
VkMemoryAllocateInfo mai = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
|
mai.allocationSize = mr.size; mai.memoryTypeIndex = mt;
|
|
if (vkAllocateMemory(g_dev, &mai, NULL, &g_stageMem) != VK_SUCCESS) return 0;
|
|
vkBindBufferMemory(g_dev, g_stageBuf, g_stageMem, 0);
|
|
if (vkMapMemory(g_dev, g_stageMem, 0, sz, 0, &g_stagePtr) != VK_SUCCESS) return 0;
|
|
g_stageW = w; g_stageH = h;
|
|
return 1;
|
|
}
|
|
|
|
/* rotate vector v by quaternion q (x,y,z,w): v + 2*qw*(qv x v) + 2*(qv x (qv x v)) */
|
|
static void qrot(float qx, float qy, float qz, float qw,
|
|
float vx, float vy, float vz, float *ox, float *oy, float *oz) {
|
|
float tx = 2.0f * (qy * vz - qz * vy);
|
|
float ty = 2.0f * (qz * vx - qx * vz);
|
|
float tz = 2.0f * (qx * vy - qy * vx);
|
|
*ox = vx + qw * tx + (qy * tz - qz * ty);
|
|
*oy = vy + qw * ty + (qz * tx - qx * tz);
|
|
*oz = vz + qw * tz + (qx * ty - qy * tx);
|
|
}
|
|
|
|
/* Procedural world-locked scene along a world-space ray: checkerboard floor 1.6m below the
|
|
* eye, sky gradient, and an orbiting sun (the motion). Writes linear RGB into r/g/b. */
|
|
static void shade(float ox, float oy, float oz, float dx, float dy, float dz, float t,
|
|
float *r, float *g, float *b) {
|
|
if (dy < -1e-3f) {
|
|
float floorY = oy - 1.6f;
|
|
float tt = (floorY - oy) / dy; /* = 1.6 / -dy > 0 */
|
|
if (tt > 0.0f) {
|
|
float hx = ox + dx * tt, hz = oz + dz * tt;
|
|
int chk = (((int)floorf(hx)) + ((int)floorf(hz))) & 1;
|
|
float base = chk ? 0.85f : 0.25f;
|
|
float fog = 1.0f / (1.0f + tt * 0.04f); /* fade distant floor into sky */
|
|
*r = base * fog + 0.55f * (1.0f - fog);
|
|
*g = base * fog + 0.65f * (1.0f - fog);
|
|
*b = base * fog + 0.85f * (1.0f - fog);
|
|
return;
|
|
}
|
|
}
|
|
float up = dy * 0.5f + 0.5f; /* sky gradient */
|
|
*r = 0.30f + 0.20f * up; *g = 0.50f + 0.30f * up; *b = 0.70f + 0.30f * up;
|
|
float sx = cosf(t), sy = 0.40f, sz = sinf(t); /* orbiting sun */
|
|
float sl = 1.0f / sqrtf(sx * sx + sy * sy + sz * sz); sx *= sl; sy *= sl; sz *= sl;
|
|
if (dx * sx + dy * sy + dz * sz > 0.995f) { *r = 1.0f; *g = 0.95f; *b = 0.70f; }
|
|
}
|
|
|
|
/* Best-effort pose-driven render: for each eye, build per-pixel world rays from the shim's
|
|
* located eye pose + FOV, shade the procedural scene, and copy into that array layer. This
|
|
* exercises the shim's pose/FOV math (stereo parallax between eyes; world-locked content
|
|
* counter-moves as the head pose changes). Failures here don't fail the harness. */
|
|
static void render_scene(uint64_t image, uint32_t arrayLayers, const ovrpLayerDesc *desc,
|
|
const ovrpPoseStatef pose[2], float t) {
|
|
if (!image || !g_stagePtr) return;
|
|
uint32_t W = g_stageW, H = g_stageH;
|
|
for (uint32_t eye = 0; eye < arrayLayers; eye++) {
|
|
const ovrpPosef *p = &pose[eye].Pose;
|
|
float ox = p->Position.x, oy = p->Position.y, oz = p->Position.z;
|
|
float lt = desc->Fov[eye].LeftTan, rt = desc->Fov[eye].RightTan;
|
|
float ut = desc->Fov[eye].UpTan, dt = desc->Fov[eye].DownTan;
|
|
uint8_t *px = (uint8_t *)g_stagePtr + (size_t)eye * W * H * 4u;
|
|
/* ovrpFovf tangents are positive magnitudes: horizontal spans -LeftTan..+RightTan,
|
|
* vertical spans +UpTan (top) ..-DownTan (bottom). */
|
|
for (uint32_t y = 0; y < H; y++) {
|
|
float v = ut - (ut + dt) * ((y + 0.5f) / H);
|
|
for (uint32_t x = 0; x < W; x++) {
|
|
float u = -lt + (rt + lt) * ((x + 0.5f) / W);
|
|
float il = 1.0f / sqrtf(u * u + v * v + 1.0f);
|
|
float ex = u * il, ey = v * il, ez = -1.0f * il; /* OpenXR: -Z forward */
|
|
float dx, dy, dz;
|
|
qrot(p->Orientation.x, p->Orientation.y, p->Orientation.z, p->Orientation.w,
|
|
ex, ey, ez, &dx, &dy, &dz);
|
|
float r, g, b; shade(ox, oy, oz, dx, dy, dz, t, &r, &g, &b);
|
|
uint8_t *o = px + ((size_t)y * W + x) * 4u;
|
|
o[0] = (uint8_t)(r * 255.0f); o[1] = (uint8_t)(g * 255.0f);
|
|
o[2] = (uint8_t)(b * 255.0f); o[3] = 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
VkCommandBufferAllocateInfo ai = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
|
|
ai.commandPool = g_cmdPool; ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; ai.commandBufferCount = 1;
|
|
VkCommandBuffer cb;
|
|
if (vkAllocateCommandBuffers(g_dev, &ai, &cb) != VK_SUCCESS) return;
|
|
VkCommandBufferBeginInfo bi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
|
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cb, &bi);
|
|
|
|
VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, arrayLayers };
|
|
VkImageMemoryBarrier toDst = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
|
|
toDst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
toDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
toDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
toDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
toDst.image = (VkImage)image; toDst.subresourceRange = range;
|
|
toDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
0, 0, NULL, 0, NULL, 1, &toDst);
|
|
|
|
VkBufferImageCopy region[2]; uint32_t nr = 0;
|
|
for (uint32_t eye = 0; eye < arrayLayers; eye++) {
|
|
VkBufferImageCopy c; memset(&c, 0, sizeof c);
|
|
c.bufferOffset = (VkDeviceSize)eye * W * H * 4u;
|
|
c.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
c.imageSubresource.mipLevel = 0; c.imageSubresource.baseArrayLayer = eye;
|
|
c.imageSubresource.layerCount = 1;
|
|
c.imageExtent.width = W; c.imageExtent.height = H; c.imageExtent.depth = 1;
|
|
region[nr++] = c;
|
|
}
|
|
vkCmdCopyBufferToImage(cb, g_stageBuf, (VkImage)image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, nr, region);
|
|
|
|
VkImageMemoryBarrier toRead = toDst;
|
|
toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
toRead.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; /* what the compositor reads */
|
|
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
toRead.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
0, 0, NULL, 0, NULL, 1, &toRead);
|
|
|
|
vkEndCommandBuffer(cb);
|
|
VkSubmitInfo si = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
|
|
si.commandBufferCount = 1; si.pCommandBuffers = &cb;
|
|
vkQueueSubmit(g_queue, 1, &si, VK_NULL_HANDLE);
|
|
vkQueueWaitIdle(g_queue);
|
|
vkFreeCommandBuffers(g_dev, g_cmdPool, 1, &cb);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
int frames = (argc > 1) ? atoi(argv[1]) : 300;
|
|
if (frames < 1) frames = 1;
|
|
LOG("starting; %d frames. Runtime via OpenXR loader (XR_RUNTIME_JSON / active_runtime.json).", frames);
|
|
|
|
/* 1. lifecycle: PreInitialize3 creates the XrInstance + picks the system */
|
|
if (!OVRP_SUCCESS(ovrp_PreInitialize3(NULL))) { LOG("PreInitialize3 failed"); return 1; }
|
|
LOG("PreInitialize3 OK (XrInstance + system up)");
|
|
|
|
/* 2. Vulkan, created with the runtime-required extensions (UE's VulkanRHI order) */
|
|
make_vk_instance();
|
|
pick_physical_and_device();
|
|
|
|
/* 3. Initialize5 hands the shim our Vulkan handles -> it creates the XrSession */
|
|
long versionStub[4] = {0}; /* arg9 = const ovrpVersion& — shim ignores the contents */
|
|
ovrpResult ir = ovrp_Initialize5(ovrpRenderAPI_Vulkan, NULL, NULL,
|
|
(void *)g_inst, (void *)g_phys, (void *)g_dev,
|
|
(void *)g_queue, 0, versionStub);
|
|
if (!OVRP_SUCCESS(ir)) { LOG("Initialize5 failed (%d)", ir); return 1; }
|
|
LOG("Initialize5 OK (XrSession created)");
|
|
|
|
/* 4. eye-fov layer (UE: CalculateEyeLayerDesc2 -> SetupLayer once) */
|
|
ovrpLayerDesc desc;
|
|
ovrpResult dr = ovrp_CalculateEyeLayerDesc2(ovrpLayout_Array, 1.0f, 1, 1,
|
|
ovrpTextureFormat_R8G8B8A8_sRGB, (ovrpTextureFormat)0, 0, &desc);
|
|
if (!OVRP_SUCCESS(dr)) { LOG("CalculateEyeLayerDesc2 failed (%d)", dr); return 1; }
|
|
LOG("EyeLayerDesc %dx%d arraylayout, fmt=%d", desc.TextureSize.w, desc.TextureSize.h, desc.Format);
|
|
int layerId = -1;
|
|
ovrpResult sr = ovrp_SetupLayer((void *)g_dev, &desc, &layerId);
|
|
if (!OVRP_SUCCESS(sr) || layerId < 0) { LOG("SetupLayer failed (%d)", sr); return 1; }
|
|
int stageCount = 0; ovrp_GetLayerTextureStageCount(layerId, &stageCount);
|
|
LOG("SetupLayer OK layerId=%d swapchainStages=%d", layerId, stageCount);
|
|
uint32_t arrayLayers = (desc.Layout == ovrpLayout_Array) ? 2u : 1u;
|
|
if (make_staging((uint32_t)desc.TextureSize.w, (uint32_t)desc.TextureSize.h))
|
|
LOG("scene renderer ready (%dx%d, %u eye layers)", desc.TextureSize.w, desc.TextureSize.h, arrayLayers);
|
|
else
|
|
LOG("WARN: staging buffer alloc failed — frames will be submitted blank");
|
|
|
|
/* 5. frame loop. Update3 advances the session state machine (IDLE->READY->FOCUSED);
|
|
* Wait/Begin/EndFrame no-op until the session is running, so early frames are fine. */
|
|
if (stageCount < 1) stageCount = 1;
|
|
int presented = 0, renderStage = 0; /* stage advances per presented frame, in lockstep
|
|
* with the shim's one-acquire-per-running-frame */
|
|
for (int f = 0; f < frames; f++) {
|
|
ovrp_Update3(ovrpStep_Render, f, 0.0);
|
|
ovrp_WaitToBeginFrame(f);
|
|
ovrp_BeginFrame4(f, NULL);
|
|
int stage = renderStage % stageCount; /* = the image begin_frame just acquired */
|
|
|
|
/* per-eye pose from the shim's located views (true IPD separation) + the eye FOV
|
|
* from the layer desc -> render a world-locked scene into the acquired eye image. */
|
|
ovrpPoseStatef eyePose[2]; memset(eyePose, 0, sizeof eyePose);
|
|
eyePose[0].Pose.Orientation.w = eyePose[1].Pose.Orientation.w = 1.0f;
|
|
ovrp_GetNodePoseState3(ovrpStep_Render, f, ovrpNode_EyeLeft, &eyePose[0]);
|
|
ovrp_GetNodePoseState3(ovrpStep_Render, f, ovrpNode_EyeRight, &eyePose[1]);
|
|
if (f < 3)
|
|
LOG("frame %d eyeL pos=(%.3f %.3f %.3f) eyeR pos=(%.3f %.3f %.3f) fovL(R%.3f L%.3f)",
|
|
f, eyePose[0].Pose.Position.x, eyePose[0].Pose.Position.y, eyePose[0].Pose.Position.z,
|
|
eyePose[1].Pose.Position.x, eyePose[1].Pose.Position.y, eyePose[1].Pose.Position.z,
|
|
desc.Fov[0].RightTan, desc.Fov[0].LeftTan);
|
|
|
|
uint64_t color = 0, depthTex = 0;
|
|
if (OVRP_SUCCESS(ovrp_GetLayerTexture2(layerId, stage, 0, &color, &depthTex)) && color)
|
|
render_scene(color, arrayLayers, &desc, eyePose, (float)f * 0.03f);
|
|
|
|
ovrpLayerSubmit submit;
|
|
memset(&submit, 0, sizeof submit);
|
|
submit.LayerId = layerId;
|
|
submit.TextureStage = stage;
|
|
submit.Pose.Orientation.w = 1.0f; /* pose/FOV come from the shim's located views */
|
|
const ovrpLayerSubmit *ptrs[1] = { &submit };
|
|
ovrpResult er = ovrp_EndFrame4(f, ptrs, 1, NULL);
|
|
if (OVRP_SUCCESS(er)) {
|
|
if (presented == 0) /* views are located now — refresh FOV (setup value was the fallback) */
|
|
ovrp_CalculateEyeLayerDesc2(ovrpLayout_Array, 1.0f, 1, 1,
|
|
ovrpTextureFormat_R8G8B8A8_sRGB, (ovrpTextureFormat)0, 0, &desc);
|
|
presented++; renderStage++;
|
|
}
|
|
|
|
if (f < 5 || (f % 60) == 0)
|
|
LOG("frame %d: end=%d (presented=%d)", f, er, presented);
|
|
}
|
|
|
|
LOG("loop done: %d/%d frames presented", presented, frames);
|
|
ovrp_Shutdown2();
|
|
LOG("Shutdown2 OK — clean exit");
|
|
return presented > 0 ? 0 : 2;
|
|
}
|