824b4a2506
Aiming looked and behaved differently depending on whether the keyboard/menu was open: xr_input_try_overlay() claimed a hand's ray for its overlay whenever the overlay was merely visible, regardless of whether the ray was actually pointed at it - so with the keyboard open, aiming at the main game screen still showed a beam terminating at the keyboard's fixed depth, while closing the keyboard swapped in the old flat cross-shaped reticle at whatever the ray was really pointing at. Only claim an overlay when the ray is actually relevant this frame - a real hit, or a touch/drag begun on a previous frame still pending release - so a visible-but-unaimed-at overlay now falls through to the next-priority target. Give the game quad its own hit-test (xr_input_try_game_quad()) and the same billboarded laser-beam treatment as the overlays instead of the old flat reticle, so aiming looks and behaves identically everywhere - overlay open or not. The old reticle GL program/shaders are now fully dead and removed. No click/touch dispatch is added to the game quad itself; that's future work.
160 lines
7.0 KiB
C
160 lines
7.0 KiB
C
#include "xr_swapchain.h"
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#include <android/log.h>
|
|
|
|
#define TAG "QuestShock"
|
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
|
|
|
static bool xr_check(XrInstance instance, XrResult result, const char *what) {
|
|
if (XR_SUCCEEDED(result))
|
|
return true;
|
|
char resultString[XR_MAX_RESULT_STRING_SIZE] = {0};
|
|
if (instance != XR_NULL_HANDLE)
|
|
xrResultToString(instance, result, resultString);
|
|
LOGE("XR: %s failed: %s (%d)", what, resultString[0] ? resultString : "?", (int)result);
|
|
return false;
|
|
}
|
|
|
|
// This file's other GL calls otherwise resolve to gl4es (the only GL
|
|
// symbol provider linked into this binary - see android/engine-patches/
|
|
// 02-android-opengl-es.patch), which is fine for anything shared with the
|
|
// engine's own gl4es-routed rendering. But the swapchain images OpenXR
|
|
// hands us are real driver texture objects gl4es never created itself,
|
|
// and gl4es's own glFramebufferTexture2D can't attach a texture it has no
|
|
// tracked metadata for. So the FBO *container* is created via gl4es's own
|
|
// glGenFramebuffers/glBindFramebuffer (so gl4es recognizes the id as its
|
|
// own and its own per-frame glBindFramebuffer succeeds), while the
|
|
// texture-attach step - the specifically foreign part - goes through the
|
|
// real driver directly, via dlsym against libGLESv2.so.
|
|
typedef void (*PFNQSFRAMEBUFFERTEXTURE2D)(GLenum, GLenum, GLenum, GLuint, GLint);
|
|
typedef GLenum (*PFNQSCHECKFRAMEBUFFERSTATUS)(GLenum);
|
|
|
|
static PFNQSFRAMEBUFFERTEXTURE2D real_glFramebufferTexture2D;
|
|
static PFNQSCHECKFRAMEBUFFERSTATUS real_glCheckFramebufferStatus;
|
|
static bool g_real_gles_loaded = false;
|
|
|
|
static bool xr_swapchain_load_real_gles(void) {
|
|
if (g_real_gles_loaded)
|
|
return true;
|
|
void *lib = dlopen("libGLESv2.so", RTLD_NOW | RTLD_LOCAL);
|
|
if (lib == NULL) {
|
|
LOGE("XR: swapchain dlopen(libGLESv2.so) failed: %s", dlerror());
|
|
return false;
|
|
}
|
|
real_glFramebufferTexture2D = (PFNQSFRAMEBUFFERTEXTURE2D)dlsym(lib, "glFramebufferTexture2D");
|
|
real_glCheckFramebufferStatus =
|
|
(PFNQSCHECKFRAMEBUFFERSTATUS)dlsym(lib, "glCheckFramebufferStatus");
|
|
g_real_gles_loaded = real_glFramebufferTexture2D && real_glCheckFramebufferStatus;
|
|
return g_real_gles_loaded;
|
|
}
|
|
|
|
bool xr_swapchain_create(XrInstance instance, XrSession session, int64_t format, int width,
|
|
int height, XrSwapchainState *out) {
|
|
if (!xr_swapchain_load_real_gles()) {
|
|
LOGE("XR: swapchain couldn't resolve real GLES FBO functions via dlsym");
|
|
return false;
|
|
}
|
|
|
|
out->width = width;
|
|
out->height = height;
|
|
|
|
XrSwapchainCreateInfo swapchainInfo = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
|
|
swapchainInfo.usageFlags =
|
|
XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT;
|
|
swapchainInfo.format = format;
|
|
swapchainInfo.sampleCount = 1;
|
|
swapchainInfo.width = (uint32_t)width;
|
|
swapchainInfo.height = (uint32_t)height;
|
|
swapchainInfo.faceCount = 1;
|
|
swapchainInfo.arraySize = 1;
|
|
swapchainInfo.mipCount = 1;
|
|
if (!xr_check(instance, xrCreateSwapchain(session, &swapchainInfo, &out->swapchain),
|
|
"xrCreateSwapchain"))
|
|
return false;
|
|
|
|
uint32_t imageCount = 0;
|
|
xrEnumerateSwapchainImages(out->swapchain, 0, &imageCount, NULL);
|
|
if (imageCount > XR_SWAPCHAIN_MAX_IMAGES) {
|
|
LOGE("XR: swapchain reports %u images, only room for %d", imageCount,
|
|
XR_SWAPCHAIN_MAX_IMAGES);
|
|
return false;
|
|
}
|
|
// Zero-initialized, not just `.type` set per element - these structs
|
|
// also carry a `next` field the runtime may read, and an uninitialized
|
|
// stack array would leave it as garbage.
|
|
XrSwapchainImageOpenGLESKHR images[XR_SWAPCHAIN_MAX_IMAGES] = {0};
|
|
for (uint32_t i = 0; i < imageCount; i++)
|
|
images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
|
|
if (!xr_check(instance,
|
|
xrEnumerateSwapchainImages(out->swapchain, imageCount, &imageCount,
|
|
(XrSwapchainImageBaseHeader *)images),
|
|
"xrEnumerateSwapchainImages"))
|
|
return false;
|
|
out->image_count = imageCount;
|
|
|
|
// Wraps each swapchain-provided texture in its own framebuffer, matching
|
|
// how OpenGL.cc's own CreateFrameBuffer() wraps backupBuffer - just
|
|
// without a depth/stencil attachment, since the final composite draw
|
|
// (see opengl_swap_and_restore) never needs one. The Gen/Bind calls are
|
|
// gl4es's own (linked, not dlsym'd) - see the comment above
|
|
// real_glFramebufferTexture2D for why.
|
|
bool all_complete = true;
|
|
for (uint32_t i = 0; i < imageCount; i++) {
|
|
glGenFramebuffers(1, &out->fbos[i]);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, out->fbos[i]);
|
|
real_glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
images[i].image, 0);
|
|
GLenum status = real_glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
LOGE("XR: swapchain FBO %u incomplete: 0x%x", i, status);
|
|
all_complete = false;
|
|
}
|
|
}
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
return all_complete;
|
|
}
|
|
|
|
void xr_swapchain_destroy(XrSwapchainState *state) {
|
|
for (uint32_t i = 0; i < state->image_count; i++) {
|
|
if (state->fbos[i] != 0)
|
|
glDeleteFramebuffers(1, &state->fbos[i]);
|
|
}
|
|
state->image_count = 0;
|
|
if (state->swapchain != XR_NULL_HANDLE)
|
|
xrDestroySwapchain(state->swapchain);
|
|
state->swapchain = XR_NULL_HANDLE;
|
|
}
|
|
|
|
// Acquires the next image of the given swapchain, waits for the runtime to
|
|
// finish with it, and binds its framebuffer (sized to exactly fill it) as
|
|
// the current render target.
|
|
bool xr_swapchain_acquire(XrInstance instance, XrSwapchainState *state) {
|
|
uint32_t imageIndex = 0;
|
|
XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
|
|
if (!xr_check(instance, xrAcquireSwapchainImage(state->swapchain, &acquireInfo, &imageIndex),
|
|
"xrAcquireSwapchainImage"))
|
|
return false;
|
|
|
|
XrSwapchainImageWaitInfo waitImageInfo = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
|
|
waitImageInfo.timeout = XR_INFINITE_DURATION;
|
|
if (!xr_check(instance, xrWaitSwapchainImage(state->swapchain, &waitImageInfo),
|
|
"xrWaitSwapchainImage"))
|
|
return false;
|
|
|
|
// gl4es's own (linked, not dlsym'd) bind - this targets an FBO id gl4es
|
|
// itself created (see xr_swapchain_create()), so its own "current FBO"
|
|
// bookkeeping updates correctly and its immediate-mode draw calls
|
|
// (android_draw_surface_as_quad()) land in the right place.
|
|
glBindFramebuffer(GL_FRAMEBUFFER, state->fbos[imageIndex]);
|
|
glViewport(0, 0, state->width, state->height);
|
|
return true;
|
|
}
|
|
|
|
void xr_swapchain_release(XrInstance instance, XrSwapchainState *state) {
|
|
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
|
xr_check(instance, xrReleaseSwapchainImage(state->swapchain, &releaseInfo),
|
|
"xrReleaseSwapchainImage");
|
|
}
|