From 67cf08e8aed58f9268b00289e26761b137b508e5 Mon Sep 17 00:00:00 2001
From: Brad Davis
Date: Wed, 30 Jan 2019 10:24:30 -0800
Subject: [PATCH] Quest frame player
---
android/apps/questFramePlayer/CMakeLists.txt | 9 +
android/apps/questFramePlayer/build.gradle | 51 ++
.../apps/questFramePlayer/proguard-rules.pro | 25 +
.../src/main/AndroidManifest.xml | 55 ++
.../src/main/cpp/PlayerWindow.cpp | 25 +
.../src/main/cpp/PlayerWindow.h | 29 +
.../src/main/cpp/RenderThread.cpp | 240 ++++++
.../src/main/cpp/RenderThread.h | 44 ++
.../questFramePlayer/src/main/cpp/main.cpp | 56 ++
.../frameplayer/QuestQtActivity.java | 53 ++
.../frameplayer/QuestRenderActivity.java | 14 +
.../src/main/res/drawable/ic_launcher.xml | 17 +
.../src/main/res/values/strings.xml | 3 +
android/libraries/oculus/build.gradle | 17 +
.../oculus/src/main/AndroidManifest.xml | 2 +
.../oculus/OculusMobileActivity.java | 103 +++
android/settings.gradle | 22 +-
hifi_android.py | 7 +
libraries/oculusMobile/CMakeLists.txt | 11 +
libraries/oculusMobile/src/ovr/Forward.h | 17 +
.../oculusMobile/src/ovr/Framebuffer.cpp | 93 +++
libraries/oculusMobile/src/ovr/Framebuffer.h | 34 +
libraries/oculusMobile/src/ovr/GLContext.cpp | 182 +++++
libraries/oculusMobile/src/ovr/GLContext.h | 37 +
libraries/oculusMobile/src/ovr/Helpers.cpp | 38 +
libraries/oculusMobile/src/ovr/Helpers.h | 94 +++
libraries/oculusMobile/src/ovr/TaskQueue.cpp | 40 +
libraries/oculusMobile/src/ovr/TaskQueue.h | 42 ++
libraries/oculusMobile/src/ovr/VrHandler.cpp | 337 +++++++++
libraries/oculusMobile/src/ovr/VrHandler.h | 47 ++
libraries/oculusMobilePlugin/CMakeLists.txt | 29 +
libraries/oculusMobilePlugin/src/Logging.cpp | 4 +
libraries/oculusMobilePlugin/src/Logging.h | 13 +
.../src/OculusMobileControllerManager.cpp | 694 ++++++++++++++++++
.../src/OculusMobileControllerManager.h | 43 ++
.../src/OculusMobileDisplayPlugin.cpp | 269 +++++++
.../src/OculusMobileDisplayPlugin.h | 65 ++
37 files changed, 2859 insertions(+), 2 deletions(-)
create mode 100644 android/apps/questFramePlayer/CMakeLists.txt
create mode 100644 android/apps/questFramePlayer/build.gradle
create mode 100644 android/apps/questFramePlayer/proguard-rules.pro
create mode 100644 android/apps/questFramePlayer/src/main/AndroidManifest.xml
create mode 100644 android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp
create mode 100644 android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h
create mode 100644 android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
create mode 100644 android/apps/questFramePlayer/src/main/cpp/RenderThread.h
create mode 100644 android/apps/questFramePlayer/src/main/cpp/main.cpp
create mode 100644 android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java
create mode 100644 android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java
create mode 100644 android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml
create mode 100644 android/apps/questFramePlayer/src/main/res/values/strings.xml
create mode 100644 android/libraries/oculus/build.gradle
create mode 100644 android/libraries/oculus/src/main/AndroidManifest.xml
create mode 100644 android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
create mode 100644 libraries/oculusMobile/CMakeLists.txt
create mode 100644 libraries/oculusMobile/src/ovr/Forward.h
create mode 100644 libraries/oculusMobile/src/ovr/Framebuffer.cpp
create mode 100644 libraries/oculusMobile/src/ovr/Framebuffer.h
create mode 100644 libraries/oculusMobile/src/ovr/GLContext.cpp
create mode 100644 libraries/oculusMobile/src/ovr/GLContext.h
create mode 100644 libraries/oculusMobile/src/ovr/Helpers.cpp
create mode 100644 libraries/oculusMobile/src/ovr/Helpers.h
create mode 100644 libraries/oculusMobile/src/ovr/TaskQueue.cpp
create mode 100644 libraries/oculusMobile/src/ovr/TaskQueue.h
create mode 100644 libraries/oculusMobile/src/ovr/VrHandler.cpp
create mode 100644 libraries/oculusMobile/src/ovr/VrHandler.h
create mode 100644 libraries/oculusMobilePlugin/CMakeLists.txt
create mode 100644 libraries/oculusMobilePlugin/src/Logging.cpp
create mode 100644 libraries/oculusMobilePlugin/src/Logging.h
create mode 100644 libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp
create mode 100644 libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h
create mode 100644 libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp
create mode 100644 libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h
diff --git a/android/apps/questFramePlayer/CMakeLists.txt b/android/apps/questFramePlayer/CMakeLists.txt
new file mode 100644
index 0000000000..5889585a6c
--- /dev/null
+++ b/android/apps/questFramePlayer/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(TARGET_NAME questFramePlayer)
+setup_hifi_library(AndroidExtras)
+link_hifi_libraries(shared ktx shaders gpu gl oculusMobile ${PLATFORM_GL_BACKEND})
+target_include_directories(${TARGET_NAME} PRIVATE ${HIFI_ANDROID_PRECOMPILED}/ovr/VrApi/Include)
+target_link_libraries(${TARGET_NAME} android log m)
+target_opengl()
+target_oculus_mobile()
+
+
diff --git a/android/apps/questFramePlayer/build.gradle b/android/apps/questFramePlayer/build.gradle
new file mode 100644
index 0000000000..13d806c3a4
--- /dev/null
+++ b/android/apps/questFramePlayer/build.gradle
@@ -0,0 +1,51 @@
+apply plugin: 'com.android.application'
+
+android {
+ signingConfigs {
+ release {
+ keyAlias 'key0'
+ keyPassword 'password'
+ storeFile file('C:/android/keystore.jks')
+ storePassword 'password'
+ }
+ }
+
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "io.highfidelity.frameplayer"
+ minSdkVersion 25
+ targetSdkVersion 28
+ ndk { abiFilters 'arm64-v8a' }
+ externalNativeBuild {
+ cmake {
+ arguments '-DHIFI_ANDROID=1',
+ '-DHIFI_ANDROID_APP=questFramePlayer',
+ '-DANDROID_TOOLCHAIN=clang',
+ '-DANDROID_STL=c++_shared',
+
+ '-DCMAKE_VERBOSE_MAKEFILE=ON'
+ targets = ['questFramePlayer']
+ }
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ }
+
+ externalNativeBuild.cmake.path '../../../CMakeLists.txt'
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
+ implementation project(':oculus')
+ implementation project(':qt')
+}
diff --git a/android/apps/questFramePlayer/proguard-rules.pro b/android/apps/questFramePlayer/proguard-rules.pro
new file mode 100644
index 0000000000..b3c0078513
--- /dev/null
+++ b/android/apps/questFramePlayer/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Android\SDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/apps/questFramePlayer/src/main/AndroidManifest.xml b/android/apps/questFramePlayer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..721e8cee89
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/AndroidManifest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp
new file mode 100644
index 0000000000..ec2986298e
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp
@@ -0,0 +1,25 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "PlayerWindow.h"
+
+#include
+
+PlayerWindow::PlayerWindow() {
+ installEventFilter(this);
+ setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
+ setSurfaceType(QSurface::OpenGLSurface);
+ create();
+ showFullScreen();
+ // Ensure the window is visible and the GL context is valid
+ QCoreApplication::processEvents();
+ _renderThread.initialize(this);
+}
+
+PlayerWindow::~PlayerWindow() {
+}
diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h
new file mode 100644
index 0000000000..e4dd6cef43
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h
@@ -0,0 +1,29 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#pragma once
+#include
+#include
+
+#include
+#include "RenderThread.h"
+
+// Create a simple OpenGL window that renders text in various ways
+class PlayerWindow : public QWindow {
+public:
+ PlayerWindow();
+ virtual ~PlayerWindow();
+
+protected:
+ //bool eventFilter(QObject* obj, QEvent* event) override;
+ //void keyPressEvent(QKeyEvent* event) override;
+
+private:
+ QSettings _settings;
+ RenderThread _renderThread;
+};
diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
new file mode 100644
index 0000000000..5eabe6b9b1
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
@@ -0,0 +1,240 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "RenderThread.h"
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+static JNIEnv* _env { nullptr };
+static JavaVM* _vm { nullptr };
+static jobject _activity { nullptr };
+
+struct HandController{
+ ovrInputTrackedRemoteCapabilities caps {};
+ ovrInputStateTrackedRemote state {};
+ ovrResult stateResult{ ovrSuccess };
+ ovrTracking tracking {};
+ ovrResult trackingResult{ ovrSuccess };
+
+ void update(ovrMobile* session, double time = 0.0) {
+ const auto& deviceId = caps.Header.DeviceID;
+ stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header);
+ trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking);
+ }
+};
+
+std::vector devices;
+
+extern "C" {
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", __FUNCTION__);
+ return JNI_VERSION_1_6;
+}
+
+
+JNIEXPORT void JNICALL Java_io_highfidelity_frameplayer_QuestQtActivity_nativeOnCreate(JNIEnv* env, jobject obj) {
+ env->GetJavaVM(&_vm);
+ _activity = env->NewGlobalRef(obj);
+}
+}
+
+static const char* FRAME_FILE = "assets:/frames/20190121_1220.json";
+
+static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
+ QImage image;
+ QImageReader(filename.c_str()).read(&image);
+ if (layer > 0) {
+ return;
+ }
+ texture->assignStoredMip(0, image.byteCount(), image.constBits());
+}
+
+void RenderThread::submitFrame(const gpu::FramePointer& frame) {
+ std::unique_lock lock(_frameLock);
+ _pendingFrames.push(frame);
+}
+
+void RenderThread::move(const glm::vec3& v) {
+ std::unique_lock lock(_frameLock);
+ _correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
+}
+
+void RenderThread::initialize(QWindow* window) {
+ std::unique_lock lock(_frameLock);
+ setObjectName("RenderThread");
+ Parent::initialize();
+ _window = window;
+ _thread->setObjectName("RenderThread");
+}
+
+void RenderThread::setup() {
+ // Wait until the context has been moved to this thread
+ { std::unique_lock lock(_frameLock); }
+
+
+ ovr::VrHandler::initVr();
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity");
+ _vm->AttachCurrentThread(&_env, nullptr);
+ jclass cls = _env->GetObjectClass(_activity);
+ jmethodID mid = _env->GetMethodID(cls, "launchOculusActivity", "()V");
+ _env->CallVoidMethod(_activity, mid);
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity done");
+ ovr::VrHandler::setHandler(this);
+
+ makeCurrent();
+
+ // GPU library init
+ gpu::Context::init();
+ _gpuContext = std::make_shared();
+ _backend = _gpuContext->getBackend();
+ _gpuContext->beginFrame();
+ _gpuContext->endFrame();
+
+ makeCurrent();
+ glGenTextures(1, &_externalTexture);
+ glBindTexture(GL_TEXTURE_2D, _externalTexture);
+ static const glm::u8vec4 color{ 0,1,0,0 };
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color);
+
+ if (QFileInfo(FRAME_FILE).exists()) {
+ auto frame = gpu::readFrame(FRAME_FILE, _externalTexture, &textureLoader);
+ submitFrame(frame);
+ }
+}
+
+void RenderThread::shutdown() {
+ _activeFrame.reset();
+ while (!_pendingFrames.empty()) {
+ _gpuContext->consumeFrameUpdates(_pendingFrames.front());
+ _pendingFrames.pop();
+ }
+ _gpuContext->shutdown();
+ _gpuContext.reset();
+}
+
+void RenderThread::handleInput() {
+ static std::once_flag once;
+ std::call_once(once, [&]{
+ withOvrMobile([&](ovrMobile* session){
+ int deviceIndex = 0;
+ ovrInputCapabilityHeader capsHeader;
+ while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) {
+ if (capsHeader.Type == ovrControllerType_TrackedRemote) {
+ HandController controller = {};
+ controller.caps.Header = capsHeader;
+ controller.state.Header.ControllerType = ovrControllerType_TrackedRemote;
+ vrapi_GetInputDeviceCapabilities( session, &controller.caps.Header);
+ devices.push_back(controller);
+ }
+ ++deviceIndex;
+ }
+ });
+ });
+
+ auto readResult = ovr::VrHandler::withOvrMobile([&](ovrMobile *session) {
+ for (auto &controller : devices) {
+ controller.update(session);
+ }
+ });
+
+ if (readResult) {
+ for (auto &controller : devices) {
+ const auto &caps = controller.caps;
+ if (controller.stateResult >= 0) {
+ const auto &remote = controller.state;
+ if (remote.Joystick.x != 0.0f || remote.Joystick.y != 0.0f) {
+ glm::vec3 translation;
+ float rotation = 0.0f;
+ if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand) {
+ translation = glm::vec3{0.0f, -remote.Joystick.y, 0.0f};
+ } else {
+ translation = glm::vec3{remote.Joystick.x, 0.0f, -remote.Joystick.y};
+ }
+ float scale = 0.1f + (1.9f * remote.GripTrigger);
+ _correction = glm::translate(glm::mat4(), translation * scale) * _correction;
+ }
+ }
+ }
+ }
+}
+
+void RenderThread::renderFrame() {
+ GLuint finalTexture = 0;
+ glm::uvec2 finalTextureSize;
+ const auto& tracking = beginFrame();
+ if (_activeFrame) {
+ const auto& frame = _activeFrame;
+ auto& eyeProjections = frame->stereoState._eyeProjections;
+ auto& eyeOffsets = frame->stereoState._eyeViews;
+ // Quest
+ auto frameCorrection = _correction * ovr::toGlm(tracking.HeadPose.Pose);
+ _backend->setCameraCorrection(glm::inverse(frameCorrection), frame->view);
+ ovr::for_each_eye([&](ovrEye eye){
+ const auto& eyeInfo = tracking.Eye[eye];
+ eyeProjections[eye] = ovr::toGlm(eyeInfo.ProjectionMatrix);
+ eyeOffsets[eye] = ovr::toGlm(eyeInfo.ViewMatrix);
+ });
+ _backend->recycle();
+ _backend->syncCache();
+ _gpuContext->enableStereo(true);
+ if (frame && !frame->batches.empty()) {
+ _gpuContext->executeFrame(frame);
+ }
+ auto& glbackend = (gpu::gl::GLBackend&)(*_backend);
+ finalTextureSize = { frame->framebuffer->getWidth(), frame->framebuffer->getHeight() };
+ finalTexture = glbackend.getTextureID(frame->framebuffer->getRenderBuffer(0));
+ }
+ presentFrame(finalTexture, finalTextureSize, tracking);
+}
+
+bool RenderThread::process() {
+ pollTask();
+
+ if (!vrActive()) {
+ QThread::msleep(1);
+ return true;
+ }
+
+ std::queue pendingFrames;
+ {
+ std::unique_lock lock(_frameLock);
+ pendingFrames.swap(_pendingFrames);
+ }
+
+ makeCurrent();
+ while (!pendingFrames.empty()) {
+ _activeFrame = pendingFrames.front();
+ pendingFrames.pop();
+ _gpuContext->consumeFrameUpdates(_activeFrame);
+ _activeFrame->stereoState._enable = true;
+ }
+
+ handleInput();
+ renderFrame();
+ return true;
+}
diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.h b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h
new file mode 100644
index 0000000000..701cd25f5b
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h
@@ -0,0 +1,44 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class RenderThread : public GenericThread, ovr::VrHandler {
+ using Parent = GenericThread;
+public:
+ QWindow* _window{ nullptr };
+ std::mutex _mutex;
+ gpu::ContextPointer _gpuContext; // initialized during window creation
+ std::shared_ptr _backend;
+ std::atomic _presentCount{ 0 };
+ std::mutex _frameLock;
+ std::queue _pendingFrames;
+ gpu::FramePointer _activeFrame;
+ uint32_t _externalTexture{ 0 };
+ glm::mat4 _correction;
+
+ void move(const glm::vec3& v);
+ void setup() override;
+ bool process() override;
+ void shutdown() override;
+
+ void handleInput();
+
+ void submitFrame(const gpu::FramePointer& frame);
+ void initialize(QWindow* window);
+ void renderFrame();
+};
diff --git a/android/apps/questFramePlayer/src/main/cpp/main.cpp b/android/apps/questFramePlayer/src/main/cpp/main.cpp
new file mode 100644
index 0000000000..4730d3fa15
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/main.cpp
@@ -0,0 +1,56 @@
+//
+// Created by Bradley Austin Davis on 2018/11/22
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "PlayerWindow.h"
+
+void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
+ if (!message.isEmpty()) {
+ const char * local=message.toStdString().c_str();
+ switch (type) {
+ case QtDebugMsg:
+ __android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
+ break;
+ case QtInfoMsg:
+ __android_log_write(ANDROID_LOG_INFO,"Interface",local);
+ break;
+ case QtWarningMsg:
+ __android_log_write(ANDROID_LOG_WARN,"Interface",local);
+ break;
+ case QtCriticalMsg:
+ __android_log_write(ANDROID_LOG_ERROR,"Interface",local);
+ break;
+ case QtFatalMsg:
+ default:
+ __android_log_write(ANDROID_LOG_FATAL,"Interface",local);
+ abort();
+ }
+ }
+}
+
+int main(int argc, char** argv) {
+ setupHifiApplication("gpuFramePlayer");
+ QGuiApplication app(argc, argv);
+ auto oldMessageHandler = qInstallMessageHandler(messageHandler);
+ DependencyManager::set();
+ PlayerWindow window;
+ __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec");
+ app.exec();
+ __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec done");
+ qInstallMessageHandler(oldMessageHandler);
+ return 0;
+}
+
+
diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java
new file mode 100644
index 0000000000..d498e27547
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java
@@ -0,0 +1,53 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+package io.highfidelity.frameplayer;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.qtproject.qt5.android.bindings.QtActivity;
+
+import io.highfidelity.oculus.OculusMobileActivity;
+
+
+public class QuestQtActivity extends QtActivity {
+ private native void nativeOnCreate();
+ private boolean launchedQuestMode = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.w("QQQ_Qt", "QuestQtActivity::onCreate");
+ super.onCreate(savedInstanceState);
+ nativeOnCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.w("QQQ_Qt", "QuestQtActivity::onDestroy");
+ super.onDestroy();
+ }
+
+ public void launchOculusActivity() {
+ Log.w("QQQ_Qt", "QuestQtActivity::launchOculusActivity");
+ runOnUiThread(()->{
+ keepInterfaceRunning = true;
+ launchedQuestMode = true;
+ moveTaskToBack(true);
+ startActivity(new Intent(this, QuestRenderActivity.class));
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (launchedQuestMode) {
+ moveTaskToBack(true);
+ }
+ }
+}
diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java
new file mode 100644
index 0000000000..a395a32b68
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java
@@ -0,0 +1,14 @@
+package io.highfidelity.frameplayer;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import io.highfidelity.oculus.OculusMobileActivity;
+
+public class QuestRenderActivity extends OculusMobileActivity {
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ startActivity(new Intent(this, QuestQtActivity.class));
+ }
+}
diff --git a/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml b/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000000..03b1edc4e9
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/apps/questFramePlayer/src/main/res/values/strings.xml b/android/apps/questFramePlayer/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..8bf550f74e
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ GPU Frame Player
+
diff --git a/android/libraries/oculus/build.gradle b/android/libraries/oculus/build.gradle
new file mode 100644
index 0000000000..b072f99eb7
--- /dev/null
+++ b/android/libraries/oculus/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 24
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
diff --git a/android/libraries/oculus/src/main/AndroidManifest.xml b/android/libraries/oculus/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..57df1a4226
--- /dev/null
+++ b/android/libraries/oculus/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
new file mode 100644
index 0000000000..01d74ea94d
--- /dev/null
+++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
@@ -0,0 +1,103 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+package io.highfidelity.oculus;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+/**
+ * Contains a native surface and forwards the activity lifecycle and surface lifecycle
+ * events to the OculusMobileDisplayPlugin
+ */
+public class OculusMobileActivity extends Activity implements SurfaceHolder.Callback {
+ private static final String TAG = OculusMobileActivity.class.getSimpleName();
+ static { System.loadLibrary("oculusMobile"); }
+ private native void nativeOnCreate();
+ private native static void nativeOnResume();
+ private native static void nativeOnPause();
+ private native static void nativeOnDestroy();
+ private native static void nativeOnSurfaceChanged(Surface s);
+
+ private SurfaceView mView;
+ private SurfaceHolder mSurfaceHolder;
+
+
+ public static void launch(Activity activity) {
+ if (activity != null) {
+ activity.runOnUiThread(()->{
+ activity.startActivity(new Intent(activity, OculusMobileActivity.class));
+ });
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.w(TAG, "QQQ onCreate");
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ // Create a native surface for VR rendering (Qt GL surfaces are not suitable
+ // because of the lack of fine control over the surface callbacks)
+ mView = new SurfaceView(this);
+ setContentView(mView);
+ mView.getHolder().addCallback(this);
+
+ // Forward the create message to the JNI code
+ nativeOnCreate();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.w(TAG, "QQQ onDestroy");
+ if (mSurfaceHolder != null) {
+ nativeOnSurfaceChanged(null);
+ }
+ nativeOnDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.w(TAG, "QQQ onResume");
+ super.onResume();
+ nativeOnResume();
+ }
+
+ @Override
+ protected void onPause() {
+ Log.w(TAG, "QQQ onPause");
+ nativeOnPause();
+ super.onPause();
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.w(TAG, "QQQ surfaceCreated");
+ nativeOnSurfaceChanged(holder.getSurface());
+ mSurfaceHolder = holder;
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.w(TAG, "QQQ surfaceChanged");
+ nativeOnSurfaceChanged(holder.getSurface());
+ mSurfaceHolder = holder;
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.w(TAG, "QQQ surfaceDestroyed");
+ nativeOnSurfaceChanged(null);
+ mSurfaceHolder = null;
+ }
+}
\ No newline at end of file
diff --git a/android/settings.gradle b/android/settings.gradle
index 1e7b3c768a..699f617cce 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,8 +1,26 @@
+//
+// Libraries
+//
+
+include ':oculus'
+project(':oculus').projectDir = new File(settingsDir, 'libraries/oculus')
+
include ':qt'
project(':qt').projectDir = new File(settingsDir, 'libraries/qt')
+//
+// Applications
+//
+
include ':interface'
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
-//include ':framePlayer'
-//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
+//
+// Test projects
+//
+
+include ':framePlayer'
+project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
+
+include ':questFramePlayer'
+project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer')
diff --git a/hifi_android.py b/hifi_android.py
index 13c9cdccf2..2e6a42d127 100644
--- a/hifi_android.py
+++ b/hifi_android.py
@@ -52,6 +52,13 @@ ANDROID_PACKAGES = {
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
'includeLibs': ['libvrapi.so']
},
+ 'oculusPlatform': {
+ 'file': 'OVRPlatformSDK_v1.32.0.zip',
+ 'versionId': 'jG9DB16zOGxSrmtZy4jcQnwO0TJUuaeL',
+ 'checksum': 'ab5b203b3a39a56ab148d68fff769e05',
+ 'sharedLibFolder': 'Android/libs/arm64-v8a',
+ 'includeLibs': ['libovrplatformloader.so']
+ },
'openssl': {
'file': 'openssl-1.1.0g_armv8.tgz',
'versionId': 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
diff --git a/libraries/oculusMobile/CMakeLists.txt b/libraries/oculusMobile/CMakeLists.txt
new file mode 100644
index 0000000000..213721722c
--- /dev/null
+++ b/libraries/oculusMobile/CMakeLists.txt
@@ -0,0 +1,11 @@
+if (ANDROID)
+ set(TARGET_NAME oculusMobile)
+ # don't use the setup_hifi_library macro, we don't want ANY qt dependencies
+ file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc")
+ add_library(${TARGET_NAME} SHARED ${LIB_SRCS})
+ target_glm()
+ target_egl()
+ target_glad()
+ target_oculus_mobile()
+ target_link_libraries(${TARGET_NAME} android log)
+endif()
diff --git a/libraries/oculusMobile/src/ovr/Forward.h b/libraries/oculusMobile/src/ovr/Forward.h
new file mode 100644
index 0000000000..5881dde9a7
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Forward.h
@@ -0,0 +1,17 @@
+//
+// Created by Bradley Austin Davis on 2018/11/23
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+#include
+#include
+
+namespace ovr {
+ using Mutex = std::mutex;
+ using Condition = std::condition_variable;
+ using Lock = std::unique_lock;
+ using Task = std::function;
+}
\ No newline at end of file
diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.cpp b/libraries/oculusMobile/src/ovr/Framebuffer.cpp
new file mode 100644
index 0000000000..4c4fd2a983
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Framebuffer.cpp
@@ -0,0 +1,93 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "Framebuffer.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace ovr;
+
+void Framebuffer::updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix ) const {
+ auto& layerTexture = layer.Textures[eye];
+ layerTexture.ColorSwapChain = _swapChain;
+ layerTexture.SwapChainIndex = _index;
+ if (projectionMatrix) {
+ layerTexture.TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection( projectionMatrix );
+ }
+ layerTexture.TextureRect = { 0, 0, 1, 1 };
+}
+
+void Framebuffer::create(const glm::uvec2& size) {
+ _size = size;
+ _index = 0;
+ _validTexture = false;
+
+ // Depth renderbuffer
+ glGenRenderbuffers(1, &_depth);
+ glBindRenderbuffer(GL_RENDERBUFFER, _depth);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, _size.x, _size.y);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+ // Framebuffer
+ glGenFramebuffers(1, &_fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
+ glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ _swapChain = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D, GL_RGBA8, _size.x, _size.y, 1, 3);
+ _length = vrapi_GetTextureSwapChainLength(_swapChain);
+ if (!_length) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Unable to count swap chain textures");
+ return;
+ }
+
+ for (int i = 0; i < _length; ++i) {
+ GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, i);
+ glBindTexture(GL_TEXTURE_2D, chainTexId);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+void Framebuffer::destroy() {
+ if (0 != _fbo) {
+ glDeleteFramebuffers(1, &_fbo);
+ _fbo = 0;
+ }
+ if (0 != _depth) {
+ glDeleteRenderbuffers(1, &_depth);
+ _depth = 0;
+ }
+ if (_swapChain != nullptr) {
+ vrapi_DestroyTextureSwapChain(_swapChain);
+ _swapChain = nullptr;
+ }
+ _index = -1;
+ _length = -1;
+}
+
+void Framebuffer::advance() {
+ _index = (_index + 1) % _length;
+ _validTexture = false;
+}
+
+void Framebuffer::bind() {
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
+ if (!_validTexture) {
+ GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, _index);
+ glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, chainTexId, 0);
+ _validTexture = true;
+ }
+}
diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.h b/libraries/oculusMobile/src/ovr/Framebuffer.h
new file mode 100644
index 0000000000..5127574462
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Framebuffer.h
@@ -0,0 +1,34 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace ovr {
+
+struct Framebuffer {
+public:
+ void updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix = nullptr) const;
+ void create(const glm::uvec2& size);
+ void advance();
+ void destroy();
+ void bind();
+
+ uint32_t _depth { 0 };
+ uint32_t _fbo{ 0 };
+ int _length{ -1 };
+ int _index{ -1 };
+ bool _validTexture{ false };
+ glm::uvec2 _size;
+ ovrTextureSwapChain* _swapChain{ nullptr };
+};
+
+} // namespace ovr
\ No newline at end of file
diff --git a/libraries/oculusMobile/src/ovr/GLContext.cpp b/libraries/oculusMobile/src/ovr/GLContext.cpp
new file mode 100644
index 0000000000..449ba67084
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/GLContext.cpp
@@ -0,0 +1,182 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "GLContext.h"
+
+#include
+#include
+#include
+
+#include
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#define EGL_OPENGL_ES3_BIT_KHR 0x0040
+#endif
+
+using namespace ovr;
+
+static void* getGlProcessAddress(const char *namez) {
+ auto result = eglGetProcAddress(namez);
+ return (void*)result;
+}
+
+
+void GLContext::initModule() {
+ static std::once_flag once;
+ std::call_once(once, [&]{
+ gladLoadGLES2Loader(getGlProcessAddress);
+ });
+}
+
+void APIENTRY debugMessageCallback(GLenum source,
+ GLenum type,
+ GLuint id,
+ GLenum severity,
+ GLsizei length,
+ const GLchar* message,
+ const void* userParam) {
+ if (type == GL_DEBUG_TYPE_PERFORMANCE_KHR) {
+ return;
+ }
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_HIGH:
+ case GL_DEBUG_SEVERITY_MEDIUM:
+ break;
+ default:
+ return;
+ }
+
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_GL", message);
+}
+
+GLContext::~GLContext() {
+ destroy();
+}
+
+EGLConfig GLContext::findConfig(EGLDisplay display) {
+ // Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample
+ // flags in eglChooseConfig if the user has selected the "force 4x MSAA" option in
+ // settings, and that is completely wasted for our warp target.
+ std::vector configs;
+ {
+ const int MAX_CONFIGS = 1024;
+ EGLConfig configsBuffer[MAX_CONFIGS];
+ EGLint numConfigs = 0;
+ if (eglGetConfigs(display, configsBuffer, MAX_CONFIGS, &numConfigs) == EGL_FALSE) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed to fetch configs");
+ return 0;
+ }
+ configs.resize(numConfigs);
+ memcpy(configs.data(), configsBuffer, sizeof(EGLConfig) * numConfigs);
+ }
+
+ std::vector> configAttribs{
+ { EGL_RED_SIZE, 8 }, { EGL_GREEN_SIZE, 8 }, { EGL_BLUE_SIZE, 8 }, { EGL_ALPHA_SIZE, 8 },
+ { EGL_DEPTH_SIZE, 0 }, { EGL_STENCIL_SIZE, 0 }, { EGL_SAMPLES, 0 },
+ };
+
+ auto matchAttrib = [&](EGLConfig config, const std::pair& attribAndValue) {
+ EGLint value = 0;
+ eglGetConfigAttrib(display, config, attribAndValue.first, &value);
+ return (attribAndValue.second == value);
+ };
+
+ auto matchAttribFlags = [&](EGLConfig config, const std::pair& attribAndValue) {
+ EGLint value = 0;
+ eglGetConfigAttrib(display, config, attribAndValue.first, &value);
+ return (value & attribAndValue.second) == attribAndValue.second;
+ };
+
+ auto matchConfig = [&](EGLConfig config) {
+ if (!matchAttribFlags(config, { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR})) {
+ return false;
+ }
+ // The pbuffer config also needs to be compatible with normal window rendering
+ // so it can share textures with the window context.
+ if (!matchAttribFlags(config, { EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT})) {
+ return false;
+ }
+
+ for (const auto& attrib : configAttribs) {
+ if (!matchAttrib(config, attrib)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+
+ for (const auto& config : configs) {
+ if (matchConfig(config)) {
+ return config;
+ }
+ }
+
+ return 0;
+}
+
+bool GLContext::makeCurrent() {
+ return eglMakeCurrent(display, surface, surface, context) != EGL_FALSE;
+}
+
+void GLContext::doneCurrent() {
+ eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+}
+
+bool GLContext::create(EGLDisplay display, EGLContext shareContext) {
+ this->display = display;
+
+ auto config = findConfig(display);
+
+ if (config == 0) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglChooseConfig");
+ return false;
+ }
+
+ EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
+
+ context = eglCreateContext(display, config, shareContext, contextAttribs);
+ if (context == EGL_NO_CONTEXT) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreateContext");
+ return false;
+ }
+
+ const EGLint surfaceAttribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE };
+ surface = eglCreatePbufferSurface(display, config, surfaceAttribs);
+ if (surface == EGL_NO_SURFACE) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreatePbufferSurface");
+ return false;
+ }
+
+ if (!makeCurrent()) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglMakeCurrent");
+ return false;
+ }
+
+ ovr::GLContext::initModule();
+
+#ifndef NDEBUG
+ glDebugMessageCallback(debugMessageCallback, this);
+ glEnable(GL_DEBUG_OUTPUT);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+#endif
+ return true;
+}
+
+void GLContext::destroy() {
+ if (context != EGL_NO_CONTEXT) {
+ eglDestroyContext(display, context);
+ context = EGL_NO_CONTEXT;
+ }
+
+ if (surface != EGL_NO_SURFACE) {
+ eglDestroySurface(display, surface);
+ surface = EGL_NO_SURFACE;
+ }
+}
diff --git a/libraries/oculusMobile/src/ovr/GLContext.h b/libraries/oculusMobile/src/ovr/GLContext.h
new file mode 100644
index 0000000000..04f96e8d47
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/GLContext.h
@@ -0,0 +1,37 @@
+//
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace ovr {
+
+struct GLContext {
+ using Pointer = std::shared_ptr;
+ EGLSurface surface{ EGL_NO_SURFACE };
+ EGLContext context{ EGL_NO_CONTEXT };
+ EGLDisplay display{ EGL_NO_DISPLAY };
+
+ ~GLContext();
+ static EGLConfig findConfig(EGLDisplay display);
+ bool makeCurrent();
+ void doneCurrent();
+ bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT);
+ void destroy();
+ operator bool() const { return context != EGL_NO_CONTEXT; }
+ static void initModule();
+};
+
+}
+
+
+#define CHECK_GL_ERROR() if(false) {}
\ No newline at end of file
diff --git a/libraries/oculusMobile/src/ovr/Helpers.cpp b/libraries/oculusMobile/src/ovr/Helpers.cpp
new file mode 100644
index 0000000000..a48d37311e
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Helpers.cpp
@@ -0,0 +1,38 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "Helpers.h"
+
+#include
+#include
+#include
+#include
+
+using namespace ovr;
+
+void Fov::extend(const Fov& other) {
+ for (size_t i = 0; i < 4; ++i) {
+ leftRightUpDown[i] = std::max(leftRightUpDown[i], other.leftRightUpDown[i]);
+ }
+}
+
+void Fov::extract(const ovrMatrix4f& mat) {
+ auto& fs = leftRightUpDown;
+ ovrMatrix4f_ExtractFov( &mat, fs, fs + 1, fs + 2, fs + 3);
+}
+
+glm::mat4 Fov::withZ(float nearZ, float farZ) const {
+ const auto& fs = leftRightUpDown;
+ return ovr::toGlm(ovrMatrix4f_CreateProjectionAsymmetricFov(fs[0], fs[1], fs[2], fs[3], nearZ, farZ));
+}
+
+glm::mat4 Fov::withZ(const glm::mat4& other) const {
+ // FIXME
+ return withZ(0.01f, 1000.0f);
+}
+
diff --git a/libraries/oculusMobile/src/ovr/Helpers.h b/libraries/oculusMobile/src/ovr/Helpers.h
new file mode 100644
index 0000000000..2bd0b7f603
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Helpers.h
@@ -0,0 +1,94 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace ovr {
+
+struct Fov {
+ float leftRightUpDown[4];
+ Fov() {}
+ Fov(const ovrMatrix4f& mat) { extract(mat); }
+ void extract(const ovrMatrix4f& mat);
+ void extend(const Fov& other);
+ glm::mat4 withZ(const glm::mat4& other) const;
+ glm::mat4 withZ(float nearZ, float farZ) const;
+};
+
+// Convenience method for looping over each eye with a lambda
+static inline void for_each_eye(const std::function& f) {
+ f(VRAPI_EYE_LEFT);
+ f(VRAPI_EYE_RIGHT);
+}
+
+static inline void for_each_hand(const std::function& f) {
+ f(VRAPI_HAND_LEFT);
+ f(VRAPI_HAND_RIGHT);
+}
+
+static inline glm::mat4 toGlm(const ovrMatrix4f& om) {
+ return glm::transpose(glm::make_mat4(&om.M[0][0]));
+}
+
+static inline glm::vec3 toGlm(const ovrVector3f& ov) {
+ return glm::make_vec3(&ov.x);
+}
+
+static inline glm::vec2 toGlm(const ovrVector2f& ov) {
+ return glm::make_vec2(&ov.x);
+}
+
+static inline glm::quat toGlm(const ovrQuatf& oq) {
+ return glm::make_quat(&oq.x);
+}
+
+static inline glm::mat4 toGlm(const ovrPosef& op) {
+ glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation));
+ glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position));
+ return translation * orientation;
+}
+
+static inline ovrMatrix4f fromGlm(const glm::mat4& m) {
+ ovrMatrix4f result;
+ glm::mat4 transposed(glm::transpose(m));
+ memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16);
+ return result;
+}
+
+static inline ovrVector3f fromGlm(const glm::vec3& v) {
+ return { v.x, v.y, v.z };
+}
+
+static inline ovrVector2f fromGlm(const glm::vec2& v) {
+ return { v.x, v.y };
+}
+
+static inline ovrQuatf fromGlm(const glm::quat& q) {
+ return { q.x, q.y, q.z, q.w };
+}
+
+static inline ovrPosef poseFromGlm(const glm::mat4& m) {
+ glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
+ glm::quat orientation = glm::quat_cast(m);
+ ovrPosef result;
+ result.Orientation = fromGlm(orientation);
+ result.Position = fromGlm(translation);
+ return result;
+}
+
+}
+
+
+
+
+
diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.cpp b/libraries/oculusMobile/src/ovr/TaskQueue.cpp
new file mode 100644
index 0000000000..5506a35acd
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/TaskQueue.cpp
@@ -0,0 +1,40 @@
+//
+// Created by Bradley Austin Davis on 2018/11/23
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "TaskQueue.h"
+
+using namespace ovr;
+
+void TaskQueue::submitTaskBlocking(Lock& lock, const Task& newTask) {
+ _task = newTask;
+ _taskPending = true;
+ _taskCondition.wait(lock, [=]() -> bool { return !_taskPending; });
+}
+
+void TaskQueue::submitTaskBlocking(const Task& task) {
+ Lock lock(_mutex);
+ submitTaskBlocking(lock, task);
+}
+
+void TaskQueue::pollTask() {
+ Lock lock(_mutex);
+ if (_taskPending) {
+ _task();
+ _taskPending = false;
+ _taskCondition.notify_one();
+ }
+}
+
+void TaskQueue::withLock(const Task& task) {
+ Lock lock(_mutex);
+ task();
+}
+
+void TaskQueue::withLockConditional(const LockTask& task) {
+ Lock lock(_mutex);
+ task(lock);
+}
diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.h b/libraries/oculusMobile/src/ovr/TaskQueue.h
new file mode 100644
index 0000000000..4ec055ece9
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/TaskQueue.h
@@ -0,0 +1,42 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+
+namespace ovr {
+
+using Mutex = std::mutex;
+using Condition = std::condition_variable;
+using Lock = std::unique_lock;
+using Task = std::function;
+using LockTask = std::function;
+
+class TaskQueue {
+public:
+ // Execute a task on another thread
+ void submitTaskBlocking(const Task& task);
+ void submitTaskBlocking(Lock& lock, const Task& task);
+ void pollTask();
+
+ void withLock(const Task& task);
+ void withLockConditional(const LockTask& task);
+private:
+ Mutex _mutex;
+ Task _task;
+ bool _taskPending{ false };
+ Condition _taskCondition;
+};
+
+}
+
+
+
+
+
diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp
new file mode 100644
index 0000000000..de2b4e1ff6
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp
@@ -0,0 +1,337 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "VrHandler.h"
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+//#include
+
+#include "GLContext.h"
+#include "Helpers.h"
+#include "Framebuffer.h"
+
+
+
+using namespace ovr;
+
+static thread_local bool isRenderThread { false };
+
+struct VrSurface : public TaskQueue {
+ using HandlerTask = VrHandler::HandlerTask;
+
+ JavaVM* vm{nullptr};
+ jobject oculusActivity{ nullptr };
+ ANativeWindow* nativeWindow{ nullptr };
+
+ VrHandler* handler{nullptr};
+ ovrMobile* session{nullptr};
+ bool resumed { false };
+ GLContext vrglContext;
+ Framebuffer eyeFbos[2];
+ uint32_t readFbo{0};
+ std::atomic presentIndex{1};
+ double displayTime{0};
+
+ static constexpr float EYE_BUFFER_SCALE = 1.0f;
+
+ void onCreate(JNIEnv* env, jobject activity) {
+ env->GetJavaVM(&vm);
+ oculusActivity = env->NewGlobalRef(activity);
+ }
+
+ void setResumed(bool newResumed) {
+ this->resumed = newResumed;
+ submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); });
+ }
+
+ void setNativeWindow(ANativeWindow* newNativeWindow) {
+ auto oldNativeWindow = nativeWindow;
+ nativeWindow = newNativeWindow;
+ if (oldNativeWindow) {
+ ANativeWindow_release(oldNativeWindow);
+ }
+ submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); });
+ }
+
+ void init() {
+ if (!handler) {
+ return;
+ }
+
+ EGLContext currentContext = eglGetCurrentContext();
+ EGLDisplay currentDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ vrglContext.create(currentDisplay, currentContext);
+ vrglContext.makeCurrent();
+
+ glm::uvec2 eyeTargetSize;
+ withEnv([&](JNIEnv* env){
+ ovrJava java{ vm, env, oculusActivity };
+ eyeTargetSize = glm::uvec2 {
+ vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH) * EYE_BUFFER_SCALE,
+ vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT) * EYE_BUFFER_SCALE,
+ };
+ });
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "QQQ Eye Size %d, %d", eyeTargetSize.x, eyeTargetSize.y);
+ ovr::for_each_eye([&](ovrEye eye) {
+ eyeFbos[eye].create(eyeTargetSize);
+ });
+ glGenFramebuffers(1, &readFbo);
+ vrglContext.doneCurrent();
+ }
+
+ void shutdown() {
+ }
+
+ void setHandler(VrHandler *newHandler) {
+ withLock([&] {
+ isRenderThread = newHandler != nullptr;
+ if (handler != newHandler) {
+ shutdown();
+ handler = newHandler;
+ init();
+ if (handler) {
+ updateVrMode();
+ }
+ }
+ });
+ }
+
+ void submitRenderThreadTask(const HandlerTask &task) {
+ withLockConditional([&](Lock &lock) {
+ if (handler != nullptr) {
+ submitTaskBlocking(lock, [&] {
+ task(handler);
+ });
+ }
+ });
+ }
+
+ void withEnv(const std::function& f) {
+ JNIEnv* env = nullptr;
+ bool attached = false;
+ vm->GetEnv((void**)&env, JNI_VERSION_1_6);
+ if (!env) {
+ attached = true;
+ vm->AttachCurrentThread(&env, nullptr);
+ }
+ f(env);
+ if (attached) {
+ vm->DetachCurrentThread();
+ }
+ }
+
+ void updateVrMode() {
+ // For VR mode to be valid, the activity must be between an onResume and
+ // an onPause call and must additionally have a valid native window handle
+ bool vrReady = resumed && nullptr != nativeWindow;
+ // If we're IN VR mode, we'll have a non-null ovrMobile pointer in session
+ bool vrRunning = session != nullptr;
+ if (vrReady != vrRunning) {
+ if (vrRunning) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode");
+ vrapi_LeaveVrMode(session);
+ session = nullptr;
+ oculusActivity = nullptr;
+ } else {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_EnterVrMode");
+ withEnv([&](JNIEnv* env){
+ ovrJava java{ vm, env, oculusActivity };
+ ovrModeParms modeParms = vrapi_DefaultModeParms(&java);
+ modeParms.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW;
+ modeParms.Display = (unsigned long long) vrglContext.display;
+ modeParms.ShareContext = (unsigned long long) vrglContext.context;
+ modeParms.WindowSurface = (unsigned long long) nativeWindow;
+ session = vrapi_EnterVrMode(&modeParms);
+ ovrPosef trackingTransform = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_EYE_LEVEL);
+ vrapi_SetTrackingTransform( session, trackingTransform );
+ vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, pthread_self());
+ vrapi_SetClockLevels(session, 2, 4);
+ vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC);
+ vrapi_SetDisplayRefreshRate(session, 72);
+ });
+ }
+ }
+ }
+
+ void presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) {
+ ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2();
+ layer.HeadPose = tracking.HeadPose;
+ if (sourceTexture) {
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
+ glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, sourceTexture, 0);
+ GLenum framebufferStatus = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
+ if (GL_FRAMEBUFFER_COMPLETE != framebufferStatus) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "incomplete framebuffer");
+ }
+ }
+ GLenum invalidateAttachment = GL_COLOR_ATTACHMENT0;
+
+ ovr::for_each_eye([&](ovrEye eye) {
+ const auto &eyeTracking = tracking.Eye[eye];
+ auto &eyeFbo = eyeFbos[eye];
+ const auto &destSize = eyeFbo._size;
+ eyeFbo.bind();
+ glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &invalidateAttachment);
+ if (sourceTexture) {
+ auto sourceWidth = sourceSize.x / 2;
+ auto sourceX = (eye == VRAPI_EYE_LEFT) ? 0 : sourceWidth;
+ glBlitFramebuffer(
+ sourceX, 0, sourceX + sourceWidth, sourceSize.y,
+ 0, 0, destSize.x, destSize.y,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ }
+ eyeFbo.updateLayer(eye, layer, &eyeTracking.ProjectionMatrix);
+ eyeFbo.advance();
+ });
+ if (sourceTexture) {
+ glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &invalidateAttachment);
+ glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0);
+ }
+ glFlush();
+
+ ovrLayerHeader2 *layerHeader = &layer.Header;
+ ovrSubmitFrameDescription2 frameDesc = {};
+ frameDesc.SwapInterval = 2;
+ frameDesc.FrameIndex = presentIndex;
+ frameDesc.DisplayTime = displayTime;
+ frameDesc.LayerCount = 1;
+ frameDesc.Layers = &layerHeader;
+ vrapi_SubmitFrame2(session, &frameDesc);
+ ++presentIndex;
+ }
+
+ ovrTracking2 beginFrame() {
+ displayTime = vrapi_GetPredictedDisplayTime(session, presentIndex);
+ return vrapi_GetPredictedTracking2(session, displayTime);
+ }
+};
+
+static VrSurface SURFACE;
+
+bool VrHandler::vrActive() const {
+ return SURFACE.session != nullptr;
+}
+
+void VrHandler::setHandler(VrHandler* handler) {
+ SURFACE.setHandler(handler);
+}
+
+void VrHandler::pollTask() {
+ SURFACE.pollTask();
+}
+
+void VrHandler::makeCurrent() {
+ if (!SURFACE.vrglContext.makeCurrent()) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "Failed to make GL current");
+ }
+}
+
+void VrHandler::doneCurrent() {
+ SURFACE.vrglContext.doneCurrent();
+}
+
+uint32_t VrHandler::currentPresentIndex() const {
+ return SURFACE.presentIndex;
+}
+
+ovrTracking2 VrHandler::beginFrame() {
+ return SURFACE.beginFrame();
+}
+
+void VrHandler::presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) const {
+ SURFACE.presentFrame(sourceTexture, sourceSize, tracking);
+}
+
+bool VrHandler::withOvrJava(const OvrJavaTask& task) {
+ SURFACE.withEnv([&](JNIEnv* env){
+ ovrJava java{ SURFACE.vm, env, SURFACE.oculusActivity };
+ task(&java);
+ });
+ return true;
+}
+
+bool VrHandler::withOvrMobile(const OvrMobileTask &task) {
+ auto sessionTask = [&]()->bool{
+ if (!SURFACE.session) {
+ return false;
+ }
+ task(SURFACE.session);
+ return true;
+ };
+
+ if (isRenderThread) {
+ return sessionTask();
+ }
+
+ bool result = false;
+ SURFACE.withLock([&]{
+ result = sessionTask();
+ });
+ return result;
+}
+
+
+void VrHandler::initVr(const char* appId) {
+ withOvrJava([&](const ovrJava* java){
+ ovrInitParms initParms = vrapi_DefaultInitParms(java);
+ initParms.GraphicsAPI = VRAPI_GRAPHICS_API_OPENGL_ES_3;
+ if (vrapi_Initialize(&initParms) != VRAPI_INITIALIZE_SUCCESS) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed vrapi init");
+ }
+ });
+
+ // if (appId) {
+ // auto platformInitResult = ovr_PlatformInitializeAndroid(appId, activity.object(), env);
+ // if (ovrPlatformInitialize_Success != platformInitResult) {
+ // __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed ovr platform init");
+ // }
+ // }
+}
+
+void VrHandler::shutdownVr() {
+ vrapi_Shutdown();
+}
+
+extern "C" {
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "oculusMobile::JNI_OnLoad");
+ return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnCreate(JNIEnv* env, jobject obj) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.onCreate(env, obj);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnDestroy(JNIEnv*, jclass) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnResume(JNIEnv*, jclass) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.setResumed(true);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnPause(JNIEnv*, jclass) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.setResumed(false);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnSurfaceChanged(JNIEnv* env, jclass, jobject surface) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.setNativeWindow(surface ? ANativeWindow_fromSurface( env, surface ) : nullptr);
+}
+
+} // extern "C"
diff --git a/libraries/oculusMobile/src/ovr/VrHandler.h b/libraries/oculusMobile/src/ovr/VrHandler.h
new file mode 100644
index 0000000000..3c36ee8626
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/VrHandler.h
@@ -0,0 +1,47 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+#include
+
+#include "TaskQueue.h"
+
+typedef struct ovrMobile ovrMobile;
+namespace ovr {
+
+class VrHandler {
+public:
+ using HandlerTask = std::function;
+ using OvrMobileTask = std::function;
+ using OvrJavaTask = std::function;
+ static void setHandler(VrHandler* handler);
+ static bool withOvrMobile(const OvrMobileTask& task);
+
+protected:
+ static void initVr(const char* appId = nullptr);
+ static void shutdownVr();
+ static bool withOvrJava(const OvrJavaTask& task);
+
+ uint32_t currentPresentIndex() const;
+ ovrTracking2 beginFrame();
+ void presentFrame(uint32_t textureId, const glm::uvec2& size, const ovrTracking2& tracking) const;
+
+ bool vrActive() const;
+ void pollTask();
+ void makeCurrent();
+ void doneCurrent();
+};
+
+}
+
+
+
+
+
diff --git a/libraries/oculusMobilePlugin/CMakeLists.txt b/libraries/oculusMobilePlugin/CMakeLists.txt
new file mode 100644
index 0000000000..b404c51f02
--- /dev/null
+++ b/libraries/oculusMobilePlugin/CMakeLists.txt
@@ -0,0 +1,29 @@
+#
+# Created by Bradley Austin Davis on 2018/11/15
+# Copyright 2013-2018 High Fidelity, Inc.
+#
+# Distributed under the Apache License, Version 2.0.
+# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
+#
+
+if (ANDROID)
+ set(TARGET_NAME oculusMobilePlugin)
+ setup_hifi_library(AndroidExtras Multimedia)
+
+ # if we were passed an Oculus App ID for entitlement checks, send that along
+ if (DEFINED ENV{OCULUS_APP_ID})
+ target_compile_definitions(${TARGET_NAME} -DOCULUS_APP_ID="$ENV{OCULUS_APP_ID}")
+ endif ()
+
+ link_hifi_libraries(
+ shared task gl shaders gpu controllers ui qml
+ plugins ui-plugins display-plugins input-plugins
+ audio-client networking render-utils
+ render graphics
+ oculusMobile
+ ${PLATFORM_GL_BACKEND}
+ )
+
+ include_hifi_library_headers(octree)
+ target_oculus_mobile()
+endif()
diff --git a/libraries/oculusMobilePlugin/src/Logging.cpp b/libraries/oculusMobilePlugin/src/Logging.cpp
new file mode 100644
index 0000000000..3d8628b0cb
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/Logging.cpp
@@ -0,0 +1,4 @@
+#include "Logging.h"
+
+Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
+Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus")
diff --git a/libraries/oculusMobilePlugin/src/Logging.h b/libraries/oculusMobilePlugin/src/Logging.h
new file mode 100644
index 0000000000..066712ef6a
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/Logging.h
@@ -0,0 +1,13 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+
+Q_DECLARE_LOGGING_CATEGORY(displayplugins)
+Q_DECLARE_LOGGING_CATEGORY(oculusLog)
diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp
new file mode 100644
index 0000000000..8de563ee4c
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp
@@ -0,0 +1,694 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "OculusMobileControllerManager.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Logging.h"
+#include
+
+const char* OculusMobileControllerManager::NAME = "Oculus";
+const quint64 LOST_TRACKING_DELAY = 3000000;
+
+namespace ovr {
+
+ controller::Pose toControllerPose(ovrHandedness hand, const ovrRigidBodyPosef& handPose) {
+ // When the sensor-to-world rotation is identity the coordinate axes look like this:
+ //
+ // user
+ // forward
+ // -z
+ // |
+ // y| user
+ // y o----x right
+ // o-----x user
+ // | up
+ // |
+ // z
+ //
+ // Rift
+
+ // From ABOVE the hand canonical axes looks like this:
+ //
+ // | | | | y | | | |
+ // | | | | | | | | |
+ // | | | | |
+ // |left | / x---- + \ |right|
+ // | _/ z \_ |
+ // | | | |
+ // | | | |
+ //
+
+ // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
+ // the rotation to align the Touch axes with those of the hands is:
+ //
+ // touchToHand = halfTurnAboutY * quaterTurnAboutX
+
+ // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
+ // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
+ // the combination (measurement * offset) is identity at this orientation.
+ //
+ // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
+ //
+ // An approximate offset for the Touch can be obtained by inspection:
+ //
+ // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
+ //
+ // So the full equation is:
+ //
+ // Q = combinedMeasurement * touchToHand
+ //
+ // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
+ //
+ // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
+ static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
+ static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
+ static const glm::quat touchToHand = yFlip * quarterX;
+
+ static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
+ static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
+
+ static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand;
+ static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
+
+ static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
+ static const glm::vec3 CONTROLLER_OFFSET =
+ glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
+ static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
+ static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
+
+ auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset);
+ auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset);
+
+ glm::quat rotation = toGlm(handPose.Pose.Orientation);
+
+ controller::Pose pose;
+ pose.translation = toGlm(handPose.Pose.Position);
+ pose.translation += rotation * translationOffset;
+ pose.rotation = rotation * rotationOffset;
+ pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity);
+ pose.velocity = toGlm(handPose.LinearVelocity);
+ pose.valid = true;
+ return pose;
+ }
+
+ controller::Pose toControllerPose(ovrHandedness hand,
+ const ovrRigidBodyPosef& handPose,
+ const ovrRigidBodyPosef& lastHandPose) {
+ static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
+ static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
+ static const glm::quat touchToHand = yFlip * quarterX;
+
+ static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
+ static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
+
+ static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand;
+ static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
+
+ static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
+ static const glm::vec3 CONTROLLER_OFFSET =
+ glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
+ static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
+ static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
+
+ auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset);
+ auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset);
+
+ glm::quat rotation = toGlm(handPose.Pose.Orientation);
+
+ controller::Pose pose;
+ pose.translation = toGlm(lastHandPose.Pose.Position);
+ pose.translation += rotation * translationOffset;
+ pose.rotation = rotation * rotationOffset;
+ pose.angularVelocity = toGlm(lastHandPose.AngularVelocity);
+ pose.velocity = toGlm(lastHandPose.LinearVelocity);
+ pose.valid = true;
+ return pose;
+ }
+
+}
+
+
+class OculusMobileInputDevice : public controller::InputDevice {
+ friend class OculusMobileControllerManager;
+public:
+ using Pointer = std::shared_ptr;
+ static Pointer check(ovrMobile* session);
+
+ OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps);
+ void updateHands(ovrMobile* session);
+
+ controller::Input::NamedVector getAvailableInputs() const override;
+ QString getDefaultMappingConfig() const override;
+ void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
+ void focusOutEvent() override;
+ bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
+
+private:
+ void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
+ ovrHandedness hand, const ovrRigidBodyPosef& handPose);
+ void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
+ ovrHandedness hand, const ovrRigidBodyPosef& handPose);
+ void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
+ const ovrRigidBodyPosef& headPose);
+
+ // perform an action when the TouchDevice mutex is acquired.
+ using Locker = std::unique_lock;
+
+ template
+ void withLock(F&& f) { Locker locker(_lock); f(); }
+
+ mutable std::recursive_mutex _lock;
+ ovrTracking2 _headTracking;
+ struct HandData {
+ HandData() {
+ state.Header.ControllerType =ovrControllerType_TrackedRemote;
+ }
+
+ float hapticDuration { 0.0f };
+ float hapticStrength { 0.0f };
+ bool valid{ false };
+ bool lostTracking{ false };
+ quint64 regainTrackingDeadline;
+ ovrRigidBodyPosef lastPose;
+ ovrInputTrackedRemoteCapabilities caps;
+ ovrInputStateTrackedRemote state;
+ ovrResult stateResult{ ovrError_NotInitialized };
+ ovrTracking tracking;
+ ovrResult trackingResult{ ovrError_NotInitialized };
+ bool setHapticFeedback(float strength, float duration) {
+#if 0
+ bool success = true;
+ bool sessionSuccess = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ if (strength == 0.0f) {
+ hapticStrength = 0.0f;
+ hapticDuration = 0.0f;
+ } else {
+ hapticStrength = (duration > hapticDuration) ? strength : hapticStrength;
+ if (vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, hapticStrength) != ovrSuccess) {
+ success = false;
+ }
+ hapticDuration = std::max(duration, hapticDuration);
+ }
+ });
+ return success && sessionSuccess;
+#else
+ return true;
+#endif
+ }
+
+ void stopHapticPulse() {
+ ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, 0.0f);
+ });
+ }
+
+ bool isValid() const {
+ return (stateResult == ovrSuccess) && (trackingResult == ovrSuccess);
+ }
+
+ void update(ovrMobile* session, double time = 0.0) {
+ const auto& deviceId = caps.Header.DeviceID;
+ stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header);
+ trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking);
+ }
+ };
+ std::array _hands;
+};
+
+OculusMobileInputDevice::Pointer OculusMobileInputDevice::check(ovrMobile *session) {
+ Pointer result;
+
+ std::vector devicesCaps;
+ {
+ uint32_t deviceIndex { 0 };
+ ovrInputCapabilityHeader capsHeader;
+ while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) {
+ if (capsHeader.Type == ovrControllerType_TrackedRemote) {
+ ovrInputTrackedRemoteCapabilities caps;
+ caps.Header = capsHeader;
+ vrapi_GetInputDeviceCapabilities(session, &caps.Header);
+ devicesCaps.push_back(caps);
+ }
+ ++deviceIndex;
+ }
+ }
+ if (!devicesCaps.empty()) {
+ result.reset(new OculusMobileInputDevice(session, devicesCaps));
+ }
+ return result;
+}
+
+static OculusMobileInputDevice::Pointer oculusMobileControllers;
+
+bool OculusMobileControllerManager::isHandController() const {
+ return oculusMobileControllers.operator bool();
+}
+
+bool OculusMobileControllerManager::isSupported() const {
+ return true;
+}
+
+bool OculusMobileControllerManager::activate() {
+ InputPlugin::activate();
+ checkForConnectedDevices();
+ return true;
+}
+
+void OculusMobileControllerManager::checkForConnectedDevices() {
+ if (oculusMobileControllers) {
+ return;
+ }
+
+ ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ oculusMobileControllers = OculusMobileInputDevice::check(session);
+ if (oculusMobileControllers) {
+ auto userInputMapper = DependencyManager::get();
+ userInputMapper->registerDevice(oculusMobileControllers);
+ }
+ });
+}
+
+void OculusMobileControllerManager::deactivate() {
+ InputPlugin::deactivate();
+
+ // unregister with UserInputMapper
+ auto userInputMapper = DependencyManager::get();
+ if (oculusMobileControllers) {
+ userInputMapper->removeDevice(oculusMobileControllers->getDeviceID());
+ oculusMobileControllers.reset();
+ }
+}
+
+void OculusMobileControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
+ PerformanceTimer perfTimer("OculusMobileInputDevice::update");
+
+ checkForConnectedDevices();
+
+ if (!oculusMobileControllers) {
+ return;
+ }
+
+ bool updated = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ oculusMobileControllers->updateHands(session);
+ });
+
+ if (updated) {
+ oculusMobileControllers->update(deltaTime, inputCalibrationData);
+ }
+}
+
+void OculusMobileControllerManager::pluginFocusOutEvent() {
+ if (oculusMobileControllers) {
+ oculusMobileControllers->focusOutEvent();
+ }
+}
+
+QStringList OculusMobileControllerManager::getSubdeviceNames() {
+ QStringList devices;
+ if (oculusMobileControllers) {
+ devices << oculusMobileControllers->getName();
+ }
+ return devices;
+}
+
+using namespace controller;
+
+static const std::vector> BUTTON_MAP { {
+ { ovrButton_Up, DU },
+ { ovrButton_Down, DD },
+ { ovrButton_Left, DL },
+ { ovrButton_Right, DR },
+ { ovrButton_Enter, START },
+ { ovrButton_Back, BACK },
+ { ovrButton_X, X },
+ { ovrButton_Y, Y },
+ { ovrButton_A, A },
+ { ovrButton_B, B },
+ { ovrButton_LThumb, LS },
+ { ovrButton_RThumb, RS },
+ //{ ovrButton_LShoulder, LB },
+ //{ ovrButton_RShoulder, RB },
+} };
+
+static const std::vector> LEFT_TOUCH_MAP { {
+ { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH },
+ { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH },
+ { ovrTouch_LThumb, LS_TOUCH },
+ { ovrTouch_ThumbUp, LEFT_THUMB_UP },
+ { ovrTouch_IndexTrigger, LEFT_PRIMARY_INDEX_TOUCH },
+ { ovrTouch_IndexPointing, LEFT_INDEX_POINT },
+} };
+
+
+static const std::vector> RIGHT_TOUCH_MAP { {
+ { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH },
+ { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH },
+ { ovrTouch_RThumb, RS_TOUCH },
+ { ovrTouch_ThumbUp, RIGHT_THUMB_UP },
+ { ovrTouch_IndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH },
+ { ovrTouch_IndexPointing, RIGHT_INDEX_POINT },
+} };
+
+void OculusMobileInputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
+ _buttonPressedMap.clear();
+
+ int numTrackedControllers = 0;
+ quint64 currentTime = usecTimestampNow();
+ handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose);
+
+ static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED;
+ ovr::for_each_hand([&](ovrHandedness hand) {
+ size_t handIndex = (hand == VRAPI_HAND_LEFT) ? 0 : 1;
+ int controller = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND;
+ auto& handData = _hands[handIndex];
+ const auto& tracking = handData.tracking;
+ ++numTrackedControllers;
+
+ // Disable hand tracking while in Oculus Dash (Dash renders it's own hands)
+// if (!hasInputFocus) {
+// _poseStateMap.erase(controller);
+// _poseStateMap[controller].valid = false;
+// return;
+// }
+
+ if (REQUIRED_HAND_STATUS == (tracking.Status & REQUIRED_HAND_STATUS)) {
+ _poseStateMap.erase(controller);
+ handlePose(deltaTime, inputCalibrationData, hand, tracking.HeadPose);
+ handData.lostTracking = false;
+ handData.lastPose = tracking.HeadPose;
+ return;
+ }
+
+ if (handData.lostTracking) {
+ if (currentTime > handData.regainTrackingDeadline) {
+ _poseStateMap.erase(controller);
+ _poseStateMap[controller].valid = false;
+ return;
+ }
+ } else {
+ quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY;
+ handData.regainTrackingDeadline = deadlineToRegainTracking;
+ handData.lostTracking = true;
+ }
+ handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HeadPose);
+ });
+
+
+ using namespace controller;
+ // Axes
+ {
+ const auto& inputState = _hands[0].state;
+ _axisStateMap[LX].value = inputState.JoystickNoDeadZone.x;
+ _axisStateMap[LY].value = inputState.JoystickNoDeadZone.y;
+ _axisStateMap[LT].value = inputState.IndexTrigger;
+ _axisStateMap[LEFT_GRIP].value = inputState.GripTrigger;
+ for (const auto& pair : BUTTON_MAP) {
+ if (inputState.Buttons & pair.first) {
+ _buttonPressedMap.insert(pair.second);
+ qDebug()<<"AAAA:BUTTON PRESSED "<The Controller.Hardware.OculusTouch
object has properties representing Oculus Rift. The property values are
+ * integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or
+ * Controller.Standard
items in a {@link RouteObject} mapping.
+ *
+ *
+ * Property | Type | Data | Description |
+ *
+ *
+ * Buttons |
+ * A | number | number | "A" button pressed. |
+ * B | number | number | "B" button pressed. |
+ * X | number | number | "X" button pressed. |
+ * Y | number | number | "Y" button pressed. |
+ * LeftApplicationMenu | number | number | Left application menu button pressed.
+ * |
+ * RightApplicationMenu | number | number | Right application menu button pressed.
+ * |
+ * Sticks |
+ * LX | number | number | Left stick x-axis scale. |
+ * LY | number | number | Left stick y-axis scale. |
+ * RX | number | number | Right stick x-axis scale. |
+ * RY | number | number | Right stick y-axis scale. |
+ * LS | number | number | Left stick button pressed. |
+ * RS | number | number | Right stick button pressed. |
+ * LSTouch | number | number | Left stick is touched. |
+ * RSTouch | number | number | Right stick is touched. |
+ * Triggers |
+ * LT | number | number | Left trigger scale. |
+ * RT | number | number | Right trigger scale. |
+ * LeftGrip | number | number | Left grip scale. |
+ * RightGrip | number | number | Right grip scale. |
+ * Finger Abstractions |
+ * LeftPrimaryThumbTouch | number | number | Left thumb touching primary thumb
+ * button. |
+ * LeftSecondaryThumbTouch | number | number | Left thumb touching secondary thumb
+ * button. |
+ * LeftThumbUp | number | number | Left thumb not touching primary or secondary
+ * thumb buttons. |
+ * RightPrimaryThumbTouch | number | number | Right thumb touching primary thumb
+ * button. |
+ * RightSecondaryThumbTouch | number | number | Right thumb touching secondary thumb
+ * button. |
+ * RightThumbUp | number | number | Right thumb not touching primary or secondary
+ * thumb buttons. |
+ * LeftPrimaryIndexTouch | number | number | Left index finger is touching primary
+ * index finger control. |
+ * LeftIndexPoint | number | number | Left index finger is pointing, not touching
+ * primary or secondary index finger controls. |
+ * RightPrimaryIndexTouch | number | number | Right index finger is touching primary
+ * index finger control. |
+ * RightIndexPoint | number | number | Right index finger is pointing, not touching
+ * primary or secondary index finger controls. |
+ * Avatar Skeleton |
+ * Head | number | {@link Pose} | Head pose. |
+ * LeftHand | number | {@link Pose} | Left hand pose. |
+ * RightHand | number | {@link Pose} | right hand pose. |
+ *
+ *
+ * @typedef {object} Controller.Hardware-OculusTouch
+ */
+controller::Input::NamedVector OculusMobileInputDevice::getAvailableInputs() const {
+ using namespace controller;
+ QVector availableInputs{
+ // buttons
+ makePair(A, "A"),
+ makePair(B, "B"),
+ makePair(X, "X"),
+ makePair(Y, "Y"),
+
+ // trackpad analogs
+ makePair(LX, "LX"),
+ makePair(LY, "LY"),
+ makePair(RX, "RX"),
+ makePair(RY, "RY"),
+
+ // triggers
+ makePair(LT, "LT"),
+ makePair(RT, "RT"),
+
+ // trigger buttons
+ //makePair(LB, "LB"),
+ //makePair(RB, "RB"),
+
+ // side grip triggers
+ makePair(LEFT_GRIP, "LeftGrip"),
+ makePair(RIGHT_GRIP, "RightGrip"),
+
+ // joystick buttons
+ makePair(LS, "LS"),
+ makePair(RS, "RS"),
+
+ makePair(LEFT_HAND, "LeftHand"),
+ makePair(RIGHT_HAND, "RightHand"),
+ makePair(HEAD, "Head"),
+
+ makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"),
+ makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"),
+ makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"),
+ makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"),
+ makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"),
+ makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"),
+ makePair(LS_TOUCH, "LSTouch"),
+ makePair(RS_TOUCH, "RSTouch"),
+ makePair(LEFT_THUMB_UP, "LeftThumbUp"),
+ makePair(RIGHT_THUMB_UP, "RightThumbUp"),
+ makePair(LEFT_INDEX_POINT, "LeftIndexPoint"),
+ makePair(RIGHT_INDEX_POINT, "RightIndexPoint"),
+
+ makePair(BACK, "LeftApplicationMenu"),
+ makePair(START, "RightApplicationMenu"),
+ };
+ return availableInputs;
+}
+
+OculusMobileInputDevice::OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps) : controller::InputDevice("OculusTouch") {
+ qWarning() << "QQQ" << __FUNCTION__ << "Found " << devicesCaps.size() << "devices";
+ for (const auto& deviceCaps : devicesCaps) {
+ size_t handIndex = -1;
+ if (deviceCaps.ControllerCapabilities & ovrControllerCaps_LeftHand) {
+ handIndex = 0;
+ } else if (deviceCaps.ControllerCapabilities & ovrControllerCaps_RightHand) {
+ handIndex = 1;
+ } else {
+ continue;
+ }
+ HandData& handData = _hands[handIndex];
+ handData.state.Header.ControllerType = ovrControllerType_TrackedRemote;
+ handData.valid = true;
+ handData.caps = deviceCaps;
+ handData.update(session);
+ }
+}
+
+void OculusMobileInputDevice::updateHands(ovrMobile* session) {
+ _headTracking = vrapi_GetPredictedTracking2(session, 0.0);
+ for (auto& hand : _hands) {
+ hand.update(session);
+ }
+}
+
+QString OculusMobileInputDevice::getDefaultMappingConfig() const {
+ static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_touch.json";
+ return MAPPING_JSON;
+}
+
+// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class
+InputPluginList getInputPlugins() {
+ InputPlugin* PLUGIN_POOL[] = {
+ new KeyboardMouseDevice(),
+ new OculusMobileControllerManager(),
+ nullptr
+ };
+
+ InputPluginList result;
+ for (int i = 0; PLUGIN_POOL[i]; ++i) {
+ InputPlugin* plugin = PLUGIN_POOL[i];
+ if (plugin->isSupported()) {
+ result.push_back(InputPluginPointer(plugin));
+ }
+ }
+ return result;
+}
\ No newline at end of file
diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h
new file mode 100644
index 0000000000..43ead8d6a2
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h
@@ -0,0 +1,43 @@
+//
+// Created by Bradley Austin Davis on 2016/03/04
+// Copyright 2013-2016 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi__OculusMobileControllerManager
+#define hifi__OculusMobileControllerManager
+
+#include
+#include
+#include