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:
@@ -1,6 +1,5 @@
|
||||
#include "xr_session.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -18,7 +17,8 @@
|
||||
#include <SDL.h>
|
||||
|
||||
#include "xr_input.h"
|
||||
#include "xr_menu.h"
|
||||
#include "xr_overlay.h"
|
||||
#include "xr_swapchain.h"
|
||||
|
||||
#define TAG "QuestShock"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
||||
@@ -33,9 +33,26 @@
|
||||
#define QUAD_DISTANCE_METERS 2.0f
|
||||
#define QUAD_WIDTH_METERS 1.6f
|
||||
|
||||
// Up to this many swapchain images/FBOs - real runtimes report small counts
|
||||
// (2-4); this is just a fixed upper bound for the cache arrays below.
|
||||
#define MAX_SWAPCHAIN_IMAGES 8
|
||||
// The menu launcher (just a "Keyboard" button - see MenuOverlay.java) is
|
||||
// small and sits above where the keyboard defaults to; the keyboard keeps
|
||||
// its previous size/position. They're independent XrOverlay instances (see
|
||||
// xr_overlay.h) so both can be shown/hidden/dragged separately - deliberately
|
||||
// positioned apart by default so they don't start out overlapping.
|
||||
#define MENU_JAVA_CLASS "de.ladkau.questshock.MenuOverlay"
|
||||
#define MENU_WIDTH 512
|
||||
#define MENU_HEIGHT 256
|
||||
#define MENU_DISTANCE_METERS 1.3f
|
||||
#define MENU_WIDTH_METERS 0.35f
|
||||
#define MENU_DEFAULT_CENTER_X_METERS 0.0f
|
||||
#define MENU_DEFAULT_CENTER_Y_METERS 0.15f
|
||||
|
||||
#define KEYBOARD_JAVA_CLASS "de.ladkau.questshock.KeyboardOverlay"
|
||||
#define KEYBOARD_WIDTH 1024
|
||||
#define KEYBOARD_HEIGHT 768
|
||||
#define KEYBOARD_DISTANCE_METERS 1.5f
|
||||
#define KEYBOARD_WIDTH_METERS 0.8f
|
||||
#define KEYBOARD_DEFAULT_CENTER_X_METERS 0.0f
|
||||
#define KEYBOARD_DEFAULT_CENTER_Y_METERS -0.45f
|
||||
|
||||
static XrInstance g_instance = XR_NULL_HANDLE;
|
||||
static XrSystemId g_system_id = XR_NULL_SYSTEM_ID;
|
||||
@@ -54,57 +71,13 @@ static XrSessionState g_session_state = XR_SESSION_STATE_UNKNOWN;
|
||||
// later state.
|
||||
static bool g_session_running = false;
|
||||
|
||||
// One independent swapchain per quad (game, menu), each sized to exactly
|
||||
// its own content and acquired/released on its own within the same
|
||||
// xrBeginFrame/xrEndFrame pair - a completely ordinary multi-layer OpenXR
|
||||
// setup. A GL framebuffer wrapping each swapchain image is created once, up
|
||||
// front, and reused every frame, since a swapchain's images are a small,
|
||||
// fixed, runtime-owned pool (MAX_SWAPCHAIN_IMAGES above), not something
|
||||
// recreated per frame.
|
||||
typedef struct {
|
||||
XrSwapchain swapchain;
|
||||
GLuint fbos[MAX_SWAPCHAIN_IMAGES];
|
||||
uint32_t image_count;
|
||||
int width;
|
||||
int height;
|
||||
} XrSwapchainState;
|
||||
|
||||
static XrSwapchainState g_game_swapchain;
|
||||
static XrSwapchainState g_menu_swapchain;
|
||||
static XrOverlay *g_menu_overlay = NULL;
|
||||
static XrOverlay *g_keyboard_overlay = NULL;
|
||||
|
||||
static XrTime g_predicted_display_time = 0;
|
||||
static bool g_frame_should_render = false;
|
||||
static bool g_have_acquired_game_image = false;
|
||||
static bool g_have_acquired_menu_image = false;
|
||||
|
||||
// This file's 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 xr_load_real_gles(void) {
|
||||
void *lib = dlopen("libGLESv2.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (lib == NULL) {
|
||||
LOGE("XR: dlopen(libGLESv2.so) failed: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
real_glFramebufferTexture2D = (PFNQSFRAMEBUFFERTEXTURE2D)dlsym(lib, "glFramebufferTexture2D");
|
||||
real_glCheckFramebufferStatus =
|
||||
(PFNQSCHECKFRAMEBUFFERSTATUS)dlsym(lib, "glCheckFramebufferStatus");
|
||||
return real_glFramebufferTexture2D && real_glCheckFramebufferStatus;
|
||||
}
|
||||
|
||||
static bool xr_check(XrResult result, const char *what) {
|
||||
if (XR_SUCCEEDED(result))
|
||||
@@ -160,111 +133,6 @@ static EGLConfig xr_get_current_egl_config(EGLDisplay display, EGLContext contex
|
||||
return config;
|
||||
}
|
||||
|
||||
// Creates a swapchain at the given size/format and wraps each of its images
|
||||
// in its own GL framebuffer, ready to bind and render into directly -
|
||||
// shared by the game and menu swapchains below, since both need exactly the
|
||||
// same setup, just at a different size.
|
||||
static bool xr_create_swapchain_state(int64_t format, int width, int height,
|
||||
XrSwapchainState *out) {
|
||||
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(xrCreateSwapchain(g_session, &swapchainInfo, &out->swapchain),
|
||||
"xrCreateSwapchain"))
|
||||
return false;
|
||||
|
||||
uint32_t imageCount = 0;
|
||||
xrEnumerateSwapchainImages(out->swapchain, 0, &imageCount, NULL);
|
||||
if (imageCount > MAX_SWAPCHAIN_IMAGES) {
|
||||
LOGE("XR: swapchain reports %u images, only room for %d", imageCount,
|
||||
MAX_SWAPCHAIN_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[MAX_SWAPCHAIN_IMAGES] = {0};
|
||||
for (uint32_t i = 0; i < imageCount; i++)
|
||||
images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
|
||||
if (!xr_check(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;
|
||||
}
|
||||
|
||||
static void xr_destroy_swapchain_state(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.
|
||||
static bool xr_acquire_swapchain_image(XrSwapchainState *state) {
|
||||
uint32_t imageIndex = 0;
|
||||
XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
|
||||
if (!xr_check(xrAcquireSwapchainImage(state->swapchain, &acquireInfo, &imageIndex),
|
||||
"xrAcquireSwapchainImage"))
|
||||
return false;
|
||||
|
||||
XrSwapchainImageWaitInfo waitImageInfo = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
|
||||
waitImageInfo.timeout = XR_INFINITE_DURATION;
|
||||
if (!xr_check(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_create_swapchain_state()), 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;
|
||||
}
|
||||
|
||||
static void xr_release_swapchain_image(XrSwapchainState *state) {
|
||||
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
||||
xr_check(xrReleaseSwapchainImage(state->swapchain, &releaseInfo), "xrReleaseSwapchainImage");
|
||||
}
|
||||
|
||||
static bool xr_create_instance_and_session(int game_width, int game_height) {
|
||||
JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv();
|
||||
jobject activity = (jobject)SDL_AndroidGetActivity();
|
||||
@@ -355,8 +223,9 @@ static bool xr_create_instance_and_session(int game_width, int game_height) {
|
||||
// GL_RGBA with no decode step anywhere in this path, matching the
|
||||
// desktop SDL_RenderCopy path this replaces - an sRGB swapchain format
|
||||
// would auto-gamma-encode on write and double-encode already-encoded
|
||||
// data. Both swapchains below share this one choice - the compositor's
|
||||
// supported format set doesn't depend on swapchain size.
|
||||
// data. Every swapchain below (game quad, menu/keyboard overlays)
|
||||
// shares this one choice - the compositor's supported format set
|
||||
// doesn't depend on swapchain size.
|
||||
uint32_t formatCount = 0;
|
||||
xrEnumerateSwapchainFormats(g_session, 0, &formatCount, NULL);
|
||||
int64_t *formats = (int64_t *)malloc(sizeof(int64_t) * formatCount);
|
||||
@@ -371,30 +240,38 @@ static bool xr_create_instance_and_session(int game_width, int game_height) {
|
||||
}
|
||||
free(formats);
|
||||
|
||||
if (!xr_load_real_gles()) {
|
||||
LOGE("XR: couldn't resolve real GLES FBO functions via dlsym");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!xr_create_swapchain_state(chosenFormat, game_width, game_height, &g_game_swapchain)) {
|
||||
if (!xr_swapchain_create(g_instance, g_session, chosenFormat, game_width, game_height,
|
||||
&g_game_swapchain)) {
|
||||
LOGE("XR: game swapchain setup failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int menu_width, menu_height;
|
||||
xr_menu_get_content_size(&menu_width, &menu_height);
|
||||
if (!xr_create_swapchain_state(chosenFormat, menu_width, menu_height, &g_menu_swapchain)) {
|
||||
LOGE("XR: menu swapchain setup failed");
|
||||
return false;
|
||||
}
|
||||
// Both non-fatal - the game keeps rendering with no such panel if
|
||||
// either fails (e.g. the Java class couldn't be resolved via JNI).
|
||||
XrOverlayConfig menuConfig = {
|
||||
.java_class_name = MENU_JAVA_CLASS,
|
||||
.width = MENU_WIDTH,
|
||||
.height = MENU_HEIGHT,
|
||||
.distance_m = MENU_DISTANCE_METERS,
|
||||
.width_m = MENU_WIDTH_METERS,
|
||||
.default_center_x_m = MENU_DEFAULT_CENTER_X_METERS,
|
||||
.default_center_y_m = MENU_DEFAULT_CENTER_Y_METERS,
|
||||
};
|
||||
g_menu_overlay = xr_overlay_create(g_instance, g_session, chosenFormat, &menuConfig);
|
||||
|
||||
LOGI("XR: instance/session ready (game %dx%d, menu %dx%d)", game_width, game_height,
|
||||
menu_width, menu_height);
|
||||
|
||||
// Also non-fatal - the game keeps rendering with no menu quad if this
|
||||
// fails (e.g. MenuOverlay couldn't be resolved via JNI).
|
||||
xr_menu_init(g_instance, g_session);
|
||||
XrOverlayConfig keyboardConfig = {
|
||||
.java_class_name = KEYBOARD_JAVA_CLASS,
|
||||
.width = KEYBOARD_WIDTH,
|
||||
.height = KEYBOARD_HEIGHT,
|
||||
.distance_m = KEYBOARD_DISTANCE_METERS,
|
||||
.width_m = KEYBOARD_WIDTH_METERS,
|
||||
.default_center_x_m = KEYBOARD_DEFAULT_CENTER_X_METERS,
|
||||
.default_center_y_m = KEYBOARD_DEFAULT_CENTER_Y_METERS,
|
||||
};
|
||||
g_keyboard_overlay = xr_overlay_create(g_instance, g_session, chosenFormat, &keyboardConfig);
|
||||
|
||||
LOGI("XR: instance/session ready (game %dx%d, menu %dx%d, keyboard %dx%d)", game_width,
|
||||
game_height, MENU_WIDTH, MENU_HEIGHT, KEYBOARD_WIDTH, KEYBOARD_HEIGHT);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -407,6 +284,9 @@ bool xr_init(int game_width, int game_height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
XrOverlay *xr_session_get_menu_overlay(void) { return g_menu_overlay; }
|
||||
XrOverlay *xr_session_get_keyboard_overlay(void) { return g_keyboard_overlay; }
|
||||
|
||||
void xr_poll_events(void) {
|
||||
if (g_instance == XR_NULL_HANDLE)
|
||||
return;
|
||||
@@ -461,7 +341,7 @@ bool xr_frame_begin(void) {
|
||||
if (!g_frame_should_render)
|
||||
return false;
|
||||
|
||||
g_have_acquired_game_image = xr_acquire_swapchain_image(&g_game_swapchain);
|
||||
g_have_acquired_game_image = xr_swapchain_acquire(g_instance, &g_game_swapchain);
|
||||
return g_have_acquired_game_image;
|
||||
}
|
||||
|
||||
@@ -474,32 +354,25 @@ void xr_frame_end(void) {
|
||||
// this frame's game content already drew there. Syncing actions
|
||||
// happens even when there's nothing to draw (no acquired image this
|
||||
// frame), so edge detection (trigger/menu-button clicks) doesn't miss a
|
||||
// frame. Only ever draws while the menu isn't visible (see xr_input.c) -
|
||||
// the menu renders into its own independent swapchain image below.
|
||||
// frame. Only ever draws while neither overlay is visible (see
|
||||
// xr_input.c) - the menu/keyboard render into their own independent
|
||||
// swapchain images below.
|
||||
if (xr_is_session_running())
|
||||
xr_input_sync_and_draw(g_local_space, g_predicted_display_time,
|
||||
g_have_acquired_game_image);
|
||||
|
||||
if (g_have_acquired_game_image)
|
||||
xr_release_swapchain_image(&g_game_swapchain);
|
||||
|
||||
// The menu gets its own acquire/render/release cycle against its own
|
||||
// swapchain - unlike the game quad, there's no per-frame engine
|
||||
// rendering to wrap around here, just xr_menu.c's own blit, so this
|
||||
// can happen any time before xrEndFrame rather than needing to bracket
|
||||
// anything.
|
||||
g_have_acquired_menu_image = false;
|
||||
if (g_frame_should_render && xr_menu_is_visible()) {
|
||||
g_have_acquired_menu_image = xr_acquire_swapchain_image(&g_menu_swapchain);
|
||||
if (g_have_acquired_menu_image) {
|
||||
xr_menu_render_if_visible();
|
||||
xr_release_swapchain_image(&g_menu_swapchain);
|
||||
}
|
||||
}
|
||||
xr_swapchain_release(g_instance, &g_game_swapchain);
|
||||
|
||||
if (!xr_is_session_running())
|
||||
return;
|
||||
|
||||
// Up to 3 layers: the game quad (if a frame was actually rendered),
|
||||
// and, while visible, the menu launcher and keyboard overlays - each
|
||||
// gets its own acquire/render/release cycle against its own swapchain
|
||||
// (see xr_overlay_render_and_build_layer()) any time before xrEndFrame,
|
||||
// unlike the game quad there's no per-frame engine rendering to wrap
|
||||
// around here, just each overlay's own blit.
|
||||
XrCompositionLayerQuad gameQuad = {XR_TYPE_COMPOSITION_LAYER_QUAD};
|
||||
gameQuad.space = g_local_space;
|
||||
gameQuad.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
@@ -512,33 +385,34 @@ void xr_frame_end(void) {
|
||||
gameQuad.size.height =
|
||||
QUAD_WIDTH_METERS * (float)g_game_swapchain.height / (float)g_game_swapchain.width;
|
||||
|
||||
// Up to 2 layers: the game quad (if a frame was actually rendered) and,
|
||||
// while toggled on and rendered this frame, the menu quad in front of
|
||||
// it - see xr_input.c's menu_toggle handling. Each references its own
|
||||
// independent swapchain (see XrSwapchainState above).
|
||||
const XrCompositionLayerBaseHeader *layers[2];
|
||||
const XrCompositionLayerBaseHeader *layers[3];
|
||||
uint32_t layerCount = 0;
|
||||
if (g_have_acquired_game_image)
|
||||
layers[layerCount++] = (XrCompositionLayerBaseHeader *)&gameQuad;
|
||||
|
||||
XrCompositionLayerQuad menuQuad = {XR_TYPE_COMPOSITION_LAYER_QUAD};
|
||||
if (g_have_acquired_menu_image) {
|
||||
float centerX, centerY, distance, halfWidth, halfHeight;
|
||||
xr_menu_get_quad_extent(¢erX, ¢erY, &distance, &halfWidth, &halfHeight);
|
||||
XrCompositionLayerQuad menuQuad;
|
||||
if (g_frame_should_render && xr_overlay_render_and_build_layer(g_menu_overlay, &menuQuad)) {
|
||||
menuQuad.space = g_local_space;
|
||||
menuQuad.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
menuQuad.subImage.swapchain = g_menu_swapchain.swapchain;
|
||||
menuQuad.subImage.imageRect.extent.width = g_menu_swapchain.width;
|
||||
menuQuad.subImage.imageRect.extent.height = g_menu_swapchain.height;
|
||||
menuQuad.pose.orientation.w = 1.0f;
|
||||
menuQuad.pose.position.x = centerX;
|
||||
menuQuad.pose.position.y = centerY;
|
||||
menuQuad.pose.position.z = -distance;
|
||||
menuQuad.size.width = halfWidth * 2.0f;
|
||||
menuQuad.size.height = halfHeight * 2.0f;
|
||||
layers[layerCount++] = (XrCompositionLayerBaseHeader *)&menuQuad;
|
||||
}
|
||||
|
||||
XrCompositionLayerQuad keyboardQuad;
|
||||
if (g_frame_should_render &&
|
||||
xr_overlay_render_and_build_layer(g_keyboard_overlay, &keyboardQuad)) {
|
||||
keyboardQuad.space = g_local_space;
|
||||
keyboardQuad.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
// Lets the keyboard sit on screen translucently while playing
|
||||
// rather than fully hiding whatever's behind it - KeyboardOverlay
|
||||
// writes real sub-255 alpha into the swapchain texture (see
|
||||
// OverlayPanel.compositeAndPublish()), already in Android's
|
||||
// default premultiplied format, which is what this flag's absence
|
||||
// of UNPREMULTIPLIED_ALPHA_BIT assumes. The menu launcher above
|
||||
// deliberately doesn't get this flag - it stays fully opaque.
|
||||
keyboardQuad.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||
layers[layerCount++] = (XrCompositionLayerBaseHeader *)&keyboardQuad;
|
||||
}
|
||||
|
||||
XrFrameEndInfo endInfo = {XR_TYPE_FRAME_END_INFO};
|
||||
endInfo.displayTime = g_predicted_display_time;
|
||||
endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||
@@ -548,8 +422,7 @@ void xr_frame_end(void) {
|
||||
}
|
||||
|
||||
void xr_shutdown(void) {
|
||||
xr_destroy_swapchain_state(&g_game_swapchain);
|
||||
xr_destroy_swapchain_state(&g_menu_swapchain);
|
||||
xr_swapchain_destroy(&g_game_swapchain);
|
||||
|
||||
if (g_local_space != XR_NULL_HANDLE)
|
||||
xrDestroySpace(g_local_space);
|
||||
@@ -557,7 +430,10 @@ void xr_shutdown(void) {
|
||||
|
||||
// Before the session/instance they were created from.
|
||||
xr_input_shutdown();
|
||||
xr_menu_shutdown();
|
||||
xr_overlay_destroy(g_menu_overlay);
|
||||
g_menu_overlay = NULL;
|
||||
xr_overlay_destroy(g_keyboard_overlay);
|
||||
g_keyboard_overlay = NULL;
|
||||
|
||||
if (g_session != XR_NULL_HANDLE)
|
||||
xrDestroySession(g_session);
|
||||
|
||||
Reference in New Issue
Block a user