Add WinlatorXR by lvonasek (https://github.com/lvonasek)

This commit is contained in:
brunodev85 committed 2024-03-28 11:29:54 -03:00
1 parent 8f79ebcd31
commit 3af161cdef
21 files changed
+2584 -8

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
[submodule "src/main/cpp/OpenXR-SDK"]
path = src/main/cpp/OpenXR-SDK
url = https://github.com/KhronosGroup/OpenXR-SDK.git
+24
View File
@@ -3,6 +3,15 @@
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-feature
android:name="android.hardware.vr.headtracking"
android:required="false" />
<uses-feature
android:name="com.oculus.feature.PASSTHROUGH"
android:required="false" />
<uses-feature
android:name="oculus.software.overlay_keyboard"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@@ -37,6 +46,21 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation"
android:screenOrientation="sensorLandscape" />
<activity
android:name="com.winlator.XrActivity"
android:configChanges="density|orientation|screenSize|keyboard|keyboardHidden|uiMode"
android:exported="true"
android:launchMode="singleTask"
android:resizeableActivity="false"
android:screenOrientation="landscape"
android:process=":vr_process"
android:theme="@style/AppThemeFullscreen">
<intent-filter>
<category android:name="com.oculus.intent.category.VR" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.winlator.ControlsEditorActivity"
android:exported="false"
android:theme="@style/AppThemeFullscreen"
+10
View File
@@ -1,11 +1,20 @@
cmake_minimum_required(VERSION 3.22.1)
Project(Winlator)
set(CMAKE_C_FLAGS "-O2 -Wno-unused-function -Wimplicit-function-declaration")
add_subdirectory(proot)
add_subdirectory(virglrenderer)
add_subdirectory(OpenXR-SDK)
add_library(winlator SHARED
xr/engine.c
xr/framebuffer.c
xr/input.c
xr/main.c
xr/math.c
xr/renderer.c
drawable.c
gpu_image.c
sysvshared_memory.c
@@ -15,6 +24,7 @@ target_link_libraries(winlator
log
android
jnigraphics
openxr_loader
EGL
GLESv2
GLESv3)
Submodule app/src/main/cpp/OpenXR-SDK added at 288d3a7ebc.
+207
View File
@@ -0,0 +1,207 @@
#include "engine.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void XrEngineInit(struct XrEngine* engine, void* system, const char* name, int version)
{
if (engine->Initialized)
return;
memset(engine, 0, sizeof(engine));
#ifdef ANDROID
PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR",
(PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
if (xrInitializeLoaderKHR != NULL)
{
xrJava* java = (xrJava*)system;
XrLoaderInitInfoAndroidKHR loader_info;
memset(&loader_info, 0, sizeof(loader_info));
loader_info.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
loader_info.next = NULL;
loader_info.applicationVM = java->vm;
loader_info.applicationContext = java->activity;
xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loader_info);
}
#endif
int count = 0;
const char* extensions[32];
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
extensions[count++] = XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME;
#endif
#ifdef ANDROID
if (engine->PlatformFlag[PLATFORM_EXTENSION_INSTANCE])
{
extensions[count++] = XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME;
}
if (engine->PlatformFlag[PLATFORM_EXTENSION_PASSTHROUGH])
{
extensions[count++] = XR_FB_PASSTHROUGH_EXTENSION_NAME;
}
if (engine->PlatformFlag[PLATFORM_EXTENSION_PERFORMANCE])
{
extensions[count++] = XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME;
extensions[count++] = XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME;
}
#endif
// Create the OpenXR instance.
XrApplicationInfo app_info;
memset(&app_info, 0, sizeof(app_info));
strcpy(app_info.applicationName, name);
strcpy(app_info.engineName, name);
app_info.applicationVersion = version;
app_info.engineVersion = version;
app_info.apiVersion = XR_CURRENT_API_VERSION;
XrInstanceCreateInfo instance_info;
memset(&instance_info, 0, sizeof(instance_info));
instance_info.type = XR_TYPE_INSTANCE_CREATE_INFO;
instance_info.next = NULL;
instance_info.createFlags = 0;
instance_info.applicationInfo = app_info;
instance_info.enabledApiLayerCount = 0;
instance_info.enabledApiLayerNames = NULL;
instance_info.enabledExtensionCount = count;
instance_info.enabledExtensionNames = extensions;
#ifdef ANDROID
XrInstanceCreateInfoAndroidKHR instance_info_android = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
if (engine->PlatformFlag[PLATFORM_EXTENSION_INSTANCE])
{
xrJava* java = (xrJava*)system;
instance_info_android.applicationVM = java->vm;
instance_info_android.applicationActivity = java->activity;
instance_info.next = (XrBaseInStructure*)&instance_info_android;
}
#endif
XrResult result;
OXR(result = xrCreateInstance(&instance_info, &engine->Instance));
if (result != XR_SUCCESS)
{
ALOGE("Failed to create XR instance: %d", (int)result);
exit(1);
}
XrInstanceProperties instance_properties;
instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES;
instance_properties.next = NULL;
OXR(xrGetInstanceProperties(engine->Instance, &instance_properties));
ALOGV("Runtime %s: Version : %d.%d.%d", instance_properties.runtimeName,
XR_VERSION_MAJOR(instance_properties.runtimeVersion),
XR_VERSION_MINOR(instance_properties.runtimeVersion),
XR_VERSION_PATCH(instance_properties.runtimeVersion));
XrSystemGetInfo system_info;
memset(&system_info, 0, sizeof(system_info));
system_info.type = XR_TYPE_SYSTEM_GET_INFO;
system_info.next = NULL;
system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
OXR(result = xrGetSystem(engine->Instance, &system_info, &engine->SystemId));
if (result != XR_SUCCESS)
{
ALOGE("Failed to get system");
exit(1);
}
// Get the graphics requirements.
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL;
OXR(xrGetInstanceProcAddr(engine->Instance, "xrGetOpenGLESGraphicsRequirementsKHR",
(PFN_xrVoidFunction*)(&pfnGetOpenGLESGraphicsRequirementsKHR)));
XrGraphicsRequirementsOpenGLESKHR graphics_requirements = {};
graphics_requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR;
OXR(pfnGetOpenGLESGraphicsRequirementsKHR(engine->Instance, engine->SystemId, &graphics_requirements));
#endif
#ifdef ANDROID
engine->MainThreadId = gettid();
#endif
engine->Initialized = true;
}
void XrEngineDestroy(struct XrEngine* engine)
{
if (engine->Initialized)
{
xrDestroyInstance(engine->Instance);
engine->Initialized = false;
}
}
void XrEngineEnter(struct XrEngine* engine)
{
if (engine->Session)
{
ALOGE("EnterXR called with existing session");
return;
}
// Create the OpenXR Session.
XrSessionCreateInfo session_info;
memset(&session_info, 0, sizeof(session_info));
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl = {};
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR;
graphics_binding_gl.next = NULL;
graphics_binding_gl.display = eglGetCurrentDisplay();
graphics_binding_gl.config = NULL;
graphics_binding_gl.context = eglGetCurrentContext();
session_info.next = &graphics_binding_gl;
#endif
session_info.type = XR_TYPE_SESSION_CREATE_INFO;
session_info.createFlags = 0;
session_info.systemId = engine->SystemId;
XrResult result;
OXR(result = xrCreateSession(engine->Instance, &session_info, &engine->Session));
if (result != XR_SUCCESS)
{
ALOGE("Failed to create XR session: %d", (int)result);
exit(1);
}
// Create a space to the first path
XrReferenceSpaceCreateInfo space_info = {};
space_info.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
space_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
space_info.poseInReferenceSpace.orientation.w = 1.0f;
OXR(xrCreateReferenceSpace(engine->Session, &space_info, &engine->HeadSpace));
}
void XrEngineLeave(struct XrEngine* engine)
{
if (engine->Session)
{
OXR(xrDestroySpace(engine->HeadSpace));
// StageSpace is optional.
if (engine->StageSpace != XR_NULL_HANDLE)
{
OXR(xrDestroySpace(engine->StageSpace));
}
OXR(xrDestroySpace(engine->FakeSpace));
engine->CurrentSpace = XR_NULL_HANDLE;
OXR(xrDestroySession(engine->Session));
engine->Session = XR_NULL_HANDLE;
}
}
void XrEngineWaitForFrame(struct XrEngine* engine)
{
XrFrameWaitInfo wait_frame_info = {};
wait_frame_info.type = XR_TYPE_FRAME_WAIT_INFO;
wait_frame_info.next = NULL;
XrFrameState frame_state = {};
frame_state.type = XR_TYPE_FRAME_STATE;
frame_state.next = NULL;
OXR(xrWaitFrame(engine->Session, &wait_frame_info, &frame_state));
engine->PredictedDisplayTime = frame_state.predictedDisplayTime;
}
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include <stdbool.h>
//#define _DEBUG
#ifndef ANDROID
#define ANDROID 1
#endif
#if defined(_DEBUG)
void GLCheckErrors(const char* file, int line);
void OXRCheckErrors(XrResult result, const char* file, int line);
#define GL(func) func; GLCheckErrors(__FILE__ , __LINE__);
#define OXR(func) OXRCheckErrors(func, __FILE__ , __LINE__);
#else
#define GL(func) func;
#define OXR(func) func;
#endif
#ifdef ANDROID
#include <android/log.h>
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, "OpenXR", __VA_ARGS__);
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "OpenXR", __VA_ARGS__);
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <jni.h>
#define XR_USE_PLATFORM_ANDROID 1
#define XR_USE_GRAPHICS_API_OPENGL_ES 1
#else
#include <cstdio>
#define ALOGE(...) printf(__VA_ARGS__)
#define ALOGV(...) printf(__VA_ARGS__)
#endif
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
enum
{
XrMaxLayerCount = 2
};
enum
{
XrMaxNumEyes = 2
};
enum XrPlatformFlag
{
PLATFORM_CONTROLLER_PICO,
PLATFORM_CONTROLLER_QUEST,
PLATFORM_EXTENSION_INSTANCE,
PLATFORM_EXTENSION_PASSTHROUGH,
PLATFORM_EXTENSION_PERFORMANCE,
PLATFORM_TRACKING_FLOOR,
PLATFORM_MAX
};
typedef union
{
XrCompositionLayerProjection projection;
XrCompositionLayerQuad quad;
} XrCompositorLayer;
#ifdef ANDROID
typedef struct
{
jobject activity;
JNIEnv* env;
JavaVM* vm;
} xrJava;
#endif
struct XrEngine {
XrInstance Instance;
XrSession Session;
XrSystemId SystemId;
XrSpace CurrentSpace;
XrSpace FakeSpace;
XrSpace HeadSpace;
XrSpace StageSpace;
XrTime PredictedDisplayTime;
int MainThreadId;
int RenderThreadId;
bool PlatformFlag[PLATFORM_MAX];
bool Initialized;
};
void XrEngineInit(struct XrEngine* engine, void* system, const char* name, int version);
void XrEngineDestroy(struct XrEngine* engine);
void XrEngineEnter(struct XrEngine* engine);
void XrEngineLeave(struct XrEngine* engine);
void XrEngineWaitForFrame(struct XrEngine* engine);
+148
View File
@@ -0,0 +1,148 @@
#include "framebuffer.h"
#if XR_USE_GRAPHICS_API_OPENGL_ES
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#endif
#include <stdlib.h>
#include <string.h>
bool XrFramebufferCreate(struct XrFramebuffer *framebuffer, XrSession session, int width, int height)
{
memset(framebuffer, 0, sizeof(framebuffer));
#if XR_USE_GRAPHICS_API_OPENGL_ES
return XrFramebufferCreateGL(framebuffer, session, width, height);
#else
return false;
#endif
}
void XrFramebufferDestroy(struct XrFramebuffer *framebuffer)
{
#if XR_USE_GRAPHICS_API_OPENGL_ES
GL(glDeleteRenderbuffers(framebuffer->SwapchainLength, framebuffer->GLDepthBuffers));
GL(glDeleteFramebuffers(framebuffer->SwapchainLength, framebuffer->GLFrameBuffers));
free(framebuffer->GLDepthBuffers);
free(framebuffer->GLFrameBuffers);
#endif
OXR(xrDestroySwapchain(framebuffer->Handle));
free(framebuffer->SwapchainImage);
}
void XrFramebufferAcquire(struct XrFramebuffer *framebuffer)
{
XrSwapchainImageAcquireInfo acquire_info = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, NULL};
OXR(xrAcquireSwapchainImage(framebuffer->Handle, &acquire_info, &framebuffer->SwapchainIndex));
XrSwapchainImageWaitInfo wait_info;
wait_info.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO;
wait_info.next = NULL;
wait_info.timeout = 1000000; /* timeout in nanoseconds */
XrResult res = xrWaitSwapchainImage(framebuffer->Handle, &wait_info);
int i = 0;
while ((res != XR_SUCCESS) && (i < 10))
{
res = xrWaitSwapchainImage(framebuffer->Handle, &wait_info);
i++;
ALOGV("Retry xrWaitSwapchainImage %d times due XR_TIMEOUT_EXPIRED (duration %lf ms",
i, wait_info.timeout * (1E-9));
}
framebuffer->Acquired = res == XR_SUCCESS;
XrFramebufferSetCurrent(framebuffer);
#if XR_USE_GRAPHICS_API_OPENGL_ES
GL(glEnable(GL_SCISSOR_TEST));
GL(glViewport(0, 0, framebuffer->Width, framebuffer->Height));
GL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
GL(glScissor(0, 0, framebuffer->Width, framebuffer->Height));
GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
GL(glScissor(0, 0, 0, 0));
GL(glDisable(GL_SCISSOR_TEST));
#endif
}
void XrFramebufferRelease(struct XrFramebuffer *framebuffer)
{
if (framebuffer->Acquired)
{
XrSwapchainImageReleaseInfo release_info = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, NULL};
OXR(xrReleaseSwapchainImage(framebuffer->Handle, &release_info));
framebuffer->Acquired = false;
}
}
void XrFramebufferSetCurrent(struct XrFramebuffer *framebuffer)
{
#if XR_USE_GRAPHICS_API_OPENGL_ES
GL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->GLFrameBuffers[framebuffer->SwapchainIndex]));
#endif
}
#if XR_USE_GRAPHICS_API_OPENGL_ES
bool XrFramebufferCreateGL(struct XrFramebuffer *framebuffer, XrSession session, int width, int height)
{
XrSwapchainCreateInfo swapchain_info;
memset(&swapchain_info, 0, sizeof(swapchain_info));
swapchain_info.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
swapchain_info.sampleCount = 1;
swapchain_info.width = width;
swapchain_info.height = height;
swapchain_info.faceCount = 1;
swapchain_info.mipCount = 1;
swapchain_info.arraySize = 1;
framebuffer->Width = swapchain_info.width;
framebuffer->Height = swapchain_info.height;
// Create the color swapchain.
swapchain_info.format = GL_SRGB8_ALPHA8_EXT;
swapchain_info.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
OXR(xrCreateSwapchain(session, &swapchain_info, &framebuffer->Handle));
OXR(xrEnumerateSwapchainImages(framebuffer->Handle, 0, &framebuffer->SwapchainLength, NULL));
framebuffer->SwapchainImage = malloc(framebuffer->SwapchainLength * sizeof(XrSwapchainImageOpenGLESKHR));
// Populate the swapchain image array.
for (uint32_t i = 0; i < framebuffer->SwapchainLength; i++)
{
XrSwapchainImageOpenGLESKHR* swapchain = (XrSwapchainImageOpenGLESKHR*)framebuffer->SwapchainImage;
swapchain[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
swapchain[i].next = NULL;
}
OXR(xrEnumerateSwapchainImages(framebuffer->Handle, framebuffer->SwapchainLength,
&framebuffer->SwapchainLength,
(XrSwapchainImageBaseHeader*)framebuffer->SwapchainImage));
framebuffer->GLDepthBuffers = (GLuint*)malloc(framebuffer->SwapchainLength * sizeof(GLuint));
framebuffer->GLFrameBuffers = (GLuint*)malloc(framebuffer->SwapchainLength * sizeof(GLuint));
for (uint32_t i = 0; i < framebuffer->SwapchainLength; i++)
{
// Create color and depth buffers.
GLuint color_texture = ((XrSwapchainImageOpenGLESKHR*)framebuffer->SwapchainImage)[i].image;
GL(glGenRenderbuffers(1, &framebuffer->GLDepthBuffers[i]));
GL(glBindRenderbuffer(GL_RENDERBUFFER, framebuffer->GLDepthBuffers[i]));
GL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, width, height));
GL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
// Create the frame buffer.
GL(glGenFramebuffers(1, &framebuffer->GLFrameBuffers[i]));
GL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->GLFrameBuffers[i]));
GL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
framebuffer->GLDepthBuffers[i]));
GL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
framebuffer->GLDepthBuffers[i]));
GL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
color_texture, 0));
GL(GLenum renderFramebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER));
GL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
if (renderFramebufferStatus != GL_FRAMEBUFFER_COMPLETE)
{
ALOGE("Incomplete frame buffer object: %d", renderFramebufferStatus);
return false;
}
}
return true;
}
#endif
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "engine.h"
struct XrFramebuffer {
int Width;
int Height;
bool Acquired;
XrSwapchain Handle;
uint32_t SwapchainIndex;
uint32_t SwapchainLength;
void* SwapchainImage;
unsigned int* GLDepthBuffers;
unsigned int* GLFrameBuffers;
};
bool XrFramebufferCreate(struct XrFramebuffer *framebuffer, XrSession session, int width, int height);
void XrFramebufferDestroy(struct XrFramebuffer *framebuffer);
void XrFramebufferAcquire(struct XrFramebuffer *framebuffer);
void XrFramebufferRelease(struct XrFramebuffer *framebuffer);
void XrFramebufferSetCurrent(struct XrFramebuffer *framebuffer);
#if XR_USE_GRAPHICS_API_OPENGL_ES
bool XrFramebufferCreateGL(struct XrFramebuffer *framebuffer, XrSession session, int width, int height);
#endif
+443
View File
@@ -0,0 +1,443 @@
#include "input.h"
#include "math.h"
#include <sys/time.h>
#include <string.h>
void XrInputInit(struct XrEngine* engine, struct XrInput* input)
{
if (input->Initialized)
return;
memset(input, 0, sizeof(input));
// Actions
input->ActionSet = XrInputCreateActionSet(engine->Instance, "running_action_set", "Actionset");
input->IndexLeft = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "index_left", "Index left", 0, NULL);
input->IndexRight = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "index_right","Index right", 0, NULL);
input->ButtonMenu = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "menu_action", "ButtonMenu", 0, NULL);
input->ButtonA = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_a", "XrButton A", 0, NULL);
input->ButtonB = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_b", "XrButton B", 0, NULL);
input->ButtonX = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_x", "XrButton X", 0, NULL);
input->ButtonY = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_y", "XrButton Y", 0, NULL);
input->GripLeft = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_FLOAT_INPUT, "grip_left", "Grip left", 0, NULL);
input->GripRight = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_FLOAT_INPUT, "grip_right", "Grip right", 0, NULL);
input->JoystickLeft = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_VECTOR2F_INPUT, "move_on_left_joy","Move on left Joy", 0, NULL);
input->JoystickRight = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_VECTOR2F_INPUT, "move_on_right_joy","Move on right Joy", 0, NULL);
input->ThumbLeft = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "thumbstick_left","Thumbstick left", 0, NULL);
input->ThumbRight = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "thumbstick_right","Thumbstick right", 0, NULL);
input->VibrateLeftFeedback = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_left_feedback","Vibrate Left Controller", 0, NULL);
input->VibrateRightFeedback = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_right_feedback","Vibrate Right Controller", 0, NULL);
OXR(xrStringToPath(engine->Instance, "/user/hand/left", &input->LeftHandPath));
OXR(xrStringToPath(engine->Instance, "/user/hand/right", &input->RightHandPath));
input->HandPoseLeft = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_POSE_INPUT, "hand_pose_left", NULL,1, &input->LeftHandPath);
input->HandPoseRight = XrInputCreateAction(input->ActionSet, XR_ACTION_TYPE_POSE_INPUT, "hand_pose_right", NULL,1, &input->RightHandPath);
XrPath interactionProfilePath = XR_NULL_PATH;
if (engine->PlatformFlag[PLATFORM_CONTROLLER_QUEST])
{
OXR(xrStringToPath(engine->Instance, "/interaction_profiles/oculus/touch_controller",&interactionProfilePath));
}
else if (engine->PlatformFlag[PLATFORM_CONTROLLER_PICO])
{
OXR(xrStringToPath(engine->Instance, "/interaction_profiles/pico/neo3_controller",&interactionProfilePath));
}
// Map bindings
XrInstance instance = engine->Instance;
XrActionSuggestedBinding bindings[32]; // large enough for all profiles
int curr = 0;
if (engine->PlatformFlag[PLATFORM_CONTROLLER_QUEST])
{
bindings[curr++] = XrInputGetBinding(instance, input->IndexLeft, "/user/hand/left/input/trigger");
bindings[curr++] = XrInputGetBinding(instance, input->IndexRight, "/user/hand/right/input/trigger");
bindings[curr++] = XrInputGetBinding(instance, input->ButtonMenu, "/user/hand/left/input/menu/click");
}
else if (engine->PlatformFlag[PLATFORM_CONTROLLER_PICO])
{
bindings[curr++] = XrInputGetBinding(instance, input->IndexLeft, "/user/hand/left/input/trigger/click");
bindings[curr++] = XrInputGetBinding(instance, input->IndexRight, "/user/hand/right/input/trigger/click");
bindings[curr++] = XrInputGetBinding(instance, input->ButtonMenu, "/user/hand/left/input/back/click");
bindings[curr++] = XrInputGetBinding(instance, input->ButtonMenu, "/user/hand/right/input/back/click");
}
bindings[curr++] = XrInputGetBinding(instance, input->ButtonX, "/user/hand/left/input/x/click");
bindings[curr++] = XrInputGetBinding(instance, input->ButtonY, "/user/hand/left/input/y/click");
bindings[curr++] = XrInputGetBinding(instance, input->ButtonA, "/user/hand/right/input/a/click");
bindings[curr++] = XrInputGetBinding(instance, input->ButtonB, "/user/hand/right/input/b/click");
bindings[curr++] = XrInputGetBinding(instance, input->GripLeft, "/user/hand/left/input/squeeze/value");
bindings[curr++] = XrInputGetBinding(instance, input->GripRight, "/user/hand/right/input/squeeze/value");
bindings[curr++] = XrInputGetBinding(instance, input->JoystickLeft, "/user/hand/left/input/thumbstick");
bindings[curr++] = XrInputGetBinding(instance, input->JoystickRight, "/user/hand/right/input/thumbstick");
bindings[curr++] = XrInputGetBinding(instance, input->ThumbLeft, "/user/hand/left/input/thumbstick/click");
bindings[curr++] = XrInputGetBinding(instance, input->ThumbRight, "/user/hand/right/input/thumbstick/click");
bindings[curr++] = XrInputGetBinding(instance, input->VibrateLeftFeedback, "/user/hand/left/output/haptic");
bindings[curr++] = XrInputGetBinding(instance, input->VibrateRightFeedback, "/user/hand/right/output/haptic");
bindings[curr++] = XrInputGetBinding(instance, input->HandPoseLeft, "/user/hand/left/input/aim/pose");
bindings[curr++] = XrInputGetBinding(instance, input->HandPoseRight, "/user/hand/right/input/aim/pose");
XrInteractionProfileSuggestedBinding suggested_bindings = {};
suggested_bindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
suggested_bindings.next = NULL;
suggested_bindings.interactionProfile = interactionProfilePath;
suggested_bindings.suggestedBindings = bindings;
suggested_bindings.countSuggestedBindings = curr;
OXR(xrSuggestInteractionProfileBindings(engine->Instance, &suggested_bindings));
// Attach actions
XrSessionActionSetsAttachInfo attach_info = {};
attach_info.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
attach_info.next = NULL;
attach_info.countActionSets = 1;
attach_info.actionSets = &input->ActionSet;
OXR(xrAttachSessionActionSets(engine->Session, &attach_info));
// Enumerate actions
char string_buffer[256];
XrPath action_paths_buffer[32];
XrAction actions_to_enumerate[] = {input->IndexLeft,
input->IndexRight,
input->ButtonMenu,
input->ButtonA,
input->ButtonB,
input->ButtonX,
input->ButtonY,
input->GripLeft,
input->GripRight,
input->JoystickLeft,
input->JoystickRight,
input->ThumbLeft,
input->ThumbRight,
input->VibrateLeftFeedback,
input->VibrateRightFeedback,
input->HandPoseLeft,
input->HandPoseRight};
for (int i = 0; i < sizeof(actions_to_enumerate) / sizeof(XrAction); i++)
{
XrBoundSourcesForActionEnumerateInfo e = {};
e.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO;
e.next = NULL;
e.action = actions_to_enumerate[i];
// Get Count
uint32_t count_output = 0;
OXR(xrEnumerateBoundSourcesForAction(engine->Session, &e, 0, &count_output, NULL));
if (count_output < 32)
{
OXR(xrEnumerateBoundSourcesForAction(engine->Session, &e, 32, &count_output,
action_paths_buffer));
for (uint32_t a = 0; a < count_output; ++a)
{
XrInputSourceLocalizedNameGetInfo name_info = {};
name_info.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO;
name_info.next = NULL;
name_info.sourcePath = action_paths_buffer[a];
name_info.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT |
XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT |
XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT;
uint32_t str_count = 0u;
OXR(xrGetInputSourceLocalizedName(engine->Session, &name_info, 0, &str_count, NULL));
if (str_count < 256)
{
OXR(xrGetInputSourceLocalizedName(engine->Session, &name_info, 256, &str_count,
string_buffer));
char path_str[256];
uint32_t str_len = 0;
OXR(xrPathToString(engine->Instance, action_paths_buffer[a],
(uint32_t)sizeof(path_str), &str_len, path_str));
ALOGV("mapped %s -> %s", path_str, string_buffer);
}
}
}
}
input->Initialized = true;
}
uint32_t XrInputGetButtonState(struct XrInput* input, int controller)
{
switch (controller)
{
case 0:
return input->ButtonsLeft;
case 1:
return input->ButtonsRight;
default:
return 0;
}
}
XrVector2f XrInputGetJoystickState(struct XrInput* input, int controller)
{
return input->JoystickState[controller].currentState;
}
XrPosef XrInputGetPose(struct XrInput* input, int controller)
{
return input->ControllerPose[controller].pose;
}
void XrInputUpdate(struct XrEngine* engine, struct XrInput* input)
{
// sync action data
XrActiveActionSet activeActionSet = {};
activeActionSet.actionSet = input->ActionSet;
activeActionSet.subactionPath = XR_NULL_PATH;
XrActionsSyncInfo sync_info = {};
sync_info.type = XR_TYPE_ACTIONS_SYNC_INFO;
sync_info.next = NULL;
sync_info.countActiveActionSets = 1;
sync_info.activeActionSets = &activeActionSet;
OXR(xrSyncActions(engine->Session, &sync_info));
// query input action states
XrActionStateGetInfo get_info = {};
get_info.type = XR_TYPE_ACTION_STATE_GET_INFO;
get_info.next = NULL;
get_info.subactionPath = XR_NULL_PATH;
XrSession session = engine->Session;
XrInputProcessHaptics(input, session);
if (input->LeftControllerSpace == XR_NULL_HANDLE)
{
input->LeftControllerSpace = XrInputCreateActionSpace(session, input->HandPoseLeft, input->LeftHandPath);
}
if (input->RightControllerSpace == XR_NULL_HANDLE)
{
input->RightControllerSpace = XrInputCreateActionSpace(session, input->HandPoseRight, input->RightHandPath);
}
// button mapping
input->ButtonsLeft = 0;
if (XrInputGetActionStateBoolean(session, input->ButtonMenu).currentState)
input->ButtonsLeft |= (int)Enter;
if (XrInputGetActionStateBoolean(session, input->ButtonX).currentState)
input->ButtonsLeft |= (int)X;
if (XrInputGetActionStateBoolean(session, input->ButtonY).currentState)
input->ButtonsLeft |= (int)Y;
if (XrInputGetActionStateBoolean(session, input->IndexLeft).currentState)
input->ButtonsLeft |= (int)Trigger;
if (XrInputGetActionStateFloat(session, input->GripLeft).currentState > 0.5f)
input->ButtonsLeft |= (int)Grip;
if (XrInputGetActionStateBoolean(session, input->ThumbLeft).currentState)
input->ButtonsLeft |= (int)LThumb;
input->ButtonsRight = 0;
if (XrInputGetActionStateBoolean(session, input->ButtonA).currentState)
input->ButtonsRight |= (int)A;
if (XrInputGetActionStateBoolean(session, input->ButtonB).currentState)
input->ButtonsRight |= (int)B;
if (XrInputGetActionStateBoolean(session, input->IndexRight).currentState)
input->ButtonsRight |= (int)Trigger;
if (XrInputGetActionStateFloat(session, input->GripRight).currentState > 0.5f)
input->ButtonsRight |= (int)Grip;
if (XrInputGetActionStateBoolean(session, input->ThumbRight).currentState)
input->ButtonsRight |= (int)RThumb;
// thumbstick
input->JoystickState[0] = XrInputGetActionStateVector2(session, input->JoystickLeft);
input->JoystickState[1] = XrInputGetActionStateVector2(session, input->JoystickRight);
if (input->JoystickState[0].currentState.x > 0.5)
input->ButtonsLeft |= (int)Right;
if (input->JoystickState[0].currentState.x < -0.5)
input->ButtonsLeft |= (int)Left;
if (input->JoystickState[0].currentState.y > 0.5)
input->ButtonsLeft |= (int)Up;
if (input->JoystickState[0].currentState.y < -0.5)
input->ButtonsLeft |= (int)Down;
if (input->JoystickState[1].currentState.x > 0.5)
input->ButtonsRight |= (int)Right;
if (input->JoystickState[1].currentState.x < -0.5)
input->ButtonsRight |= (int)Left;
if (input->JoystickState[1].currentState.y > 0.5)
input->ButtonsRight |= (int)Up;
if (input->JoystickState[1].currentState.y < -0.5)
input->ButtonsRight |= (int)Down;
// pose
for (int i = 0; i < 2; i++)
{
memset(&input->ControllerPose[i], 0, sizeof(input->ControllerPose[i]));
input->ControllerPose[i].type = XR_TYPE_SPACE_LOCATION;
XrSpace aim_space[] = {input->LeftControllerSpace, input->RightControllerSpace};
xrLocateSpace(aim_space[i], engine->CurrentSpace,
(XrTime)(engine->PredictedDisplayTime), &input->ControllerPose[i]);
}
}
void XrInputVibrate(struct XrInput* input, int duration, int chan, float intensity)
{
for (int i = 0; i < 2; ++i)
{
int channel = i & chan;
if (channel)
{
if (input->VibrationChannelDuration[channel] > 0.0f)
return;
if (input->VibrationChannelDuration[channel] == -1.0f && duration != 0.0f)
return;
input->VibrationChannelDuration[channel] = (float)duration;
input->VibrationChannelIntensity[channel] = intensity;
}
}
}
XrAction XrInputCreateAction(XrActionSet output_set, XrActionType type, const char* name,
const char* desc, int count_subaction_path, XrPath* subaction_path)
{
XrActionCreateInfo action_info = {};
action_info.type = XR_TYPE_ACTION_CREATE_INFO;
action_info.next = NULL;
action_info.actionType = type;
if (count_subaction_path > 0)
{
action_info.countSubactionPaths = count_subaction_path;
action_info.subactionPaths = subaction_path;
}
strcpy(action_info.actionName, name);
strcpy(action_info.localizedActionName, desc ? desc : name);
XrAction output = XR_NULL_HANDLE;
OXR(xrCreateAction(output_set, &action_info, &output));
return output;
}
XrActionSet XrInputCreateActionSet(XrInstance instance, const char* name, const char* desc)
{
XrActionSetCreateInfo action_set_info = {};
action_set_info.type = XR_TYPE_ACTION_SET_CREATE_INFO;
action_set_info.next = NULL;
action_set_info.priority = 1;
strcpy(action_set_info.actionSetName, name);
strcpy(action_set_info.localizedActionSetName, desc);
XrActionSet output = XR_NULL_HANDLE;
OXR(xrCreateActionSet(instance, &action_set_info, &output));
return output;
}
XrSpace XrInputCreateActionSpace(XrSession session, XrAction action, XrPath subaction_path)
{
XrActionSpaceCreateInfo action_space_info = {};
action_space_info.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
action_space_info.action = action;
action_space_info.poseInActionSpace.orientation.w = 1.0f;
action_space_info.subactionPath = subaction_path;
XrSpace output = XR_NULL_HANDLE;
OXR(xrCreateActionSpace(session, &action_space_info, &output));
return output;
}
XrActionStateBoolean XrInputGetActionStateBoolean(XrSession session, XrAction action)
{
XrActionStateGetInfo get_info = {};
get_info.type = XR_TYPE_ACTION_STATE_GET_INFO;
get_info.action = action;
XrActionStateBoolean state = {};
state.type = XR_TYPE_ACTION_STATE_BOOLEAN;
OXR(xrGetActionStateBoolean(session, &get_info, &state));
return state;
}
XrActionStateFloat XrInputGetActionStateFloat(XrSession session, XrAction action)
{
XrActionStateGetInfo get_info = {};
get_info.type = XR_TYPE_ACTION_STATE_GET_INFO;
get_info.action = action;
XrActionStateFloat state = {};
state.type = XR_TYPE_ACTION_STATE_FLOAT;
OXR(xrGetActionStateFloat(session, &get_info, &state));
return state;
}
XrActionStateVector2f XrInputGetActionStateVector2(XrSession session, XrAction action)
{
XrActionStateGetInfo get_info = {};
get_info.type = XR_TYPE_ACTION_STATE_GET_INFO;
get_info.action = action;
XrActionStateVector2f state = {};
state.type = XR_TYPE_ACTION_STATE_VECTOR2F;
OXR(xrGetActionStateVector2f(session, &get_info, &state));
return state;
}
XrActionSuggestedBinding XrInputGetBinding(XrInstance instance, XrAction action, const char* name)
{
XrPath bindingPath;
OXR(xrStringToPath(instance, name, &bindingPath));
XrActionSuggestedBinding output;
output.action = action;
output.binding = bindingPath;
return output;
}
int XrInputGetMilliseconds(struct XrInput* input)
{
struct timeval tp;
gettimeofday(&tp, NULL);
if (!input->SysTimeBase)
{
input->SysTimeBase = tp.tv_sec;
return tp.tv_usec / 1000;
}
return (tp.tv_sec - input->SysTimeBase) * 1000 + tp.tv_usec / 1000;
}
void XrInputProcessHaptics(struct XrInput* input, XrSession session)
{
static float last_frame_timestamp = 0.0f;
float timestamp = (float)(XrInputGetMilliseconds(input));
float frametime = timestamp - last_frame_timestamp;
last_frame_timestamp = timestamp;
for (int i = 0; i < 2; ++i)
{
if (input->VibrationChannelDuration[i] > 0.0f || input->VibrationChannelDuration[i] == -1.0f)
{
// fire haptics using output action
XrHapticVibration vibration = {};
vibration.type = XR_TYPE_HAPTIC_VIBRATION;
vibration.next = NULL;
vibration.amplitude = input->VibrationChannelIntensity[i];
vibration.duration = ToXrTime(input->VibrationChannelDuration[i]);
vibration.frequency = 3000;
XrHapticActionInfo haptic_info = {};
haptic_info.type = XR_TYPE_HAPTIC_ACTION_INFO;
haptic_info.next = NULL;
haptic_info.action = i == 0 ? input->VibrateLeftFeedback : input->VibrateRightFeedback;
OXR(xrApplyHapticFeedback(session, &haptic_info, (const XrHapticBaseHeader*)&vibration));
if (input->VibrationChannelDuration[i] != -1.0f)
{
input->VibrationChannelDuration[i] -= frametime;
if (input->VibrationChannelDuration[i] < 0.0f)
{
input->VibrationChannelDuration[i] = 0.0f;
input->VibrationChannelIntensity[i] = 0.0f;
}
}
}
else
{
// Stop haptics
XrHapticActionInfo haptic_info = {};
haptic_info.type = XR_TYPE_HAPTIC_ACTION_INFO;
haptic_info.next = NULL;
haptic_info.action = i == 0 ? input->VibrateLeftFeedback : input->VibrateRightFeedback;
OXR(xrStopHapticFeedback(session, &haptic_info));
}
}
}
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include "engine.h"
enum XrButton
{
A = 0x00000001, // Set for trigger pulled on the Gear VR and Go Controllers
B = 0x00000002,
RThumb = 0x00000004,
X = 0x00000100,
Y = 0x00000200,
LThumb = 0x00000400,
Up = 0x00010000,
Down = 0x00020000,
Left = 0x00040000,
Right = 0x00080000,
Enter = 0x00100000, //< Set for touchpad click on the Go Controller, menu
// button on Left Quest Controller
Back = 0x00200000, //< Back button on the Go Controller (only set when
// a short press comes up)
Grip = 0x04000000, //< grip trigger engaged
Trigger = 0x20000000 //< Index Trigger engaged
};
struct XrInput {
bool Initialized;
// OpenXR controller mapping
XrActionSet ActionSet;
XrPath LeftHandPath;
XrPath RightHandPath;
XrAction HandPoseLeft;
XrAction HandPoseRight;
XrAction IndexLeft;
XrAction IndexRight;
XrAction ButtonMenu;
XrAction ButtonA;
XrAction ButtonB;
XrAction ButtonX;
XrAction ButtonY;
XrAction GripLeft;
XrAction GripRight;
XrAction JoystickLeft;
XrAction JoystickRight;
XrAction ThumbLeft;
XrAction ThumbRight;
XrAction VibrateLeftFeedback;
XrAction VibrateRightFeedback;
XrSpace LeftControllerSpace;
XrSpace RightControllerSpace;
// Controller state
uint32_t ButtonsLeft;
uint32_t ButtonsRight;
XrSpaceLocation ControllerPose[2];
XrActionStateVector2f JoystickState[2];
float VibrationChannelDuration[2];
float VibrationChannelIntensity[2];
// Timer
unsigned long SysTimeBase;
};
void XrInputInit(struct XrEngine* engine, struct XrInput* input);
uint32_t XrInputGetButtonState(struct XrInput* input, int controller);
XrVector2f XrInputGetJoystickState(struct XrInput* input, int controller);
XrPosef XrInputGetPose(struct XrInput* input, int controller);
void XrInputUpdate(struct XrEngine* engine, struct XrInput* input);
void XrInputVibrate(struct XrInput* input, int duration, int chan, float intensity);
XrAction XrInputCreateAction(XrActionSet output_set, XrActionType type, const char* name,
const char* desc, int count_subaction_path, XrPath* subaction_path);
XrActionSet XrInputCreateActionSet(XrInstance instance, const char* name, const char* desc);
XrSpace XrInputCreateActionSpace(XrSession session, XrAction action, XrPath subaction_path);
XrActionStateBoolean XrInputGetActionStateBoolean(XrSession session, XrAction action);
XrActionStateFloat XrInputGetActionStateFloat(XrSession session, XrAction action);
XrActionStateVector2f XrInputGetActionStateVector2(XrSession session, XrAction action);
XrActionSuggestedBinding XrInputGetBinding(XrInstance instance, XrAction action, const char* name);
int XrInputGetMilliseconds(struct XrInput* input);
void XrInputProcessHaptics(struct XrInput* input, XrSession session);
+189
View File
@@ -0,0 +1,189 @@
#include "engine.h"
#include "input.h"
#include "math.h"
#include "renderer.h"
#include <string.h>
struct XrEngine xr_module_engine;
struct XrInput xr_module_input;
struct XrRenderer xr_module_renderer;
bool xr_initialized = false;
#if defined(_DEBUG)
#include <GLES2/gl2.h>
void GLCheckErrors(const char* file, int line) {
for (int i = 0; i < 10; i++) {
const GLenum error = glGetError();
if (error == GL_NO_ERROR) {
break;
}
ALOGE("OpenGL error on line %s:%d %d", file, line, error);
}
}
void OXRCheckErrors(XrResult result, const char* file, int line) {
if (XR_FAILED(result)) {
char errorBuffer[XR_MAX_RESULT_STRING_SIZE];
xrResultToString(xr_module_engine->Instance, result, errorBuffer);
ALOGE("OpenXR error on line %s:%d %s", file, line, errorBuffer);
}
}
#endif
JNIEXPORT void JNICALL Java_com_winlator_XrActivity_init(JNIEnv *env, jobject obj) {
// Do not allow second initialization
if (xr_initialized) {
return;
}
// Set platform flags
memset(&xr_module_engine, 0, sizeof(xr_module_engine));
xr_module_engine.PlatformFlag[PLATFORM_CONTROLLER_QUEST] = true;
xr_module_engine.PlatformFlag[PLATFORM_EXTENSION_PASSTHROUGH] = true;
xr_module_engine.PlatformFlag[PLATFORM_EXTENSION_PERFORMANCE] = true;
// Get Java VM
JavaVM* vm;
(*env)->GetJavaVM(env, &vm);
// Init XR
xrJava java;
java.vm = vm;
java.activity = (*env)->NewGlobalRef(env, obj);
XrEngineInit(&xr_module_engine, &java, "Winlator", 1);
// Enter XR
XrEngineEnter(&xr_module_engine);
XrInputInit(&xr_module_engine, &xr_module_input);
XrRendererInit(&xr_module_engine, &xr_module_renderer);
xr_initialized = true;
ALOGV("Init called");
}
JNIEXPORT void JNICALL Java_com_winlator_XrActivity_bindFramebuffer(JNIEnv *env, jobject obj) {
if (xr_initialized) {
XrRendererBindFramebuffer(&xr_module_renderer);
}
}
JNIEXPORT jint JNICALL Java_com_winlator_XrActivity_getWidth(JNIEnv *env, jobject obj) {
int w, h;
XrRendererGetResolution(&xr_module_engine, &xr_module_renderer, &w, &h);
return w;
}
JNIEXPORT jint JNICALL Java_com_winlator_XrActivity_getHeight(JNIEnv *env, jobject obj) {
int w, h;
XrRendererGetResolution(&xr_module_engine, &xr_module_renderer, &w, &h);
return h;
}
JNIEXPORT jboolean JNICALL Java_com_winlator_XrActivity_beginFrame(JNIEnv *env, jobject obj, jboolean immersive, jboolean sbs) {
if (XrRendererInitFrame(&xr_module_engine, &xr_module_renderer)) {
// Set render canvas
int mode = immersive ? RENDER_MODE_MONO_6DOF : RENDER_MODE_MONO_SCREEN;
xr_module_renderer.ConfigFloat[CONFIG_CANVAS_DISTANCE] = 5.0f;
xr_module_renderer.ConfigInt[CONFIG_PASSTHROUGH] = !immersive;
xr_module_renderer.ConfigInt[CONFIG_MODE] = mode;
xr_module_renderer.ConfigInt[CONFIG_SBS] = sbs;
// Recenter if mode switched
static bool last_immersive = false;
if (last_immersive != immersive) {
XrRendererRecenter(&xr_module_engine, &xr_module_renderer);
last_immersive = immersive;
}
// Update controllers state
XrInputUpdate(&xr_module_engine, &xr_module_input);
// Lock framebuffer
XrRendererBeginFrame(&xr_module_renderer, 0);
return true;
}
return false;
}
JNIEXPORT void JNICALL Java_com_winlator_XrActivity_endFrame(JNIEnv *env, jobject obj) {
XrRendererEndFrame(&xr_module_renderer);
XrRendererFinishFrame(&xr_module_engine, &xr_module_renderer);
}
JNIEXPORT jfloatArray JNICALL Java_com_winlator_XrActivity_getAxes(JNIEnv *env, jobject obj) {
XrPosef lPose = XrInputGetPose(&xr_module_input, 0);
XrPosef rPose = XrInputGetPose(&xr_module_input, 1);
XrVector2f lThumbstick = XrInputGetJoystickState(&xr_module_input, 0);
XrVector2f rThumbstick = XrInputGetJoystickState(&xr_module_input, 1);
XrVector3f lPosition = xr_module_renderer.Projections[0].pose.position;
XrVector3f rPosition = xr_module_renderer.Projections[1].pose.position;
XrVector3f angles = xr_module_renderer.HmdOrientation;
int count = 0;
float data[32];
data[count++] = XrQuaternionfEulerAngles(lPose.orientation).x; //L_PITCH
data[count++] = XrQuaternionfEulerAngles(lPose.orientation).y; //L_YAW
data[count++] = XrQuaternionfEulerAngles(lPose.orientation).z; //L_ROLL
data[count++] = lThumbstick.x; //L_THUMBSTICK_X
data[count++] = lThumbstick.y; //L_THUMBSTICK_Y
data[count++] = lPose.position.x; //L_X
data[count++] = lPose.position.y; //L_Y
data[count++] = lPose.position.z; //L_Z
data[count++] = XrQuaternionfEulerAngles(rPose.orientation).x; //R_PITCH
data[count++] = XrQuaternionfEulerAngles(rPose.orientation).y; //R_YAW
data[count++] = XrQuaternionfEulerAngles(rPose.orientation).z; //R_ROLL
data[count++] = rThumbstick.x; //R_THUMBSTICK_X
data[count++] = rThumbstick.y; //R_THUMBSTICK_Y
data[count++] = rPose.position.x; //R_X
data[count++] = rPose.position.y; //R_Y
data[count++] = rPose.position.z; //R_Z
data[count++] = angles.x; //HMD_PITCH
data[count++] = angles.y; //HMD_YAW
data[count++] = angles.z; //HMD_ROLL
data[count++] = (lPosition.x + rPosition.x) * 0.5f; //HMD_X
data[count++] = (lPosition.y + rPosition.y) * 0.5f; //HMD_Y
data[count++] = (lPosition.z + rPosition.z) * 0.5f; //HMD_Z
data[count++] = XrVector3fDistance(lPosition, rPosition); //HMD_IPD
jfloat values[count];
memcpy(values, data, count * sizeof(float));
jfloatArray output = (*env)->NewFloatArray(env, count);
(*env)->SetFloatArrayRegion(env, output, (jsize)0, (jsize)count, values);
return output;
}
JNIEXPORT jbooleanArray JNICALL Java_com_winlator_XrActivity_getButtons(JNIEnv *env, jobject obj) {
uint32_t l = XrInputGetButtonState(&xr_module_input, 0);
uint32_t r = XrInputGetButtonState(&xr_module_input, 1);
int count = 0;
bool data[32];
data[count++] = l & (int)Grip; //L_GRIP
data[count++] = l & (int)Enter; //L_MENU
data[count++] = l & (int)LThumb; //L_THUMBSTICK_PRESS
data[count++] = l & (int)Left; //L_THUMBSTICK_LEFT
data[count++] = l & (int)Right; //L_THUMBSTICK_RIGHT
data[count++] = l & (int)Up; //L_THUMBSTICK_UP
data[count++] = l & (int)Down; //L_THUMBSTICK_DOWN
data[count++] = l & (int)Trigger; //L_TRIGGER
data[count++] = l & (int)X; //L_X
data[count++] = l & (int)Y; //L_Y
data[count++] = r & (int)A; //R_A
data[count++] = r & (int)B; //R_B
data[count++] = r & (int)Grip; //R_GRIP
data[count++] = r & (int)RThumb; //R_THUMBSTICK_PRESS
data[count++] = r & (int)Left; //R_THUMBSTICK_LEFT
data[count++] = r & (int)Right; //R_THUMBSTICK_RIGHT
data[count++] = r & (int)Up; //R_THUMBSTICK_UP
data[count++] = r & (int)Down; //R_THUMBSTICK_DOWN
data[count++] = r & (int)Trigger; //R_TRIGGER
jboolean values[count];
memcpy(values, data, count * sizeof(jboolean));
jbooleanArray output = (*env)->NewBooleanArray(env, count);
(*env)->SetBooleanArrayRegion(env, output, (jsize)0, (jsize)count, values);
return output;
}
+218
View File
@@ -0,0 +1,218 @@
#include "math.h"
#include <math.h>
#include <string.h>
double FromXrTime(const XrTime time)
{
return (time * 1e-9);
}
XrTime ToXrTime(const double time_in_seconds)
{
return (XrTime)(time_in_seconds * 1e9);
}
float ToDegrees(float rad)
{
return (float)(rad / M_PI * 180.0f);
}
float ToRadians(float deg)
{
return (float)(deg * M_PI / 180.0f);
}
/*
================================================================================
XrQuaternionf
================================================================================
*/
XrQuaternionf XrQuaternionfCreateFromVectorAngle(const XrVector3f axis, const float angle)
{
XrQuaternionf r;
if (XrVector3fLengthSquared(axis) == 0.0f)
{
r.x = 0;
r.y = 0;
r.z = 0;
r.w = 1;
return r;
}
XrVector3f unitAxis = XrVector3fNormalized(axis);
float sinHalfAngle = sinf(angle * 0.5f);
r.w = cosf(angle * 0.5f);
r.x = unitAxis.x * sinHalfAngle;
r.y = unitAxis.y * sinHalfAngle;
r.z = unitAxis.z * sinHalfAngle;
return r;
}
XrQuaternionf XrQuaternionfMultiply(const XrQuaternionf a, const XrQuaternionf b)
{
XrQuaternionf c;
c.x = a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y;
c.y = a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x;
c.z = a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w;
c.w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z;
return c;
}
XrVector3f XrQuaternionfEulerAngles(const XrQuaternionf q)
{
float M[16];
XrQuaternionfToMatrix4f(&q, M);
XrVector4f v1 = {0, 0, -1, 0};
XrVector4f v2 = {1, 0, 0, 0};
XrVector4f v3 = {0, 1, 0, 0};
XrVector4f forwardInVRSpace = XrVector4fMultiplyMatrix4f(M, &v1);
XrVector4f rightInVRSpace = XrVector4fMultiplyMatrix4f(M, &v2);
XrVector4f upInVRSpace = XrVector4fMultiplyMatrix4f(M, &v3);
XrVector3f forward = {-forwardInVRSpace.z, -forwardInVRSpace.x, forwardInVRSpace.y};
XrVector3f right = {-rightInVRSpace.z, -rightInVRSpace.x, rightInVRSpace.y};
XrVector3f up = {-upInVRSpace.z, -upInVRSpace.x, upInVRSpace.y};
XrVector3f forwardNormal = XrVector3fNormalized(forward);
XrVector3f rightNormal = XrVector3fNormalized(right);
XrVector3f upNormal = XrVector3fNormalized(up);
return XrVector3fGetAnglesFromVectors(forwardNormal, rightNormal, upNormal);
}
void XrQuaternionfToMatrix4f(const XrQuaternionf* q, float* m)
{
const float ww = q->w * q->w;
const float xx = q->x * q->x;
const float yy = q->y * q->y;
const float zz = q->z * q->z;
float M[4][4];
M[0][0] = ww + xx - yy - zz;
M[0][1] = 2 * (q->x * q->y - q->w * q->z);
M[0][2] = 2 * (q->x * q->z + q->w * q->y);
M[0][3] = 0;
M[1][0] = 2 * (q->x * q->y + q->w * q->z);
M[1][1] = ww - xx + yy - zz;
M[1][2] = 2 * (q->y * q->z - q->w * q->x);
M[1][3] = 0;
M[2][0] = 2 * (q->x * q->z - q->w * q->y);
M[2][1] = 2 * (q->y * q->z + q->w * q->x);
M[2][2] = ww - xx - yy + zz;
M[2][3] = 0;
M[3][0] = 0;
M[3][1] = 0;
M[3][2] = 0;
M[3][3] = 1;
memcpy(m, &M, sizeof(float) * 16);
}
/*
================================================================================
XrVector3f, XrVector4f
================================================================================
*/
float XrVector3fDistance(const XrVector3f a, const XrVector3f b)
{
XrVector3f diff;
diff.x = a.x - b.x;
diff.y = a.y - b.y;
diff.z = a.z - b.z;
return sqrt(XrVector3fLengthSquared(diff));
}
float XrVector3fLengthSquared(const XrVector3f v)
{
return v.x * v.x + v.y * v.y + v.z * v.z;
}
XrVector3f XrVector3fGetAnglesFromVectors(XrVector3f forward, XrVector3f right, XrVector3f up)
{
float sp = -forward.z;
float cp_x_cy = forward.x;
float cp_x_sy = forward.y;
float cp_x_sr = -right.z;
float cp_x_cr = up.z;
float yaw = atan2(cp_x_sy, cp_x_cy);
float roll = atan2(cp_x_sr, cp_x_cr);
float cy = cos(yaw);
float sy = sin(yaw);
float cr = cos(roll);
float sr = sin(roll);
float cp;
if (fabs(cy) > EPSILON)
{
cp = cp_x_cy / cy;
}
else if (fabs(sy) > EPSILON)
{
cp = cp_x_sy / sy;
}
else if (fabs(sr) > EPSILON)
{
cp = cp_x_sr / sr;
}
else if (fabs(cr) > EPSILON)
{
cp = cp_x_cr / cr;
}
else
{
cp = cos(asin(sp));
}
float pitch = atan2(sp, cp);
XrVector3f angles;
angles.x = ToDegrees(pitch);
angles.y = ToDegrees(yaw);
angles.z = ToDegrees(roll);
return angles;
}
XrVector3f XrVector3fNormalized(const XrVector3f v)
{
float rcpLen = 1.0f / sqrtf(XrVector3fLengthSquared(v));
return XrVector3fScalarMultiply(v, rcpLen);
}
XrVector3f XrVector3fScalarMultiply(const XrVector3f v, float scale)
{
XrVector3f u;
u.x = v.x * scale;
u.y = v.y * scale;
u.z = v.z * scale;
return u;
}
XrVector4f XrVector4fMultiplyMatrix4f(const float* m, const XrVector4f* v)
{
float M[4][4];
memcpy(&M, m, sizeof(float) * 16);
XrVector4f out;
out.x = M[0][0] * v->x + M[0][1] * v->y + M[0][2] * v->z + M[0][3] * v->w;
out.y = M[1][0] * v->x + M[1][1] * v->y + M[1][2] * v->z + M[1][3] * v->w;
out.z = M[2][0] * v->x + M[2][1] * v->y + M[2][2] * v->z + M[2][3] * v->w;
out.w = M[3][0] * v->x + M[3][1] * v->y + M[3][2] * v->z + M[3][3] * v->w;
return out;
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <openxr/openxr.h>
#ifndef EPSILON
#define EPSILON 0.001f
#endif
double FromXrTime(const XrTime time);
XrTime ToXrTime(const double time_in_seconds);
float ToDegrees(float rad);
float ToRadians(float deg);
// XrQuaternionf
XrQuaternionf XrQuaternionfCreateFromVectorAngle(const XrVector3f axis, const float angle);
XrQuaternionf XrQuaternionfMultiply(const XrQuaternionf a, const XrQuaternionf b);
XrVector3f XrQuaternionfEulerAngles(const XrQuaternionf q);
void XrQuaternionfToMatrix4f(const XrQuaternionf* q, float* m);
// XrVector3f, XrVector4f
float XrVector3fDistance(const XrVector3f a, const XrVector3f b);
float XrVector3fLengthSquared(const XrVector3f v);
XrVector3f XrVector3fGetAnglesFromVectors(XrVector3f forward, XrVector3f right, XrVector3f up);
XrVector3f XrVector3fNormalized(const XrVector3f v);
XrVector3f XrVector3fScalarMultiply(const XrVector3f v, float scale);
XrVector4f XrVector4fMultiplyMatrix4f(const float* m, const XrVector4f* v);
+625
View File
@@ -0,0 +1,625 @@
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include "engine.h"
#include "math.h"
#include "renderer.h"
#define DECL_PFN(pfn) PFN_##pfn pfn = NULL
#define INIT_PFN(pfn) OXR(xrGetInstanceProcAddr(engine->Instance, #pfn, (PFN_xrVoidFunction*)(&pfn)))
DECL_PFN(xrCreatePassthroughFB);
DECL_PFN(xrDestroyPassthroughFB);
DECL_PFN(xrPassthroughStartFB);
DECL_PFN(xrPassthroughPauseFB);
DECL_PFN(xrCreatePassthroughLayerFB);
DECL_PFN(xrDestroyPassthroughLayerFB);
DECL_PFN(xrPassthroughLayerPauseFB);
DECL_PFN(xrPassthroughLayerResumeFB);
void XrRendererInit(struct XrEngine* engine, struct XrRenderer* renderer)
{
if (renderer->Initialized)
{
XrRendererDestroy(engine, renderer);
}
memset(renderer, 0, sizeof(renderer));
if (engine->PlatformFlag[PLATFORM_EXTENSION_PASSTHROUGH])
{
INIT_PFN(xrCreatePassthroughFB);
INIT_PFN(xrDestroyPassthroughFB);
INIT_PFN(xrPassthroughStartFB);
INIT_PFN(xrPassthroughPauseFB);
INIT_PFN(xrCreatePassthroughLayerFB);
INIT_PFN(xrDestroyPassthroughLayerFB);
INIT_PFN(xrPassthroughLayerPauseFB);
INIT_PFN(xrPassthroughLayerResumeFB);
}
int eyeW, eyeH;
XrRendererGetResolution(engine, renderer, &eyeW, &eyeH);
renderer->ConfigInt[CONFIG_VIEWPORT_WIDTH] = eyeW;
renderer->ConfigInt[CONFIG_VIEWPORT_HEIGHT] = eyeH;
// Get the viewport configuration info for the chosen viewport configuration type.
renderer->ViewportConfig.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES;
OXR(xrGetViewConfigurationProperties(engine->Instance, engine->SystemId,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
&renderer->ViewportConfig));
uint32_t num_spaces = 0;
OXR(xrEnumerateReferenceSpaces(engine->Session, 0, &num_spaces, NULL));
XrReferenceSpaceType* spaces = (XrReferenceSpaceType*)malloc(num_spaces * sizeof(XrReferenceSpaceType));
OXR(xrEnumerateReferenceSpaces(engine->Session, num_spaces, &num_spaces, spaces));
for (uint32_t i = 0; i < num_spaces; i++)
{
if (spaces[i] == XR_REFERENCE_SPACE_TYPE_STAGE)
{
renderer->StageSupported = true;
break;
}
}
free(spaces);
if (engine->CurrentSpace == XR_NULL_HANDLE)
{
XrRendererRecenter(engine, renderer);
}
renderer->Projections = (XrView*)(malloc(XrMaxNumEyes * sizeof(XrView)));
for (int eye = 0; eye < XrMaxNumEyes; eye++) {
memset(&renderer->Projections[eye], 0, sizeof(XrView));
renderer->Projections[eye].type = XR_TYPE_VIEW;
}
// Create framebuffers.
int width = renderer->ViewConfig[0].recommendedImageRectWidth;
int height = renderer->ViewConfig[0].recommendedImageRectHeight;
for (int i = 0; i < XrMaxNumEyes; i++)
{
XrFramebufferCreate(&renderer->Framebuffer[i], engine->Session, width, height);
}
if (engine->PlatformFlag[PLATFORM_EXTENSION_PASSTHROUGH])
{
XrPassthroughCreateInfoFB ptci = {XR_TYPE_PASSTHROUGH_CREATE_INFO_FB};
XrResult result;
OXR(result = xrCreatePassthroughFB(engine->Session, &ptci, &renderer->Passthrough));
if (XR_SUCCEEDED(result))
{
XrPassthroughLayerCreateInfoFB plci = {XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB};
plci.passthrough = renderer->Passthrough;
plci.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB;
OXR(xrCreatePassthroughLayerFB(engine->Session, &plci, &renderer->PassthroughLayer));
}
OXR(xrPassthroughStartFB(renderer->Passthrough));
OXR(xrPassthroughLayerResumeFB(renderer->PassthroughLayer));
}
renderer->Initialized = true;
}
void XrRendererDestroy(struct XrEngine* engine, struct XrRenderer* renderer)
{
if (engine->PlatformFlag[PLATFORM_EXTENSION_PASSTHROUGH])
{
if (renderer->PassthroughRunning)
{
OXR(xrPassthroughLayerPauseFB(renderer->PassthroughLayer));
}
OXR(xrPassthroughPauseFB(renderer->Passthrough));
OXR(xrDestroyPassthroughFB(renderer->Passthrough));
renderer->Passthrough = XR_NULL_HANDLE;
}
for (int i = 0; i < XrMaxNumEyes; i++)
{
XrFramebufferDestroy(&renderer->Framebuffer[i]);
}
free(renderer->Projections);
renderer->Initialized = false;
}
void XrRendererGetResolution(struct XrEngine* engine, struct XrRenderer* renderer, int* pWidth, int* pHeight)
{
static int width = 0;
static int height = 0;
if (engine)
{
// Enumerate the viewport configurations.
uint32_t viewport_config_count = 0;
OXR(xrEnumerateViewConfigurations(engine->Instance, engine->SystemId, 0,
&viewport_config_count, NULL));
XrViewConfigurationType* viewport_configs =
(XrViewConfigurationType*)malloc(viewport_config_count * sizeof(XrViewConfigurationType));
OXR(xrEnumerateViewConfigurations(engine->Instance, engine->SystemId,
viewport_config_count, &viewport_config_count,
viewport_configs));
for (uint32_t i = 0; i < viewport_config_count; i++)
{
const XrViewConfigurationType viewport_config_type = viewport_configs[i];
ALOGV("Viewport configuration type %d", (int)viewport_config_type);
XrViewConfigurationProperties viewport_config;
viewport_config.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES;
OXR(xrGetViewConfigurationProperties(engine->Instance, engine->SystemId,
viewport_config_type, &viewport_config));
uint32_t view_count;
OXR(xrEnumerateViewConfigurationViews(engine->Instance, engine->SystemId,
viewport_config_type, 0, &view_count, NULL));
if (view_count > 0)
{
XrViewConfigurationView* elements =
(XrViewConfigurationView*)malloc(view_count * sizeof(XrViewConfigurationView));
for (uint32_t e = 0; e < view_count; e++)
{
elements[e].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
elements[e].next = NULL;
}
OXR(xrEnumerateViewConfigurationViews(engine->Instance, engine->SystemId,
viewport_config_type, view_count, &view_count,
elements));
// Cache the view config properties for the selected config type.
if (viewport_config_type == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)
{
assert(view_count == XrMaxNumEyes);
for (uint32_t e = 0; e < view_count; e++)
{
renderer->ViewConfig[e] = elements[e];
}
}
free(elements);
}
else
{
ALOGE("Empty viewport configuration");
}
}
free(viewport_configs);
*pWidth = width = renderer->ViewConfig[0].recommendedImageRectWidth;
*pHeight = height = renderer->ViewConfig[0].recommendedImageRectHeight;
}
else
{
// use cached values
*pWidth = width;
*pHeight = height;
}
}
bool XrRendererInitFrame(struct XrEngine* engine, struct XrRenderer* renderer)
{
if (!renderer->Initialized)
{
return false;
}
XrRendererHandleXrEvents(engine, renderer);
if (!renderer->SessionActive)
{
return false;
}
XrRendererUpdateStageBounds(engine);
// Update passthrough
if (renderer->PassthroughRunning != renderer->ConfigInt[CONFIG_PASSTHROUGH])
{
if (renderer->ConfigInt[CONFIG_PASSTHROUGH])
{
OXR(xrPassthroughLayerResumeFB(renderer->PassthroughLayer));
}
else
{
OXR(xrPassthroughLayerPauseFB(renderer->PassthroughLayer));
}
renderer->PassthroughRunning = renderer->ConfigInt[CONFIG_PASSTHROUGH];
}
XrEngineWaitForFrame(engine);
XrViewLocateInfo projection_info = {};
projection_info.type = XR_TYPE_VIEW_LOCATE_INFO;
projection_info.next = NULL;
projection_info.viewConfigurationType = renderer->ViewportConfig.viewConfigurationType;
projection_info.displayTime = engine->PredictedDisplayTime;
projection_info.space = engine->CurrentSpace;
XrViewState view_state = {XR_TYPE_VIEW_STATE, NULL};
uint32_t projection_capacity = XrMaxNumEyes;
uint32_t projection_count = projection_capacity;
OXR(xrLocateViews(engine->Session, &projection_info, &view_state, projection_capacity,
&projection_count, renderer->Projections));
// Get the HMD pose, predicted for the middle of the time period during which
// the new eye images will be displayed. The number of frames predicted ahead
// depends on the pipeline depth of the engine and the synthesis rate.
// The better the prediction, the less black will be pulled in at the edges.
XrFrameBeginInfo begin_frame_info = {};
begin_frame_info.type = XR_TYPE_FRAME_BEGIN_INFO;
begin_frame_info.next = NULL;
OXR(xrBeginFrame(engine->Session, &begin_frame_info));
renderer->Fov.angleLeft = 0;
renderer->Fov.angleRight = 0;
renderer->Fov.angleUp = 0;
renderer->Fov.angleDown = 0;
for (int eye = 0; eye < XrMaxNumEyes; eye++)
{
renderer->Fov.angleLeft += renderer->Projections[eye].fov.angleLeft / 2.0f;
renderer->Fov.angleRight += renderer->Projections[eye].fov.angleRight / 2.0f;
renderer->Fov.angleUp += renderer->Projections[eye].fov.angleUp / 2.0f;
renderer->Fov.angleDown += renderer->Projections[eye].fov.angleDown / 2.0f;
renderer->InvertedViewPose[eye] = renderer->Projections[eye].pose;
}
renderer->HmdOrientation = XrQuaternionfEulerAngles(renderer->InvertedViewPose[0].orientation);
renderer->LayerCount = 0;
memset(renderer->Layers, 0, sizeof(XrCompositorLayer) * XrMaxLayerCount);
return true;
}
void XrRendererBeginFrame(struct XrRenderer* renderer, int fbo_index)
{
renderer->ConfigInt[CONFIG_CURRENT_FBO] = fbo_index;
XrFramebufferAcquire(&renderer->Framebuffer[fbo_index]);
}
void XrRendererEndFrame(struct XrRenderer* renderer)
{
int fbo_index = renderer->ConfigInt[CONFIG_CURRENT_FBO];
XrFramebufferRelease(&renderer->Framebuffer[fbo_index]);
}
void XrRendererFinishFrame(struct XrEngine* engine, struct XrRenderer* renderer)
{
int x = 0;
int y = 0;
int w = renderer->Framebuffer[0].Width;
int h = renderer->Framebuffer[0].Height;
if (renderer->ConfigInt[CONFIG_SBS])
{
w /= 2;
}
int mode = renderer->ConfigInt[CONFIG_MODE];
XrCompositionLayerProjectionView projection_layer_elements[2] = {};
if ((mode == RENDER_MODE_MONO_6DOF) || (mode == RENDER_MODE_STEREO_6DOF))
{
renderer->ConfigFloat[CONFIG_MENU_YAW] = renderer->HmdOrientation.y;
for (int eye = 0; eye < XrMaxNumEyes; eye++)
{
struct XrFramebuffer* framebuffer = &renderer->Framebuffer[0];
XrPosef pose = renderer->InvertedViewPose[0];
if (renderer->ConfigInt[CONFIG_SBS] && (eye == 1))
{
x += w;
}
else if (mode != RENDER_MODE_MONO_6DOF)
{
framebuffer = &renderer->Framebuffer[eye];
pose = renderer->InvertedViewPose[eye];
}
memset(&projection_layer_elements[eye], 0, sizeof(XrCompositionLayerProjectionView));
projection_layer_elements[eye].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
projection_layer_elements[eye].pose = pose;
projection_layer_elements[eye].fov = renderer->Fov;
memset(&projection_layer_elements[eye].subImage, 0, sizeof(XrSwapchainSubImage));
projection_layer_elements[eye].subImage.swapchain = framebuffer->Handle;
projection_layer_elements[eye].subImage.imageRect.offset.x = x;
projection_layer_elements[eye].subImage.imageRect.offset.y = y;
projection_layer_elements[eye].subImage.imageRect.extent.width = w;
projection_layer_elements[eye].subImage.imageRect.extent.height = h;
projection_layer_elements[eye].subImage.imageArrayIndex = 0;
}
XrCompositionLayerProjection projection_layer = {};
projection_layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
projection_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
projection_layer.layerFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
projection_layer.space = engine->CurrentSpace;
projection_layer.viewCount = XrMaxNumEyes;
projection_layer.views = projection_layer_elements;
renderer->Layers[renderer->LayerCount++].projection = projection_layer;
}
else if ((mode == RENDER_MODE_MONO_SCREEN) || (mode == RENDER_MODE_STEREO_SCREEN))
{
// Flat screen pose
float distance = renderer->ConfigFloat[CONFIG_CANVAS_DISTANCE];
float menu_pitch = ToRadians(renderer->ConfigFloat[CONFIG_MENU_PITCH]);
float menu_yaw = ToRadians(renderer->ConfigFloat[CONFIG_MENU_YAW]);
XrVector3f pos = {renderer->InvertedViewPose[0].position.x - sinf(menu_yaw) * cosf(menu_pitch) * distance,
renderer->InvertedViewPose[0].position.y - sinf(menu_pitch) * distance,
renderer->InvertedViewPose[0].position.z - cosf(menu_yaw) * cosf(menu_pitch) * distance};
XrVector3f pitch_axis = {1, 0, 0};
XrVector3f yaw_axis = {0, 1, 0};
XrQuaternionf pitch = XrQuaternionfCreateFromVectorAngle(pitch_axis, -menu_pitch);
XrQuaternionf yaw = XrQuaternionfCreateFromVectorAngle(yaw_axis, menu_yaw);
// Setup quad layer
struct XrFramebuffer* framebuffer = &renderer->Framebuffer[0];
XrCompositionLayerQuad quad_layer = {};
quad_layer.type = XR_TYPE_COMPOSITION_LAYER_QUAD;
quad_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
quad_layer.space = engine->CurrentSpace;
memset(&quad_layer.subImage, 0, sizeof(XrSwapchainSubImage));
quad_layer.subImage.imageRect.offset.x = x;
quad_layer.subImage.imageRect.offset.y = y;
quad_layer.subImage.imageRect.extent.width = w;
quad_layer.subImage.imageRect.extent.height = h;
quad_layer.subImage.swapchain = framebuffer->Handle;
quad_layer.subImage.imageArrayIndex = 0;
quad_layer.pose.orientation = XrQuaternionfMultiply(pitch, yaw);
quad_layer.pose.position = pos;
quad_layer.size.width = 4;
quad_layer.size.height = 4;
// Build the cylinder layer
if (renderer->ConfigInt[CONFIG_SBS])
{
quad_layer.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
renderer->Layers[renderer->LayerCount++].quad = quad_layer;
quad_layer.eyeVisibility = XR_EYE_VISIBILITY_RIGHT;
quad_layer.subImage.imageRect.offset.x = w;
renderer->Layers[renderer->LayerCount++].quad = quad_layer;
}
else if (mode == RENDER_MODE_MONO_SCREEN)
{
quad_layer.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
renderer->Layers[renderer->LayerCount++].quad = quad_layer;
}
else
{
quad_layer.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
renderer->Layers[renderer->LayerCount++].quad = quad_layer;
quad_layer.eyeVisibility = XR_EYE_VISIBILITY_RIGHT;
quad_layer.subImage.swapchain = renderer->Framebuffer[1].Handle;
renderer->Layers[renderer->LayerCount++].quad = quad_layer;
}
}
else
{
assert(false);
}
// Compose the layers for this frame.
const XrCompositionLayerBaseHeader* layers[XrMaxLayerCount] = {};
for (int i = 0; i < renderer->LayerCount; i++)
{
layers[i] = (const XrCompositionLayerBaseHeader*)&renderer->Layers[i];
}
XrFrameEndInfo end_frame_info = {};
end_frame_info.type = XR_TYPE_FRAME_END_INFO;
end_frame_info.displayTime = engine->PredictedDisplayTime;
end_frame_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
end_frame_info.layerCount = renderer->LayerCount;
end_frame_info.layers = layers;
OXR(xrEndFrame(engine->Session, &end_frame_info));
}
void XrRendererBindFramebuffer(struct XrRenderer* renderer)
{
if (!renderer->Initialized)
return;
int fbo_index = renderer->ConfigInt[CONFIG_CURRENT_FBO];
XrFramebufferSetCurrent(&renderer->Framebuffer[fbo_index]);
}
void XrRendererRecenter(struct XrEngine* engine, struct XrRenderer* renderer)
{
// Calculate recenter reference
XrReferenceSpaceCreateInfo space_info = {};
space_info.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
space_info.poseInReferenceSpace.orientation.w = 1.0f;
if (engine->CurrentSpace != XR_NULL_HANDLE)
{
XrSpaceLocation loc = {};
loc.type = XR_TYPE_SPACE_LOCATION;
OXR(xrLocateSpace(engine->HeadSpace, engine->CurrentSpace,
engine->PredictedDisplayTime, &loc));
renderer->HmdOrientation = XrQuaternionfEulerAngles(loc.pose.orientation);
renderer->ConfigFloat[CONFIG_RECENTER_YAW] += renderer->HmdOrientation.y;
float renceter_yaw = ToRadians(renderer->ConfigFloat[CONFIG_RECENTER_YAW]);
space_info.poseInReferenceSpace.orientation.x = 0;
space_info.poseInReferenceSpace.orientation.y = sinf(renceter_yaw / 2);
space_info.poseInReferenceSpace.orientation.z = 0;
space_info.poseInReferenceSpace.orientation.w = cosf(renceter_yaw / 2);
}
// Delete previous space instances
if (engine->StageSpace != XR_NULL_HANDLE)
{
OXR(xrDestroySpace(engine->StageSpace));
}
if (engine->FakeSpace != XR_NULL_HANDLE)
{
OXR(xrDestroySpace(engine->FakeSpace));
}
// Create a default stage space to use if SPACE_TYPE_STAGE is not
// supported, or calls to xrGetReferenceSpaceBoundsRect fail.
space_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
if (engine->PlatformFlag[PLATFORM_TRACKING_FLOOR])
{
space_info.poseInReferenceSpace.position.y = -1.6750f;
}
OXR(xrCreateReferenceSpace(engine->Session, &space_info, &engine->FakeSpace));
ALOGV("Created fake stage space from local space with offset");
engine->CurrentSpace = engine->FakeSpace;
if (renderer->StageSupported)
{
space_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
space_info.poseInReferenceSpace.position.y = 0.0;
OXR(xrCreateReferenceSpace(engine->Session, &space_info, &engine->StageSpace));
ALOGV("Created stage space");
if (engine->PlatformFlag[PLATFORM_TRACKING_FLOOR])
{
engine->CurrentSpace = engine->StageSpace;
}
}
// Update menu orientation
renderer->ConfigFloat[CONFIG_MENU_PITCH] = renderer->HmdOrientation.x;
renderer->ConfigFloat[CONFIG_MENU_YAW] = 0.0f;
}
void XrRendererHandleSessionStateChanges(struct XrEngine* engine, struct XrRenderer* renderer, XrSessionState state)
{
if (state == XR_SESSION_STATE_READY)
{
assert(renderer->SessionActive == false);
XrSessionBeginInfo session_begin_info;
memset(&session_begin_info, 0, sizeof(session_begin_info));
session_begin_info.type = XR_TYPE_SESSION_BEGIN_INFO;
session_begin_info.next = NULL;
session_begin_info.primaryViewConfigurationType = renderer->ViewportConfig.viewConfigurationType;
XrResult result;
OXR(result = xrBeginSession(engine->Session, &session_begin_info));
renderer->SessionActive = (result == XR_SUCCESS);
ALOGV("Session active = %d", renderer->SessionActive);
#ifdef ANDROID
if (renderer->SessionActive && engine->PlatformFlag[PLATFORM_EXTENSION_PERFORMANCE])
{
PFN_xrPerfSettingsSetPerformanceLevelEXT pfnPerfSettingsSetPerformanceLevelEXT = NULL;
OXR(xrGetInstanceProcAddr(engine->Instance, "xrPerfSettingsSetPerformanceLevelEXT",
(PFN_xrVoidFunction*)(&pfnPerfSettingsSetPerformanceLevelEXT)));
OXR(pfnPerfSettingsSetPerformanceLevelEXT(
engine->Session, XR_PERF_SETTINGS_DOMAIN_CPU_EXT, XR_PERF_SETTINGS_LEVEL_BOOST_EXT));
OXR(pfnPerfSettingsSetPerformanceLevelEXT(
engine->Session, XR_PERF_SETTINGS_DOMAIN_GPU_EXT, XR_PERF_SETTINGS_LEVEL_BOOST_EXT));
PFN_xrSetAndroidApplicationThreadKHR pfnSetAndroidApplicationThreadKHR = NULL;
OXR(xrGetInstanceProcAddr(engine->Instance, "xrSetAndroidApplicationThreadKHR",
(PFN_xrVoidFunction*)(&pfnSetAndroidApplicationThreadKHR)));
OXR(pfnSetAndroidApplicationThreadKHR(engine->Session,
XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR,
engine->MainThreadId));
OXR(pfnSetAndroidApplicationThreadKHR(engine->Session,
XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR,
engine->RenderThreadId));
}
#endif
}
else if (state == XR_SESSION_STATE_STOPPING)
{
assert(renderer->SessionActive);
OXR(xrEndSession(engine->Session));
renderer->SessionActive = false;
}
}
void XrRendererHandleXrEvents(struct XrEngine* engine, struct XrRenderer* renderer)
{
XrEventDataBuffer event_data_bufer = {};
// Poll for events
for (;;)
{
XrEventDataBaseHeader* base_event_handler = (XrEventDataBaseHeader*)(&event_data_bufer);
base_event_handler->type = XR_TYPE_EVENT_DATA_BUFFER;
base_event_handler->next = NULL;
XrResult r;
OXR(r = xrPollEvent(engine->Instance, &event_data_bufer));
if (r != XR_SUCCESS)
{
break;
}
switch (base_event_handler->type)
{
case XR_TYPE_EVENT_DATA_EVENTS_LOST:
ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_EVENTS_LOST");
break;
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
{
const XrEventDataInstanceLossPending* instance_loss_pending_event =
(XrEventDataInstanceLossPending*)(base_event_handler);
ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: time %lf",
FromXrTime(instance_loss_pending_event->lossTime));
}
break;
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED");
break;
case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT:
break;
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
XrRendererRecenter(engine, renderer);
break;
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
{
const XrEventDataSessionStateChanged* session_state_changed_event =
(XrEventDataSessionStateChanged*)(base_event_handler);
switch (session_state_changed_event->state)
{
case XR_SESSION_STATE_FOCUSED:
renderer->SessionFocused = true;
break;
case XR_SESSION_STATE_VISIBLE:
renderer->SessionFocused = false;
break;
case XR_SESSION_STATE_READY:
case XR_SESSION_STATE_STOPPING:
XrRendererHandleSessionStateChanges(engine, renderer, session_state_changed_event->state);
break;
default:
break;
}
break;
}
default:
ALOGV("xrPollEvent: Unknown event");
break;
}
}
}
void XrRendererUpdateStageBounds(struct XrEngine* engine)
{
XrExtent2Df stage_bounds = {};
XrResult result;
OXR(result = xrGetReferenceSpaceBoundsRect(engine->Session, XR_REFERENCE_SPACE_TYPE_STAGE,
&stage_bounds));
if (result != XR_SUCCESS)
{
stage_bounds.width = 1.0f;
stage_bounds.height = 1.0f;
engine->CurrentSpace = engine->FakeSpace;
}
}
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include "engine.h"
#include "framebuffer.h"
enum XrConfigFloat
{
// 2D canvas positioning
CONFIG_CANVAS_DISTANCE,
CONFIG_MENU_PITCH,
CONFIG_MENU_YAW,
CONFIG_RECENTER_YAW,
CONFIG_FLOAT_MAX
};
enum XrConfigInt
{
// switching between modes
CONFIG_MODE,
CONFIG_PASSTHROUGH,
CONFIG_SBS,
// viewport setup
CONFIG_VIEWPORT_WIDTH,
CONFIG_VIEWPORT_HEIGHT,
// render status
CONFIG_CURRENT_FBO,
// end
CONFIG_INT_MAX
};
enum XrRenderMode
{
RENDER_MODE_MONO_SCREEN,
RENDER_MODE_STEREO_SCREEN,
RENDER_MODE_MONO_6DOF,
RENDER_MODE_STEREO_6DOF
};
struct XrRenderer {
bool SessionActive;
bool SessionFocused;
bool Initialized;
bool StageSupported;
float ConfigFloat[CONFIG_FLOAT_MAX];
int ConfigInt[CONFIG_INT_MAX];
struct XrFramebuffer Framebuffer[XrMaxNumEyes];
int LayerCount;
XrCompositorLayer Layers[XrMaxLayerCount];
XrPassthroughFB Passthrough;
XrPassthroughLayerFB PassthroughLayer;
bool PassthroughRunning;
XrViewConfigurationProperties ViewportConfig;
XrViewConfigurationView ViewConfig[XrMaxNumEyes];
XrFovf Fov;
XrView* Projections;
XrPosef InvertedViewPose[2];
XrVector3f HmdOrientation;
};
void XrRendererInit(struct XrEngine* engine, struct XrRenderer* renderer);
void XrRendererDestroy(struct XrEngine* engine, struct XrRenderer* renderer);
void XrRendererGetResolution(struct XrEngine* engine, struct XrRenderer* renderer, int* pWidth, int* pHeight);
bool XrRendererInitFrame(struct XrEngine* engine, struct XrRenderer* renderer);
void XrRendererBeginFrame(struct XrRenderer* renderer, int fbo_index);
void XrRendererEndFrame(struct XrRenderer* renderer);
void XrRendererFinishFrame(struct XrEngine* engine, struct XrRenderer* renderer);
void XrRendererBindFramebuffer(struct XrRenderer* renderer);
void XrRendererRecenter(struct XrEngine* engine, struct XrRenderer* renderer);
void XrRendererHandleSessionStateChanges(struct XrEngine* engine, struct XrRenderer* renderer, XrSessionState state);
void XrRendererHandleXrEvents(struct XrEngine* engine, struct XrRenderer* renderer);
void XrRendererUpdateStageBounds(struct XrEngine* engine);
@@ -145,9 +145,12 @@ public class ContainersFragment extends Fragment {
listItemMenu.setOnMenuItemClickListener((menuItem) -> {
switch (menuItem.getItemId()) {
case R.id.container_run:
Intent intent = new Intent(context, XServerDisplayActivity.class);
intent.putExtra("container_id", container.id);
context.startActivity(intent);
if (!XrActivity.isSupported()) {
Intent intent = new Intent(context, XServerDisplayActivity.class);
intent.putExtra("container_id", container.id);
context.startActivity(intent);
}
else XrActivity.openIntent(getActivity(), container.id, null);
break;
case R.id.container_edit:
FragmentManager fragmentManager = getParentFragmentManager();
@@ -1,5 +1,6 @@
package com.winlator;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
@@ -133,11 +134,15 @@ public class ShortcutsFragment extends Fragment {
}
private void runFromShortcut(Shortcut shortcut) {
Context context = getContext();
Intent intent = new Intent(context, XServerDisplayActivity.class);
intent.putExtra("container_id", shortcut.container.id);
intent.putExtra("shortcut_path", shortcut.file.getPath());
context.startActivity(intent);
Activity activity = getActivity();
if (!XrActivity.isSupported()) {
Intent intent = new Intent(activity, XServerDisplayActivity.class);
intent.putExtra("container_id", shortcut.container.id);
intent.putExtra("shortcut_path", shortcut.file.getPath());
activity.startActivity(intent);
}
else XrActivity.openIntent(activity, shortcut.container.id, shortcut.file.getPath());
}
}
}
@@ -0,0 +1,353 @@
package com.winlator;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Display;
import android.view.View;
import android.widget.EditText;
import androidx.preference.PreferenceManager;
import com.winlator.core.AppUtils;
import com.winlator.xserver.Keyboard;
import com.winlator.xserver.Pointer;
import com.winlator.xserver.XKeycode;
import com.winlator.xserver.XLock;
import com.winlator.xserver.XServer;
/*
WinlatorXR implementation by lvonasek (https://github.com/lvonasek)
*/
public class XrActivity extends XServerDisplayActivity implements TextWatcher {
// Order of the enum has to be the as in xr/main.cpp
public enum ControllerAxis {
L_PITCH, L_YAW, L_ROLL, L_THUMBSTICK_X, L_THUMBSTICK_Y, L_X, L_Y, L_Z,
R_PITCH, R_YAW, R_ROLL, R_THUMBSTICK_X, R_THUMBSTICK_Y, R_X, R_Y, R_Z,
HMD_PITCH, HMD_YAW, HMD_ROLL, HMD_X, HMD_Y, HMD_Z, HMD_IPD
}
// Order of the enum has to be the as in xr/main.cpp
public enum ControllerButton {
L_GRIP, L_MENU, L_THUMBSTICK_PRESS, L_THUMBSTICK_LEFT, L_THUMBSTICK_RIGHT, L_THUMBSTICK_UP, L_THUMBSTICK_DOWN, L_TRIGGER, L_X, L_Y,
R_A, R_B, R_GRIP, R_THUMBSTICK_PRESS, R_THUMBSTICK_LEFT, R_THUMBSTICK_RIGHT, R_THUMBSTICK_UP, R_THUMBSTICK_DOWN, R_TRIGGER,
}
private static boolean isDeviceDetectionFinished = false;
private static boolean isDeviceSupported = false;
private static boolean isImmersive = false;
private static boolean isSBS = false;
private static final float[] lastAxes = new float[ControllerAxis.values().length];
private static final boolean[] lastButtons = new boolean[ControllerButton.values().length];
private static String lastText = "";
private static float mouseSpeed = 1;
private static final float[] smoothedMouse = new float[2];
private static XrActivity instance;
@Override
public synchronized void onPause() {
EditText text = findViewById(R.id.XRTextInput);
text.removeTextChangedListener(this);
super.onPause();
}
@Override
public synchronized void onResume() {
super.onResume();
instance = this;
mouseSpeed = PreferenceManager.getDefaultSharedPreferences(this).getFloat("cursor_speed", 1.0f);
EditText text = findViewById(R.id.XRTextInput);
text.setVisibility(View.VISIBLE);
text.getEditableText().clear();
text.addTextChangedListener(this);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public synchronized void afterTextChanged(Editable e) {
EditText text = findViewById(R.id.XRTextInput);
String s = text.getEditableText().toString();
if (s.length() > lastText.length()) {
lastText = s;
char c = s.charAt(s.length() - 1);
boolean uppercase = c >= 'A' && c <= 'Z';
if (uppercase) {
c = (char)((int)c - (int)'A' + (int)'a');
}
switch (c) {
case '0': setKeyPressed(XKeycode.KEY_0, uppercase); break;
case '1': setKeyPressed(XKeycode.KEY_1, uppercase); break;
case '2': setKeyPressed(XKeycode.KEY_2, uppercase); break;
case '3': setKeyPressed(XKeycode.KEY_3, uppercase); break;
case '4': setKeyPressed(XKeycode.KEY_4, uppercase); break;
case '5': setKeyPressed(XKeycode.KEY_5, uppercase); break;
case '6': setKeyPressed(XKeycode.KEY_6, uppercase); break;
case '7': setKeyPressed(XKeycode.KEY_7, uppercase); break;
case '8': setKeyPressed(XKeycode.KEY_8, uppercase); break;
case '9': setKeyPressed(XKeycode.KEY_9, uppercase); break;
case 'a': setKeyPressed(XKeycode.KEY_A, uppercase); break;
case 'b': setKeyPressed(XKeycode.KEY_B, uppercase); break;
case 'c': setKeyPressed(XKeycode.KEY_C, uppercase); break;
case 'd': setKeyPressed(XKeycode.KEY_D, uppercase); break;
case 'e': setKeyPressed(XKeycode.KEY_E, uppercase); break;
case 'f': setKeyPressed(XKeycode.KEY_F, uppercase); break;
case 'g': setKeyPressed(XKeycode.KEY_G, uppercase); break;
case 'h': setKeyPressed(XKeycode.KEY_H, uppercase); break;
case 'i': setKeyPressed(XKeycode.KEY_I, uppercase); break;
case 'j': setKeyPressed(XKeycode.KEY_J, uppercase); break;
case 'k': setKeyPressed(XKeycode.KEY_K, uppercase); break;
case 'l': setKeyPressed(XKeycode.KEY_L, uppercase); break;
case 'm': setKeyPressed(XKeycode.KEY_M, uppercase); break;
case 'n': setKeyPressed(XKeycode.KEY_N, uppercase); break;
case 'o': setKeyPressed(XKeycode.KEY_O, uppercase); break;
case 'p': setKeyPressed(XKeycode.KEY_P, uppercase); break;
case 'q': setKeyPressed(XKeycode.KEY_Q, uppercase); break;
case 'r': setKeyPressed(XKeycode.KEY_R, uppercase); break;
case 's': setKeyPressed(XKeycode.KEY_S, uppercase); break;
case 't': setKeyPressed(XKeycode.KEY_T, uppercase); break;
case 'u': setKeyPressed(XKeycode.KEY_U, uppercase); break;
case 'v': setKeyPressed(XKeycode.KEY_V, uppercase); break;
case 'w': setKeyPressed(XKeycode.KEY_W, uppercase); break;
case 'x': setKeyPressed(XKeycode.KEY_X, uppercase); break;
case 'y': setKeyPressed(XKeycode.KEY_Y, uppercase); break;
case 'z': setKeyPressed(XKeycode.KEY_Z, uppercase); break;
case ' ': setKeyPressed(XKeycode.KEY_SPACE, uppercase); break;
case '.': setKeyPressed(XKeycode.KEY_PERIOD, uppercase); break;
case ',': setKeyPressed(XKeycode.KEY_COMMA, uppercase); break;
}
}
else {
lastText = s;
setKeyPressed(XKeycode.KEY_BKSP, false);
}
resetText();
}
private synchronized void resetText() {
EditText text = findViewById(R.id.XRTextInput);
text.removeTextChangedListener(this);
text.getEditableText().clear();
text.getEditableText().append(" ");
text.addTextChangedListener(this);
}
public static XrActivity getInstance() {
return instance;
}
public static boolean getImmersive() {
return isImmersive;
}
public static boolean getSBS() {
return isSBS;
}
public static boolean isSupported() {
if (!isDeviceDetectionFinished) {
if (Build.MANUFACTURER.compareToIgnoreCase("META") == 0) {
isDeviceSupported = true;
}
if (Build.MANUFACTURER.compareToIgnoreCase("OCULUS") == 0) {
isDeviceSupported = true;
}
isDeviceDetectionFinished = true;
}
return isDeviceSupported;
}
public static void openIntent(Activity context, int containerId, String path) {
// 0. Create the launch intent
Intent intent = new Intent(context, XrActivity.class);
intent.putExtra("container_id", containerId);
if (path != null) {
intent.putExtra("shortcut_path", path);
}
// 1. Locate the main display ID and add that to the intent
final int mainDisplayId = getMainDisplay(context);
ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(mainDisplayId);
// 2. Set the flags: start in a new task and replace any existing tasks in the app stack
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// 3. Launch the activity.
// Don't use the container's ContextWrapper, which is adding arguments
context.getBaseContext().startActivity(intent, options.toBundle());
// 4. Finish the previous activity: this avoids an audio bug
context.finish();
}
public static void updateControllers() {
// Get OpenXR data
float[] axes = instance.getAxes();
boolean[] buttons = instance.getButtons();
try (XLock lock = instance.getXServer().lock(XServer.Lockable.WINDOW_MANAGER, XServer.Lockable.INPUT_DEVICE)) {
// Mouse control with hand
float f = 0.75f;
float meter2px = instance.getXServer().screenInfo.width * 10.0f;
float dx = (axes[ControllerAxis.R_X.ordinal()] - lastAxes[ControllerAxis.R_X.ordinal()]) * meter2px;
float dy = (axes[ControllerAxis.R_Y.ordinal()] - lastAxes[ControllerAxis.R_Y.ordinal()]) * meter2px;
// Mouse control with head
Pointer mouse = instance.getXServer().pointer;
if (isImmersive) {
float angle2px = instance.getXServer().screenInfo.width * 0.05f / f;
dx = getAngleDiff(lastAxes[ControllerAxis.HMD_YAW.ordinal()], axes[ControllerAxis.HMD_YAW.ordinal()]) * angle2px;
dy = getAngleDiff(lastAxes[ControllerAxis.HMD_PITCH.ordinal()], axes[ControllerAxis.HMD_PITCH.ordinal()]) * angle2px;
if (Float.isNaN(dy)) {
dy = 0;
}
smoothedMouse[0] = mouse.getClampedX() + 0.5f;
smoothedMouse[1] = mouse.getClampedY() + 0.5f;
}
// Mouse smoothing
dx *= mouseSpeed;
dy *= mouseSpeed;
smoothedMouse[0] = smoothedMouse[0] * f + (mouse.getClampedX() + 0.5f + dx) * (1 - f);
smoothedMouse[1] = smoothedMouse[1] * f + (mouse.getClampedY() + 0.5f - dy) * (1 - f);
// Mouse "snap turn"
int snapturn = isImmersive ? 125 : 25;
if (getButtonClicked(buttons, ControllerButton.R_THUMBSTICK_LEFT)) {
smoothedMouse[0] = mouse.getClampedX() - snapturn;
}
if (getButtonClicked(buttons, ControllerButton.R_THUMBSTICK_RIGHT)) {
smoothedMouse[0] = mouse.getClampedX() + snapturn;
}
// Set mouse status
mouse.moveTo((int) smoothedMouse[0], (int) smoothedMouse[1]);
mouse.setButton(Pointer.Button.BUTTON_LEFT, buttons[ControllerButton.R_TRIGGER.ordinal()]);
mouse.setButton(Pointer.Button.BUTTON_RIGHT, buttons[ControllerButton.R_GRIP.ordinal()]);
mouse.setButton(Pointer.Button.BUTTON_SCROLL_UP, buttons[ControllerButton.R_THUMBSTICK_UP.ordinal()]);
mouse.setButton(Pointer.Button.BUTTON_SCROLL_DOWN, buttons[ControllerButton.R_THUMBSTICK_DOWN.ordinal()]);
// Switch immersive/SBS mode
if (getButtonClicked(buttons, ControllerButton.L_THUMBSTICK_PRESS)) {
if (buttons[ControllerButton.R_GRIP.ordinal()]) {
isSBS = !isSBS;
}
else {
isImmersive = !isImmersive;
}
}
// Show system keyboard
if (getButtonClicked(buttons, ControllerButton.R_THUMBSTICK_PRESS)) {
instance.runOnUiThread(() -> {
isSBS = false;
isImmersive = false;
instance.resetText();
AppUtils.showKeyboard(instance);
instance.findViewById(R.id.XRTextInput).requestFocus();
});
}
// Store the OpenXR data
System.arraycopy(axes, 0, lastAxes, 0, axes.length);
System.arraycopy(buttons, 0, lastButtons, 0, buttons.length);
// Update keyboard
mapKey(ControllerButton.R_A, XKeycode.KEY_A.id);
mapKey(ControllerButton.R_B, XKeycode.KEY_B.id);
mapKey(ControllerButton.L_X, XKeycode.KEY_X.id);
mapKey(ControllerButton.L_Y, XKeycode.KEY_Y.id);
mapKey(ControllerButton.L_GRIP, XKeycode.KEY_SPACE.id);
mapKey(ControllerButton.L_MENU, XKeycode.KEY_ESC.id);
mapKey(ControllerButton.L_TRIGGER, XKeycode.KEY_ENTER.id);
mapKey(ControllerButton.L_THUMBSTICK_LEFT, XKeycode.KEY_LEFT.id);
mapKey(ControllerButton.L_THUMBSTICK_RIGHT, XKeycode.KEY_RIGHT.id);
mapKey(ControllerButton.L_THUMBSTICK_UP, XKeycode.KEY_UP.id);
mapKey(ControllerButton.L_THUMBSTICK_DOWN, XKeycode.KEY_DOWN.id);
}
}
private static float getAngleDiff(float oldAngle, float newAngle) {
float diff = oldAngle - newAngle;
while (diff > 180) {
diff -= 360;
}
while (diff < -180) {
diff += 360;
}
return diff;
}
private static boolean getButtonClicked(boolean[] buttons, ControllerButton button) {
return buttons[button.ordinal()] && !lastButtons[button.ordinal()];
}
private static int getMainDisplay(Context context) {
final DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
for (Display display : displayManager.getDisplays()) {
if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
return display.getDisplayId();
}
}
return -1;
}
private static void mapKey(ControllerButton xrButton, byte xKeycode) {
Keyboard keyboard = instance.getXServer().keyboard;
if (lastButtons[xrButton.ordinal()]) {
keyboard.setKeyPress(xKeycode, 0);
} else {
keyboard.setKeyRelease(xKeycode);
}
}
private static void setKeyPressed(XKeycode xKeycode, boolean uppercase) {
XServer server = instance.getXServer();
if (uppercase) {
server.injectKeyPress(XKeycode.KEY_SHIFT_L);
}
server.injectKeyPress(xKeycode);
// Give the system bit of time to notice the key press
try {
Thread.sleep(50);
}
catch (Exception e) {
e.printStackTrace();
}
server.injectKeyRelease(xKeycode);
if (uppercase) {
server.injectKeyRelease(XKeycode.KEY_SHIFT_L);
}
}
// Rendering
public native void init();
public native void bindFramebuffer();
public native int getWidth();
public native int getHeight();
public native boolean beginFrame(boolean immersive, boolean sbs);
public native void endFrame();
// Input
public native float[] getAxes();
public native boolean[] getButtons();
}
@@ -7,6 +7,7 @@ import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import com.winlator.R;
import com.winlator.XrActivity;
import com.winlator.math.Mathf;
import com.winlator.math.XForm;
import com.winlator.renderer.material.CursorMaterial;
@@ -83,6 +84,13 @@ public class GLRenderer implements GLSurfaceView.Renderer, WindowManager.OnWindo
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
if (XrActivity.isSupported()) {
XrActivity activity = XrActivity.getInstance();
activity.init();
width = activity.getWidth();
height = activity.getHeight();
}
surfaceWidth = width;
surfaceHeight = height;
viewport.update(width, height, xServer.screenInfo.width, xServer.screenInfo.height);
@@ -100,6 +108,11 @@ public class GLRenderer implements GLSurfaceView.Renderer, WindowManager.OnWindo
}
private void drawFrame() {
boolean xrFrame = false;
if (XrActivity.isSupported()) {
xrFrame = XrActivity.getInstance().beginFrame(XrActivity.getImmersive(), XrActivity.getSBS());
}
if (viewportNeedsUpdate) {
if (fullscreen) {
GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight);
@@ -128,6 +141,12 @@ public class GLRenderer implements GLSurfaceView.Renderer, WindowManager.OnWindo
renderWindows();
if (cursorVisible) renderCursor();
if (xrFrame) {
XrActivity.getInstance().endFrame();
XrActivity.updateControllers();
xServerView.requestRender();
}
}
@Override
@@ -3,6 +3,7 @@ package com.winlator.renderer;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import com.winlator.XrActivity;
import com.winlator.xserver.Drawable;
import java.nio.ByteBuffer;
@@ -35,6 +36,7 @@ public class Texture {
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, minFilter);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
if (XrActivity.isSupported()) XrActivity.getInstance().bindFramebuffer();
}
public int getWrapS() {
@@ -2,6 +2,7 @@
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/DrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -20,4 +21,14 @@
app:headerLayout="@layout/main_menu_header"
app:itemIconTint="@color/colorAccent"
app:menu="@menu/xserver_menu"/>
<EditText
android:id="@+id/XRTextInput"
android:layout_width="0dp"
android:layout_height="0dp"
android:focusableInTouchMode="true"
android:inputType="text"
android:autofillHints="no"
tools:ignore="LabelFor"
android:visibility="gone" />
</androidx.drawerlayout.widget.DrawerLayout>