Files
questshock/android/app/src/main/cpp/xr_swapchain.c
T
ml e0a2e1f4bf
build / build (push) Successful in 2m44s
Fix persistent GL_INVALID_OPERATION spam and resolution-change bugs on Android
- opengl_end_frame() bound framebuffer 0 on Android, which has no valid
  target there (real presentation goes through the OpenXR swapchain's own
  FBOs) - gl4es reported this every frame. Skip it there.
- render_hack_cameras() re-entered opengl_start_frame()/opengl_end_frame()
  for camera-monitor renders that never touch the GL screen; excluded them
  to match use_opengl()'s existing behavior. Also drains a stray error left
  by xrPollEvent().
- ChangeScreenSize() never called opengl_resize() on Android, freezing the
  3D render target at launch size after an in-game resolution change; and
  xr_get_game_resolution() used the swapchain's fixed size instead of the
  current screen resolution, misplacing the laser-pointer cursor after such
  a change.
- Removes all temporary bisection diagnostics added while tracking down the
  GL_INVALID_OPERATION source.
2026-09-04 16:34:52 +02:00

161 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");
}