├── .gitignore ├── sample ├── sample.pro └── main.cpp ├── README.md ├── LICENSE.md ├── src ├── ovrwindow.pri ├── OVRWindow.h └── OVRWindow.cpp ├── test └── pre-commit └── INSTALL.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore prototypes and auto-generated files. 2 | *.pro.user* 3 | prototype 4 | Makefile 5 | build 6 | -------------------------------------------------------------------------------- /sample/sample.pro: -------------------------------------------------------------------------------- 1 | # Path to the OVRWindow source code tree. 2 | OVRWINDOW = ../src 3 | 4 | # OVRWindow configuration. 5 | include($$OVRWINDOW/ovrwindow.pri) 6 | 7 | # OVRWindow source. 8 | INCLUDEPATH += $$OVRWINDOW 9 | HEADERS += $$OVRWINDOW/OVRWindow.h 10 | SOURCES += $$OVRWINDOW/OVRWindow.cpp 11 | 12 | # The sample project's build configuration. 13 | TEMPLATE = app 14 | TARGET = sample 15 | DESTDIR = build 16 | UI_DIR = $$DESTDIR/ui 17 | MOC_DIR = $$DESTDIR/moc 18 | OBJECTS_DIR = $$DESTDIR/obj 19 | QMAKE_CXXFLAGS += -Wall -Wextra 20 | SOURCES += main.cpp 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OVRWindow 2 | 3 | OVRWindow is an Oculus SDK wrapper for Qt applications that uses the OpenGL API. The API's full documentation can be found [__here__](https://othieno.github.io/OVRWindow). 4 | 5 | 6 | ## Using OVRWindow 7 | 8 | Before using OVRWindow, make sure you have installed its dependencies and correctly set up your build environment. For help on doing so, please refer to the [installation guide](INSTALL.md). 9 | 10 | Included in the source code tree is __ovrwindow.pri__, a project include file that makes it easy to integrate OVRWindow and its dependencies into your own projects. Simply include it in your project file (*.pro). 11 | 12 | Next, add the locations of __OVRWindow.h__ and __OVRWindow.cpp__ to the 13 | __HEADERS__ and __SOURCES__ variables in your project file, respectively. 14 | 15 | Check out the sample's project's [configuration](sample/sample.pro) for a working project file example. 16 | 17 | ## Project Hierarchy 18 | 19 | The folders provided with this software are structured in the following manner: 20 | * __sample__ contains a simple example on how to use OVRWindow. 21 | * __src__ contains the source code tree. 22 | * __tst__ contains unit tests. 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2014 Jeremy Othieno. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/ovrwindow.pri: -------------------------------------------------------------------------------- 1 | lessThan(QT_MAJOR_VERSION, 5):error(The OVRWindow API requires Qt5 or later. Make sure your version of qmake is using Qt5 libraries at least.) 2 | 3 | # Make sure the OVRSDK environment variable is defined... 4 | OVRSDK = $$(OVRSDK) 5 | isEmpty(OVRSDK):error(The OVRSDK environment variable is not defined.) 6 | !exists($$OVRSDK):error("The OVRSDK environment variable is defined, but \'$$OVRSDK\' does not exist.") 7 | 8 | # ...and contains a path to a valid Oculus SDK installation. 9 | LIBOVR = $$OVRSDK/LibOVR 10 | !exists($$LIBOVR/Include/OVR.h):error("The OVRSDK environment variable is defined, but \'$$OVRSDK\' does not contain a valid Oculus SDK installation.") 11 | 12 | # Use gui-private to gain access to the platform's native interface. 13 | QT += gui-private 14 | 15 | # Add modern C++ support. 16 | CONFIG += c++14 17 | 18 | # Common build configuration. 19 | INCLUDEPATH += $$LIBOVR/Include $$LIBOVR/Src 20 | LIBS += -lovr 21 | 22 | # GNU/Linux build configuration. 23 | unix:!macx { 24 | eval(QMAKE_HOST.arch = x86_64): LIBS += -L$$LIBOVR/Lib/Linux/Release/x86_64 25 | else: LIBS += -L$$LIBOVR/Lib/Linux/Release/i386 26 | LIBS += -lX11 -lXinerama -lXrandr -ludev 27 | } 28 | -------------------------------------------------------------------------------- /test/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script is part of the OVRWindow project (https://github.com/othieno/OVRWindow). 4 | # It performs static code analysis on the project's source code tree and fails if 5 | # any one of the commands has an exit status that is not zero (failure). 6 | # 7 | # The MIT License (MIT) 8 | # 9 | # Copyright (c) 2014-2016 Jeremy Othieno. 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy 12 | # of this software and associated documentation files (the "Software"), to deal 13 | # in the Software without restriction, including without limitation the rights 14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | # copies of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be included in 19 | # all copies or substantial portions of the Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | # THE SOFTWARE. 28 | set -e 29 | set -o pipefail 30 | 31 | SRCDIR="$(git rev-parse --show-toplevel)/src" 32 | 33 | # Perform static analysis on the OVRWindow API. 34 | cppcheck "$SRCDIR" --enable=warning,style,performance,portability --language=c++ -q --error-exitcode=1 35 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Installation Guide 2 | 3 | This document describes how to install the dependencies required by OVRWindow. 4 | 5 | OVRWindow requires the [Qt GUI module][qtgui] introduced in [Qt 5][qt5]. It also uses modern C++ features and therefore requires a compiler that implements the C++14 standard, at least. 6 | 7 | 8 | 9 | ### Linux (Debian) 10 | 11 | #### Qt5 private header files 12 | 13 | While Qt5 provides a modern way of using the OpenGL API, it is still does not provide (as of September 2016) an easy way to access to the native display device (required by the Oculus SDK). The current approach is to use a part of the [Qt Platform Abstraction][qpa] (QPA), an abstraction layer still in development, that does not guarantee source or binary compatibility. This is not a portable solution but will very likely become one as the QPA matures. 14 | To use the QPA, you will need to install Qt's private header development files 15 | ```bash 16 | sudo apt-get install qtbase5-private-dev 17 | ``` 18 | 19 | 20 | #### Oculus SDK 21 | 22 | OVRWindow depends on the C API introduced in Oculus SDK 0.3 and later. It is currently developed against [Oculus SDK v0.3.2-preview][ovrsdk] on Linux. 23 | 24 | OVRWindow will need to know where to find your Oculus SDK installation. It will look for the __OVRSDK__1 environment variable which should contain the location of your SDK installation. 25 | 26 | If you are building your project from the command line, the variable can be exported by running 27 | ```bash 28 | export OVRSDK= 29 | ``` 30 | where `` is the location of your SDK installation. 31 | 32 | On the other hand, if you are building a project from Qt Creator, the variable can be added to the current build configuration's *Build Environment* found under [Projects] > [Build & Run]. Remember that each build configuration has its own build environment so if you do change the build configuration, you may have to update its build environment. 33 | 34 |
35 |
36 | 37 | 1. 38 | If the name __OVRSDK__ conflicts with your current environment, line #4 of __ovrwindow.pri__ can be changed from `OVRSDK = $$(OVRSDK)` to `OVRSDK = $$()` where `` 39 | is either an environment variable that contains the absolute path to a valid installation of the Oculus SDK, or the path itself. 40 | 41 | 42 | 43 | [qt5]: https://doc.qt.io/qt-5/qt5-intro.html 44 | [qtgui]: https://doc.qt.io/qt-5/qtgui-index.html 45 | [qpa]: https://wiki.qt.io/Qt_Platform_Abstraction 46 | [ovrsdk]: https://developer3.oculus.com/downloads/pc/0.3.2-preview-2/Oculus_SDK_for_Linux/ 47 | -------------------------------------------------------------------------------- /sample/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2016 Jeremy Othieno. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | class SpinningCubeWindow : public OVRWindow { 30 | public: 31 | void initializeGL() override final; 32 | void paintGL(const ovrEyeType, const OVRWindow::RenderTransforms&, const float) override final; 33 | private: 34 | GLfloat _angle; 35 | }; 36 | 37 | 38 | int main(int argc, char **argv) { 39 | QGuiApplication application(argc, argv); 40 | 41 | SpinningCubeWindow window; 42 | window.setTitle("OVRWindow : Spinning Cube"); 43 | window.showFullScreen(); 44 | 45 | return application.exec(); 46 | } 47 | 48 | 49 | void 50 | SpinningCubeWindow::initializeGL() { 51 | glClearColor(0.25f, 0.5f, 0.75f, 1.0f); 52 | glEnable(GL_CULL_FACE); 53 | glEnable(GL_DEPTH_TEST); 54 | glEnable(GL_LIGHTING); 55 | glEnable(GL_BLEND); 56 | 57 | glDisable(GL_TEXTURE_2D); 58 | 59 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 60 | glShadeModel(GL_SMOOTH); 61 | 62 | // Add some stationary lights. 63 | const GLfloat position[2][4] = {{5.0f, 6.0f, 3.0f, 0.0f}, {-5.0f,-6.0f, 5.0f, 0.0f}}; 64 | const GLfloat diffuse[2][4] = {{1.0f, 0.8f, 0.6f, 1.0f}, {0.6f, 0.8f, 1.0f, 1.0f}}; 65 | const GLenum light[2] = {GL_LIGHT0, GL_LIGHT1}; 66 | for (unsigned int i = 0; i < 2; ++i) { 67 | glLightfv(light[i], GL_POSITION, position[i]); 68 | glLightfv(light[i], GL_DIFFUSE, diffuse[i]); 69 | glEnable(light[i]); 70 | } 71 | 72 | // Define the object's material. 73 | const GLfloat specular[] = {0.3f, 0.3f, 0.3f, 1.0f}; 74 | const GLfloat shininess = 10.0f; 75 | glMaterialfv(GL_FRONT, GL_SPECULAR, specular); 76 | glMaterialfv(GL_FRONT, GL_SHININESS, &shininess); 77 | } 78 | 79 | 80 | void 81 | SpinningCubeWindow::paintGL(const ovrEyeType, const OVRWindow::RenderTransforms& transforms, const float dt) { 82 | _angle = static_cast(std::fmod(_angle + (dt * 5.0), 360.0)); 83 | 84 | glMatrixMode(GL_PROJECTION); 85 | glLoadIdentity(); 86 | glMultMatrixf(transforms.perspective.constData()); 87 | 88 | glMatrixMode(GL_MODELVIEW); 89 | glLoadIdentity(); 90 | glMultMatrixf(transforms.view.constData()); 91 | 92 | // Move the triangle away from the screen and rotate it. 93 | glTranslatef(0.0f, 0.0f, -1.5f); 94 | glRotatef(_angle, 1.0f, 0.0f, 0.0f); 95 | glRotatef(_angle, 0.0f, 1.0f, 0.0f); 96 | 97 | glBegin(GL_QUADS); 98 | glNormal3f( 0.0f, 0.0f, 1.0f); 99 | glVertex3f( 0.5f, 0.5f, 0.5f); 100 | glVertex3f(-0.5f, 0.5f, 0.5f); 101 | glVertex3f(-0.5f,-0.5f, 0.5f); 102 | glVertex3f( 0.5f,-0.5f, 0.5f); 103 | 104 | glNormal3f( 0.0f, 0.0f,-1.0f); 105 | glVertex3f(-0.5f,-0.5f,-0.5f); 106 | glVertex3f(-0.5f, 0.5f,-0.5f); 107 | glVertex3f( 0.5f, 0.5f,-0.5f); 108 | glVertex3f( 0.5f,-0.5f,-0.5f); 109 | 110 | glNormal3f( 0.0f, 1.0f, 0.0f); 111 | glVertex3f( 0.5f, 0.5f, 0.5f); 112 | glVertex3f( 0.5f, 0.5f,-0.5f); 113 | glVertex3f(-0.5f, 0.5f,-0.5f); 114 | glVertex3f(-0.5f, 0.5f, 0.5f); 115 | 116 | glNormal3f( 0.0f,-1.0f, 0.0f); 117 | glVertex3f(-0.5f,-0.5f,-0.5f); 118 | glVertex3f( 0.5f,-0.5f,-0.5f); 119 | glVertex3f( 0.5f,-0.5f, 0.5f); 120 | glVertex3f(-0.5f,-0.5f, 0.5f); 121 | 122 | glNormal3f( 1.0f, 0.0f, 0.0f); 123 | glVertex3f( 0.5f, 0.5f, 0.5f); 124 | glVertex3f( 0.5f,-0.5f, 0.5f); 125 | glVertex3f( 0.5f,-0.5f,-0.5f); 126 | glVertex3f( 0.5f, 0.5f,-0.5f); 127 | 128 | glNormal3f(-1.0f, 0.0f, 0.0f); 129 | glVertex3f(-0.5f,-0.5f,-0.5f); 130 | glVertex3f(-0.5f,-0.5f, 0.5f); 131 | glVertex3f(-0.5f, 0.5f, 0.5f); 132 | glVertex3f(-0.5f, 0.5f,-0.5f); 133 | glEnd(); 134 | } 135 | -------------------------------------------------------------------------------- /src/OVRWindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2016 Jeremy Othieno. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | /** 25 | * @mainpage 26 | * 27 | * OVRWindow is an API that works in tandem with the Oculus SDK and the OpenGL 28 | * functionality provided by the Qt library to add support for the stereo rendering 29 | * model used by the Oculus Rift to Qt applications. 30 | */ 31 | #ifndef OVRWINDOW_H 32 | #define OVRWINDOW_H 33 | #define GL_GLEXT_PROTOTYPES 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | 41 | union ovrGLConfig; 42 | union ovrGLTexture_s; 43 | typedef ovrGLTexture_s ovrGLTexture; 44 | 45 | class OVRWindow : public QWindow, protected QOpenGLFunctions { 46 | Q_OBJECT 47 | public: 48 | /** 49 | * TODO Explain me better. 50 | * 51 | * An enumeration of features that can be toggled. Be careful when disabling certain 52 | * features as it may induce simulator sickness. 53 | * 54 | * - LowPersistence eliminates motion blur and judder. Note that disabling this feature 55 | * may induce simulator sickness. 56 | * - LatencyTesting ??? 57 | * - DynamicPrediction ??? 58 | * 59 | * - OrientationTracking tracks head orientation. 60 | * - YawCorrection ??? 61 | * - PositionalTracking tracks the head's position. 62 | * 63 | * - ChromaticAberrationCorrection corrects chromatic aberration, a distortion caused by the 64 | * Rift's lenses. 65 | * - Timewarp reduces motion-to-photon latency. Note that disabling this feature may induce 66 | * simulator sickness. 67 | * - Vignette ??? 68 | */ 69 | enum class Feature : unsigned int { 70 | LowPersistence = ovrHmdCap_LowPersistence, 71 | LatencyTesting = ovrHmdCap_LatencyTest, 72 | DynamicPrediction = ovrHmdCap_DynamicPrediction, 73 | 74 | OrientationTracking = ovrSensorCap_Orientation, 75 | YawCorrection = ovrSensorCap_YawCorrection, 76 | PositionalTracking = ovrSensorCap_Position, 77 | 78 | ChromaticAberrationCorrection = ovrDistortionCap_Chromatic, 79 | Timewarp = ovrDistortionCap_TimeWarp, 80 | Vignette = ovrDistortionCap_Vignette, 81 | }; 82 | /** 83 | * TODO Explain me. 84 | */ 85 | enum class Vision { 86 | Monocular, 87 | Binocular 88 | }; 89 | /** 90 | * TODO Explain me. 91 | */ 92 | enum class LOD { 93 | Lowest, 94 | Low, 95 | Medium, 96 | High, 97 | Highest 98 | }; 99 | /** 100 | * @struct RenderTransforms 101 | * @brief An object containing the view and projection transformation matrices. 102 | * 103 | * The transformation matrices are generated based on an eye's point-of-view. 104 | */ 105 | struct RenderTransforms { 106 | QMatrix4x4 view; 107 | QMatrix4x4 perspective; 108 | QMatrix4x4 ortho; 109 | }; 110 | /** 111 | * @brief Instantiate an OVRWindow object that is attached to an Oculus Rift device. 112 | * 113 | * The instantiated object is attached to a device with the specified index, and has 114 | * has a set of enabled features. If no hardware device is detected, a debug device 115 | * that emulates some of the DK1's features is used. 116 | * 117 | * @param index a positive integer used to access an Oculus Rift device. 118 | * @param features a set of device features to enable. 119 | */ 120 | OVRWindow(const unsigned int index, const std::initializer_list& features); 121 | /** 122 | * @brief Instantiate an OVRWindow object that is attached to an Oculus Rift device. 123 | * 124 | * The instantiated object is attached to a device with the index '0', and has all device 125 | * features enabled. If no hardware device is detected, a debug device that emulates some 126 | * of the DK1's features is used. 127 | */ 128 | OVRWindow(); 129 | /** 130 | * @brief The destructor. 131 | */ 132 | virtual ~OVRWindow(); 133 | /** 134 | * @brief Returns @c true if the OVRWindow has a valid OpenGL context, @c false otherwise. 135 | */ 136 | bool hasValidGL() const; 137 | /** 138 | * @brief Return the OVRWindow's OpenGL context. 139 | */ 140 | QOpenGLContext& getGL(); 141 | /** 142 | * @brief Return the Oculus Rift's information. 143 | */ 144 | const ovrHmdDesc& getDeviceInfo() const; 145 | /** 146 | * @brief Return a set of all enabled features. 147 | */ 148 | const QSet& getEnabledFeatures() const; 149 | /** 150 | * Enable or disable a feature. 151 | * @param feature the feature to enable or disable. 152 | * @param enable true to enable the feature, false to disable it. 153 | */ 154 | void enableFeature(const OVRWindow::Feature feature, const bool enable = true); 155 | /** 156 | * Enable or disable a set of features. 157 | * @param features the features to enable or disable. 158 | * @param enable true to enable the features, false to disable them. 159 | */ 160 | void enableFeatures(const std::initializer_list& features, const bool enable = true); 161 | /** 162 | * Returns true if the specified feature is enabled, false otherwise. 163 | * @param feature the feature to query. 164 | */ 165 | bool isFeatureEnabled(const OVRWindow::Feature feature) const; 166 | /** 167 | * Returns true if the specified feature is supported by the device, false otherwise. 168 | * @param feature the feature to query. 169 | */ 170 | bool isFeatureSupported(const OVRWindow::Feature feature) const; 171 | /** 172 | * Return the current vision mode. 173 | */ 174 | OVRWindow::Vision getVision() const; 175 | /** 176 | * Set the vision. 177 | * @param vision the vision to set. 178 | */ 179 | void setVision(const OVRWindow::Vision vision); 180 | /** 181 | * Return the current level of detail. 182 | */ 183 | OVRWindow::LOD getLOD() const; 184 | /** 185 | * Set the current level of detail (LOD). The LOD determines which features 186 | * are enabled or disabled with the goal of reducing frame render time, 187 | * thereby increasing performance. Note that unless dynamic LOD is disabled, 188 | * the LOD set by this member function will change. 189 | * @param lod the level of detail to set. 190 | */ 191 | void setLOD(const OVRWindow::LOD lod); 192 | /** 193 | * Return the current interpupillary distance (IPD) in millimeters. 194 | */ 195 | float getIPD() const; 196 | /** 197 | * Set the interpupillary distance (IPD) in millimeters. 198 | * @param ipd the distance to set. 199 | */ 200 | void setIPD(const float ipd); 201 | /** 202 | * TODO Explain me. 203 | */ 204 | void forceZeroIPD(const bool force); 205 | /** 206 | * TODO Explain me. 207 | */ 208 | float getPixelDensity() const; 209 | /** 210 | * TODO Explain me. 211 | */ 212 | void setPixelDensity(const float density); 213 | /** 214 | * Return the viewing frustum's near clipping plane distance. 215 | */ 216 | float getNearClippingDistance() const; 217 | /** 218 | * Set the viewing frustum's near clipping plane distance. 219 | * @param near the near clipping plane's distance. 220 | */ 221 | void setNearClippingDistance(const float near); 222 | /** 223 | * Return the viewing frustum's far clipping plane distance. 224 | */ 225 | float getFarClippingDistance() const; 226 | /** 227 | * Set the viewing frustum's far clipping plane distance. 228 | * @param far the far clipping plane's distance. 229 | */ 230 | void setFarClippingDistance(const float far); 231 | /** 232 | * Returns true if multisampling is enabled, false otherwise. 233 | */ 234 | bool isMultisamplingEnabled() const; 235 | /** 236 | * Enable or disable multisampling. 237 | * @param enable true to enable multisampling, false to disable. 238 | */ 239 | void enableMultisampling(const bool enable = true); 240 | protected: 241 | /** 242 | * @brief Initialize OpenGL. 243 | */ 244 | virtual void initializeGL(); 245 | /** 246 | * TODO Explain me better. 247 | * @brief This virtual function is called whenever a new frame needs to be rendered. 248 | */ 249 | virtual void paintGL(const ovrEyeType eye, const OVRWindow::RenderTransforms& transforms, const float dt); 250 | /** 251 | * @brief This virtual function is called whenever the window is resized. 252 | * 253 | * @param width the window's new width. 254 | * @param height the window's new height. 255 | */ 256 | virtual void resizeGL(const unsigned int width, const unsigned int height); 257 | /** 258 | * Make the window's rendering context the current OpenGL context. 259 | */ 260 | void makeCurrent(); 261 | /** 262 | * Makes no GL context the current context. This may be useful in multi-threaded environments. 263 | */ 264 | void doneCurrent(); 265 | /** 266 | * @brief This virtual function is called whenever the level of detail (LOD) is changed. 267 | * 268 | * @param lod the new level of detail. 269 | */ 270 | virtual void changeLOD(const OVRWindow::LOD lod); 271 | private: 272 | /** 273 | * Updates the window. 274 | */ 275 | void updateGL(); 276 | /** 277 | * Make a request to update the window by pushing an UpdateRequest 278 | * event on the event loop which will call updateGL later on. 279 | */ 280 | void requestUpdateGL(); 281 | /** 282 | * Configure the underlying OpenGL API for use with this interface. 283 | */ 284 | void configureGL(); 285 | /** 286 | * TODO Explain me. 287 | */ 288 | void paintGL(); 289 | /** 290 | * TODO Explain me. 291 | */ 292 | ovrGLConfig& getOvrGlConfig() const; 293 | /** 294 | * TODO Explain me. 295 | */ 296 | ovrGLTexture& getOvrGlTexture(const ovrEyeType eye) const; 297 | /** 298 | * Update an outdated render target configuration. 299 | */ 300 | void sanitizeRenderTargetConfiguration(); 301 | /** 302 | * Update an outdated device configuration. 303 | */ 304 | void sanitizeDeviceConfiguration(); 305 | /** 306 | * Update an outdated rendering configuration. 307 | */ 308 | void sanitizeRenderingConfiguration(); 309 | /** 310 | * @see QWindow::resizeEvent. This implementation of the resize event handler 311 | * is used to update OpenGL when the window is resized. 312 | */ 313 | void resizeEvent(QResizeEvent* const) override final; 314 | /** 315 | * @see QWindow::exposeEvent. This implementation of the expose event handler 316 | * is used to initialize the OpenGL context when the window is first exposed. 317 | */ 318 | void exposeEvent(QExposeEvent* const) override final; 319 | /** 320 | * @see QObject::event. This implementation of the generic event handler 321 | * is used to process update requests. 322 | */ 323 | bool event(QEvent* const) override final; 324 | /** 325 | * Returns the transformation matrices for a given eye. 326 | * @param eye the eye for which we wish to retrieve a frame render context. 327 | * @param pose the head pose. 328 | */ 329 | const OVRWindow::RenderTransforms& getRenderTransforms(const ovrEyeType eye, const ovrPosef& pose); 330 | /** 331 | * The device structure contains information about the device and its capabilities. 332 | */ 333 | const ovrHmdDesc _device; 334 | /** 335 | * Sets of enabled features. 336 | */ 337 | QSet _enabledFeatures; 338 | /** 339 | * The OpenGL context. 340 | */ 341 | QOpenGLContext _gl; 342 | /** 343 | * TODO Explain me. 344 | */ 345 | bool _pendingUpdateRequest; 346 | /** 347 | * The render target which includes an FBO handle, texture handle and a resolution. 348 | */ 349 | struct { 350 | GLuint fbo; 351 | GLuint pixel; 352 | GLuint depth; 353 | QSize resolution; 354 | } _renderTarget; 355 | /** 356 | * The field of view (FOV) for each eye. 357 | */ 358 | ovrFovPort _FOV[ovrEye_Count]; 359 | /** 360 | * The render information for each eye. 361 | */ 362 | ovrEyeRenderDesc _renderInfo[ovrEye_Count]; 363 | /** 364 | * The render context for each eye. 365 | */ 366 | OVRWindow::RenderTransforms _renderTransforms[ovrEye_Count]; 367 | /** 368 | * The viewing frustum's near clipping plane distance. 369 | */ 370 | float _nearClippingPlaneDistance; 371 | /** 372 | * The viewing frustum's far clipping plane distance. 373 | */ 374 | float _farClippingPlaneDistance; 375 | /** 376 | * TODO Explain me. 377 | */ 378 | bool _forceZeroIPD; 379 | /** 380 | * TODO Explain me. 381 | */ 382 | float _pixelDensity; 383 | /** 384 | * The vision mode. 385 | */ 386 | OVRWindow::Vision _vision; 387 | /** 388 | * The interface's level of detail. 389 | */ 390 | OVRWindow::LOD _LOD; 391 | /** 392 | * This set of variables keeps track of dirty configurations. 393 | */ 394 | struct { 395 | bool renderTarget; 396 | bool rendering; 397 | struct { bool hmd, sensor; } device; 398 | bool projections[ovrEye_Count]; 399 | } _dirty; 400 | public slots: 401 | /** 402 | * @brief Toggle vision modes. 403 | */ 404 | void toggleVision(); 405 | /** 406 | * @brief Reduce the interface's level of detail. 407 | */ 408 | void reduceLOD(); 409 | /** 410 | * @brief Increase the interface's level of detail. 411 | */ 412 | void increaseLOD(); 413 | /** 414 | * @brief Toggle multisampling. 415 | */ 416 | void toggleMultisampling(); 417 | signals: 418 | /** 419 | * This signal is emitted when the interface has been correctly initialized and is ready for use. 420 | */ 421 | void initialized(); 422 | /** 423 | * This signal is emitted when the interface's level of detail (LOD) has been changed. 424 | * @param currentLOD the interface's current level of detail. 425 | */ 426 | void LODChanged(const OVRWindow::LOD currentLOD); 427 | }; 428 | 429 | #endif // OVRWINDOW_H 430 | -------------------------------------------------------------------------------- /src/OVRWindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2016 Jeremy Othieno. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #include "OVRWindow.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #if defined(Q_OS_LINUX) 34 | #define OVR_OS_LINUX 35 | #elif defined(Q_OS_MAC) 36 | #define OVR_OS_MAC 37 | #elif defined(Q_OS_WIN32) 38 | #define OVR_OS_WIN32 39 | #endif 40 | #include 41 | 42 | 43 | #if defined(Q_OS_LINUX) 44 | /** 45 | * Returns the specified window's native X display. 46 | */ 47 | Display* 48 | getXDisplay(QWindow* const window) { 49 | Display* display = nullptr; 50 | QPlatformNativeInterface* const native = QGuiApplication::platformNativeInterface(); 51 | if (native != nullptr) { 52 | display = static_cast(native->nativeResourceForWindow("display", window)); 53 | } 54 | return display; 55 | } 56 | #endif 57 | 58 | 59 | /** 60 | * Returns the specified OVRWindow::Feature's hash value, as required by QSet. 61 | * @param feature the feature identifier to hash. 62 | */ 63 | uint 64 | qHash(const OVRWindow::Feature feature) { 65 | // Make sure OVRWindow::Feature's underlying type is an unsigned integer, making hashing trivial. 66 | assert((std::is_same::type>::value)); 67 | return static_cast(feature); 68 | } 69 | 70 | 71 | OVRWindow::OVRWindow(const unsigned int index, const std::initializer_list& features) : 72 | QWindow(static_cast(nullptr)), 73 | _device(), 74 | _pendingUpdateRequest(false), 75 | _renderTarget({0, 0, 0, QSize(0, 0)}), 76 | _nearClippingPlaneDistance(0.01f), 77 | _farClippingPlaneDistance(10000.0f), 78 | _forceZeroIPD(false), 79 | _pixelDensity(1.0f), 80 | _vision(OVRWindow::Vision::Binocular), 81 | _LOD(OVRWindow::LOD::Highest), 82 | _dirty({true, true, {true, true}, {true, true}}) { 83 | // Only one instance of this class can be created. 84 | static std::atomic OVRWINDOW_INSTANTIATED(false); 85 | assert(!OVRWINDOW_INSTANTIATED); 86 | OVRWINDOW_INSTANTIATED = true; 87 | 88 | // Make sure the windowing system has OpenGL support. 89 | setSurfaceType(QWindow::OpenGLSurface); 90 | assert(supportsOpenGL()); 91 | 92 | // Initialize LibOVR and make sure the device index is valid. 93 | ovr_Initialize(); 94 | assert(!index || index < static_cast(ovrHmd_Detect())); 95 | 96 | // Initialize the HMD device. If no device is detected, create a debug device. 97 | auto hmd = ovrHmd_Create(index); 98 | if (!hmd) 99 | hmd = ovrHmd_CreateDebug(ovrHmd_DK1); 100 | ovrHmd_GetDesc(hmd, const_cast(&_device)); 101 | 102 | // Initialize the FOV parameters. 103 | std::copy(std::begin(_device.DefaultEyeFov), std::end(_device.DefaultEyeFov), _FOV); 104 | 105 | // Enable features. 106 | enableFeatures(features); 107 | } 108 | 109 | 110 | OVRWindow::OVRWindow() : 111 | OVRWindow(0, { 112 | OVRWindow::Feature::LowPersistence, 113 | OVRWindow::Feature::LatencyTesting, 114 | OVRWindow::Feature::DynamicPrediction, 115 | OVRWindow::Feature::OrientationTracking, 116 | OVRWindow::Feature::YawCorrection, 117 | OVRWindow::Feature::PositionalTracking, 118 | OVRWindow::Feature::ChromaticAberrationCorrection, 119 | OVRWindow::Feature::Timewarp, 120 | OVRWindow::Feature::Vignette 121 | }) {} 122 | 123 | 124 | OVRWindow::~OVRWindow() { 125 | if (_renderTarget.pixel != 0) 126 | glDeleteTextures(1, &_renderTarget.pixel); 127 | 128 | //FIXME Find out why these cause a segmentation fault in Qt5. 129 | //if (_renderTarget.depth != 0) 130 | //glDeleteRenderbuffers(1, &_renderTarget.depth); 131 | 132 | // if (_renderTarget.fbo != 0) 133 | // glDeleteFramebuffers(1, &_renderTarget.fbo); 134 | 135 | // Destroy the device and shutdown LibOVR. 136 | ovrHmd_Destroy(_device.Handle); 137 | ovr_Shutdown(); 138 | } 139 | 140 | 141 | void 142 | OVRWindow::initializeGL() {} 143 | 144 | 145 | void 146 | OVRWindow::resizeGL(const unsigned int, const unsigned int) {} 147 | 148 | 149 | void 150 | OVRWindow::paintGL(const ovrEyeType, const OVRWindow::RenderTransforms&, const float) {} 151 | 152 | 153 | bool 154 | OVRWindow::hasValidGL() const { 155 | return _gl.isValid(); 156 | } 157 | 158 | 159 | QOpenGLContext& 160 | OVRWindow::getGL() { 161 | return _gl; 162 | } 163 | 164 | 165 | void 166 | OVRWindow::makeCurrent() { 167 | const auto& result = _gl.makeCurrent(this); 168 | assert(result); 169 | } 170 | 171 | 172 | void 173 | OVRWindow::doneCurrent() { 174 | _gl.doneCurrent(); 175 | } 176 | 177 | 178 | const ovrHmdDesc& 179 | OVRWindow::getDeviceInfo() const { 180 | return _device; 181 | } 182 | 183 | 184 | const QSet& 185 | OVRWindow::getEnabledFeatures() const { 186 | return _enabledFeatures; 187 | } 188 | 189 | 190 | void 191 | OVRWindow::enableFeature(const OVRWindow::Feature feature, const bool enable) { 192 | // If the feature is already enabled and a request to enable it is made, then the 193 | // request is ignored. Likewise, if a feature is disabled and a request to disable 194 | // it is made, then the request is ignored. If a feature is enabled or disabled, 195 | // then the render configuration must be updated. 196 | if (enable != isFeatureEnabled(feature) && isFeatureSupported(feature)) { 197 | if (enable) { 198 | _enabledFeatures << feature; 199 | } else { 200 | _enabledFeatures.remove(feature); 201 | } 202 | 203 | // Mark the configuration as dirty. 204 | switch (feature) { 205 | case OVRWindow::Feature::LowPersistence: 206 | case OVRWindow::Feature::LatencyTesting: 207 | case OVRWindow::Feature::DynamicPrediction: 208 | _dirty.device.hmd = true; 209 | break; 210 | case OVRWindow::Feature::OrientationTracking: 211 | case OVRWindow::Feature::YawCorrection: 212 | case OVRWindow::Feature::PositionalTracking: 213 | _dirty.device.sensor = true; 214 | break; 215 | case OVRWindow::Feature::ChromaticAberrationCorrection: 216 | case OVRWindow::Feature::Timewarp: 217 | case OVRWindow::Feature::Vignette: 218 | _dirty.rendering = true; 219 | break; 220 | default: 221 | // Make sure all features are accounted for (incase OVRWindow::Feature gets modified in the future). 222 | assert(false); 223 | break; 224 | } 225 | } 226 | } 227 | 228 | 229 | void 230 | OVRWindow::enableFeatures(const std::initializer_list& features, const bool enable) { 231 | for (const auto& feature : features) { 232 | enableFeature(feature, enable); 233 | } 234 | } 235 | 236 | 237 | bool 238 | OVRWindow::isFeatureEnabled(const OVRWindow::Feature feature) const { 239 | return _enabledFeatures.contains(feature); 240 | } 241 | 242 | 243 | bool 244 | OVRWindow::isFeatureSupported(const OVRWindow::Feature feature) const { 245 | const auto& mask = static_cast::type>(feature); 246 | switch (feature) { 247 | // These features are not supported on all revisions of the Rift HMD. 248 | case OVRWindow::Feature::PositionalTracking: 249 | case OVRWindow::Feature::YawCorrection: 250 | return (mask & _device.SensorCaps) == _device.SensorCaps; 251 | default: 252 | return true; 253 | } 254 | } 255 | 256 | 257 | OVRWindow::Vision 258 | OVRWindow::getVision() const { 259 | return _vision; 260 | } 261 | 262 | 263 | void 264 | OVRWindow::setVision(const OVRWindow::Vision vision) { 265 | if (_vision != vision) { 266 | _vision = vision; 267 | _dirty.rendering = true; 268 | } 269 | } 270 | 271 | 272 | void 273 | OVRWindow::toggleVision() { 274 | setVision(_vision != OVRWindow::Vision::Binocular ? OVRWindow::Vision::Binocular : OVRWindow::Vision::Monocular); 275 | } 276 | 277 | 278 | OVRWindow::LOD 279 | OVRWindow::getLOD() const { 280 | return _LOD; 281 | } 282 | 283 | 284 | void 285 | OVRWindow::setLOD(const OVRWindow::LOD lod) { 286 | if (_LOD != lod) { 287 | _LOD = lod; 288 | changeLOD(_LOD); 289 | emit LODChanged(_LOD); 290 | } 291 | } 292 | 293 | 294 | void 295 | OVRWindow::changeLOD(const OVRWindow::LOD lod) { 296 | // Reset enabled features. 297 | _enabledFeatures.clear(); 298 | 299 | QSet features; 300 | switch (lod) 301 | { 302 | case OVRWindow::LOD::Highest: 303 | setPixelDensity(1.5f); 304 | break; 305 | case OVRWindow::LOD::High: 306 | setPixelDensity(1.0f); 307 | break; 308 | case OVRWindow::LOD::Medium: 309 | setPixelDensity(1.0f); 310 | break; 311 | case OVRWindow::LOD::Low: 312 | setPixelDensity(0.5f); 313 | enableMultisampling(false); 314 | break; 315 | case OVRWindow::LOD::Lowest: 316 | setPixelDensity(0.25f); 317 | enableMultisampling(false); 318 | break; 319 | default: 320 | assert(false); 321 | break; 322 | } 323 | // Enable the new feature set. 324 | for (const auto& feature : features) { 325 | enableFeature(feature, true); 326 | } 327 | } 328 | 329 | 330 | void 331 | OVRWindow::reduceLOD() { 332 | if (_LOD != OVRWindow::LOD::Lowest) { 333 | setLOD(static_cast(static_cast::type>(_LOD) - 1)); 334 | } 335 | } 336 | 337 | 338 | void 339 | OVRWindow::increaseLOD() { 340 | if (_LOD != OVRWindow::LOD::Highest) { 341 | setLOD(static_cast(static_cast::type>(_LOD) + 1)); 342 | } 343 | } 344 | 345 | 346 | float 347 | OVRWindow::getIPD() const { 348 | return ovrHmd_GetFloat(_device.Handle, OVR_KEY_IPD, OVR_DEFAULT_IPD); 349 | } 350 | 351 | 352 | void 353 | OVRWindow::setIPD(const float ipd) { 354 | // If the IPD is changed, then the render configuration needs to be updated. 355 | _dirty.rendering = ovrHmd_SetFloat(_device.Handle, OVR_KEY_IPD, ipd); 356 | } 357 | 358 | 359 | void 360 | OVRWindow::forceZeroIPD(const bool force) { 361 | // If the IPD is changed, then the render configuration needs to be updated. 362 | if (_forceZeroIPD != force) { 363 | _forceZeroIPD = force; 364 | _dirty.rendering = true; 365 | } 366 | } 367 | 368 | 369 | float 370 | OVRWindow::getPixelDensity() const { 371 | return _pixelDensity; 372 | } 373 | 374 | 375 | void 376 | OVRWindow::setPixelDensity(const float density) { 377 | // When the pixel density is changed, the render target needs to be resized. 378 | if (_pixelDensity != density) { 379 | _pixelDensity = density <= 0.0f ? 0.5f : density; 380 | _dirty.renderTarget = true; 381 | } 382 | } 383 | 384 | 385 | float 386 | OVRWindow::getNearClippingDistance() const { 387 | return _nearClippingPlaneDistance; 388 | } 389 | 390 | 391 | void 392 | OVRWindow::setNearClippingDistance(const float near) { 393 | if (_nearClippingPlaneDistance != near) { 394 | _nearClippingPlaneDistance = near; 395 | for (auto& dirty : _dirty.projections) { 396 | dirty = true; 397 | } 398 | } 399 | } 400 | 401 | 402 | float 403 | OVRWindow::getFarClippingDistance() const { 404 | return _farClippingPlaneDistance; 405 | } 406 | 407 | 408 | void 409 | OVRWindow::setFarClippingDistance(const float far) { 410 | if (_farClippingPlaneDistance != far) { 411 | _farClippingPlaneDistance = far; 412 | for (auto& dirty : _dirty.projections) { 413 | dirty = true; 414 | } 415 | } 416 | } 417 | 418 | 419 | bool 420 | OVRWindow::isMultisamplingEnabled() const { 421 | return getOvrGlConfig().OGL.Header.Multisample == 1; 422 | } 423 | 424 | 425 | void 426 | OVRWindow::enableMultisampling(const bool enable) { 427 | auto& multisample = getOvrGlConfig().OGL.Header.Multisample; 428 | if (static_cast(multisample) != enable) { 429 | multisample = enable ? 1 : 0; 430 | _dirty.rendering = true; 431 | } 432 | } 433 | 434 | 435 | void 436 | OVRWindow::toggleMultisampling() { 437 | auto& multisample = getOvrGlConfig().OGL.Header.Multisample; 438 | multisample = !multisample; 439 | _dirty.rendering = true; 440 | } 441 | 442 | 443 | void 444 | OVRWindow::updateGL() { 445 | if (isExposed() && hasValidGL()) { 446 | makeCurrent(); 447 | paintGL(); 448 | doneCurrent(); 449 | requestUpdateGL(); 450 | } 451 | } 452 | 453 | 454 | void 455 | OVRWindow::requestUpdateGL() 456 | { 457 | if (!_pendingUpdateRequest) { 458 | _pendingUpdateRequest = true; 459 | QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); 460 | } 461 | } 462 | 463 | 464 | void 465 | OVRWindow::configureGL() { 466 | auto& OGL = getOvrGlConfig().OGL; 467 | 468 | OGL.Header.API = ovrRenderAPI_OpenGL; 469 | OGL.Header.RTSize.w = _device.Resolution.w; 470 | OGL.Header.RTSize.h = _device.Resolution.h; 471 | OGL.Header.Multisample = 0; 472 | #if defined(Q_OS_LINUX) 473 | OGL.Disp = getXDisplay(this); 474 | OGL.Win = static_cast<::Window>(winId()); 475 | assert(OGL.Disp != nullptr); 476 | #elif defined(Q_OS_WIN32) 477 | __OVR_GL_CONFIG.Window = static_cast<::HWND>(winId()); 478 | #endif 479 | _dirty.rendering = true; 480 | } 481 | 482 | 483 | void 484 | OVRWindow::paintGL() { 485 | // Update all configurations before drawing the frame. 486 | sanitizeRenderTargetConfiguration(); 487 | sanitizeDeviceConfiguration(); 488 | sanitizeRenderingConfiguration(); 489 | 490 | const auto& hmd = _device.Handle; 491 | const auto& frameTiming = ovrHmd_BeginFrame(hmd, 0); 492 | const auto& dt = frameTiming.DeltaSeconds; 493 | 494 | glBindFramebuffer(GL_FRAMEBUFFER, _renderTarget.fbo); 495 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 496 | 497 | for (const auto& eye : _device.EyeRenderOrder) { 498 | auto& texConfig = getOvrGlTexture(eye); 499 | const auto& viewport = texConfig.OGL.Header.RenderViewport; 500 | const auto& pose = ovrHmd_BeginEyeRender(hmd, eye); 501 | const auto& renderTransforms = getRenderTransforms(eye, pose); 502 | 503 | glViewport(viewport.Pos.x, viewport.Pos.y, viewport.Size.w, viewport.Size.h); 504 | paintGL(eye, renderTransforms, dt); 505 | ovrHmd_EndEyeRender(hmd, eye, pose, &texConfig.Texture); 506 | } 507 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 508 | ovrHmd_EndFrame(hmd); 509 | 510 | // TODO Remove this block when ovrHmd_EndFrame cleans up after itself correctly. 511 | { 512 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 513 | glBindBuffer(GL_ARRAY_BUFFER, 0); 514 | glUseProgram(0); 515 | } 516 | } 517 | 518 | 519 | ovrGLConfig& 520 | OVRWindow::getOvrGlConfig() const { 521 | static ovrGLConfig INSTANCE; 522 | return INSTANCE; 523 | } 524 | 525 | 526 | ovrGLTexture& 527 | OVRWindow::getOvrGlTexture(const ovrEyeType eye) const { 528 | static std::array INSTANCES; 529 | return INSTANCES[eye]; 530 | } 531 | 532 | 533 | void 534 | OVRWindow::sanitizeRenderTargetConfiguration() { 535 | // Reconfigure the render target. 536 | if (_dirty.renderTarget) { 537 | bool isInitialized = _renderTarget.fbo; 538 | if (!isInitialized) { 539 | // Initialize the frame buffer object and its textures. 540 | assert(hasValidGL()); 541 | glGenFramebuffers(1, &_renderTarget.fbo); 542 | assert(_renderTarget.fbo != 0); 543 | 544 | glGenTextures(1, &_renderTarget.pixel); 545 | assert(_renderTarget.pixel != 0); 546 | 547 | glGenRenderbuffers(1, &_renderTarget.depth); 548 | assert(_renderTarget.depth != 0); 549 | 550 | // Setup the platform-independent configuration to use the OpenGL API 551 | // during the SDK distortion correction phase. 552 | for (const auto& eye : _device.EyeRenderOrder) 553 | getOvrGlTexture(eye).OGL.Header.API = ovrRenderAPI_OpenGL; 554 | } 555 | glBindFramebuffer(GL_FRAMEBUFFER, _renderTarget.fbo); 556 | 557 | const auto& hmd = _device.Handle; 558 | const auto& sizeL = ovrHmd_GetFovTextureSize(hmd, ovrEye_Left, _FOV[ovrEye_Left], _pixelDensity); 559 | const auto& sizeR = ovrHmd_GetFovTextureSize(hmd, ovrEye_Right, _FOV[ovrEye_Right], _pixelDensity); 560 | const auto& newSize = QSize(sizeL.w + sizeR.w, std::max(sizeL.h, sizeR.h)); 561 | if (_renderTarget.resolution != newSize) { 562 | _renderTarget.resolution = newSize; 563 | const auto& w = newSize.width(); 564 | const auto& h = newSize.height(); 565 | 566 | // Bind and resize the buffers. 567 | glBindTexture(GL_TEXTURE_2D, _renderTarget.pixel); 568 | glBindRenderbuffer(GL_RENDERBUFFER, _renderTarget.depth); 569 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); 570 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h); 571 | 572 | // If the framebuffer object was just initialized, configure each buffer appropriately. 573 | if (!isInitialized) { 574 | // Configure the pixel buffer. 575 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 576 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 577 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _renderTarget.pixel, 0); 578 | const GLenum& buffers = GL_COLOR_ATTACHMENT0; 579 | glDrawBuffers(1, &buffers); 580 | 581 | // Configure the depth buffer. 582 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderTarget.depth); 583 | 584 | // Make sure the framebuffer object is valid. 585 | assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); 586 | } 587 | glBindTexture(GL_TEXTURE_2D, 0); 588 | glBindRenderbuffer(GL_RENDERBUFFER, 0); 589 | 590 | // Configure SDK distortion correction parameters. 591 | for (unsigned int i = 0; i < ovrEye_Count; ++i) { 592 | auto& ogl = getOvrGlTexture(static_cast(i)).OGL; 593 | auto& header = ogl.Header; 594 | 595 | ogl.TexId = _renderTarget.pixel; 596 | header.TextureSize.w = w; 597 | header.TextureSize.h = h; 598 | header.RenderViewport.Pos.x = i * ((w + 1) * 0.5); 599 | header.RenderViewport.Pos.y = 0; 600 | header.RenderViewport.Size.w = w * 0.5f; 601 | header.RenderViewport.Size.h = h; 602 | } 603 | // Mark the rendering configuration as dirty since the render target has been resized. 604 | _dirty.rendering = true; 605 | } 606 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 607 | 608 | // Mark the render target as sanitized. 609 | _dirty.renderTarget = false; 610 | } 611 | } 612 | 613 | 614 | void 615 | OVRWindow::sanitizeDeviceConfiguration() { 616 | static const auto& getHmdCaps = [this]() { 617 | const auto& features = { 618 | OVRWindow::Feature::LowPersistence, 619 | OVRWindow::Feature::LatencyTesting, 620 | OVRWindow::Feature::DynamicPrediction 621 | }; 622 | unsigned int caps = 0; 623 | for (const auto& feature : features) { 624 | if (isFeatureEnabled(feature)) { 625 | caps |= static_cast::type>(feature); 626 | } 627 | } 628 | return caps; 629 | }; 630 | static const auto& getSensorCaps = [this]() { 631 | const auto& features = { 632 | OVRWindow::Feature::LowPersistence, 633 | OVRWindow::Feature::LatencyTesting, 634 | OVRWindow::Feature::DynamicPrediction 635 | }; 636 | unsigned int caps = 0; 637 | for (const auto& feature : features) { 638 | if (isFeatureEnabled(feature)) { 639 | caps |= static_cast::type>(feature); 640 | } 641 | } 642 | return caps; 643 | }; 644 | const auto& hmd = _device.Handle; 645 | if (_dirty.device.hmd) { 646 | ovrHmd_SetEnabledCaps(hmd, getHmdCaps()); 647 | 648 | // Mark the HMD's configuration as sanitized. Note that a change to some of the 649 | // HMD's capabilities modifies the rendering configuration. 650 | _dirty.device.hmd = false; 651 | _dirty.rendering = true; 652 | } 653 | if (_dirty.device.sensor) { 654 | // If no sensor capability is activated, stop the sensor. 655 | const auto& sensorCaps = getSensorCaps(); 656 | if (sensorCaps) { 657 | const auto result = ovrHmd_StartSensor(hmd, _device.SensorCaps, sensorCaps); 658 | assert(result); 659 | } else { 660 | ovrHmd_StopSensor(hmd); 661 | } 662 | 663 | // Mark the sensor's configuration as sanitized. 664 | _dirty.device.sensor = false; 665 | } 666 | } 667 | 668 | 669 | void 670 | OVRWindow::sanitizeRenderingConfiguration() { 671 | static const auto& getDistortionCaps = [this]() { 672 | const auto& features = { 673 | OVRWindow::Feature::ChromaticAberrationCorrection, 674 | OVRWindow::Feature::Timewarp, 675 | OVRWindow::Feature::Vignette 676 | }; 677 | unsigned int caps = 0; 678 | for (const auto& feature : features) { 679 | if (isFeatureEnabled(feature)) { 680 | caps |= static_cast::type>(feature); 681 | } 682 | } 683 | return caps; 684 | }; 685 | const auto& hmd = _device.Handle; 686 | if (_dirty.rendering) { 687 | const auto result = ovrHmd_ConfigureRendering(hmd, &getOvrGlConfig().Config, getDistortionCaps(), _FOV, _renderInfo); 688 | assert(result); 689 | if (_forceZeroIPD) { 690 | for (auto& info : _renderInfo) { 691 | info.ViewAdjust = OVR::Vector3f(0); 692 | } 693 | } 694 | // Mark the rendering configuration as sanitized. 695 | _dirty.rendering = false; 696 | } 697 | } 698 | 699 | 700 | void 701 | OVRWindow::resizeEvent(QResizeEvent* const e) { 702 | if (_gl.isValid()) { 703 | makeCurrent(); 704 | const auto& newSize = e->size(); 705 | resizeGL(newSize.width(), newSize.height()); 706 | doneCurrent(); 707 | } 708 | QWindow::resizeEvent(e); 709 | } 710 | 711 | 712 | void 713 | OVRWindow::exposeEvent(QExposeEvent* const e) { 714 | // When the window is exposed the first time, it needs to be initialized. 715 | static bool isInitialized = false; 716 | if (!isInitialized && isExposed()) { 717 | _gl.setFormat(requestedFormat()); 718 | auto result = _gl.create(); 719 | assert(result); 720 | makeCurrent(); 721 | assert(hasValidGL()); 722 | initializeOpenGLFunctions(); 723 | initializeGL(); 724 | configureGL(); 725 | doneCurrent(); 726 | requestUpdateGL(); 727 | isInitialized = true; 728 | emit initialized(); 729 | } 730 | QWindow::exposeEvent(e); 731 | } 732 | 733 | 734 | bool 735 | OVRWindow::event(QEvent* const e) 736 | { 737 | if (e->type() == QEvent::UpdateRequest) { 738 | _pendingUpdateRequest = false; 739 | updateGL(); 740 | return true; 741 | } 742 | return QWindow::event(e); 743 | } 744 | 745 | 746 | const OVRWindow::RenderTransforms& 747 | OVRWindow::getRenderTransforms(const ovrEyeType eye, const ovrPosef& pose) { 748 | auto& transformations = _renderTransforms[eye]; 749 | const auto& renderInfo = _renderInfo[eye]; 750 | const auto& viewAdjust = renderInfo.ViewAdjust; 751 | 752 | // Calculate the view matrix. 753 | const auto& viewMatrix = ( 754 | OVR::Matrix4f::Translation(viewAdjust) * 755 | OVR::Matrix4f(OVR::Quatf(pose.Orientation).Inverted()) 756 | ); 757 | const auto* V = &viewMatrix.M[0][0]; 758 | for (unsigned int i = 0; i < 4; ++i) { 759 | auto& view = transformations.view; 760 | view(i, 0) = *V++; 761 | view(i, 1) = *V++; 762 | view(i, 2) = *V++; 763 | view(i, 3) = *V++; 764 | } 765 | 766 | // Calculate the projection matrices, if need be. 767 | auto& dirtyProjection = _dirty.projections[eye]; 768 | if (dirtyProjection) { 769 | // Calculate the perspective projection. 770 | const auto& znear = _nearClippingPlaneDistance; 771 | const auto& zfar = _farClippingPlaneDistance; 772 | const auto& perspective = ovrMatrix4f_Projection(renderInfo.Fov, znear, zfar, true); 773 | 774 | // Calculate the orthogonal (orthographic) projection. 775 | const auto& distance = 0.8f; // 2D is 0.8 meters from the camera. 776 | const auto& scale = OVR::Vector2f(1.0f) / OVR::Vector2f(renderInfo.PixelsPerTanAngleAtCenter); 777 | const auto& ortho = ovrMatrix4f_OrthoSubProjection(perspective, scale, distance, viewAdjust.x); 778 | 779 | // Convert OVR::Matrix4f to QMatrix4x4. 780 | const auto* P = &perspective.M[0][0]; 781 | const auto* O = &ortho.M[0][0]; 782 | for (unsigned int i = 0; i < 4; ++i) { 783 | auto& perspective = transformations.perspective; 784 | perspective(i, 0) = *P++; 785 | perspective(i, 1) = *P++; 786 | perspective(i, 2) = *P++; 787 | perspective(i, 3) = *P++; 788 | 789 | auto& ortho = transformations.ortho; 790 | ortho(i, 0) = *O++; 791 | ortho(i, 1) = *O++; 792 | ortho(i, 2) = *O++; 793 | ortho(i, 3) = *O++; 794 | } 795 | dirtyProjection = false; 796 | } 797 | return transformations; 798 | } 799 | --------------------------------------------------------------------------------