#include "xr_swapchain.h" #include #include #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"); }