From 8a1a55189a514fddb4e7e4b248eb22ddbe6a9305 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 10:37:56 -0800 Subject: [PATCH 01/10] CMake cleanup and modernization --- cmake/macros/IncludeHifiLibraryHeaders.cmake | 2 +- cmake/macros/LinkHifiLibraries.cmake | 4 ++-- interface/CMakeLists.txt | 2 +- libraries/entities/CMakeLists.txt | 2 +- libraries/gl/CMakeLists.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmake/macros/IncludeHifiLibraryHeaders.cmake b/cmake/macros/IncludeHifiLibraryHeaders.cmake index 913d1e1181..008d76a8dc 100644 --- a/cmake/macros/IncludeHifiLibraryHeaders.cmake +++ b/cmake/macros/IncludeHifiLibraryHeaders.cmake @@ -10,5 +10,5 @@ # macro(include_hifi_library_headers LIBRARY) - include_directories("${HIFI_LIBRARY_DIR}/${LIBRARY}/src") + target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${LIBRARY}/src") endmacro(include_hifi_library_headers _library _root_dir) \ No newline at end of file diff --git a/cmake/macros/LinkHifiLibraries.cmake b/cmake/macros/LinkHifiLibraries.cmake index 7a6a136799..6a430f5b13 100644 --- a/cmake/macros/LinkHifiLibraries.cmake +++ b/cmake/macros/LinkHifiLibraries.cmake @@ -19,8 +19,8 @@ function(LINK_HIFI_LIBRARIES) endforeach() foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK}) - include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src") - include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}") + target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src") + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}") # link the actual library - it is static so don't bubble it up target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY}) endforeach() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index c013cfacd3..81b9935aa5 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -265,7 +265,7 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) endforeach() # include headers for interface and InterfaceConfig. -include_directories("${PROJECT_SOURCE_DIR}/src") +target_include_directories(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/src") if (ANDROID) find_library(ANDROID_LOG_LIB log) diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index fcbe563f88..e359c7132f 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) -include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") +target_include_directories(${TARGET_NAME} PRIVATE "${OPENSSL_INCLUDE_DIR}") include_hifi_library_headers(hfm) include_hifi_library_headers(fbx) include_hifi_library_headers(gpu) diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt index 925cf9b288..855452e8f0 100644 --- a/libraries/gl/CMakeLists.txt +++ b/libraries/gl/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME gl) -setup_hifi_library(Gui Widgets Qml Quick) +setup_hifi_library(Gui Widgets) link_hifi_libraries(shared) target_opengl() From c3c22aa84c759a8308aee98e7790a5ffab58af9b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 10:37:24 -0800 Subject: [PATCH 02/10] EGL and Oculus depedency macros --- cmake/macros/TargetEGL.cmake | 4 ++++ cmake/macros/TargetOculusMobile.cmake | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 cmake/macros/TargetEGL.cmake create mode 100644 cmake/macros/TargetOculusMobile.cmake diff --git a/cmake/macros/TargetEGL.cmake b/cmake/macros/TargetEGL.cmake new file mode 100644 index 0000000000..1d8ce26d83 --- /dev/null +++ b/cmake/macros/TargetEGL.cmake @@ -0,0 +1,4 @@ +macro(target_egl) + find_library(EGL EGL) + target_link_libraries(${TARGET_NAME} ${EGL}) +endmacro() diff --git a/cmake/macros/TargetOculusMobile.cmake b/cmake/macros/TargetOculusMobile.cmake new file mode 100644 index 0000000000..3eaa008b14 --- /dev/null +++ b/cmake/macros/TargetOculusMobile.cmake @@ -0,0 +1,20 @@ + +macro(target_oculus_mobile) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi) + + # Mobile SDK + set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include) + target_include_directories(${TARGET_NAME} PRIVATE ${OVR_MOBILE_INCLUDE_DIRS}) + set(OVR_MOBILE_LIBRARY_DIR ${INSTALL_DIR}/Libs/Android/arm64-v8a) + set(OVR_MOBILE_LIBRARY_RELEASE ${OVR_MOBILE_LIBRARY_DIR}/Release/libvrapi.so) + set(OVR_MOBILE_LIBRARY_DEBUG ${OVR_MOBILE_LIBRARY_DIR}/Debug/libvrapi.so) + select_library_configurations(OVR_MOBILE) + target_link_libraries(${TARGET_NAME} ${OVR_MOBILE_LIBRARIES}) + + # Platform SDK + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculusPlatform) + set(OVR_PLATFORM_INCLUDE_DIRS ${INSTALL_DIR}/Include) + target_include_directories(${TARGET_NAME} PRIVATE ${OVR_PLATFORM_INCLUDE_DIRS}) + set(OVR_PLATFORM_LIBRARIES ${INSTALL_DIR}/Android/libs/arm64-v8a/libovrplatformloader.so) + target_link_libraries(${TARGET_NAME} ${OVR_PLATFORM_LIBRARIES}) +endmacro() From fed9e27a66e5f3842cfb010e0a59b3d038cd568a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 10:38:24 -0800 Subject: [PATCH 03/10] Expose current android app name to source code --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6956fd22c3..4d616e1f3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ endif() if (ANDROID) set(GLES_OPTION ON) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) + add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\") else () set(PLATFORM_QT_COMPONENTS WebEngine) endif () From 5d1277e1bbe2e1ecf31289a88d9ce324d5ee33fc Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 10:42:33 -0800 Subject: [PATCH 04/10] GL helpers cleanup --- libraries/gl/src/gl/Context.cpp | 21 +++++++++++++- libraries/gl/src/gl/ContextQt.cpp | 7 +++-- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 12 -------- libraries/gl/src/gl/QOpenGLContextWrapper.cpp | 29 ++++++++++--------- libraries/gl/src/gl/QOpenGLContextWrapper.h | 22 +++++++++++--- 5 files changed, 59 insertions(+), 32 deletions(-) diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 7d27b42909..a0d52ee223 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -195,6 +195,21 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); +#if defined(GL_CUSTOM_CONTEXT) +bool Context::makeCurrent() { + BOOL result = wglMakeCurrent(_hdc, _hglrc); + assert(result); + updateSwapchainMemoryCounter(); + return result; +} + void Context::swapBuffers() { + SwapBuffers(_hdc); +} + void Context::doneCurrent() { + wglMakeCurrent(0, 0); +} +#endif + void Context::create(QOpenGLContext* shareContext) { if (!shareContext) { shareContext = qt_gl_global_share_context(); @@ -297,7 +312,11 @@ void Context::create(QOpenGLContext* shareContext) { contextAttribs.push_back(0); } contextAttribs.push_back(0); - HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext); + HGLRC shareHglrc = nullptr; + if (shareContext) { + auto nativeContextPointer = QOpenGLContextWrapper(shareContext).getNativeContext(); + shareHglrc = (HGLRC)nativeContextPointer->context(); + } _hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]); } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index 82724dfd62..24ae29e4ca 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -61,12 +61,12 @@ void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { switch (severity) { case QOpenGLDebugMessage::NotificationSeverity: case QOpenGLDebugMessage::LowSeverity: + qCDebug(glLogging) << debugMessage; return; default: + qCWarning(glLogging) << debugMessage; break; } - qWarning(glLogging) << debugMessage; - return; } void Context::setupDebugLogging(QOpenGLContext *context) { @@ -82,6 +82,8 @@ void Context::setupDebugLogging(QOpenGLContext *context) { } } + +#if !defined(GL_CUSTOM_CONTEXT) bool Context::makeCurrent() { updateSwapchainMemoryCounter(); bool result = _qglContext->makeCurrent(_window); @@ -98,6 +100,7 @@ void Context::doneCurrent() { _qglContext->doneCurrent(); } } +#endif Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index f05acb50e9..69b41da821 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -65,21 +65,9 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _offscreenSurface->setFormat(_context->format()); _offscreenSurface->create(); - - // Due to a https://bugreports.qt.io/browse/QTBUG-65125 we can't rely on `isValid` - // to determine if the offscreen surface was successfully created, so we use - // makeCurrent as a proxy test. Bug is fixed in Qt 5.9.4 -#if defined(Q_OS_ANDROID) - if (!_context->makeCurrent(_offscreenSurface)) { - qFatal("Unable to make offscreen surface current"); - } - _context->doneCurrent(); -#else if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); } -#endif - return true; } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index fbebb1128d..842c7abd08 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -17,6 +17,22 @@ #include #endif +QOpenGLContextWrapper::Pointer QOpenGLContextWrapper::currentContextWrapper() { + return std::make_shared(QOpenGLContext::currentContext()); +} + + +QOpenGLContextWrapper::NativeContextPointer QOpenGLContextWrapper::getNativeContext() const { + QOpenGLContextWrapper::NativeContextPointer result; + auto nativeHandle = _context->nativeHandle(); + if (nativeHandle.canConvert()) { + result = std::make_shared(); + *result = nativeHandle.value(); + } + return result; +} + + uint32_t QOpenGLContextWrapper::currentContextVersion() { QOpenGLContext* context = QOpenGLContext::currentContext(); if (!context) { @@ -49,19 +65,6 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { _context->setFormat(format); } -#ifdef Q_OS_WIN -void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) { - HGLRC result = 0; - if (context != nullptr) { - auto nativeHandle = context->nativeHandle(); - if (nativeHandle.canConvert()) { - result = nativeHandle.value().context(); - } - } - return result; -} -#endif - bool QOpenGLContextWrapper::create() { return _context->create(); } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index 32ba7f22e8..1fade3e7fa 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -12,19 +12,31 @@ #ifndef hifi_QOpenGLContextWrapper_h #define hifi_QOpenGLContextWrapper_h -#include #include +#include class QOpenGLContext; class QSurface; class QSurfaceFormat; class QThread; +#if defined(Q_OS_ANDROID) +#include +#include +using QGLNativeContext = QEGLNativeContext; +#elif defined(Q_OS_WIN) +class QWGLNativeContext; +using QGLNativeContext = QWGLNativeContext; +#else +using QGLNativeContext = void*; +#endif + class QOpenGLContextWrapper { public: -#ifdef Q_OS_WIN - static void* nativeContext(QOpenGLContext* context); -#endif + using Pointer = std::shared_ptr; + using NativeContextPointer = std::shared_ptr; + static Pointer currentContextWrapper(); + QOpenGLContextWrapper(); QOpenGLContextWrapper(QOpenGLContext* context); @@ -37,6 +49,8 @@ public: void setShareContext(QOpenGLContext* otherContext); void moveToThread(QThread* thread); + NativeContextPointer getNativeContext() const; + static QOpenGLContext* currentContext(); static uint32_t currentContextVersion(); From b1eb0b0a46fabdea7ca3122a64253c7265b95095 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 13:10:52 -0800 Subject: [PATCH 05/10] GPU tweaks --- .../gpu-gl-common/src/gpu/gl/GLBackend.cpp | 3 + .../gpu-gl-common/src/gpu/gl/GLBackend.h | 2 +- libraries/gpu-gles/src/gpu/gles/GLESBackend.h | 1 + .../src/gpu/gles/GLESBackendOutput.cpp | 66 ++++++++++++++++++- libraries/gpu/src/gpu/Context.h | 3 +- 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 82f4f97e3b..1cf331cd1a 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -426,6 +426,9 @@ void GLBackend::render(const Batch& batch) { GL_PROFILE_RANGE(render_gpu_gl, batch.getName().c_str()); _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); + // FIXME move this to between the transfer and draw passes, so that + // framebuffer setup can see the proper stereo state and enable things + // like foveation // Allow the batch to override the rendering stereo settings // for things like full framebuffer copy operations (deferred lighting passes) bool savedStereo = _stereo._enable; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index b5a279a54c..671d4e11d7 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -95,7 +95,7 @@ public: // Shutdown rendering and persist any required resources void shutdown() override; - void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false); + void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) override; void render(const Batch& batch) final override; // This call synchronize the Full Backend cache with the current GLState diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index aaa1be5892..636518c85a 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -48,6 +48,7 @@ public: class GLESTexture : public GLTexture { using Parent = GLTexture; friend class GLESBackend; + friend class GLESFramebuffer; GLuint allocate(const Texture& texture); protected: GLESTexture(const std::weak_ptr& backend, const Texture& buffer); diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp index 9c3a83ce13..90ce8c853a 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp @@ -17,6 +17,34 @@ namespace gpu { namespace gles { + +// returns the FOV from the projection matrix +static inline vec4 extractFov( const glm::mat4& m) { + static const std::array CLIPS{ { + { 1, 0, 0, 1 }, + { -1, 0, 0, 1 }, + { 0, 1, 0, 1 }, + { 0, -1, 0, 1 } + } }; + + glm::mat4 mt = glm::transpose(m); + vec4 v, result; + // Left + v = mt * CLIPS[0]; + result.x = -atanf(v.z / v.x); + // Right + v = mt * CLIPS[1]; + result.y = atanf(v.z / v.x); + // Down + v = mt * CLIPS[2]; + result.z = -atanf(v.z / v.y); + // Up + v = mt * CLIPS[3]; + result.w = atanf(v.z / v.y); + return result; +} + + class GLESFramebuffer : public gl::GLFramebuffer { using Parent = gl::GLFramebuffer; static GLuint allocate() { @@ -29,6 +57,24 @@ public: GLint currentFBO = -1; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + + vec2 focalPoint{ -1.0f }; + +#if 0 + { + auto backend = _backend.lock(); + if (backend && backend->isStereo()) { + glm::mat4 projections[2]; + backend->getStereoProjections(projections); + vec4 fov = extractFov(projections[0]); + float fovwidth = fov.x + fov.y; + float fovheight = fov.z + fov.w; + focalPoint.x = fov.y / fovwidth; + focalPoint.y = (fov.z / fovheight) - 0.5f; + } + } +#endif + gl::GLTexture* gltexture = nullptr; TexturePointer surface; if (_gpuObject.getColorStamps() != _colorStamps) { @@ -58,7 +104,7 @@ public: surface = b._texture; if (surface) { Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); - gltexture = backend->syncGPUObject(surface); + gltexture = backend->syncGPUObject(surface); } else { gltexture = nullptr; } @@ -66,6 +112,24 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); +#if 0 + if (glTextureFoveationParametersQCOM && focalPoint.x != -1.0f) { + static GLint FOVEATION_QUERY = 0; + static std::once_flag once; + std::call_once(once, [&]{ + glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_QUERY_QCOM, &FOVEATION_QUERY); + }); + static const float foveaArea = 4.0f; + static const float gain = 16.0f; + GLESBackend::GLESTexture* glestexture = static_cast(gltexture); + glestexture->withPreservedTexture([=]{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_BITS_QCOM, GL_FOVEATION_ENABLE_BIT_QCOM | GL_FOVEATION_SCALED_BIN_METHOD_BIT_QCOM); + glTextureFoveationParametersQCOM(_id, 0, 0, -focalPoint.x, focalPoint.y, gain * 2.0f, gain, foveaArea); + glTextureFoveationParametersQCOM(_id, 0, 1, focalPoint.x, focalPoint.y, gain * 2.0f, gain, foveaArea); + }); + + } +#endif } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0, b._subresource); diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index b080b0ceac..7109b3dfeb 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -66,6 +66,7 @@ public: virtual void syncProgram(const gpu::ShaderPointer& program) = 0; virtual void recycle() const = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; + virtual void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) {} virtual bool supportedTextureFormat(const gpu::Element& format) = 0; @@ -117,7 +118,6 @@ public: static ContextMetricSize textureResourcePopulatedGPUMemSize; static ContextMetricSize textureResourceIdealGPUMemSize; -protected: virtual bool isStereo() const { return _stereo.isStereo(); } @@ -127,6 +127,7 @@ protected: eyeProjections[i] = _stereo._eyeProjections[i]; } } +protected: void getStereoViews(mat4* eyeViews) const { for (int i = 0; i < 2; ++i) { From 67cf08e8aed58f9268b00289e26761b137b508e5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 10:24:30 -0800 Subject: [PATCH 06/10] 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.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyTypeDataDescription
Buttons
Anumbernumber"A" button pressed.
Bnumbernumber"B" button pressed.
Xnumbernumber"X" button pressed.
Ynumbernumber"Y" button pressed.
LeftApplicationMenunumbernumberLeft application menu button pressed. + *
RightApplicationMenunumbernumberRight application menu button pressed. + *
Sticks
LXnumbernumberLeft stick x-axis scale.
LYnumbernumberLeft stick y-axis scale.
RXnumbernumberRight stick x-axis scale.
RYnumbernumberRight stick y-axis scale.
LSnumbernumberLeft stick button pressed.
RSnumbernumberRight stick button pressed.
LSTouchnumbernumberLeft stick is touched.
RSTouchnumbernumberRight stick is touched.
Triggers
LTnumbernumberLeft trigger scale.
RTnumbernumberRight trigger scale.
LeftGripnumbernumberLeft grip scale.
RightGripnumbernumberRight grip scale.
Finger Abstractions
LeftPrimaryThumbTouchnumbernumberLeft thumb touching primary thumb + * button.
LeftSecondaryThumbTouchnumbernumberLeft thumb touching secondary thumb + * button.
LeftThumbUpnumbernumberLeft thumb not touching primary or secondary + * thumb buttons.
RightPrimaryThumbTouchnumbernumberRight thumb touching primary thumb + * button.
RightSecondaryThumbTouchnumbernumberRight thumb touching secondary thumb + * button.
RightThumbUpnumbernumberRight thumb not touching primary or secondary + * thumb buttons.
LeftPrimaryIndexTouchnumbernumberLeft index finger is touching primary + * index finger control.
LeftIndexPointnumbernumberLeft index finger is pointing, not touching + * primary or secondary index finger controls.
RightPrimaryIndexTouchnumbernumberRight index finger is touching primary + * index finger control.
RightIndexPointnumbernumberRight index finger is pointing, not touching + * primary or secondary index finger controls.
Avatar Skeleton
Headnumber{@link Pose}Head pose.
LeftHandnumber{@link Pose}Left hand pose.
RightHandnumber{@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 + +#include + +#include +#include + +class OculusMobileControllerManager : public InputPlugin { +Q_OBJECT +public: + // Plugin functions + bool isSupported() const override; + const QString getName() const override { return NAME; } + bool isHandController() const override; + bool isHeadController() const override { return true; } + QStringList getSubdeviceNames() override; + + bool activate() override; + void deactivate() override; + + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + +private: + static const char* NAME; + + void checkForConnectedDevices(); +}; + +#endif // hifi__OculusMobileControllerManager diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp new file mode 100644 index 0000000000..34ba130c71 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -0,0 +1,269 @@ +// +// 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 "OculusMobileDisplayPlugin.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ovr; + +const char* OculusMobileDisplayPlugin::NAME { "Oculus Rift" }; +//thread_local bool renderThread = false; +#define OCULUS_APP_ID 2331695256865113 + +OculusMobileDisplayPlugin::OculusMobileDisplayPlugin() { + +} + +OculusMobileDisplayPlugin::~OculusMobileDisplayPlugin() { +} + +void OculusMobileDisplayPlugin::init() { + Parent::init(); + initVr(); + + emit deviceConnected(getName()); +} + +void OculusMobileDisplayPlugin::deinit() { + shutdownVr(); + Parent::deinit(); +} + +bool OculusMobileDisplayPlugin::internalActivate() { + _renderTargetSize = { 1024, 512 }; + _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 0.0f, 0.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); + + + withOvrJava([&](const ovrJava* java){ + _renderTargetSize = glm::uvec2{ + vrapi_GetSystemPropertyInt(java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH), + vrapi_GetSystemPropertyInt(java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT), + }; + }); + + ovr::for_each_eye([&](ovrEye eye){ + _eyeProjections[eye] = _cullingProjection; + }); + + // This must come after the initialization, so that the values calculated + // above are available during the customizeContext call (when not running + // in threaded present mode) + return Parent::internalActivate(); +} + +void OculusMobileDisplayPlugin::internalDeactivate() { + Parent::internalDeactivate(); + // ovr::releaseRenderSession(_session); +} + +void OculusMobileDisplayPlugin::customizeContext() { + qWarning() << "QQQ" << __FUNCTION__ << "done"; + gl::initModuleGl(); + _mainContext = _container->getPrimaryWidget()->context(); + _mainContext->makeCurrent(); + ovr::VrHandler::setHandler(this); + _mainContext->doneCurrent(); + _mainContext->makeCurrent(); + Parent::customizeContext(); + qWarning() << "QQQ" << __FUNCTION__ << "done"; +} + +void OculusMobileDisplayPlugin::uncustomizeContext() { + ovr::VrHandler::setHandler(nullptr); + _mainContext->doneCurrent(); + _mainContext->makeCurrent(); + Parent::uncustomizeContext(); +} + +QRectF OculusMobileDisplayPlugin::getPlayAreaRect() { + QRectF result; + VrHandler::withOvrMobile([&](ovrMobile* session){ + ovrPosef pose; + ovrVector3f scale; + if (ovrSuccess != vrapi_GetBoundaryOrientedBoundingBox(session, &pose, &scale)) { + return; + } + // FIXME extract the center from the pose + glm::vec2 center { 0 }; + glm::vec2 dimensions = glm::vec2(scale.x, scale.z); + dimensions *= 2.0f; + result = QRectF(center.x, center.y, dimensions.x, dimensions.y); + }); + return result; +} + +glm::mat4 OculusMobileDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ + auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); + result = ovr::Fov{ trackingState.Eye[eye].ProjectionMatrix }.withZ(baseProjection); + }); + return result; +} + +glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ + auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); + ovr::Fov fovs[2]; + for (size_t i = 0; i < 2; ++i) { + fovs[i].extract(trackingState.Eye[i].ProjectionMatrix); + } + fovs[0].extend(fovs[1]); + return fovs[0].withZ(baseProjection); + }); + return result; +} + +void OculusMobileDisplayPlugin::resetSensors() { + VrHandler::withOvrMobile([&](ovrMobile* session){ + vrapi_RecenterPose(session); + }); + _currentRenderFrameInfo.renderPose = glm::mat4(); // identity +} + +float OculusMobileDisplayPlugin::getTargetFrameRate() const { + float result = 0.0f; + VrHandler::withOvrJava([&](const ovrJava* java){ + result = vrapi_GetSystemPropertyFloat(java, VRAPI_SYS_PROP_DISPLAY_REFRESH_RATE); + }); + return result; +} + +bool OculusMobileDisplayPlugin::isHmdMounted() const { + bool result = false; + VrHandler::withOvrJava([&](const ovrJava* java){ + result = VRAPI_FALSE != vrapi_GetSystemStatusInt(java, VRAPI_SYS_STATUS_MOUNTED); + }); + return result; +} + +static void goToDevMobile() { + auto addressManager = DependencyManager::get(); + auto currentAddress = addressManager->currentAddress().toString().toStdString(); + if (std::string::npos == currentAddress.find("dev-mobile")) { + addressManager->handleLookupString("hifi://dev-mobile/495.236,501.017,482.434/0,0.97452,0,-0.224301"); + //addressManager->handleLookupString("hifi://dev-mobile/504,498,491/0,0,0,0"); + //addressManager->handleLookupString("hifi://dev-mobile/0,-1,1"); + } +} + +// Called on the render thread, establishes the rough tracking for the upcoming +bool OculusMobileDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + static QAndroidJniEnvironment* jniEnv = nullptr; + if (nullptr == jniEnv) { + jniEnv = new QAndroidJniEnvironment(); + } + bool result = false; + _currentRenderFrameInfo = FrameInfo(); + ovrTracking2 trackingState = {}; + static bool resetTrackingTransform = true; + static glm::mat4 transformOffset; + + VrHandler::withOvrMobile([&](ovrMobile* session){ + if (resetTrackingTransform) { + auto pose = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_FLOOR_LEVEL); + transformOffset = glm::inverse(ovr::toGlm(pose)); + vrapi_SetTrackingTransform( session, pose); + resetTrackingTransform = false; + } + // Find a better way of + _currentRenderFrameInfo.predictedDisplayTime = vrapi_GetPredictedDisplayTime(session, currentPresentIndex() + 2); + trackingState = vrapi_GetPredictedTracking2(session, _currentRenderFrameInfo.predictedDisplayTime); + result = true; + }); + + + + if (result) { + _currentRenderFrameInfo.renderPose = transformOffset; + withNonPresentThreadLock([&] { + _currentRenderFrameInfo.sensorSampleTime = trackingState.HeadPose.TimeInSeconds; + _currentRenderFrameInfo.renderPose = transformOffset * ovr::toGlm(trackingState.HeadPose.Pose); + _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + _frameInfos[frameIndex] = _currentRenderFrameInfo; + _ipd = vrapi_GetInterpupillaryDistance(&trackingState); + ovr::for_each_eye([&](ovrEye eye){ + _eyeProjections[eye] = ovr::toGlm(trackingState.Eye[eye].ProjectionMatrix); + _eyeOffsets[eye] = glm::translate(mat4(), vec3{ _ipd * (eye == VRAPI_EYE_LEFT ? -0.5f : 0.5f), 0.0f, 0.0f }); + }); + }); + } + + // static uint32_t count = 0; + // if ((++count % 1000) == 0) { + // AbstractViewStateInterface::instance()->postLambdaEvent([] { + // goToDevMobile(); + // }); + // } + + return result && Parent::beginFrameRender(frameIndex); +} + +ovrTracking2 presentTracking; + +void OculusMobileDisplayPlugin::updatePresentPose() { + static QAndroidJniEnvironment* jniEnv = nullptr; + if (nullptr == jniEnv) { + jniEnv = new QAndroidJniEnvironment(); + } + VrHandler::withOvrMobile([&](ovrMobile* session){ + presentTracking = beginFrame(); + _currentPresentFrameInfo.sensorSampleTime = vrapi_GetTimeInSeconds(); + _currentPresentFrameInfo.predictedDisplayTime = presentTracking.HeadPose.TimeInSeconds; + _currentPresentFrameInfo.presentPose = ovr::toGlm(presentTracking.HeadPose.Pose); + }); +} + +void OculusMobileDisplayPlugin::internalPresent() { + VrHandler::pollTask(); + + if (!vrActive()) { + QThread::msleep(1); + return; + } + + auto sourceTexture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); + glm::uvec2 sourceSize{ _compositeFramebuffer->getWidth(), _compositeFramebuffer->getHeight() }; + VrHandler::presentFrame(sourceTexture, sourceSize, presentTracking); + _presentRate.increment(); +} + +DisplayPluginList getDisplayPlugins() { + static DisplayPluginList result; + static std::once_flag once; + std::call_once(once, [&]{ + auto plugin = std::make_shared(); + plugin->isSupported(); + result.push_back(plugin); + }); + return result; +} + diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h new file mode 100644 index 0000000000..4a0a21e995 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h @@ -0,0 +1,65 @@ +// +// 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 + +#include +#include + +typedef struct ovrTextureSwapChain ovrTextureSwapChain; +typedef struct ovrMobile ovrMobile; +typedef struct ANativeWindow ANativeWindow; + +class OculusMobileDisplayPlugin : public HmdDisplayPlugin, public ovr::VrHandler { + using Parent = HmdDisplayPlugin; +public: + OculusMobileDisplayPlugin(); + virtual ~OculusMobileDisplayPlugin(); + bool isSupported() const override { return true; }; + bool hasAsyncReprojection() const override { return true; } + bool getSupportsAutoSwitch() override final { return false; } + QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; } + + glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; + glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; + + // Stereo specific methods + void resetSensors() override final; + bool beginFrameRender(uint32_t frameIndex) override; + + QRectF getPlayAreaRect() override; + float getTargetFrameRate() const override; + void init() override; + void deinit() override; + +protected: + const QString getName() const override { return NAME; } + + bool internalActivate() override; + void internalDeactivate() override; + + void customizeContext() override; + void uncustomizeContext() override; + + void updatePresentPose() override; + void internalPresent() override; + void hmdPresent() override { throw std::runtime_error("Unused"); } + bool isHmdMounted() const override; + + static const char* NAME; + mutable gl::Context* _mainContext{ nullptr }; + uint32_t _readFbo; +}; + From eb0566b94eeb47324302dc51237e831fcb0219db Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 13:21:38 -0800 Subject: [PATCH 07/10] Fix signing configs --- android/apps/framePlayer/build.gradle | 8 ++++---- android/apps/questFramePlayer/build.gradle | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/apps/framePlayer/build.gradle b/android/apps/framePlayer/build.gradle index fc8651fce1..a210c8300e 100644 --- a/android/apps/framePlayer/build.gradle +++ b/android/apps/framePlayer/build.gradle @@ -3,10 +3,10 @@ apply plugin: 'com.android.application' android { signingConfigs { release { - keyAlias 'key0' - keyPassword 'password' - storeFile file('C:/android/keystore.jks') - storePassword 'password' + storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null + storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' + keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' + keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' } } diff --git a/android/apps/questFramePlayer/build.gradle b/android/apps/questFramePlayer/build.gradle index 13d806c3a4..899f9cb955 100644 --- a/android/apps/questFramePlayer/build.gradle +++ b/android/apps/questFramePlayer/build.gradle @@ -3,10 +3,10 @@ apply plugin: 'com.android.application' android { signingConfigs { release { - keyAlias 'key0' - keyPassword 'password' - storeFile file('C:/android/keystore.jks') - storePassword 'password' + storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null + storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' + keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' + keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' } } From 3866c7568cb7b67d004b0102a0b1a32d68d247c5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 13:27:52 -0800 Subject: [PATCH 08/10] Update dockerfile --- android/docker/Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index c37f73cb2a..fe3a83950a 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -73,13 +73,10 @@ RUN mkdir "$HIFI_BASE" && \ RUN git clone https://github.com/jherico/hifi.git && \ cd ~/hifi && \ - git checkout feature/quest_move_interface + git checkout feature/quest_frame_player WORKDIR /home/jenkins/hifi -RUN touch .test6 && \ - git fetch && git reset origin/feature/quest_move_interface --hard - RUN mkdir build # Pre-cache the vcpkg managed dependencies From 2db5bbd38103a19ca076c6116bea0ecf350479fc Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 30 Jan 2019 14:45:31 -0800 Subject: [PATCH 09/10] Fix mac frame player --- tools/gpu-frame-player/src/RenderThread.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index a9dcf8900f..ff0d7630e5 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -32,9 +32,12 @@ void RenderThread::initialize(QWindow* window) { _window = window; #ifdef USE_GL + _window->setFormat(getDefaultOpenGLSurfaceFormat()); _context.setWindow(window); _context.create(); - _context.makeCurrent(); + if (!_context.makeCurrent()) { + qFatal("Unable to make context current"); + } QOpenGLContextWrapper(_context.qglContext()).makeCurrent(_window); glGenTextures(1, &_externalTexture); glBindTexture(GL_TEXTURE_2D, _externalTexture); From 9714271a2a3080ef56813c8a1cd470d5b316ab19 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Jan 2019 15:22:13 -0800 Subject: [PATCH 10/10] Migrate shutdown hack applied to QtActivity to InterfaceActivity --- .../hifiinterface/InterfaceActivity.java | 22 ++++++++++++++++++- .../qt5/android/bindings/QtActivity.java | 20 +---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index b7d2157737..6428044df0 100644 --- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -31,6 +31,7 @@ import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.SlidingDrawer; +import org.qtproject.qt5.android.QtNative; import org.qtproject.qt5.android.QtLayout; import org.qtproject.qt5.android.QtSurface; import org.qtproject.qt5.android.bindings.QtActivity; @@ -166,8 +167,27 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW @Override protected void onDestroy() { - super.onDestroy(); nativeOnDestroy(); + /* + cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly + After Qt upgrade to 5.11 we had a black screen crash after closing the application with + the hardware button "Back" and trying to start the app again. It could only be fixed after + totally closing the app swiping it in the list of running apps. + This problem did not happen with the previous Qt version. + After analysing changes we came up with this case and change: + https://codereview.qt-project.org/#/c/218882/ + In summary they've moved libs loading to the same thread as main() and as a matter of correctness + in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();` + That exit call is the main reason of this problem. + + In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the + entire onDestroy method including that thread exit line for other three lines that purposely + terminate qt (borrowed from QtActivityDelegate::onDestroy as well). + */ + QtNative.terminateQt(); + QtNative.setActivity(null, null); + System.exit(0); + super.onDestroy(); } @Override diff --git a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java index 93ae2bc227..40e1863d69 100644 --- a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java @@ -364,25 +364,7 @@ public class QtActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); - /* - cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly - After Qt upgrade to 5.11 we had a black screen crash after closing the application with - the hardware button "Back" and trying to start the app again. It could only be fixed after - totally closing the app swiping it in the list of running apps. - This problem did not happen with the previous Qt version. - After analysing changes we came up with this case and change: - https://codereview.qt-project.org/#/c/218882/ - In summary they've moved libs loading to the same thread as main() and as a matter of correctness - in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();` - That exit call is the main reason of this problem. - - In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the - entire onDestroy method including that thread exit line for other three lines that purposely - terminate qt (borrowed from QtActivityDelegate::onDestroy as well). - */ - QtNative.terminateQt(); - QtNative.setActivity(null, null); - System.exit(0); + QtApplication.invokeDelegate(); } //---------------------------------------------------------------------------