mirror of
https://github.com/brunodev85/winlator.git
synced 2026-10-20 22:11:47 +03:00
Add WinlatorXR by lvonasek (https://github.com/lvonasek)
This commit is contained in:
1 parent
8f79ebcd31
commit
3af161cdef
21 files changed
+2584
-8
No files matched your search
@@ -0,0 +1,3 @@
|
||||
[submodule "src/main/cpp/OpenXR-SDK"]
|
||||
path = src/main/cpp/OpenXR-SDK
|
||||
url = https://github.com/KhronosGroup/OpenXR-SDK.git
|
||||
@@ -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"
|
||||
|
||||
@@ -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
+1
Submodule app/src/main/cpp/OpenXR-SDK added at 288d3a7ebc.
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user