Expand the on-screen keyboard to a full US layout (letters, digits, punctuation, Shift layer, Tab, arrows, F1-F12, one-shot Ctrl/Alt modifiers), make it a persistent, movable, closeable panel via a title-bar drag handle, and lower its opacity. Previously the keyboard was just a second panel swapped into the same quad as the menu launcher, which made the launcher translucent too and made the keyboard and menu mutually exclusive. Extract the shared swapchain/JNI/touch-dispatch plumbing into two generic, reusable modules - xr_swapchain.c (swapchain + per-image FBO setup) and xr_overlay.c (an off-screen Android View rendered into its own OpenXR quad, hit-tested via a laser pointer) - and make the menu launcher and keyboard two independent XrOverlay instances instead of one. The controller's menu button now only opens/closes the launcher; the launcher's "Keyboard" button only opens the keyboard - so the keyboard can stay up while picking something from the menu, and only the keyboard's own Close button hides it. xr_menu.c/.h are gone, fully absorbed into xr_overlay.c. On the Java side, OverlayPanel.java carries the shared off-screen-render/touch/ cursor plumbing that MenuOverlay and the new KeyboardOverlay both subclass.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
#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(), the laser reticle) 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");
|
||||
}
|
||||
Reference in New Issue
Block a user