diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 315eeb6b83..dc20763953 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME assignment-client) -setup_hifi_project(Core Gui Network Script Widgets) +setup_hifi_project(Core Gui Network Script Widgets WebSockets) add_dependency_external_projects(glm) find_package(GLM REQUIRED) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d656464c10..300976f81c 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -24,6 +24,7 @@ #include #include +#include #include // TODO: consider moving to scriptengine.h #include "avatars/ScriptableAvatar.h" @@ -180,10 +181,17 @@ void Agent::run() { // register ourselves to the script engine _scriptEngine.registerGlobalObject("Agent", this); + if (!_payload.isEmpty()) { + _scriptEngine.setParentURL(_payload); + } + _scriptEngine.init(); // must be done before we set up the viewers _scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get().data()); + QScriptValue webSocketServerConstructorValue = _scriptEngine.newFunction(WebSocketServerClass::constructor); + _scriptEngine.globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); + auto entityScriptingInterface = DependencyManager::get(); _scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer); diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index b0bf34a594..e03a3af484 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -5,6 +5,21 @@ set(EXTERNAL_NAME LibOVR) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +# These are all provided in order to allow easier testing of both +# the legacy display plugin and the new windows only plugin on +# various versions of the SDK, all on windows +# +# 0.5 public +# URL http://static.oculus.com/sdk-downloads/ovr_sdk_win_0.5.0.1.zip +# URL_MD5 d3fc4c02db9be5ff08af4ef4c97b32f9 +# 0.6 public +# URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip +# URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9 +# 0.7 alpha +# URL https://s3.amazonaws.com/static.oculus.com/sdk-downloads/0.7.0.0/Public/Alpha/ovr_sdk_win_0.7.0.0_RC1.zip +# URL_MD5 a562bb9d117087b2cf9d86653ea70fd8 + + if (WIN32) ExternalProject_Add( diff --git a/examples/libraries/unitTest.js b/examples/libraries/unitTest.js index beb3387898..7d5234933f 100644 --- a/examples/libraries/unitTest.js +++ b/examples/libraries/unitTest.js @@ -11,17 +11,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var test = function(name, func) { +test = function(name, func, timeout) { print("Running test: " + name); - - var unitTest = new UnitTest(name, func); - - try { - unitTest.run(); + var unitTest = new UnitTest(name, func, timeout); + unitTest.run(function(unitTest) { print(" Success: " + unitTest.numAssertions + " assertions passed"); - } catch (error) { + }, function(unitTest, error) { print(" Failure: " + error.name + " " + error.message); - } + }); }; AssertionException = function(expected, actual, message) { @@ -36,13 +33,86 @@ UnthrownException = function(message) { this.name = 'UnthrownException'; }; -UnitTest = function(name, func) { - this.numAssertions = 0; - this.func = func; +TimeoutException = function() { + print("Creating exception"); + this.message = "UnitTest timed out\n"; + this.name = 'TimeoutException'; }; -UnitTest.prototype.run = function() { - this.func(); +SequentialUnitTester = function() { + this.tests = []; + this.testIndex = -1; +}; + +SequentialUnitTester.prototype.addTest = function(name, func, timeout) { + var _this = this; + this.tests.push(function() { + print("Running test: " + name); + var unitTest = new UnitTest(name, func, timeout); + unitTest.run(function(unitTest) { + print(" Success: " + unitTest.numAssertions + " assertions passed"); + _this._nextTest(); + }, function(unitTest, error) { + print(" Failure: " + error.name + " " + error.message); + _this._nextTest(); + }); + }); +}; + +SequentialUnitTester.prototype._nextTest = function() { + this.testIndex++; + if (this.testIndex < this.tests.length) { + this.tests[this.testIndex](); + return; + } + print("Completed all UnitTests"); +}; + +SequentialUnitTester.prototype.run = function() { + this._nextTest(); +}; + +UnitTest = function(name, func, timeout) { + this.numAssertions = 0; + this.func = func; + this.timeout = timeout; +}; + +UnitTest.prototype.run = function(successCallback, failureCallback) { + var _this = this; + this.successCallback = successCallback; + this.failureCallback = failureCallback; + if (this.timeout !== undefined) { + this.timeoutTimer = Script.setTimeout(function() { + _this.failureCallback(this, new TimeoutException()); + }, this.timeout); + } + try { + this.func(); + if (this.timeout === undefined) { + successCallback(this); + } + } catch (exception) { + this.handleException(exception); + } +}; + +UnitTest.prototype.registerCallbackFunction = function(func) { + var _this = this; + return function(one, two, three, four, five, six) { + try { + func(one, two, three, four, five, six); + } catch (exception) { + _this.handleException(exception); + } + }; +}; + +UnitTest.prototype.handleException = function(exception) { + if (this.timeout !== undefined) { + Script.clearTimeout(this.timeoutTimer); + } + this.failureCallback(this, exception); }; UnitTest.prototype.assertNotEquals = function(expected, actual, message) { @@ -83,7 +153,7 @@ UnitTest.prototype.assertNull = function(value, message) { UnitTest.prototype.arrayEqual = function(array1, array2, message) { this.numAssertions++; if (array1.length !== array2.length) { - throw new AssertionException(array1.length , array2.length , message); + throw new AssertionException(array1.length, array2.length , message); } for (var i = 0; i < array1.length; ++i) { if (array1[i] !== array2[i]) { @@ -101,4 +171,11 @@ UnitTest.prototype.raises = function(func, message) { } throw new UnthrownException(message); -} \ No newline at end of file +} + +UnitTest.prototype.done = function() { + if (this.timeout !== undefined) { + Script.clearTimeout(this.timeoutTimer); + this.successCallback(this); + } +} diff --git a/examples/utilities/diagnostics/testWebSocket.js b/examples/utilities/diagnostics/testWebSocket.js new file mode 100644 index 0000000000..c356fd99a8 --- /dev/null +++ b/examples/utilities/diagnostics/testWebSocket.js @@ -0,0 +1,102 @@ +// +// testWebSocket.js +// examples +// +// Created by Thijs Wenker on 8/18/15 +// Copyright 2015 High Fidelity, Inc. +// +// WebSocket and WebSocketServer Tests +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("../../libraries/unitTest.js"); + +// We set the unit testing timeout to 1000 milliseconds by default. Please increase if the test fails due to a slow connection. +const UNITTEST_TIMEOUT = 1000; +const WEBSOCKET_PING_URL = "ws://echo.websocket.org"; +// Please do not register the following domain + gTLD: +const WEBSOCKET_INVALID_URL = "ws://thisisnotavaliddomainname.invalid"; +const TEST_MESSAGE = "This is a test message."; + +var unitTests = new SequentialUnitTester(); + +unitTests.addTest("Test default WebSocket values", function(finished) { + var _this = this; + var webSocket = new WebSocket(WEBSOCKET_PING_URL); + + webSocket.onmessage = this.registerCallbackFunction(function(event) { + _this.assertEquals(TEST_MESSAGE, event.data, "event.data should be '" + TEST_MESSAGE + "'"); + webSocket.close(); + }); + webSocket.onopen = this.registerCallbackFunction(function(event) { + _this.assertEquals(webSocket.OPEN, webSocket.readyState, "readyState should be OPEN"); + webSocket.send(TEST_MESSAGE); + }); + webSocket.onclose = this.registerCallbackFunction(function(event) { + _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); + _this.done(); + }); + this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); + this.assertEquals("blob", webSocket.binaryType, "binaryType should be 'blob'"); + this.assertEquals(0, webSocket.bufferedAmount, "bufferedAmount should be 0"); + this.assertEquals("", webSocket.extensions, "extensions should be an empty string by default"); + this.assertEquals("", webSocket.protocol, "protocol should be an empty string by default"); + this.assertEquals(WEBSOCKET_PING_URL, webSocket.url, "url should be '" + WEBSOCKET_PING_URL + "'"); +}, UNITTEST_TIMEOUT); + +unitTests.addTest("Test WebSocket invalid URL", function(finished) { + var _this = this; + var webSocket = new WebSocket(WEBSOCKET_INVALID_URL); + var hadError = false; + webSocket.onerror = this.registerCallbackFunction(function() { + hadError = true; + _this.done(); + }); + webSocket.onclose = this.registerCallbackFunction(function(event) { + _this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED"); + }); + this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING"); + this.assertEquals(WEBSOCKET_INVALID_URL, webSocket.url, "url should be '" + WEBSOCKET_INVALID_URL + "'"); +}, UNITTEST_TIMEOUT); + +if (this.WebSocketServer === undefined) { + print("Skipping WebSocketServer tests."); +} else { + unitTests.addTest("Test WebSocketServer with three clients", function(finished) { + var _this = this; + const NUMBER_OF_CLIENTS = 3; + var connectedClients = 0; + var respondedClients = 0; + var webSocketServer = new WebSocketServer(); + _this.assertEquals(true, webSocketServer.listening, "listening should be true"); + webSocketServer.newConnection.connect(this.registerCallbackFunction(function(newClient) { + connectedClients++; + newClient.onmessage = _this.registerCallbackFunction(function(event) { + var data = JSON.parse(event.data); + _this.assertEquals(TEST_MESSAGE, data.message, "data.message should be '" + TEST_MESSAGE + "'"); + respondedClients++; + if (respondedClients === NUMBER_OF_CLIENTS) { + webSocketServer.close(); + _this.assertEquals(false, webSocketServer.listening, "listening should be false"); + _this.done(); + } + }); + newClient.send(JSON.stringify({message: TEST_MESSAGE, client: connectedClients})); + })); + var newSocket1 = new WebSocket(webSocketServer.url); + newSocket1.onmessage = this.registerCallbackFunction(function(event) { + newSocket1.send(event.data); + }); + var newSocket2 = new WebSocket(webSocketServer.url); + newSocket2.onmessage = this.registerCallbackFunction(function(event) { + newSocket2.send(event.data); + }); + var newSocket3 = new WebSocket(webSocketServer.url); + newSocket3.onmessage = this.registerCallbackFunction(function(event) { + newSocket3.send(event.data); + }); + }, UNITTEST_TIMEOUT); +} + +unitTests.run(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d858673774..0c531dfacb 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -40,7 +40,7 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets) +find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec8a6b87f9..bcdf75648e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,8 @@ #include #include #include +#include +#include #include #include @@ -148,6 +150,8 @@ #include "ui/overlays/Cube3DOverlay.h" +#include "PluginContainerProxy.h" + // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) @@ -301,7 +305,7 @@ bool setupEssentials(int& argc, char** argv) { // continuing to overburden Application.cpp Cube3DOverlay* _keyboardFocusHighlight{ nullptr }; int _keyboardFocusHighlightID{ -1 }; - +PluginContainer* _pluginContainer; Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QApplication(argc, argv), @@ -351,7 +355,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _applicationOverlay() { setInstance(this); - Plugin::setContainer(this); + + _pluginContainer = new PluginContainerProxy(); + Plugin::setContainer(_pluginContainer); #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif @@ -995,6 +1001,8 @@ void Application::paintGL() { auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); _offscreenContext->makeCurrent(); + // update the avatar with a fresh HMD pose + _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); auto lodManager = DependencyManager::get(); @@ -1091,7 +1099,6 @@ void Application::paintGL() { glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; } - // Update camera position if (!isHMDMode()) { _myCamera.update(1.0f / _fps); @@ -1100,64 +1107,46 @@ void Application::paintGL() { // Primary rendering pass auto framebufferCache = DependencyManager::get(); - QSize size = framebufferCache->getFrameBufferSize(); + const QSize size = framebufferCache->getFrameBufferSize(); { PROFILE_RANGE(__FUNCTION__ "/mainRender"); // Viewport is assigned to the size of the framebuffer - QSize size = DependencyManager::get()->getFrameBufferSize(); - renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); - - { - PROFILE_RANGE(__FUNCTION__ "/clear"); - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); - batch.setFramebuffer(primaryFbo); - // clear the normal and specular buffers - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | - gpu::Framebuffer::BUFFER_COLOR1 | - gpu::Framebuffer::BUFFER_COLOR2 | - gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(0), 1), 1.0, 0.0); - }); - } - + renderArgs._viewport = ivec4(0, 0, size.width(), size.height()); if (displayPlugin->isStereo()) { - PROFILE_RANGE(__FUNCTION__ "/stereoRender"); - QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height())); - glEnable(GL_SCISSOR_TEST); - for_each_eye([&](Eye eye){ - // Load the view frustum, used by meshes - Camera eyeCamera; - if (qApp->isHMDMode()) { - // Allow the displayPlugin to compose the final eye transform, based on the most up-to-date head motion. - eyeCamera.setTransform(displayPlugin->getModelview(eye, _myAvatar->getSensorToWorldMatrix())); - } else { - eyeCamera.setTransform(displayPlugin->getModelview(eye, _myCamera.getTransform())); - } - eyeCamera.setProjection(displayPlugin->getProjection(eye, _myCamera.getProjection())); - renderArgs._viewport = toGlm(currentViewport); - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - batch.setViewportTransform(renderArgs._viewport); - batch.setStateScissorRect(renderArgs._viewport); - }); - displaySide(&renderArgs, eyeCamera); - }, [&] { - currentViewport.moveLeft(currentViewport.width()); + // Stereo modes will typically have a larger projection matrix overall, + // so we ask for the 'mono' projection matrix, which for stereo and HMD + // plugins will imply the combined projection for both eyes. + // + // This is properly implemented for the Oculus plugins, but for OpenVR + // and Stereo displays I'm not sure how to get / calculate it, so we're + // just relying on the left FOV in each case and hoping that the + // overall culling margin of error doesn't cause popping in the + // right eye. There are FIXMEs in the relevant plugins + _myCamera.setProjection(displayPlugin->getProjection(Mono, _myCamera.getProjection())); + renderArgs._context->enableStereo(true); + mat4 eyeViews[2]; + mat4 eyeProjections[2]; + auto baseProjection = renderArgs._viewFrustum->getProjection(); + // FIXME we probably don't need to set the projection matrix every frame, + // only when the display plugin changes (or in non-HMD modes when the user + // changes the FOV manually, which right now I don't think they can. + for_each_eye([&](Eye eye) { + // For providing the stereo eye views, the HMD head pose has already been + // applied to the avatar, so we need to get the difference between the head + // pose applied to the avatar and the per eye pose, and use THAT as + // the per-eye stereo matrix adjustment. + mat4 eyePose = displayPlugin->getEyePose(eye); + mat4 headPose = displayPlugin->getHeadPose(); + mat4 eyeView = glm::inverse(eyePose) * headPose; + eyeViews[eye] = eyeView; + eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection); }); - glDisable(GL_SCISSOR_TEST); - } else { - PROFILE_RANGE(__FUNCTION__ "/monoRender"); - renderArgs._viewport = gpu::Vec4i(0, 0, size.width(), size.height()); - // Viewport is assigned to the size of the framebuffer - doInBatch(&renderArgs, [&](gpu::Batch& batch) { - batch.setViewportTransform(renderArgs._viewport); - batch.setStateScissorRect(renderArgs._viewport); - }); - displaySide(&renderArgs, _myCamera); + renderArgs._context->setStereoProjections(eyeProjections); + renderArgs._context->setStereoViews(eyeViews); } - - doInBatch(&renderArgs, [](gpu::Batch& batch){ + displaySide(&renderArgs, _myCamera); + renderArgs._context->enableStereo(false); + doInBatch(&renderArgs, [](gpu::Batch& batch) { batch.setFramebuffer(nullptr); }); } @@ -1191,7 +1180,6 @@ void Application::paintGL() { PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0)); - uvec2 finalSize = toGlm(size); // Ensure the rendering context commands are completed when rendering GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); // Ensure the sync object is flushed to the driver thread before releasing the context @@ -1207,7 +1195,7 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); - displayPlugin->display(finalTexture, finalSize); + displayPlugin->display(finalTexture, toGlm(size)); } { @@ -1280,6 +1268,7 @@ void Application::resizeGL() { // Possible change in aspect ratio loadViewFrustum(_myCamera, _viewFrustum); float fov = glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES); + // FIXME the aspect ratio for stereo displays is incorrect based on this. float aspectRatio = aspect(_renderResolution); _myCamera.setProjection(glm::perspective(fov, aspectRatio, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); } @@ -1439,7 +1428,15 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_Enter: case Qt::Key_Return: - Menu::getInstance()->triggerOption(MenuOption::AddressBar); + if (isOption) { + if (_window->isFullScreen()) { + _pluginContainer->unsetFullscreen(); + } else { + _pluginContainer->setFullscreen(nullptr); + } + } else { + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + } break; case Qt::Key_B: @@ -2705,9 +2702,6 @@ void Application::update(float deltaTime) { updateLOD(); updateMouseRay(); // check what's under the mouse and update the mouse voxel - // update the avatar with a fresh HMD pose - _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); - { PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); @@ -4638,10 +4632,6 @@ void Application::checkSkeleton() { } } -bool Application::isForeground() { - return _isForeground && !getWindow()->isMinimized(); -} - void Application::activeChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationActive: @@ -4747,6 +4737,10 @@ const DisplayPlugin * Application::getActiveDisplayPlugin() const { return ((Application*)this)->getActiveDisplayPlugin(); } +bool _activatingDisplayPlugin{ false }; +QVector> _currentDisplayPluginActions; +QVector> _currentInputPluginActions; + static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); @@ -4768,9 +4762,6 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); } -static QVector> _currentDisplayPluginActions; -static bool _activatingDisplayPlugin{false}; - void Application::updateDisplayMode() { auto menu = Menu::getInstance(); auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); @@ -4837,7 +4828,7 @@ void Application::updateDisplayMode() { // Only show the hmd tools after the correct plugin has // been activated so that it's UI is setup correctly if (newPluginWantsHMDTools) { - showDisplayPluginsTools(); + _pluginContainer->showDisplayPluginsTools(); } if (oldDisplayPlugin) { @@ -4855,9 +4846,6 @@ void Application::updateDisplayMode() { Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } -static QVector> _currentInputPluginActions; - - static void addInputPluginToMenu(InputPluginPointer inputPlugin, bool active = false) { auto menu = Menu::getInstance(); QString name = inputPlugin->getName(); @@ -4931,42 +4919,6 @@ void Application::updateInputModes() { //} } -void Application::addMenu(const QString& menuName) { - Menu::getInstance()->addMenu(menuName); -} - -void Application::removeMenu(const QString& menuName) { - Menu::getInstance()->removeMenu(menuName); -} - -void Application::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { - auto menu = Menu::getInstance(); - MenuWrapper* parentItem = menu->getMenu(path); - QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); - connect(action, &QAction::triggered, [=] { - onClicked(action->isChecked()); - }); - action->setCheckable(checkable); - action->setChecked(checked); - if (_activatingDisplayPlugin) { - _currentDisplayPluginActions.push_back({ path, name }); - } else { - _currentInputPluginActions.push_back({ path, name }); - } -} - -void Application::removeMenuItem(const QString& menuName, const QString& menuItem) { - Menu::getInstance()->removeMenuItem(menuName, menuItem); -} - -bool Application::isOptionChecked(const QString& name) { - return Menu::getInstance()->isOptionChecked(name); -} - -void Application::setIsOptionChecked(const QString& path, bool checked) { - Menu::getInstance()->setIsOptionChecked(path, checked); -} - mat4 Application::getEyeProjection(int eye) const { if (isHMDMode()) { return getActiveDisplayPlugin()->getProjection((Eye)eye, _viewFrustum.getProjection()); @@ -4986,7 +4938,7 @@ mat4 Application::getEyePose(int eye) const { mat4 Application::getEyeOffset(int eye) const { if (isHMDMode()) { mat4 identity; - return getActiveDisplayPlugin()->getModelview((Eye)eye, identity); + return getActiveDisplayPlugin()->getView((Eye)eye, identity); } return mat4(); @@ -4999,84 +4951,6 @@ mat4 Application::getHMDSensorPose() const { return mat4(); } -void Application::setFullscreen(const QScreen* target) { - if (!_window->isFullScreen()) { - _savedGeometry = _window->geometry(); - } -#ifdef Q_OS_MAC - _window->setGeometry(target->availableGeometry()); -#endif - _window->windowHandle()->setScreen((QScreen*)target); - _window->showFullScreen(); - -#ifndef Q_OS_MAC - // also hide the QMainWindow's menuBar - QMenuBar* menuBar = _window->menuBar(); - if (menuBar) { - menuBar->setVisible(false); - } -#endif -} - -void Application::unsetFullscreen(const QScreen* avoid) { - _window->showNormal(); - - QRect targetGeometry = _savedGeometry; - if (avoid != nullptr) { - QRect avoidGeometry = avoid->geometry(); - if (avoidGeometry.contains(targetGeometry.topLeft())) { - QScreen* newTarget = primaryScreen(); - if (newTarget == avoid) { - foreach(auto screen, screens()) { - if (screen != avoid) { - newTarget = screen; - break; - } - } - } - targetGeometry = newTarget->availableGeometry(); - } - } -#ifdef Q_OS_MAC - QTimer* timer = new QTimer(); - timer->singleShot(2000, [=] { - _window->setGeometry(targetGeometry); - timer->deleteLater(); - }); -#else - _window->setGeometry(targetGeometry); -#endif - -#ifndef Q_OS_MAC - // also show the QMainWindow's menuBar - QMenuBar* menuBar = _window->menuBar(); - if (menuBar) { - menuBar->setVisible(true); - } -#endif -} - - -void Application::showDisplayPluginsTools() { - DependencyManager::get()->hmdTools(true); -} - -QGLWidget* Application::getPrimarySurface() { - return _glWidget; -} - -void Application::setActiveDisplayPlugin(const QString& pluginName) { - auto menu = Menu::getInstance(); - foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { - QString name = displayPlugin->getName(); - QAction* action = menu->getActionForOption(name); - if (pluginName == name) { - action->setChecked(true); - } - } - updateDisplayMode(); -} - void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index) { PalmData* palm; bool foundHand = false; diff --git a/interface/src/Application.h b/interface/src/Application.h index a1765109ce..6394aa12d0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -26,18 +26,18 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include -#include -#include #include #include "AudioClient.h" @@ -50,7 +50,6 @@ #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/MyAvatar.h" -#include #include "scripting/ControllerScriptingInterface.h" #include "scripting/DialogsManagerScriptingInterface.h" #include "scripting/WebWindowClass.h" @@ -132,7 +131,7 @@ class Application; typedef bool (Application::* AcceptURLMethod)(const QString &); -class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, PluginContainer { +class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface { Q_OBJECT friend class OctreePacketProcessor; @@ -280,23 +279,10 @@ public: virtual void endOverrideEnvironmentData() { _environment.endOverride(); } virtual qreal getDevicePixelRatio(); - // Plugin container support - virtual void addMenu(const QString& menuName); - virtual void removeMenu(const QString& menuName); - virtual void addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName); - virtual void removeMenuItem(const QString& menuName, const QString& menuItem); - virtual bool isOptionChecked(const QString& name); - virtual void setIsOptionChecked(const QString& path, bool checked); - virtual void setFullscreen(const QScreen* target) override; - virtual void unsetFullscreen(const QScreen* avoid) override; - virtual void showDisplayPluginsTools() override; - virtual QGLWidget* getPrimarySurface() override; - virtual bool isForeground() override; - void setActiveDisplayPlugin(const QString& pluginName); - DisplayPlugin * getActiveDisplayPlugin(); - const DisplayPlugin * getActiveDisplayPlugin() const; + DisplayPlugin* getActiveDisplayPlugin(); + const DisplayPlugin* getActiveDisplayPlugin() const; public: @@ -691,6 +677,8 @@ private: int _simsPerSecondReport = 0; quint64 _lastSimsPerSecondUpdate = 0; bool _isForeground = true; // starts out assumed to be in foreground + + friend class PluginContainerProxy; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index af80320157..1c2cdbaa26 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include + #include "Application.h" #include "AccountManager.h" #include "audio/AudioScope.h" @@ -220,7 +222,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); - MenuWrapper* displayMenu = addMenu("Display"); + MenuWrapper* displayMenu = addMenu(DisplayPlugin::MENU_PATH); { MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp new file mode 100644 index 0000000000..e172dbbd9e --- /dev/null +++ b/interface/src/PluginContainerProxy.cpp @@ -0,0 +1,161 @@ +#include "PluginContainerProxy.h" + +#include +#include + +#include +#include +#include + +#include "Application.h" +#include "MainWindow.h" +#include "GLCanvas.h" +#include "ui/DialogsManager.h" + +PluginContainerProxy::PluginContainerProxy() { + Plugin::setContainer(this); +} + +bool PluginContainerProxy::isForeground() { + return qApp->_isForeground && !qApp->getWindow()->isMinimized(); +} + +void PluginContainerProxy::addMenu(const QString& menuName) { + Menu::getInstance()->addMenu(menuName); +} + +void PluginContainerProxy::removeMenu(const QString& menuName) { + Menu::getInstance()->removeMenu(menuName); +} + +extern bool _activatingDisplayPlugin; +extern QVector> _currentDisplayPluginActions; +extern QVector> _currentInputPluginActions; +std::map _exclusiveGroups; + +QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { + auto menu = Menu::getInstance(); + MenuWrapper* parentItem = menu->getMenu(path); + QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); + if (!groupName.isEmpty()) { + QActionGroup* group{ nullptr }; + if (!_exclusiveGroups.count(groupName)) { + group = _exclusiveGroups[groupName] = new QActionGroup(menu); + group->setExclusive(true); + } else { + group = _exclusiveGroups[groupName]; + } + group->addAction(action); + } + connect(action, &QAction::triggered, [=] { + onClicked(action->isChecked()); + }); + action->setCheckable(checkable); + action->setChecked(checked); + if (_activatingDisplayPlugin) { + _currentDisplayPluginActions.push_back({ path, name }); + } else { + _currentInputPluginActions.push_back({ path, name }); + } + return action; +} + +void PluginContainerProxy::removeMenuItem(const QString& menuName, const QString& menuItem) { + Menu::getInstance()->removeMenuItem(menuName, menuItem); +} + +bool PluginContainerProxy::isOptionChecked(const QString& name) { + return Menu::getInstance()->isOptionChecked(name); +} + +void PluginContainerProxy::setIsOptionChecked(const QString& path, bool checked) { + Menu::getInstance()->setIsOptionChecked(path, checked); +} + +// FIXME there is a bug in the fullscreen setting, where leaving +// fullscreen does not restore the window frame, making it difficult +// or impossible to move or size the window. +// Additionally, setting fullscreen isn't hiding the menu on windows +// make it useless for stereoscopic modes. +void PluginContainerProxy::setFullscreen(const QScreen* target, bool hideMenu) { + auto _window = qApp->_window; + if (!_window->isFullScreen()) { + _savedGeometry = _window->geometry(); + } + if (nullptr == target) { + // FIXME target the screen where the window currently is + target = qApp->primaryScreen(); + } + + _window->setGeometry(target->availableGeometry()); + _window->windowHandle()->setScreen((QScreen*)target); + _window->showFullScreen(); + +#ifndef Q_OS_MAC + // also hide the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar && hideMenu) { + menuBar->setVisible(false); + } +#endif +} + +void PluginContainerProxy::unsetFullscreen(const QScreen* avoid) { + auto _window = qApp->_window; + _window->showNormal(); + + QRect targetGeometry = _savedGeometry; + if (avoid != nullptr) { + QRect avoidGeometry = avoid->geometry(); + if (avoidGeometry.contains(targetGeometry.topLeft())) { + QScreen* newTarget = qApp->primaryScreen(); + if (newTarget == avoid) { + foreach(auto screen, qApp->screens()) { + if (screen != avoid) { + newTarget = screen; + break; + } + } + } + targetGeometry = newTarget->availableGeometry(); + } + } +#ifdef Q_OS_MAC + QTimer* timer = new QTimer(); + timer->singleShot(2000, [=] { + _window->setGeometry(targetGeometry); + timer->deleteLater(); + }); +#else + _window->setGeometry(targetGeometry); +#endif + +#ifndef Q_OS_MAC + // also show the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + menuBar->setVisible(true); + } +#endif +} + + +void PluginContainerProxy::showDisplayPluginsTools() { + DependencyManager::get()->hmdTools(true); +} + +QGLWidget* PluginContainerProxy::getPrimarySurface() { + return qApp->_glWidget; +} + +void Application::setActiveDisplayPlugin(const QString& pluginName) { + auto menu = Menu::getInstance(); + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { + QString name = displayPlugin->getName(); + QAction* action = menu->getActionForOption(name); + if (pluginName == name) { + action->setChecked(true); + } + } + updateDisplayMode(); +} diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h new file mode 100644 index 0000000000..e01cabc3b8 --- /dev/null +++ b/interface/src/PluginContainerProxy.h @@ -0,0 +1,30 @@ +#pragma once +#ifndef hifi_PluginContainerProxy_h +#define hifi_PluginContainerProxy_h + +#include +#include + +#include +#include + +class PluginContainerProxy : public QObject, PluginContainer { + Q_OBJECT + PluginContainerProxy(); + virtual void addMenu(const QString& menuName) override; + virtual void removeMenu(const QString& menuName) override; + virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override; + virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override; + virtual bool isOptionChecked(const QString& name) override; + virtual void setIsOptionChecked(const QString& path, bool checked); + virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = true) override; + virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override; + virtual void showDisplayPluginsTools() override; + virtual QGLWidget* getPrimarySurface() override; + virtual bool isForeground() override; + QRect _savedGeometry{ 10, 120, 800, 600 }; + + friend class Application; +}; + +#endif \ No newline at end of file diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 42b1a3f2e2..119b9ed1a2 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -191,7 +191,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { auto geometryCache = DependencyManager::get(); auto textureCache = DependencyManager::get(); - gpu::Batch batch; + gpu::Batch& batch = *renderArgs->_batch; batch.setViewTransform(Transform()); batch.setProjectionTransform(renderArgs->_viewFrustum->getProjection()); batch.setModelTransform(Transform().setRotation(glm::inverse(renderArgs->_viewFrustum->getOrientation()) * @@ -219,6 +219,4 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { batch.setInputBuffer(VERTICES_SLOT, posView); batch.setInputBuffer(COLOR_SLOT, colView); batch.draw(gpu::Primitive::POINTS, STARFIELD_NUM_STARS); - - renderArgs->_context->render(batch); } diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 4aeb0f6ac3..dad34e9243 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -40,6 +40,7 @@ void renderWorldBox(gpu::Batch& batch) { static const glm::vec3 GREEN(0.0f, 1.0f, 0.0f); static const glm::vec3 BLUE(0.0f, 0.0f, 1.0f); static const glm::vec3 GREY(0.5f, 0.5f, 0.5f); + static const glm::vec4 GREY4(0.5f, 0.5f, 0.5f, 1.0f); static const glm::vec4 DASHED_RED(1.0f, 0.0f, 0.0f, 1.0f); static const glm::vec4 DASHED_GREEN(0.0f, 1.0f, 0.0f, 1.0f); @@ -62,8 +63,37 @@ void renderWorldBox(gpu::Batch& batch) { geometryCache->renderDashedLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -HALF_TREE_SCALE), DASHED_BLUE, DASH_LENGTH, GAP_LENGTH); - geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); - geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, 0.0f), GREY); + // X center boundaries + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), + glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), + glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), + glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, -HALF_TREE_SCALE, 0.0f), + glm::vec3(HALF_TREE_SCALE, HALF_TREE_SCALE, 0.0f), GREY); + + // Z center boundaries + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), + glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, -HALF_TREE_SCALE), + glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, HALF_TREE_SCALE, -HALF_TREE_SCALE), + glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(0.0f, -HALF_TREE_SCALE, HALF_TREE_SCALE), + glm::vec3(0.0f, HALF_TREE_SCALE, HALF_TREE_SCALE), GREY); + + // Center boundaries + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), + glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), + glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(HALF_TREE_SCALE, 0.0f, -HALF_TREE_SCALE), + glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), + glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); + + geometryCache->renderWireCube(batch, TREE_SCALE, GREY4); // Draw meter markers along the 3 axis to help with measuring things const float MARKER_DISTANCE = 1.0f; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 19f84018f8..6d6120f3ba 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -748,11 +748,12 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa qDebug() << "ASSERT because isinf(scale)"; } qDebug() << "textPosition =" << textPosition; + qDebug() << "projMat =" << projMat; + qDebug() << "viewMat =" << viewMat; + qDebug() << "viewProj =" << viewProj; qDebug() << "windowSizeY =" << windowSizeY; - qDebug() << "p1.y =" << p1.y; - qDebug() << "p1.w =" << p1.w; - qDebug() << "p0.y =" << p0.y; - qDebug() << "p0.w =" << p0.w; + qDebug() << "p1 =" << p1; + qDebug() << "p0 =" << p0; qDebug() << "qApp->getDevicePixelRatio() =" << qApp->getDevicePixelRatio(); qDebug() << "fontSize =" << fontSize; qDebug() << "pixelHeight =" << pixelHeight; diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index abc1e49101..98634d7aed 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -211,7 +211,6 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { //Handle fading and deactivation/activation of UI gpu::Batch batch; - renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get(); geometryCache->useSimpleDrawPipeline(batch); @@ -279,7 +278,6 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int vec2 canvasSize = qApp->getCanvasSize(); _textureAspectRatio = aspect(canvasSize); - renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get(); gpu::Batch batch; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6f18cac127..7254295c2f 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -92,7 +92,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope renderStatsAndLogs(renderArgs); // currently renders nothing - renderArgs->_context->syncCache(); renderArgs->_context->render(batch); renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 7667fa1a29..da8deefa74 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -13,8 +13,7 @@ const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display"); -const QString MENU_PATH = "Display"; -const QString FULLSCREEN = "Fullscreen"; +static const QString FULLSCREEN = "Fullscreen"; const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { return NAME; @@ -30,11 +29,11 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() { CONTAINER->unsetFullscreen(); } }, true, false); - MainWindowOpenGLDisplayPlugin::activate(); + WindowOpenGLDisplayPlugin::activate(); } void Basic2DWindowOpenGLDisplayPlugin::deactivate() { - MainWindowOpenGLDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); } int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval(bool isThrottled) const { diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 477e214f4e..64edfe3600 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -7,10 +7,10 @@ // #pragma once -#include "MainWindowOpenGLDisplayPlugin.h" +#include "WindowOpenGLDisplayPlugin.h" class QScreen; -class Basic2DWindowOpenGLDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT public: diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 2316ff70c4..5840b3cbba 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -15,8 +15,10 @@ #include "Basic2DWindowOpenGLDisplayPlugin.h" #include "openvr/OpenVrDisplayPlugin.h" -#include "oculus/Oculus_0_5_DisplayPlugin.h" -#include "oculus/Oculus_0_6_DisplayPlugin.h" +#include "oculus/OculusDisplayPlugin.h" +#include "oculus/OculusLegacyDisplayPlugin.h" + +const QString DisplayPlugin::MENU_PATH{ "Display" }; // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class DisplayPluginList getDisplayPlugins() { @@ -27,14 +29,20 @@ DisplayPluginList getDisplayPlugins() { #endif // Stereo modes - // FIXME fix stereo display plugins - //new SideBySideStereoDisplayPlugin(), - //new InterleavedStereoDisplayPlugin(), + + // SBS left/right + new SideBySideStereoDisplayPlugin(), + // Interleaved left/right + new InterleavedStereoDisplayPlugin(), // HMDs - new Oculus_0_5_DisplayPlugin(), - new Oculus_0_6_DisplayPlugin(), + + // Windows Oculus SDK + new OculusDisplayPlugin(), + // Mac/Linux Oculus SDK (0.5) + new OculusLegacyDisplayPlugin(), #ifdef Q_OS_WIN + // SteamVR SDK new OpenVrDisplayPlugin(), #endif nullptr diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index 45a5923a1f..8ee2d698a6 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -97,12 +97,12 @@ public: return baseProjection; } - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const { - return glm::inverse(getEyePose(eye)) * baseModelview; + virtual glm::mat4 getView(Eye eye, const glm::mat4& baseView) const { + return glm::inverse(getEyePose(eye)) * baseView; } // HMD specific methods - // TODO move these into another class + // TODO move these into another class? virtual glm::mat4 getEyePose(Eye eye) const { static const glm::mat4 pose; return pose; } @@ -115,12 +115,7 @@ public: virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } - //// The window for the surface, used for event interception. May be null. - //virtual QWindow* getWindow() const = 0; - - //virtual void installEventFilter(QObject* filter) {} - //virtual void removeEventFilter(QObject* filter) {} - + static const QString MENU_PATH; signals: void recommendedFramebufferSizeChanged(const QSize & size); void requestRender(); diff --git a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp deleted file mode 100644 index 68fe92c943..0000000000 --- a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp +++ /dev/null @@ -1,9 +0,0 @@ - -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 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 "MainWindowOpenGLDisplayPlugin.h" diff --git a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h deleted file mode 100644 index 5b28ec7c21..0000000000 --- a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 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 "WindowOpenGLDisplayPlugin.h" - -class MainWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { -}; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 7269c9410c..0409899739 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -105,7 +105,7 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { void OpenGLDisplayPlugin::display( GLuint finalTexture, const glm::uvec2& sceneSize) { using namespace oglplus; - uvec2 size = getRecommendedRenderSize(); + uvec2 size = getSurfaceSize(); Context::Viewport(size.x, size.y); glBindTexture(GL_TEXTURE_2D, finalTexture); drawUnitQuad(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 9b6b92d8d4..3152500232 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -33,6 +33,7 @@ public: protected: virtual void customizeContext(); virtual void drawUnitQuad(); + virtual glm::uvec2 getSurfaceSize() const = 0; virtual void makeCurrent() = 0; virtual void doneCurrent() = 0; virtual void swapBuffers() = 0; diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp index 658aa2c767..ffea6605af 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp @@ -16,6 +16,10 @@ WindowOpenGLDisplayPlugin::WindowOpenGLDisplayPlugin() { } glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { + return getSurfaceSize(); +} + +glm::uvec2 WindowOpenGLDisplayPlugin::getSurfaceSize() const { uvec2 result; if (_window) { result = toGlm(_window->geometry().size() * _window->devicePixelRatio()); @@ -23,6 +27,7 @@ glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { return result; } + glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedUiSize() const { uvec2 result; if (_window) { diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h index c75cf1484c..fc7691fc56 100644 --- a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h @@ -21,6 +21,7 @@ public: virtual void deactivate() override; protected: + virtual glm::uvec2 getSurfaceSize() const override final; virtual void makeCurrent() override; virtual void doneCurrent() override; virtual void swapBuffers() override; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp deleted file mode 100644 index 7d0fb705df..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 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 "OculusBaseDisplayPlugin.h" - -#include - -#include "OculusHelpers.h" - - -using namespace Oculus; - -void OculusBaseDisplayPlugin::activate() { - glm::uvec2 eyeSizes[2]; - ovr_for_each_eye([&](ovrEyeType eye) { - _eyeFovs[eye] = _hmd->MaxEyeFov[eye]; - ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovrHmd_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); - ovrMatrix4f ovrPerspectiveProjection = - ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); - _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); - - ovrPerspectiveProjection = - ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); - _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); - - _eyeOffsets[eye] = erd.HmdToEyeViewOffset; - eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); - }); - _desiredFramebufferSize = uvec2( - eyeSizes[0].x + eyeSizes[1].x, - std::max(eyeSizes[0].y, eyeSizes[1].y)); - - _frameIndex = 0; - - if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, - ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { - qFatal("Could not attach to sensor device"); - } - - MainWindowOpenGLDisplayPlugin::activate(); -} - -uvec2 OculusBaseDisplayPlugin::getRecommendedRenderSize() const { - return _desiredFramebufferSize; -} - -void OculusBaseDisplayPlugin::preRender() { - ovrHmd_GetEyePoses(_hmd, _frameIndex, _eyeOffsets, _eyePoses, nullptr); -} - -glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { - return _eyeProjections[eye]; -} - -glm::mat4 OculusBaseDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelview) const { - return baseModelview * toGlm(_eyePoses[eye]); -} - -void OculusBaseDisplayPlugin::resetSensors() { - ovrHmd_RecenterPose(_hmd); -} - -glm::mat4 OculusBaseDisplayPlugin::getEyePose(Eye eye) const { - return toGlm(_eyePoses[eye]); -} - -// Should NOT be used for rendering as this will mess up timewarp. Use the getModelview() method above for -// any use of head poses for rendering, ensuring you use the correct eye -glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const { - ovrTrackingState state = ovrHmd_GetTrackingState(_hmd, 0.0f); - return toGlm(state.HeadPose.ThePose); -} diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h deleted file mode 100644 index e376ae12ba..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 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 "../MainWindowOpenGLDisplayPlugin.h" - -class OculusBaseDisplayPlugin : public MainWindowOpenGLDisplayPlugin { -public: - // Stereo specific methods - virtual bool isHmd() const override { return true; } - virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; - virtual void activate() override; - virtual void preRender() override; - virtual glm::uvec2 getRecommendedRenderSize() const override; - virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } - virtual void resetSensors() override; - virtual glm::mat4 getEyePose(Eye eye) const override; - virtual glm::mat4 getHeadPose() const override; - -}; diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp similarity index 59% rename from libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp rename to libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index 6ed8977f47..ff218987ec 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Oculus_0_6_DisplayPlugin.h" +#include "OculusDisplayPlugin.h" #include @@ -15,9 +15,7 @@ #include #include #include - -#include - +#include #include #include @@ -27,18 +25,34 @@ #include #include +#include #include "OculusHelpers.h" -using namespace Oculus; -#if (OVR_MAJOR_VERSION == 6) -SwapFboPtr _sceneFbo; -MirrorFboPtr _mirrorFbo; -ovrLayerEyeFov _sceneLayer; +#if (OVR_MAJOR_VERSION == 6) +#define ovr_Create ovrHmd_Create +#define ovr_CreateSwapTextureSetGL ovrHmd_CreateSwapTextureSetGL +#define ovr_CreateMirrorTextureGL ovrHmd_CreateMirrorTextureGL +#define ovr_Destroy ovrHmd_Destroy +#define ovr_DestroySwapTextureSet ovrHmd_DestroySwapTextureSet +#define ovr_DestroyMirrorTexture ovrHmd_DestroyMirrorTexture +#define ovr_GetFloat ovrHmd_GetFloat +#define ovr_GetFovTextureSize ovrHmd_GetFovTextureSize +#define ovr_GetFrameTiming ovrHmd_GetFrameTiming +#define ovr_GetTrackingState ovrHmd_GetTrackingState +#define ovr_GetRenderDesc ovrHmd_GetRenderDesc +#define ovr_RecenterPose ovrHmd_RecenterPose +#define ovr_SubmitFrame ovrHmd_SubmitFrame +#define ovr_ConfigureTracking ovrHmd_ConfigureTracking + +#define ovr_GetHmdDesc(X) *X +#endif + +#if (OVR_MAJOR_VERSION >= 6) // A base class for FBO wrappers that need to use the Oculus C -// API to manage textures via ovrHmd_CreateSwapTextureSetGL, -// ovrHmd_CreateMirrorTextureGL, etc +// API to manage textures via ovr_CreateSwapTextureSetGL, +// ovr_CreateMirrorTextureGL, etc template struct RiftFramebufferWrapper : public FramebufferWrapper { ovrHmd hmd; @@ -73,7 +87,7 @@ struct SwapFramebufferWrapper : public RiftFramebufferWrapper { virtual ~MirrorFramebufferWrapper() { if (color) { - ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); + ovr_DestroyMirrorTexture(hmd, (ovrTexture*)color); color = nullptr; } } @@ -135,10 +149,10 @@ struct MirrorFramebufferWrapper : public RiftFramebufferWrapper { private: void initColor() override { if (color) { - ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); + ovr_DestroyMirrorTexture(hmd, (ovrTexture*)color); color = nullptr; } - ovrResult result = ovrHmd_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); + ovrResult result = ovr_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); Q_ASSERT(OVR_SUCCESS(result)); } @@ -149,17 +163,47 @@ private: } }; + #endif +const QString OculusDisplayPlugin::NAME("Oculus Rift"); -const QString Oculus_0_6_DisplayPlugin::NAME("Oculus Rift"); +uvec2 OculusDisplayPlugin::getRecommendedRenderSize() const { + return _desiredFramebufferSize; +} -const QString & Oculus_0_6_DisplayPlugin::getName() const { +void OculusDisplayPlugin::preRender() { +#if (OVR_MAJOR_VERSION >= 6) + ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex); + _trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds); + ovr_CalcEyePoses(_trackingState.HeadPose.ThePose, _eyeOffsets, _eyePoses); +#endif +} + +glm::mat4 OculusDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +void OculusDisplayPlugin::resetSensors() { +#if (OVR_MAJOR_VERSION >= 6) + ovr_RecenterPose(_hmd); +#endif +} + +glm::mat4 OculusDisplayPlugin::getEyePose(Eye eye) const { + return toGlm(_eyePoses[eye]); +} + +glm::mat4 OculusDisplayPlugin::getHeadPose() const { + return toGlm(_trackingState.HeadPose.ThePose); +} + +const QString & OculusDisplayPlugin::getName() const { return NAME; } -bool Oculus_0_6_DisplayPlugin::isSupported() const { -#if (OVR_MAJOR_VERSION == 6) +bool OculusDisplayPlugin::isSupported() const { +#if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { return false; } @@ -174,27 +218,81 @@ bool Oculus_0_6_DisplayPlugin::isSupported() const { #endif } +void OculusDisplayPlugin::init() { + if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { + qFatal("Could not init OVR"); + } +} -#if (OVR_MAJOR_VERSION == 6) -ovrLayerEyeFov& getSceneLayer() { +void OculusDisplayPlugin::deinit() { + ovr_Shutdown(); +} + +#if (OVR_MAJOR_VERSION >= 6) +ovrLayerEyeFov& OculusDisplayPlugin::getSceneLayer() { return _sceneLayer; } #endif //static gpu::TexturePointer _texture; -void Oculus_0_6_DisplayPlugin::activate() { -#if (OVR_MAJOR_VERSION == 6) +void OculusDisplayPlugin::activate() { +#if (OVR_MAJOR_VERSION >= 6) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { Q_ASSERT(false); qFatal("Failed to Initialize SDK"); } - if (!OVR_SUCCESS(ovrHmd_Create(0, &_hmd))) { + +// CONTAINER->getPrimarySurface()->makeCurrent(); +#if (OVR_MAJOR_VERSION == 6) + if (!OVR_SUCCESS(ovr_Create(0, &_hmd))) { +#elif (OVR_MAJOR_VERSION == 7) + if (!OVR_SUCCESS(ovr_Create(&_hmd, &_luid))) { +#endif Q_ASSERT(false); qFatal("Failed to acquire HMD"); } - OculusBaseDisplayPlugin::activate(); + _hmdDesc = ovr_GetHmdDesc(_hmd); + + _ipd = ovr_GetFloat(_hmd, OVR_KEY_IPD, _ipd); + + glm::uvec2 eyeSizes[2]; + ovr_for_each_eye([&](ovrEyeType eye) { + _eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye]; + ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); + _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); + }); + ovrFovPort combined = _eyeFovs[Left]; + combined.LeftTan = std::max(_eyeFovs[Left].LeftTan, _eyeFovs[Right].LeftTan); + combined.RightTan = std::max(_eyeFovs[Left].RightTan, _eyeFovs[Right].RightTan); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(combined, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[Mono] = toGlm(ovrPerspectiveProjection); + + + + _desiredFramebufferSize = uvec2( + eyeSizes[0].x + eyeSizes[1].x, + std::max(eyeSizes[0].y, eyeSizes[1].y)); + + _frameIndex = 0; + + if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, + ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { + qFatal("Could not attach to sensor device"); + } + + WindowOpenGLDisplayPlugin::activate(); // Parent class relies on our _hmd intialization, so it must come after that. ovrLayerEyeFov& sceneLayer = getSceneLayer(); @@ -203,7 +301,7 @@ void Oculus_0_6_DisplayPlugin::activate() { sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; ovr_for_each_eye([&](ovrEyeType eye) { ovrFovPort & fov = sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov; - ovrSizei & size = sceneLayer.Viewport[eye].Size = ovrHmd_GetFovTextureSize(_hmd, eye, fov, 1.0f); + ovrSizei & size = sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_hmd, eye, fov, 1.0f); sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 }; }); // We're rendering both eyes to the same texture, so only one of the @@ -214,17 +312,16 @@ void Oculus_0_6_DisplayPlugin::activate() { PerformanceTimer::setActive(true); - if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, + if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd, ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { qFatal("Could not attach to sensor device"); } #endif } -void Oculus_0_6_DisplayPlugin::customizeContext() { -#if (OVR_MAJOR_VERSION == 6) - OculusBaseDisplayPlugin::customizeContext(); - +void OculusDisplayPlugin::customizeContext() { + WindowOpenGLDisplayPlugin::customizeContext(); +#if (OVR_MAJOR_VERSION >= 6) //_texture = DependencyManager::get()-> // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); uvec2 mirrorSize = toGlm(_window->geometry().size()); @@ -236,24 +333,29 @@ void Oculus_0_6_DisplayPlugin::customizeContext() { #endif } -void Oculus_0_6_DisplayPlugin::deactivate() { -#if (OVR_MAJOR_VERSION == 6) +void OculusDisplayPlugin::deactivate() { +#if (OVR_MAJOR_VERSION >= 6) makeCurrent(); _sceneFbo.reset(); _mirrorFbo.reset(); doneCurrent(); PerformanceTimer::setActive(false); - OculusBaseDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); - ovrHmd_Destroy(_hmd); + ovr_Destroy(_hmd); _hmd = nullptr; ovr_Shutdown(); #endif } -void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { -#if (OVR_MAJOR_VERSION == 6) +void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { + static bool inDisplay = false; + if (inDisplay) { + return; + } + inDisplay = true; +#if (OVR_MAJOR_VERSION >= 6) using namespace oglplus; // Need to make sure only the display plugin is responsible for // controlling vsync @@ -263,6 +365,7 @@ void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc auto size = _sceneFbo->size; Context::Viewport(size.x, size.y); glBindTexture(GL_TEXTURE_2D, finalTexture); + GLenum err = glGetError(); drawUnitQuad(); }); @@ -280,17 +383,25 @@ void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc the UI visible in the output window (unlikely). This should be done before _sceneFbo->Increment or we're be using the wrong texture */ - //_sceneFbo->Bound(GL_READ_FRAMEBUFFER, [&] { - // glBlitFramebuffer( - // 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, - // 0, 0, windowSize.x, _mirrorFbo.y, - // GL_COLOR_BUFFER_BIT, GL_NEAREST); - //}); + _sceneFbo->Bound(Framebuffer::Target::Read, [&] { + glBlitFramebuffer( + 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, + 0, 0, windowSize.x, windowSize.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + }); { PerformanceTimer("OculusSubmit"); + ovrViewScaleDesc viewScaleDesc; + viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; + viewScaleDesc.HmdToEyeViewOffset[0] = _eyeOffsets[0]; + viewScaleDesc.HmdToEyeViewOffset[1] = _eyeOffsets[1]; + ovrLayerHeader* layers = &sceneLayer.Header; - ovrResult result = ovrHmd_SubmitFrame(_hmd, _frameIndex, nullptr, &layers, 1); + ovrResult result = ovr_SubmitFrame(_hmd, 0, &viewScaleDesc, &layers, 1); + if (!OVR_SUCCESS(result)) { + qDebug() << result; + } } _sceneFbo->Increment(); @@ -299,21 +410,22 @@ void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc will contain the post-distorted and fully composited scene regardless of how many layers we send. */ - auto mirrorSize = _mirrorFbo->size; - _mirrorFbo->Bound(Framebuffer::Target::Read, [&] { - Context::BlitFramebuffer( - 0, mirrorSize.y, mirrorSize.x, 0, - 0, 0, windowSize.x, windowSize.y, - BufferSelectBit::ColorBuffer, BlitFilter::Nearest); - }); + //auto mirrorSize = _mirrorFbo->size; + //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] { + // Context::BlitFramebuffer( + // 0, mirrorSize.y, mirrorSize.x, 0, + // 0, 0, windowSize.x, windowSize.y, + // BufferSelectBit::ColorBuffer, BlitFilter::Nearest); + //}); ++_frameIndex; #endif + inDisplay = false; } // Pass input events on to the application -bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { -#if (OVR_MAJOR_VERSION == 6) +bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { +#if (OVR_MAJOR_VERSION >= 6) if (event->type() == QEvent::Resize) { QResizeEvent* resizeEvent = static_cast(event); qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height(); @@ -323,7 +435,7 @@ bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { doneCurrent(); } #endif - return OculusBaseDisplayPlugin::eventFilter(receiver, event); + return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); } /* @@ -331,8 +443,8 @@ bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { However, it should only be done if we can reliably disable v-sync on the mirror surface, otherwise the swapbuffer delay will interefere with the framerate of the headset */ -void Oculus_0_6_DisplayPlugin::finishFrame() { - swapBuffers(); +void OculusDisplayPlugin::finishFrame() { + //swapBuffers(); doneCurrent(); }; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h new file mode 100644 index 0000000000..75173fd2bd --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h @@ -0,0 +1,78 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 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 "../WindowOpenGLDisplayPlugin.h" + +#include + +#include + +class OffscreenGlCanvas; +struct SwapFramebufferWrapper; +struct MirrorFramebufferWrapper; + +using SwapFboPtr = QSharedPointer; +using MirrorFboPtr = QSharedPointer; + +class OculusDisplayPlugin : public WindowOpenGLDisplayPlugin { +public: + virtual bool isSupported() const override; + virtual const QString & getName() const override; + + virtual void init() override; + virtual void deinit() override; + + virtual void activate() override; + virtual void deactivate() override; + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + + // Stereo specific methods + virtual bool isHmd() const override { return true; } + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } + virtual void resetSensors() override; + virtual glm::mat4 getEyePose(Eye eye) const override; + virtual glm::mat4 getHeadPose() const override; + +protected: + virtual void preRender() override; + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + virtual void customizeContext() override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; + + ovrHmd _hmd; + float _ipd{ OVR_DEFAULT_IPD }; + unsigned int _frameIndex; + ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrPosef _eyePoses[2]; + ovrVector3f _eyeOffsets[2]; + ovrFovPort _eyeFovs[2]; + mat4 _eyeProjections[3]; + mat4 _compositeEyeProjections[2]; + uvec2 _desiredFramebufferSize; + ovrTrackingState _trackingState; + +#if (OVR_MAJOR_VERSION >= 6) + ovrLayerEyeFov& getSceneLayer(); + ovrHmdDesc _hmdDesc; + SwapFboPtr _sceneFbo; + MirrorFboPtr _mirrorFbo; + ovrLayerEyeFov _sceneLayer; +#endif +#if (OVR_MAJOR_VERSION == 7) + ovrGraphicsLuid _luid; +#endif +}; + diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h index 31b7c246af..df0a6c5228 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h @@ -7,7 +7,7 @@ // #pragma once -#include +#include #include #include #include @@ -79,14 +79,3 @@ inline ovrQuatf ovrFromGlm(const glm::quat & q) { return{ q.x, q.y, q.z, q.w }; } -namespace Oculus { - extern ovrHmd _hmd; - extern unsigned int _frameIndex; - extern ovrEyeRenderDesc _eyeRenderDescs[2]; - extern ovrPosef _eyePoses[2]; - extern ovrVector3f _eyeOffsets[2]; - extern ovrFovPort _eyeFovs[2]; - extern mat4 _eyeProjections[2]; - extern mat4 _compositeEyeProjections[2]; - extern uvec2 _desiredFramebufferSize; -} diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp similarity index 52% rename from libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp rename to libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp index 86ee3b41f2..e823f456ce 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.cpp @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Oculus_0_5_DisplayPlugin.h" +#include "OculusLegacyDisplayPlugin.h" #include @@ -19,34 +19,66 @@ #include #include - -#include - #include #include +#include #include "plugins/PluginContainer.h" #include "OculusHelpers.h" -using namespace Oculus; -ovrTexture _eyeTextures[2]; -int _hmdScreen{ -1 }; -bool _hswDismissed{ false }; - -DisplayPlugin* makeOculusDisplayPlugin() { - return new Oculus_0_5_DisplayPlugin(); -} - using namespace oglplus; -const QString Oculus_0_5_DisplayPlugin::NAME("Oculus Rift (0.5)"); +const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift (0.5)"); -const QString & Oculus_0_5_DisplayPlugin::getName() const { +const QString & OculusLegacyDisplayPlugin::getName() const { return NAME; } +OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() : _ipd(OVR_DEFAULT_IPD) { +} -bool Oculus_0_5_DisplayPlugin::isSupported() const { +uvec2 OculusLegacyDisplayPlugin::getRecommendedRenderSize() const { + return _desiredFramebufferSize; +} + +void OculusLegacyDisplayPlugin::preRender() { +#if (OVR_MAJOR_VERSION == 5) + ovrHmd_GetEyePoses(_hmd, _frameIndex, _eyeOffsets, _eyePoses, &_trackingState); + ovrHmd_BeginFrame(_hmd, _frameIndex); +#endif + WindowOpenGLDisplayPlugin::preRender(); +} + +glm::mat4 OculusLegacyDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +void OculusLegacyDisplayPlugin::resetSensors() { +#if (OVR_MAJOR_VERSION == 5) + ovrHmd_RecenterPose(_hmd); +#endif +} + +glm::mat4 OculusLegacyDisplayPlugin::getEyePose(Eye eye) const { +#if (OVR_MAJOR_VERSION == 5) + return toGlm(_eyePoses[eye]); +#else + return WindowOpenGLDisplayPlugin::getEyePose(eye); +#endif +} + +// Should NOT be used for rendering as this will mess up timewarp. Use the getModelview() method above for +// any use of head poses for rendering, ensuring you use the correct eye +glm::mat4 OculusLegacyDisplayPlugin::getHeadPose() const { +#if (OVR_MAJOR_VERSION == 5) + return toGlm(_trackingState.HeadPose.ThePose); +#else + return WindowOpenGLDisplayPlugin::getHeadPose(); +#endif +} + + +bool OculusLegacyDisplayPlugin::isSupported() const { #if (OVR_MAJOR_VERSION == 5) if (!ovr_Initialize(nullptr)) { return false; @@ -77,7 +109,7 @@ bool Oculus_0_5_DisplayPlugin::isSupported() const { #endif } -void Oculus_0_5_DisplayPlugin::activate() { +void OculusLegacyDisplayPlugin::activate() { #if (OVR_MAJOR_VERSION == 5) if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { Q_ASSERT(false); @@ -89,7 +121,41 @@ void Oculus_0_5_DisplayPlugin::activate() { qFatal("Failed to acquire HMD"); } - OculusBaseDisplayPlugin::activate(); + glm::uvec2 eyeSizes[2]; + ovr_for_each_eye([&](ovrEyeType eye) { + _eyeFovs[eye] = _hmd->MaxEyeFov[eye]; + ovrEyeRenderDesc erd = _eyeRenderDescs[eye] = ovrHmd_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); + _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); + }); + ovrFovPort combined = _eyeFovs[Left]; + combined.LeftTan = std::max(_eyeFovs[Left].LeftTan, _eyeFovs[Right].LeftTan); + combined.RightTan = std::max(_eyeFovs[Left].RightTan, _eyeFovs[Right].RightTan); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(combined, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[Mono] = toGlm(ovrPerspectiveProjection); + + _desiredFramebufferSize = uvec2( + eyeSizes[0].x + eyeSizes[1].x, + std::max(eyeSizes[0].y, eyeSizes[1].y)); + + _frameIndex = 0; + + if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, + ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { + qFatal("Could not attach to sensor device"); + } + + WindowOpenGLDisplayPlugin::activate(); + int screen = getHmdScreen(); if (screen != -1) { CONTAINER->setFullscreen(qApp->screens()[screen]); @@ -118,17 +184,16 @@ void Oculus_0_5_DisplayPlugin::activate() { } }); - ovrEyeRenderDesc _eyeRenderDescs[ovrEye_Count]; ovrBool result = ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); Q_ASSERT(result); #endif } -void Oculus_0_5_DisplayPlugin::deactivate() { +void OculusLegacyDisplayPlugin::deactivate() { #if (OVR_MAJOR_VERSION == 5) _window->removeEventFilter(this); - OculusBaseDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); QScreen* riftScreen = nullptr; if (_hmdScreen >= 0) { @@ -142,18 +207,11 @@ void Oculus_0_5_DisplayPlugin::deactivate() { #endif } -void Oculus_0_5_DisplayPlugin::preRender() { -#if (OVR_MAJOR_VERSION == 5) - OculusBaseDisplayPlugin::preRender(); - ovrHmd_BeginFrame(_hmd, _frameIndex); -#endif -} - -void Oculus_0_5_DisplayPlugin::preDisplay() { +void OculusLegacyDisplayPlugin::preDisplay() { _window->makeCurrent(); } -void Oculus_0_5_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { +void OculusLegacyDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { ++_frameIndex; #if (OVR_MAJOR_VERSION == 5) ovr_for_each_eye([&](ovrEyeType eye) { @@ -164,7 +222,7 @@ void Oculus_0_5_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sc } // Pass input events on to the application -bool Oculus_0_5_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { +bool OculusLegacyDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { #if (OVR_MAJOR_VERSION == 5) if (!_hswDismissed && (event->type() == QEvent::KeyPress)) { static ovrHSWDisplayState hswState; @@ -176,17 +234,19 @@ bool Oculus_0_5_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { } } #endif - return OculusBaseDisplayPlugin::eventFilter(receiver, event); + return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); } // FIXME mirroring tot he main window is diffucult on OSX because it requires that we // trigger a swap, which causes the client to wait for the v-sync of the main screen running // at 60 Hz. This would introduce judder. Perhaps we can push mirroring to a separate // thread -void Oculus_0_5_DisplayPlugin::finishFrame() { +// FIXME If we move to the 'batch rendering on a different thread' we can possibly do this. +// however, we need to make sure it doesn't block the event handling. +void OculusLegacyDisplayPlugin::finishFrame() { _window->doneCurrent(); }; -int Oculus_0_5_DisplayPlugin::getHmdScreen() const { +int OculusLegacyDisplayPlugin::getHmdScreen() const { return _hmdScreen; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h new file mode 100644 index 0000000000..219b6c54b3 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusLegacyDisplayPlugin.h @@ -0,0 +1,63 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 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 "../WindowOpenGLDisplayPlugin.h" + +#include + +#include + +class OculusLegacyDisplayPlugin : public WindowOpenGLDisplayPlugin { +public: + OculusLegacyDisplayPlugin(); + virtual bool isSupported() const override; + virtual const QString & getName() const override; + + virtual void activate() override; + virtual void deactivate() override; + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + virtual int getHmdScreen() const override; + + // Stereo specific methods + virtual bool isHmd() const override { return true; } + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } + virtual void resetSensors() override; + virtual glm::mat4 getEyePose(Eye eye) const override; + virtual glm::mat4 getHeadPose() const override; + +protected: + virtual void preRender() override; + virtual void preDisplay() override; + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; + + float _ipd{ OVR_DEFAULT_IPD }; + ovrHmd _hmd; + unsigned int _frameIndex; + ovrTrackingState _trackingState; + ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrPosef _eyePoses[2]; + ovrVector3f _eyeOffsets[2]; + ovrFovPort _eyeFovs[2]; + mat4 _eyeProjections[3]; + mat4 _compositeEyeProjections[2]; + uvec2 _desiredFramebufferSize; + ovrTexture _eyeTextures[2]; + mutable int _hmdScreen{ -1 }; + bool _hswDismissed{ false }; +}; + + diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h deleted file mode 100644 index b539d07fb0..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 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 "OculusBaseDisplayPlugin.h" - -#include - -class Oculus_0_5_DisplayPlugin : public OculusBaseDisplayPlugin { -public: - virtual bool isSupported() const override; - virtual const QString & getName() const override; - - virtual void activate() override; - virtual void deactivate() override; - - virtual bool eventFilter(QObject* receiver, QEvent* event) override; - - virtual int getHmdScreen() const override; - -protected: - virtual void preRender() override; - virtual void preDisplay() override; - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; - // Do not perform swap in finish - virtual void finishFrame() override; - -private: - static const QString NAME; -}; - - diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h deleted file mode 100644 index 0fde5e76b3..0000000000 --- a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/05/29 -// Copyright 2015 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 "OculusBaseDisplayPlugin.h" - -#include - -class OffscreenGlCanvas; -struct SwapFramebufferWrapper; -struct MirrorFramebufferWrapper; - -using SwapFboPtr = QSharedPointer; -using MirrorFboPtr = QSharedPointer; - -class Oculus_0_6_DisplayPlugin : public OculusBaseDisplayPlugin { -public: - virtual bool isSupported() const override; - virtual const QString & getName() const override; - - virtual void activate() override; - virtual void deactivate() override; - - - virtual bool eventFilter(QObject* receiver, QEvent* event) override; - -protected: - virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; - virtual void customizeContext() override; - // Do not perform swap in finish - virtual void finishFrame() override; - -private: - static const QString NAME; -}; - diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp index 181546d428..1e3e7699f4 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -128,7 +128,7 @@ void OpenVrDisplayPlugin::activate() { delete[] buffer; } Q_ASSERT(unSize <= 1); - MainWindowOpenGLDisplayPlugin::activate(); + WindowOpenGLDisplayPlugin::activate(); } void OpenVrDisplayPlugin::deactivate() { @@ -141,7 +141,7 @@ void OpenVrDisplayPlugin::deactivate() { _hmd = nullptr; } _compositor = nullptr; - MainWindowOpenGLDisplayPlugin::deactivate(); + WindowOpenGLDisplayPlugin::deactivate(); } uvec2 OpenVrDisplayPlugin::getRecommendedRenderSize() const { @@ -149,19 +149,19 @@ uvec2 OpenVrDisplayPlugin::getRecommendedRenderSize() const { } mat4 OpenVrDisplayPlugin::getProjection(Eye eye, const mat4& baseProjection) const { + // FIXME hack to ensure that we don't crash trying to get the combined matrix + if (eye == Mono) { + eye = Left; + } return _eyesData[eye]._projectionMatrix; } -glm::mat4 OpenVrDisplayPlugin::getModelview(Eye eye, const mat4& baseModelview) const { - return baseModelview * getEyePose(eye); -} - void OpenVrDisplayPlugin::resetSensors() { _sensorResetMat = glm::inverse(cancelOutRollAndPitch(_trackedDevicePoseMat4[0])); } glm::mat4 OpenVrDisplayPlugin::getEyePose(Eye eye) const { - return getHeadPose() * _eyesData[eye]._eyeOffset; + return _eyesData[eye]._eyeOffset * getHeadPose(); } glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { @@ -169,7 +169,7 @@ glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { } void OpenVrDisplayPlugin::customizeContext() { - MainWindowOpenGLDisplayPlugin::customizeContext(); + WindowOpenGLDisplayPlugin::customizeContext(); } void OpenVrDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h index 608a869341..afe024e72b 100644 --- a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h @@ -11,9 +11,9 @@ #if defined(Q_OS_WIN) -#include "../MainWindowOpenGLDisplayPlugin.h" +#include "../WindowOpenGLDisplayPlugin.h" -class OpenVrDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +class OpenVrDisplayPlugin : public WindowOpenGLDisplayPlugin { public: virtual bool isSupported() const override; virtual const QString & getName() const override; @@ -27,7 +27,6 @@ public: // Stereo specific methods virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; virtual void resetSensors() override; virtual glm::mat4 getEyePose(Eye eye) const override; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 7d017f714d..23bc176d65 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -17,6 +17,42 @@ #include +static const char * INTERLEAVED_TEXTURED_VS = R"VS(#version 410 core +#pragma line __LINE__ + +in vec3 Position; +in vec2 TexCoord; + +out vec2 vTexCoord; + +void main() { + gl_Position = vec4(Position, 1); + vTexCoord = TexCoord; +} + +)VS"; + +static const char * INTERLEAVED_TEXTURED_FS = R"FS(#version 410 core +#pragma line __LINE__ + +uniform sampler2D sampler; +uniform ivec2 textureSize; + +in vec2 vTexCoord; +out vec4 FragColor; + +void main() { + ivec2 texCoord = ivec2(floor(vTexCoord * textureSize)); + texCoord.x /= 2; + int row = int(floor(gl_FragCoord.y)); + if (row % 2 > 0) { + texCoord.x += (textureSize.x / 2); + } + FragColor = texelFetch(sampler, texCoord, 0); //texture(sampler, texCoord); +} + +)FS"; + const QString InterleavedStereoDisplayPlugin::NAME("Interleaved Stereo Display"); const QString & InterleavedStereoDisplayPlugin::getName() const { @@ -29,5 +65,20 @@ InterleavedStereoDisplayPlugin::InterleavedStereoDisplayPlugin() { void InterleavedStereoDisplayPlugin::customizeContext() { StereoDisplayPlugin::customizeContext(); // Set up the stencil buffers? Or use a custom shader? - + compileProgram(_program, INTERLEAVED_TEXTURED_VS, INTERLEAVED_TEXTURED_FS); } + +glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { + uvec2 result = WindowOpenGLDisplayPlugin::getRecommendedRenderSize(); + result.x *= 2; + result.y /= 2; + return result; +} + +void InterleavedStereoDisplayPlugin::display( + GLuint finalTexture, const glm::uvec2& sceneSize) { + using namespace oglplus; + _program->Bind(); + Uniform(*_program, "textureSize").SetValue(sceneSize); + WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize); +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 1dd38efed5..3044d91247 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -18,6 +18,9 @@ public: // initialize OpenGL context settings needed by the plugin virtual void customizeContext() override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp index e348143250..1bdb02fd26 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -10,14 +10,16 @@ #include #include +#include #include #include #include #include +#include -const QString SideBySideStereoDisplayPlugin::NAME("SBS Stereo Display"); +const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo"); const QString & SideBySideStereoDisplayPlugin::getName() const { return NAME; @@ -26,3 +28,10 @@ const QString & SideBySideStereoDisplayPlugin::getName() const { SideBySideStereoDisplayPlugin::SideBySideStereoDisplayPlugin() { } +glm::uvec2 SideBySideStereoDisplayPlugin::getRecommendedRenderSize() const { + uvec2 result = WindowOpenGLDisplayPlugin::getRecommendedRenderSize(); + result.x *= 2; + return result; +} + + diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h index ead9ea7dc4..9f8440227f 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h @@ -9,11 +9,14 @@ #include "StereoDisplayPlugin.h" +class QScreen; + class SideBySideStereoDisplayPlugin : public StereoDisplayPlugin { Q_OBJECT public: SideBySideStereoDisplayPlugin(); - virtual const QString & getName() const override; + virtual const QString& getName() const override; + virtual glm::uvec2 getRecommendedRenderSize() const override; private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index c741967328..43c3b727c4 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -10,11 +10,14 @@ #include #include +#include #include #include #include #include +#include +#include StereoDisplayPlugin::StereoDisplayPlugin() { } @@ -32,6 +35,11 @@ glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProje // Refer to http://www.nvidia.com/content/gtc-2010/pdfs/2010_gtc2010.pdf on creating // stereo projection matrices. Do NOT use "toe-in", use translation. + if (eye == Mono) { + // FIXME provide a combined matrix, needed for proper culling + return baseProjection; + } + float nearZ = DEFAULT_NEAR_CLIP; // near clipping plane float screenZ = 0.25f; // screen projection plane // FIXME verify this is the right calculation @@ -42,16 +50,44 @@ glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProje return glm::translate(baseProjection, vec3(frustumshift, 0, 0)); } -glm::mat4 StereoDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelview) const { +glm::mat4 StereoDisplayPlugin::getEyePose(Eye eye) const { float modelviewShift = HALF_DEFAULT_IPD; if (eye == Left) { modelviewShift = -modelviewShift; } - return baseModelview * glm::translate(mat4(), vec3(modelviewShift, 0, 0)); + return glm::translate(mat4(), vec3(modelviewShift, 0, 0)); } +std::vector _screenActions; void StereoDisplayPlugin::activate() { - WindowOpenGLDisplayPlugin::activate(); + auto screens = qApp->screens(); + _screenActions.resize(screens.size()); + for (int i = 0; i < screens.size(); ++i) { + auto screen = screens.at(i); + QString name = QString("Screen %1: %2").arg(i + 1).arg(screen->name()); + bool checked = false; + if (screen == qApp->primaryScreen()) { + checked = true; + } + auto action = CONTAINER->addMenuItem(MENU_PATH, name, + [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); + _screenActions[i] = action; + } CONTAINER->setFullscreen(qApp->primaryScreen()); - // FIXME Add menu items + WindowOpenGLDisplayPlugin::activate(); +} + +void StereoDisplayPlugin::updateScreen() { + for (int i = 0; i < _screenActions.size(); ++i) { + if (_screenActions[i]->isChecked()) { + CONTAINER->setFullscreen(qApp->screens().at(i)); + break; + } + } +} + +void StereoDisplayPlugin::deactivate() { + _screenActions.clear(); + CONTAINER->unsetFullscreen(); + WindowOpenGLDisplayPlugin::deactivate(); } diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index bb1a4fd42c..2009c32e1c 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -7,9 +7,9 @@ // #pragma once -#include "../MainWindowOpenGLDisplayPlugin.h" +#include "../WindowOpenGLDisplayPlugin.h" -class StereoDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +class StereoDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT public: StereoDisplayPlugin(); @@ -17,8 +17,12 @@ public: virtual bool isSupported() const override final; virtual void activate() override; + virtual void deactivate() override; virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; - virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; + virtual glm::mat4 getEyePose(Eye eye) const override; +protected: + void updateScreen(); + float _ipd{ 0.064f }; }; diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 72436fc55e..1f1dfa1735 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -30,6 +30,7 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH _port(port) { bindSocket(); + _isListeningTimer = new QTimer(this); connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); _isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS); @@ -171,9 +172,19 @@ void HTTPManager::isTcpServerListening() { bool HTTPManager::bindSocket() { qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); - if (!listen(QHostAddress::Any, _port)) { + + if (listen(QHostAddress::AnyIPv4, _port)) { + qCDebug(embeddedwebserver) << "TCP socket is listening on" << serverAddress() << "and port" << serverPort(); + + return true; + } else { qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; - QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); + QMetaObject::invokeMethod(this, "queuedExit", Qt::QueuedConnection); + + return false; } - return true; -} \ No newline at end of file +} + +void HTTPManager::queuedExit() { + QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); +} diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 6375b10205..03498fbe8d 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -39,6 +39,7 @@ public: private slots: void isTcpServerListening(); + void queuedExit(); private: bool bindSocket(); diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 3ecbc3b2f3..fb6618e953 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -288,3 +288,18 @@ void Batch::resetStages() { ADD_COMMAND(resetStages); } +void Batch::enableStereo(bool enable) { + _enableStereo = enable; +} + +bool Batch::isStereoEnabled() const { + return _enableStereo; +} + +void Batch::enableSkybox(bool enable) { + _enableSkybox = enable; +} + +bool Batch::isSkyboxEnabled() const { + return _enableSkybox; +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index d48ffef209..ca74032c5e 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -26,7 +26,7 @@ ProfileRange(const char *name); ~ProfileRange(); }; - #define PROFILE_RANGE(name) ProfileRange profileRangeThis(name); +#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name); #else #define PROFILE_RANGE(name) #endif @@ -47,6 +47,19 @@ public: ~Batch(); void clear(); + + // Batches may need to override the context level stereo settings + // if they're performing framebuffer copy operations, like the + // deferred lighting resolution mechanism + void enableStereo(bool enable = true); + bool isStereoEnabled() const; + + // Stereo batches will pre-translate the view matrix, but this isn't + // appropriate for skyboxes or other things intended to be drawn at + // infinite distance, so provide a mechanism to render in stereo + // without the pre-translation of the view. + void enableSkybox(bool enable = true); + bool isSkyboxEnabled() const; // Drawcalls void draw(Primitive primitiveType, uint32 numVertices, uint32 startVertex = 0); @@ -276,6 +289,9 @@ public: FramebufferCaches _framebuffers; QueryCaches _queries; + bool _enableStereo{ true }; + bool _enableSkybox{ false }; + protected: }; diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 239c460c77..561a51b477 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -40,6 +40,18 @@ void Context::render(Batch& batch) { _backend->render(batch); } +void Context::enableStereo(bool enable) { + _backend->enableStereo(enable); +} + +void Context::setStereoProjections(const mat4 eyeProjections[2]) { + _backend->setStereoProjections(eyeProjections); +} + +void Context::setStereoViews(const mat4 eyeViews[2]) { + _backend->setStereoViews(eyeViews); +} + void Context::syncCache() { PROFILE_RANGE(__FUNCTION__); _backend->syncCache(); @@ -49,3 +61,26 @@ void Context::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, cons _backend->downloadFramebuffer(srcFramebuffer, region, destImage); } +const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived() const { + _projectionInverse = glm::inverse(_projection); + _viewInverse = glm::inverse(_view); + + Mat4 viewUntranslated = _view; + viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); + _projectionViewUntranslated = _projection * viewUntranslated; + return *this; +} + +Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo) const { + TransformCamera result = *this; + if (!_stereo._skybox) { + result._view = _stereo._eyeViews[eye] * result._view; + } else { + glm::mat4 skyboxView = _stereo._eyeViews[eye]; + skyboxView[3] = vec4(0, 0, 0, 1); + result._view = skyboxView * result._view; + } + result._projection = _stereo._eyeProjections[eye]; + result.recomputeDerived(); + return result; +} diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 7158bd1a6d..9aca83e577 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -14,6 +14,8 @@ #include #include +#include + #include "Batch.h" #include "Resource.h" @@ -25,28 +27,58 @@ class QImage; namespace gpu { +struct StereoState { + bool _enable{ false }; + bool _skybox{ false }; + // 0 for left eye, 1 for right eye + uint8_t _pass{ 0 }; + mat4 _eyeViews[2]; + mat4 _eyeProjections[2]; +}; + class Backend { public: virtual~ Backend() {}; virtual void render(Batch& batch) = 0; + virtual void enableStereo(bool enable) { + _stereo._enable = enable; + } + + void setStereoProjections(const mat4 eyeProjections[2]) { + for (int i = 0; i < 2; ++i) { + _stereo._eyeProjections[i] = eyeProjections[i]; + } + } + + void setStereoViews(const mat4 views[2]) { + for (int i = 0; i < 2; ++i) { + _stereo._eyeViews[i] = views[i]; + } + } + virtual void syncCache() = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; + // UBO class... layout MUST match the layout in TransformCamera.slh class TransformObject { public: Mat4 _model; Mat4 _modelInverse; }; + // UBO class... layout MUST match the layout in TransformCamera.slh class TransformCamera { public: Mat4 _view; - Mat4 _viewInverse; - Mat4 _projectionViewUntranslated; + mutable Mat4 _viewInverse; + mutable Mat4 _projectionViewUntranslated; Mat4 _projection; - Mat4 _projectionInverse; + mutable Mat4 _projectionInverse; Vec4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. + + const Backend::TransformCamera& recomputeDerived() const; + TransformCamera getEyeCamera(int eye, const StereoState& stereo) const; }; template< typename T > @@ -113,7 +145,7 @@ public: } protected: - + StereoState _stereo; }; class Context { @@ -136,7 +168,9 @@ public: ~Context(); void render(Batch& batch); - + void enableStereo(bool enable = true); + void setStereoProjections(const mat4 eyeProjections[2]); + void setStereoViews(const mat4 eyeViews[2]); void syncCache(); // Downloading the Framebuffer is a synchronous action that is not efficient. diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h new file mode 100644 index 0000000000..0fa315ef1c --- /dev/null +++ b/libraries/gpu/src/gpu/Forward.h @@ -0,0 +1,77 @@ +// +// Created by Bradley Austin Davis on 2015/08/15 +// Copyright 2015 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 +#ifndef hifi_gpu_Forward_h +#define hifi_gpu_Forward_h + +namespace gpu { + class Batch; + class Backend; + class Context; + typedef std::shared_ptr ContextPointer; + class GPUObject; + + typedef int Stamp; + typedef uint32_t uint32; + typedef int32_t int32; + typedef uint16_t uint16; + typedef int16_t int16; + typedef uint8_t uint8; + typedef int8_t int8; + + typedef uint8 Byte; + typedef uint32 Offset; + typedef std::vector Offsets; + + typedef glm::mat4 Mat4; + typedef glm::mat3 Mat3; + typedef glm::vec4 Vec4; + typedef glm::ivec4 Vec4i; + typedef glm::vec3 Vec3; + typedef glm::vec2 Vec2; + typedef glm::ivec2 Vec2i; + typedef glm::uvec2 Vec2u; + + class Element; + typedef Element Format; + class Swapchain; + typedef std::shared_ptr SwapchainPointer; + class Framebuffer; + typedef std::shared_ptr FramebufferPointer; + class Pipeline; + typedef std::shared_ptr PipelinePointer; + typedef std::vector Pipelines; + class Query; + typedef std::shared_ptr QueryPointer; + typedef std::vector Queries; + class Resource; + class Buffer; + typedef std::shared_ptr BufferPointer; + typedef std::vector Buffers; + class BufferView; + class Shader; + typedef Shader::Pointer ShaderPointer; + typedef std::vector Shaders; + class State; + typedef std::shared_ptr StatePointer; + typedef std::vector States; + class Stream; + class BufferStream; + typedef std::shared_ptr BufferStreamPointer; + class Texture; + class SphericalHarmonics; + typedef std::shared_ptr SHPointer; + class Sampler; + class Texture; + typedef std::shared_ptr TexturePointer; + typedef std::vector Textures; + class TextureView; + typedef std::vector TextureViews; +} + +#endif diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 4afd2ba940..2270c0dce7 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -127,39 +127,29 @@ void GLBackend::renderPassTransfer(Batch& batch) { const Batch::Commands::value_type* command = batch.getCommands().data(); const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); - _transform._cameraTransforms.resize(0); - _transform._cameraTransforms.push_back(TransformCamera()); + // Reset the transform buffers + _transform._cameras.resize(0); _transform._cameraOffsets.clear(); - _transform._cameraOffsets.push_back(TransformStageState::Pair(0, 0)); - - _transform._objectTransforms.push_back(TransformObject()); - _transform._objectOffsets.push_back(TransformStageState::Pair(0, 0)); + _transform._objects.resize(0); _transform._objectOffsets.clear(); - _transform._objectTransforms.resize(0); - - _commandIndex = 0; - preUpdateTransform(); - int drawCount = 0; for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { switch (*command) { case Batch::COMMAND_draw: case Batch::COMMAND_drawIndexed: case Batch::COMMAND_drawInstanced: case Batch::COMMAND_drawIndexedInstanced: - preUpdateTransform(); - ++drawCount; + _transform.preUpdate(_commandIndex, _stereo); break; case Batch::COMMAND_setModelTransform: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: - case Batch::COMMAND_setProjectionTransform: - { + case Batch::COMMAND_setProjectionTransform: { CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); + break; } - break; default: break; @@ -167,44 +157,31 @@ void GLBackend::renderPassTransfer(Batch& batch) { command++; offset++; } - - - static QByteArray bufferData; - glBindBuffer(GL_UNIFORM_BUFFER, _transform._transformCameraBuffer); - bufferData.resize(_transform._cameraUboSize * _transform._cameraTransforms.size()); - for (size_t i = 0; i < _transform._cameraTransforms.size(); ++i) { - memcpy(bufferData.data() + (_transform._cameraUboSize * i), &_transform._cameraTransforms[i], sizeof(TransformCamera)); - } - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, _transform._transformObjectBuffer); - bufferData.resize(_transform._objectUboSize * _transform._objectTransforms.size()); - for (size_t i = 0; i < _transform._objectTransforms.size(); ++i) { - memcpy(bufferData.data() + (_transform._objectUboSize * i), &_transform._objectTransforms[i], sizeof(TransformObject)); - } - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - CHECK_GL_ERROR(); + _transform.transfer(); } void GLBackend::renderPassDraw(Batch& batch) { + _transform._objectsItr = _transform._objectOffsets.begin(); + _transform._camerasItr = _transform._cameraOffsets.begin(); const size_t numCommands = batch.getCommands().size(); const Batch::Commands::value_type* command = batch.getCommands().data(); const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) { switch (*command) { // Ignore these commands on this pass, taken care of in the transfer pass + // Note we allow COMMAND_setViewportTransform to occur in both passes + // as it both updates the transform object (and thus the uniforms in the + // UBO) as well as executes the actual viewport call case Batch::COMMAND_setModelTransform: - case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: break; - default: - { + default: { CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); + break; } - break; } command++; @@ -213,8 +190,33 @@ void GLBackend::renderPassDraw(Batch& batch) { } void GLBackend::render(Batch& batch) { - renderPassTransfer(batch); - renderPassDraw(batch); + _stereo._skybox = batch.isSkyboxEnabled(); + // Allow the batch to override the rendering stereo settings + // for things like full framebuffer copy operations (deferred lighting passes) + bool savedStereo = _stereo._enable; + if (!batch.isStereoEnabled()) { + _stereo._enable = false; + } + + { + PROFILE_RANGE("Transfer"); + renderPassTransfer(batch); + } + + { + PROFILE_RANGE(_stereo._enable ? "LeftRender" : "Render"); + renderPassDraw(batch); + } + + if (_stereo._enable) { + PROFILE_RANGE("RightRender"); + _stereo._pass = 1; + renderPassDraw(batch); + _stereo._pass = 0; + } + + // Restore the saved stereo state for the next batch + _stereo._enable = savedStereo; } bool GLBackend::checkGLError(const char* name) { diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 455240deb8..c97ea4e615 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -310,19 +310,22 @@ protected: void killTransform(); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncTransformStateCache(); - void updateTransform(); - void preUpdateTransform(); + void updateTransform() const; void resetTransformStage(); - struct TransformStageState { - TransformObject _transformObject; - TransformCamera _transformCamera; - std::vector _objectTransforms; - std::vector _cameraTransforms; + struct TransformStageState { + using TransformObjects = std::vector; + using TransformCameras = std::vector; + + TransformObject _object; + TransformCamera _camera; + TransformObjects _objects; + TransformCameras _cameras; + size_t _cameraUboSize{ 0 }; size_t _objectUboSize{ 0 }; - GLuint _transformObjectBuffer{ 0 }; - GLuint _transformCameraBuffer{ 0 }; + GLuint _objectBuffer{ 0 }; + GLuint _cameraBuffer{ 0 }; Transform _model; Transform _view; Mat4 _projection; @@ -336,6 +339,12 @@ protected: using List = std::list; List _cameraOffsets; List _objectOffsets; + mutable List::const_iterator _objectsItr; + mutable List::const_iterator _camerasItr; + + void preUpdate(size_t commandIndex, const StereoState& stereo); + void update(size_t commandIndex, const StereoState& stereo) const; + void transfer() const; } _transform; int32_t _uboAlignment{ 0 }; diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index d75d0cf521..33ae1dd0a3 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -198,6 +198,9 @@ void GLBackend::do_setFramebuffer(Batch& batch, uint32 paramOffset) { } void GLBackend::do_clearFramebuffer(Batch& batch, uint32 paramOffset) { + if (_stereo._enable && !_pipeline._stateCache.scissorEnable) { + qWarning("Clear without scissor in stereo mode"); + } uint32 masks = batch._params[paramOffset + 7]._uint; Vec4 color; diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu/src/gpu/GLBackendState.cpp index c5cc987fd1..9fdcbc0870 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu/src/gpu/GLBackendState.cpp @@ -768,6 +768,12 @@ void GLBackend::do_setStateScissorRect(Batch& batch, uint32 paramOffset) { Vec4i rect; memcpy(&rect, batch.editData(batch._params[paramOffset]._uint), sizeof(Vec4i)); + if (_stereo._enable) { + rect.z /= 2; + if (_stereo._pass) { + rect.x += rect.z; + } + } glScissor(rect.x, rect.y, rect.z, rect.w); (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 23b3dba14e..e33a8f8cde 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -33,16 +33,26 @@ void GLBackend::do_setProjectionTransform(Batch& batch, uint32 paramOffset) { void GLBackend::do_setViewportTransform(Batch& batch, uint32 paramOffset) { memcpy(&_transform._viewport, batch.editData(batch._params[paramOffset]._uint), sizeof(Vec4i)); + ivec4& vp = _transform._viewport; + // Where we assign the GL viewport - glViewport(_transform._viewport.x, _transform._viewport.y, _transform._viewport.z, _transform._viewport.w); + if (_stereo._enable) { + vp.z /= 2; + if (_stereo._pass) { + vp.x += vp.z; + } + int i = 0; + } + + glViewport(vp.x, vp.y, vp.z, vp.w); // The Viewport is tagged invalid because the CameraTransformUBO is not up to date and willl need update on next drawcall _transform._invalidViewport = true; } void GLBackend::initTransform() { - glGenBuffers(1, &_transform._transformObjectBuffer); - glGenBuffers(1, &_transform._transformCameraBuffer); + glGenBuffers(1, &_transform._objectBuffer); + glGenBuffers(1, &_transform._cameraBuffer); size_t cameraSize = sizeof(TransformCamera); while (_transform._cameraUboSize < cameraSize) { _transform._cameraUboSize += _uboAlignment; @@ -54,8 +64,8 @@ void GLBackend::initTransform() { } void GLBackend::killTransform() { - glDeleteBuffers(1, &_transform._transformObjectBuffer); - glDeleteBuffers(1, &_transform._transformCameraBuffer); + glDeleteBuffers(1, &_transform._objectBuffer); + glDeleteBuffers(1, &_transform._cameraBuffer); } void GLBackend::syncTransformStateCache() { @@ -72,73 +82,98 @@ void GLBackend::syncTransformStateCache() { _transform._model.setIdentity(); } -void GLBackend::preUpdateTransform() { +void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo) { // Check all the dirty flags and update the state accordingly - if (_transform._invalidViewport) { - _transform._transformCamera._viewport = glm::vec4(_transform._viewport); + if (_invalidViewport) { + _camera._viewport = glm::vec4(_viewport); } - if (_transform._invalidProj) { - _transform._transformCamera._projection = _transform._projection; - _transform._transformCamera._projectionInverse = glm::inverse(_transform._projection); + if (_invalidProj) { + _camera._projection = _projection; } - if (_transform._invalidView) { - _transform._view.getInverseMatrix(_transform._transformCamera._view); - _transform._view.getMatrix(_transform._transformCamera._viewInverse); + if (_invalidView) { + _view.getInverseMatrix(_camera._view); } - if (_transform._invalidModel) { - _transform._model.getMatrix(_transform._transformObject._model); - _transform._model.getInverseMatrix(_transform._transformObject._modelInverse); + if (_invalidModel) { + _model.getMatrix(_object._model); + _model.getInverseMatrix(_object._modelInverse); } - if (_transform._invalidView || _transform._invalidProj) { - Mat4 viewUntranslated = _transform._transformCamera._view; - viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); - _transform._transformCamera._projectionViewUntranslated = _transform._transformCamera._projection * viewUntranslated; + if (_invalidView || _invalidProj || _invalidViewport) { + size_t offset = _cameraUboSize * _cameras.size(); + if (stereo._enable) { + _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); + for (int i = 0; i < 2; ++i) { + _cameras.push_back(_camera.getEyeCamera(i, stereo)); + } + } else { + _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); + _cameras.push_back(_camera.recomputeDerived()); + } } - if (_transform._invalidView || _transform._invalidProj || _transform._invalidViewport) { - _transform._cameraOffsets.push_back(TransformStageState::Pair(_commandIndex, _transform._cameraUboSize * _transform._cameraTransforms.size())); - _transform._cameraTransforms.push_back(_transform._transformCamera); - } - - if (_transform._invalidModel) { - _transform._objectOffsets.push_back(TransformStageState::Pair(_commandIndex, _transform._objectUboSize * _transform._objectTransforms.size())); - _transform._objectTransforms.push_back(_transform._transformObject); + if (_invalidModel) { + size_t offset = _objectUboSize * _objects.size(); + _objectOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); + _objects.push_back(_object); } // Flags are clean - _transform._invalidView = _transform._invalidProj = _transform._invalidModel = _transform._invalidViewport = false; + _invalidView = _invalidProj = _invalidModel = _invalidViewport = false; } -void GLBackend::updateTransform() { +void GLBackend::TransformStageState::transfer() const { + static QByteArray bufferData; + glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); + bufferData.resize(_cameraUboSize * _cameras.size()); + for (size_t i = 0; i < _cameras.size(); ++i) { + memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(TransformCamera)); + } + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, _objectBuffer); + bufferData.resize(_objectUboSize * _objects.size()); + for (size_t i = 0; i < _objects.size(); ++i) { + memcpy(bufferData.data() + (_objectUboSize * i), &_objects[i], sizeof(TransformObject)); + } + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + CHECK_GL_ERROR(); +} + +void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const { int offset = -1; - while (!_transform._objectOffsets.empty() && _commandIndex >= _transform._objectOffsets.front().first) { - offset = _transform._objectOffsets.front().second; - _transform._objectOffsets.pop_front(); + while ((_objectsItr != _objectOffsets.end()) && (commandIndex >= (*_objectsItr).first)) { + offset = (*_objectsItr).second; + ++_objectsItr; } if (offset >= 0) { - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, - _transform._transformObjectBuffer, - offset, sizeof(Backend::TransformObject)); + glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, + _objectBuffer, offset, sizeof(Backend::TransformObject)); } offset = -1; - while (!_transform._cameraOffsets.empty() && _commandIndex >= _transform._cameraOffsets.front().first) { - offset = _transform._cameraOffsets.front().second; - _transform._cameraOffsets.pop_front(); + while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { + offset = (*_camerasItr).second; + ++_camerasItr; } if (offset >= 0) { - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, - _transform._transformCameraBuffer, - offset, sizeof(Backend::TransformCamera)); + // We include both camera offsets for stereo + if (stereo._enable && stereo._pass) { + offset += _cameraUboSize; + } + glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, + _cameraBuffer, offset, sizeof(Backend::TransformCamera)); } (void)CHECK_GL_ERROR(); } +void GLBackend::updateTransform() const { + _transform.update(_commandIndex, _stereo); +} + void GLBackend::resetTransformStage() { } diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index 4428327deb..5d6dae7ad1 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -6,7 +6,7 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) string(TOLOWER ${EXTERNAL} ${EXTERNAL}_LOWERCASE) - set(${${EXTERNAL}_UPPERCASE}_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/${${EXTERNAL}_LOWERCASE}") + set(${${EXTERNAL}_UPPERCASE}_ROOT_DIR "${CMAKE_SOURCE_DIR}/interface/external/${${EXTERNAL}_LOWERCASE}") endif () endforeach() diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index fc53670fd0..e76983cce9 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -189,7 +189,6 @@ void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePoint renderHand(rightHand, batch, RIGHT_HAND); } - args->_context->syncCache(); args->_context->render(batch); } } diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp index 525f68e640..823be727a4 100755 --- a/libraries/model/src/model/Light.cpp +++ b/libraries/model/src/model/Light.cpp @@ -58,10 +58,12 @@ const Vec3& Light::getDirection() const { void Light::setColor(const Color& color) { editSchema()._color = color; + updateLightRadius(); } void Light::setIntensity(float intensity) { editSchema()._intensity = intensity; + updateLightRadius(); } void Light::setAmbientIntensity(float intensity) { @@ -72,9 +74,14 @@ void Light::setMaximumRadius(float radius) { if (radius <= 0.f) { radius = 1.0f; } + editSchema()._attenuation.w = radius; + updateLightRadius(); +} + +void Light::updateLightRadius() { float CutOffIntensityRatio = 0.05f; - float surfaceRadius = radius / (sqrtf(1.0f / CutOffIntensityRatio) - 1.0f); - editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, radius); + float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f); + editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius()); } #include diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index 1ed07a942c..3fbaba75bf 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -127,6 +127,8 @@ protected: const Schema& getSchema() const { return _schemaBuffer.get(); } Schema& editSchema() { return _schemaBuffer.edit(); } + + void updateLightRadius(); }; typedef std::shared_ptr< Light > LightPointer; diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h new file mode 100644 index 0000000000..1a9298d13c --- /dev/null +++ b/libraries/plugins/src/plugins/Forward.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 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 + +class DisplayPlugin; +class InputPlugin; +class Plugin; +class PluginContainer; +class PluginManager; + +using DisplayPluginPointer = QSharedPointer; +using DisplayPluginList = QVector; +using InputPluginPointer = QSharedPointer; +using InputPluginList = QVector; diff --git a/libraries/plugins/src/plugins/Plugin.cpp b/libraries/plugins/src/plugins/Plugin.cpp index e0cacda474..ffcc682ebd 100644 --- a/libraries/plugins/src/plugins/Plugin.cpp +++ b/libraries/plugins/src/plugins/Plugin.cpp @@ -1,3 +1,10 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 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 "Plugin.h" PluginContainer* Plugin::CONTAINER{ nullptr }; diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index a2edbc8236..eac355b47d 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -1,9 +1,16 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 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 -class PluginContainer; +#include "Forward.h" class Plugin : public QObject { public: diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index 7f6346a181..c2cba23392 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -7,9 +7,10 @@ // #pragma once -#include #include +#include +class QAction; class QGLWidget; class QScreen; @@ -18,11 +19,11 @@ public: PluginContainer(); virtual void addMenu(const QString& menuName) = 0; virtual void removeMenu(const QString& menuName) = 0; - virtual void addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; + virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; virtual void removeMenuItem(const QString& menuName, const QString& menuItem) = 0; virtual bool isOptionChecked(const QString& name) = 0; virtual void setIsOptionChecked(const QString& path, bool checked) = 0; - virtual void setFullscreen(const QScreen* targetScreen) = 0; + virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = false) = 0; virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0; virtual void showDisplayPluginsTools() = 0; virtual QGLWidget* getPrimarySurface() = 0; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index ffee9905a0..3a71700c9e 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -1,7 +1,13 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 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 "PluginManager.h" #include - PluginManager* PluginManager::getInstance() { static PluginManager _manager; return &_manager; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 88dba3366e..b7ec453814 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -1,17 +1,13 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 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 "Plugin.h" -#include -#include -#include - -class DisplayPlugin; -class InputPlugin; - -using DisplayPluginPointer = QSharedPointer; -using DisplayPluginList = QVector; -using InputPluginPointer = QSharedPointer; -using InputPluginList = QVector; +#include "Forward.h" class PluginManager : public QObject { public: diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 5f0afd37d1..64f3bb6708 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -51,8 +51,13 @@ const gpu::PipelinePointer& AmbientOcclusion::getOcclusionPipeline() { _gBiasLoc = program->getUniforms().findLocation("g_bias"); _gSampleRadiusLoc = program->getUniforms().findLocation("g_sample_rad"); _gIntensityLoc = program->getUniforms().findLocation("g_intensity"); - _bufferWidthLoc = program->getUniforms().findLocation("bufferWidth"); - _bufferHeightLoc = program->getUniforms().findLocation("bufferHeight"); + + _nearLoc = program->getUniforms().findLocation("near"); + _depthScaleLoc = program->getUniforms().findLocation("depthScale"); + _depthTexCoordOffsetLoc = program->getUniforms().findLocation("depthTexCoordOffset"); + _depthTexCoordScaleLoc = program->getUniforms().findLocation("depthTexCoordScale"); + _renderTargetResLoc = program->getUniforms().findLocation("renderTargetRes"); + _renderTargetResInvLoc = program->getUniforms().findLocation("renderTargetResInv"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -172,9 +177,19 @@ const gpu::PipelinePointer& AmbientOcclusion::getBlendPipeline() { void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->args); assert(renderContext->args->_viewFrustum); - RenderArgs* args = renderContext->args; gpu::Batch batch; + RenderArgs* args = renderContext->args; + + auto framebufferCache = DependencyManager::get(); + QSize framebufferSize = framebufferCache->getFrameBufferSize(); + float fbWidth = framebufferSize.width(); + float fbHeight = framebufferSize.height(); + float sMin = args->_viewport.x / fbWidth; + float sWidth = args->_viewport.z / fbWidth; + float tMin = args->_viewport.y / fbHeight; + float tHeight = args->_viewport.w / fbHeight; + glm::mat4 projMat; Transform viewMat; @@ -186,8 +201,8 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons // Occlusion step getOcclusionPipeline(); - batch.setResourceTexture(0, DependencyManager::get()->getPrimaryDepthTexture()); - batch.setResourceTexture(1, DependencyManager::get()->getPrimaryNormalTexture()); + batch.setResourceTexture(0, framebufferCache->getPrimaryDepthTexture()); + batch.setResourceTexture(1, framebufferCache->getPrimaryNormalTexture()); _occlusionBuffer->setRenderBuffer(0, _occlusionTexture); batch.setFramebuffer(_occlusionBuffer); @@ -203,8 +218,32 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons batch._glUniform1f(_gBiasLoc, g_bias); batch._glUniform1f(_gSampleRadiusLoc, g_sample_rad); batch._glUniform1f(_gIntensityLoc, g_intensity); - batch._glUniform1f(_bufferWidthLoc, DependencyManager::get()->getFrameBufferSize().width()); - batch._glUniform1f(_bufferHeightLoc, DependencyManager::get()->getFrameBufferSize().height()); + + // setup uniforms for unpacking a view-space position from the depth buffer + // This is code taken from DeferredLightEffect.render() method in DeferredLightingEffect.cpp. + // DeferredBuffer.slh shows how the unpacking is done and what variables are needed. + + // initialize the view-space unpacking uniforms using frustum data + float left, right, bottom, top, nearVal, farVal; + glm::vec4 nearClipPlane, farClipPlane; + + args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); + + float depthScale = (farVal - nearVal) / farVal; + float nearScale = -1.0f / nearVal; + float depthTexCoordScaleS = (right - left) * nearScale / sWidth; + float depthTexCoordScaleT = (top - bottom) * nearScale / tHeight; + float depthTexCoordOffsetS = left * nearScale - sMin * depthTexCoordScaleS; + float depthTexCoordOffsetT = bottom * nearScale - tMin * depthTexCoordScaleT; + + // now set the position-unpacking unforms + batch._glUniform1f(_nearLoc, nearVal); + batch._glUniform1f(_depthScaleLoc, depthScale); + batch._glUniform2f(_depthTexCoordOffsetLoc, depthTexCoordOffsetS, depthTexCoordOffsetT); + batch._glUniform2f(_depthTexCoordScaleLoc, depthTexCoordScaleS, depthTexCoordScaleT); + + batch._glUniform2f(_renderTargetResLoc, fbWidth, fbHeight); + batch._glUniform2f(_renderTargetResInvLoc, 1.0/fbWidth, 1.0/fbHeight); glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); glm::vec2 bottomLeft(-1.0f, -1.0f); @@ -238,14 +277,13 @@ void AmbientOcclusion::run(const render::SceneContextPointer& sceneContext, cons // Blend step getBlendPipeline(); batch.setResourceTexture(0, _hBlurTexture); - batch.setFramebuffer(DependencyManager::get()->getPrimaryFramebuffer()); + batch.setFramebuffer(framebufferCache->getPrimaryFramebuffer()); // Bind the fourth gpu::Pipeline we need - for blending the primary color buffer with blurred occlusion texture batch.setPipeline(getBlendPipeline()); DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color); - + // Ready to render - args->_context->syncCache(); args->_context->render((batch)); } diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 0b695dd2ad..6153795ea6 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -36,8 +36,15 @@ private: gpu::int32 _gBiasLoc; gpu::int32 _gSampleRadiusLoc; gpu::int32 _gIntensityLoc; - gpu::int32 _bufferWidthLoc; - gpu::int32 _bufferHeightLoc; + + gpu::int32 _nearLoc; + gpu::int32 _depthScaleLoc; + gpu::int32 _depthTexCoordOffsetLoc; + gpu::int32 _depthTexCoordScaleLoc; + gpu::int32 _renderTargetResLoc; + gpu::int32 _renderTargetResInvLoc; + + float g_scale; float g_bias; float g_sample_rad; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 6776fa6c79..a785a5d2ec 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -225,6 +225,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu void DeferredLightingEffect::prepare(RenderArgs* args) { gpu::Batch batch; + batch.enableStereo(false); batch.setStateScissorRect(args->_viewport); @@ -244,6 +245,9 @@ gpu::FramebufferPointer _copyFBO; void DeferredLightingEffect::render(RenderArgs* args) { gpu::Batch batch; + // Framebuffer copy operations cannot function as multipass stereo operations. + batch.enableStereo(false); + // perform deferred lighting, rendering to free fbo auto framebufferCache = DependencyManager::get(); @@ -555,6 +559,7 @@ void DeferredLightingEffect::render(RenderArgs* args) { void DeferredLightingEffect::copyBack(RenderArgs* args) { gpu::Batch batch; + batch.enableStereo(false); auto framebufferCache = DependencyManager::get(); QSize framebufferSize = framebufferCache->getFrameBufferSize(); diff --git a/libraries/render-utils/src/Environment.cpp b/libraries/render-utils/src/Environment.cpp index 9cce508946..605f67f957 100644 --- a/libraries/render-utils/src/Environment.cpp +++ b/libraries/render-utils/src/Environment.cpp @@ -92,17 +92,17 @@ void Environment::resetToDefault() { _data[QUuid()][0]; } -void Environment::renderAtmospheres(gpu::Batch& batch, ViewFrustum& camera) { +void Environment::renderAtmospheres(gpu::Batch& batch, ViewFrustum& viewFrustum) { // get the lock for the duration of the call QMutexLocker locker(&_mutex); if (_environmentIsOverridden) { - renderAtmosphere(batch, camera, _overrideData); + renderAtmosphere(batch, viewFrustum, _overrideData); } else { foreach (const ServerData& serverData, _data) { // TODO: do something about EnvironmentData foreach (const EnvironmentData& environmentData, serverData) { - renderAtmosphere(batch, camera, environmentData); + renderAtmosphere(batch, viewFrustum, environmentData); } } } @@ -196,15 +196,25 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 return found; } -void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& camera, const EnvironmentData& data) { +void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data) { glm::vec3 center = data.getAtmosphereCenter(); + // transform the model transform to the center of our atmosphere Transform transform; transform.setTranslation(center); batch.setModelTransform(transform); - - glm::vec3 relativeCameraPos = camera.getPosition() - center; + + // Make sure our view and projection transforms are correct for our viewFrustum + Transform viewTransform; + viewFrustum.evalViewTransform(viewTransform); + batch.setViewTransform(viewTransform); + + glm::mat4 projMat; + viewFrustum.evalProjectionMatrix(projMat); + batch.setProjectionTransform(projMat); + + glm::vec3 relativeCameraPos = viewFrustum.getPosition() - center; float height = glm::length(relativeCameraPos); // use the appropriate shader depending on whether we're inside or outside @@ -212,11 +222,11 @@ void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& camera, const if (height < data.getAtmosphereOuterRadius()) { batch.setPipeline(_skyFromAtmosphereProgram); locations = _skyFromAtmosphereUniformLocations; - } else { batch.setPipeline(_skyFromSpaceProgram); locations = _skyFromSpaceUniformLocations; } + // the constants here are from Sean O'Neil's GPU Gems entry // (http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html), GameEngine.cpp diff --git a/libraries/render-utils/src/Environment.h b/libraries/render-utils/src/Environment.h index 5b10f2f1a9..af5a3c3df5 100644 --- a/libraries/render-utils/src/Environment.h +++ b/libraries/render-utils/src/Environment.h @@ -29,7 +29,7 @@ public: void init(); void resetToDefault(); - void renderAtmospheres(gpu::Batch& batch, ViewFrustum& camera); + void renderAtmospheres(gpu::Batch& batch, ViewFrustum& viewFrustum); void override(const EnvironmentData& overrideData) { _overrideData = overrideData; _environmentIsOverridden = true; } void endOverride() { _environmentIsOverridden = false; } @@ -41,7 +41,7 @@ private: bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration); // NOTE: Deprecated - void renderAtmosphere(gpu::Batch& batch, ViewFrustum& camera, const EnvironmentData& data); + void renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data); bool _initialized; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index b33aab3ef1..2f81fe8b84 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1723,7 +1723,7 @@ void GeometryReader::run() { NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : _url(url), _mapping(mapping), - _textureBaseUrl(textureBaseUrl) { + _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) { if (delayLoad) { _state = DelayState; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index efef423623..ca3f87f53f 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -35,6 +35,7 @@ void SetupDeferred::run(const SceneContextPointer& sceneContext, const RenderCon auto primaryFbo = DependencyManager::get()->getPrimaryFramebufferDepthColor(); gpu::Batch batch; + batch.enableStereo(false); batch.setFramebuffer(nullptr); batch.setFramebuffer(primaryFbo); @@ -159,6 +160,8 @@ void DrawOpaqueDeferred::run(const SceneContextPointer& sceneContext, const Rend RenderArgs* args = renderContext->args; gpu::Batch batch; + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); args->_batch = &batch; renderContext->_numDrawnOpaqueItems = inItems.size(); @@ -188,6 +191,8 @@ void DrawTransparentDeferred::run(const SceneContextPointer& sceneContext, const RenderArgs* args = renderContext->args; gpu::Batch batch; + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); args->_batch = &batch; renderContext->_numDrawnTransparentItems = inItems.size(); @@ -247,30 +252,42 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon renderContext->_numFeedOverlay3DItems = inItems.size(); renderContext->_numDrawnOverlay3DItems = inItems.size(); - RenderArgs* args = renderContext->args; - gpu::Batch batch; - args->_batch = &batch; - args->_whiteTexture = DependencyManager::get()->getWhiteTexture(); - - glm::mat4 projMat; - Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - batch.setPipeline(getOpaquePipeline()); - batch.setResourceTexture(0, args->_whiteTexture); - if (!inItems.empty()) { - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true); - renderItems(sceneContext, renderContext, inItems, renderContext->_maxDrawnOverlay3DItems); - } + RenderArgs* args = renderContext->args; - args->_context->render((*args->_batch)); - args->_batch = nullptr; - args->_whiteTexture.reset(); + // Clear the framebuffer without stereo + // Needs to be distinct from the other batch because using the clear call + // while stereo is enabled triggers a warning + { + gpu::Batch batch; + batch.enableStereo(false); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true); + args->_context->render(batch); + } + + // Render the items + { + gpu::Batch batch; + args->_batch = &batch; + args->_whiteTexture = DependencyManager::get()->getWhiteTexture(); + + glm::mat4 projMat; + Transform viewMat; + args->_viewFrustum->evalProjectionMatrix(projMat); + args->_viewFrustum->evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + batch.setPipeline(getOpaquePipeline()); + batch.setResourceTexture(0, args->_whiteTexture); + renderItems(sceneContext, renderContext, inItems, renderContext->_maxDrawnOverlay3DItems); + + args->_context->render((*args->_batch)); + args->_batch = nullptr; + args->_whiteTexture.reset(); + } + } } diff --git a/libraries/render-utils/src/ambient_occlusion.slf b/libraries/render-utils/src/ambient_occlusion.slf index 649fb16c56..8ab78891b0 100644 --- a/libraries/render-utils/src/ambient_occlusion.slf +++ b/libraries/render-utils/src/ambient_occlusion.slf @@ -30,25 +30,40 @@ uniform float g_scale; uniform float g_bias; uniform float g_sample_rad; uniform float g_intensity; -uniform float bufferWidth; -uniform float bufferHeight; + +// the distance to the near clip plane +uniform float near; + +// scale factor for depth: (far - near) / far +uniform float depthScale; + +// offset for depth texture coordinates +uniform vec2 depthTexCoordOffset; + +// scale for depth texture coordinates +uniform vec2 depthTexCoordScale; + +// the resolution of the occlusion buffer +// and its inverse +uniform vec2 renderTargetRes; +uniform vec2 renderTargetResInv; + + const float PI = 3.14159265; -const vec2 FocalLen = vec2(1.0, 1.0); - -const vec2 LinMAD = vec2(0.1-10.0, 0.1+10.0) / (2.0*0.1*10.0); - -const vec2 AORes = vec2(1024.0, 768.0); -const vec2 InvAORes = vec2(1.0/1024.0, 1.0/768.0); -const vec2 NoiseScale = vec2(1024.0, 768.0) / 4.0; - const float AOStrength = 1.9; -const float R = 0.3; -const float R2 = 0.3*0.3; -const float NegInvR2 = - 1.0 / (0.3*0.3); + + +// TODO: R (radius) should be exposed as a uniform parameter +const float R = 0.01; +const float R2 = 0.01*0.01; +const float NegInvR2 = - 1.0 / (0.01*0.01); + + + // can't use tan to initialize a const value -const float TanBias = 0.57735027; // tan(30.0 * PI / 180.0); +const float TanBias = 0.57735027; // tan(30.0 * PI / 180.0); const float MaxRadiusPixels = 50.0; const int NumDirections = 6; @@ -56,113 +71,126 @@ const int NumSamples = 4; out vec4 outFragColor; +/** + * Gets the normal in view space from a normal texture. + * uv: the uv texture coordinates to look up in the texture at. + */ +vec3 GetViewNormalFromTexture(vec2 uv) { + // convert [0,1] -> [-1,1], note: since we're normalizing + // we don't need to do v*2 - 1.0, we can just do a v-0.5 + return normalize(texture(normalTexture, uv).xyz - 0.5); +} + +/** + * Gets the linearized depth in view space. + * d: the depth value [0-1], usually from a depth texture to convert. + */ float ViewSpaceZFromDepth(float d){ - // [0,1] -> [-1,1] clip space - d = d * 2.0 - 1.0; - - // Get view space Z - return -1.0 / (LinMAD.x * d + LinMAD.y); + return near / (d * depthScale - 1.0); } +/** + * Converts a uv coordinate and depth value into a 3D view space coordinate. + * uv: the uv coordinates to convert + * z: the view space depth of the uv coordinate. + */ vec3 UVToViewSpace(vec2 uv, float z){ - //uv = UVToViewA * uv + UVToViewB; - return vec3(uv * z, z); + return vec3((depthTexCoordOffset + varTexcoord * depthTexCoordScale) * z, z); } -vec3 GetViewPos(vec2 uv){ - float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); - return UVToViewSpace(uv, z); +/** + * Converts a uv coordinate into a 3D view space coordinate. + * The depth of the uv coord is determined from the depth texture. + * uv: the uv coordinates to convert + */ +vec3 GetViewPos(vec2 uv) { + float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); + return UVToViewSpace(uv, z); } -vec3 GetViewPosPoint(ivec2 uv){ - vec2 coord = vec2(gl_FragCoord.xy) + uv; - //float z = texelFetch(texture0, coord, 0).r; - float z = texture(depthTexture, uv).r; - return UVToViewSpace(uv, z); + +float TanToSin(float x) { + return x * inversesqrt(x*x + 1.0); } -float TanToSin(float x){ - return x * inversesqrt(x*x + 1.0); +float InvLength(vec2 V) { + return inversesqrt(dot(V, V)); } -float InvLength(vec2 V){ - return inversesqrt(dot(V,V)); +float Tangent(vec3 V) { + return V.z * InvLength(V.xy); } -float Tangent(vec3 V){ - return V.z * InvLength(V.xy); +float BiasedTangent(vec3 V) { + return V.z * InvLength(V.xy) + TanBias; } -float BiasedTangent(vec3 V){ - return V.z * InvLength(V.xy) + TanBias; -} - -float Tangent(vec3 P, vec3 S){ +float Tangent(vec3 P, vec3 S) { return -(P.z - S.z) * InvLength(S.xy - P.xy); } -float Length2(vec3 V){ - return dot(V,V); +float Length2(vec3 V) { + return dot(V, V); } -vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl){ +vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl) { vec3 V1 = Pr - P; vec3 V2 = P - Pl; return (Length2(V1) < Length2(V2)) ? V1 : V2; } -vec2 SnapUVOffset(vec2 uv){ - return round(uv * AORes) * InvAORes; +vec2 SnapUVOffset(vec2 uv) { + return round(uv * renderTargetRes) * renderTargetResInv; } -float Falloff(float d2){ - return d2 * NegInvR2 + 1.0f; +float Falloff(float d2) { + return d2 * NegInvR2 + 1.0f; } -float HorizonOcclusion( vec2 deltaUV, vec3 P, vec3 dPdu, vec3 dPdv, float randstep, float numSamples){ - float ao = 0; +float HorizonOcclusion(vec2 deltaUV, vec3 P, vec3 dPdu, vec3 dPdv, float randstep, float numSamples) { + float ao = 0; - // Offset the first coord with some noise - vec2 uv = varTexcoord + SnapUVOffset(randstep*deltaUV); - deltaUV = SnapUVOffset( deltaUV ); + // Offset the first coord with some noise + vec2 uv = varTexcoord + SnapUVOffset(randstep*deltaUV); + deltaUV = SnapUVOffset(deltaUV); - // Calculate the tangent vector - vec3 T = deltaUV.x * dPdu + deltaUV.y * dPdv; + // Calculate the tangent vector + vec3 T = deltaUV.x * dPdu + deltaUV.y * dPdv; - // Get the angle of the tangent vector from the viewspace axis - float tanH = BiasedTangent(T); - float sinH = TanToSin(tanH); + // Get the angle of the tangent vector from the viewspace axis + float tanH = BiasedTangent(T); + float sinH = TanToSin(tanH); - float tanS; - float d2; - vec3 S; + float tanS; + float d2; + vec3 S; - // Sample to find the maximum angle - for(float s = 1; s <= numSamples; ++s){ - uv += deltaUV; - S = GetViewPos(uv); - tanS = Tangent(P, S); - d2 = Length2(S - P); + // Sample to find the maximum angle + for (float s = 1; s <= numSamples; ++s) { + uv += deltaUV; + S = GetViewPos(uv); + tanS = Tangent(P, S); + d2 = Length2(S - P); - // Is the sample within the radius and the angle greater? - if(d2 < R2 && tanS > tanH) - { - float sinS = TanToSin(tanS); - // Apply falloff based on the distance - ao += Falloff(d2) * (sinS - sinH); + // Is the sample within the radius and the angle greater? + if (d2 < R2 && tanS > tanH) { + float sinS = TanToSin(tanS); + // Apply falloff based on the distance + ao += Falloff(d2) * (sinS - sinH); - tanH = tanS; - sinH = sinS; - } - } - return ao; + tanH = tanS; + sinH = sinS; + } + } + return ao; } -vec2 RotateDirections(vec2 Dir, vec2 CosSin){ - return vec2(Dir.x*CosSin.x - Dir.y*CosSin.y, Dir.x*CosSin.y + Dir.y*CosSin.x); +vec2 RotateDirections(vec2 Dir, vec2 CosSin) { + return vec2(Dir.x*CosSin.x - Dir.y*CosSin.y, + Dir.x*CosSin.y + Dir.y*CosSin.x); } -void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPix, float rand){ +void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPix, float rand) { // Avoid oversampling if numSteps is greater than the kernel radius in pixels numSteps = min(NumSamples, rayRadiusPix); @@ -171,8 +199,7 @@ void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPi // Clamp numSteps if it is greater than the max kernel footprint float maxNumSteps = MaxRadiusPixels / stepSizePix; - if (maxNumSteps < numSteps) - { + if (maxNumSteps < numSteps) { // Use dithering to avoid AO discontinuities numSteps = floor(maxNumSteps + rand); numSteps = max(numSteps, 1); @@ -180,69 +207,73 @@ void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPi } // Step size in uv space - stepSizeUv = stepSizePix * InvAORes; + stepSizeUv = stepSizePix * renderTargetResInv; } -float getRandom(vec2 uv){ +float getRandom(vec2 uv) { return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453); } -void main(void){ - float numDirections = NumDirections; +void main(void) { + mat4 projMatrix = getTransformCamera()._projection; - vec3 P, Pr, Pl, Pt, Pb; - P = GetViewPos(varTexcoord); + float numDirections = NumDirections; - // Sample neighboring pixels - Pr = GetViewPos(varTexcoord + vec2( InvAORes.x, 0)); - Pl = GetViewPos(varTexcoord + vec2(-InvAORes.x, 0)); - Pt = GetViewPos(varTexcoord + vec2( 0, InvAORes.y)); - Pb = GetViewPos(varTexcoord + vec2( 0,-InvAORes.y)); + vec3 P, Pr, Pl, Pt, Pb; + P = GetViewPos(varTexcoord); + + // Sample neighboring pixels + Pr = GetViewPos(varTexcoord + vec2( renderTargetResInv.x, 0)); + Pl = GetViewPos(varTexcoord + vec2(-renderTargetResInv.x, 0)); + Pt = GetViewPos(varTexcoord + vec2( 0, renderTargetResInv.y)); + Pb = GetViewPos(varTexcoord + vec2( 0,-renderTargetResInv.y)); // Calculate tangent basis vectors using the minimum difference vec3 dPdu = MinDiff(P, Pr, Pl); - vec3 dPdv = MinDiff(P, Pt, Pb) * (AORes.y * InvAORes.x); + vec3 dPdv = MinDiff(P, Pt, Pb) * (renderTargetRes.y * renderTargetResInv.x); // Get the random samples from the noise function - vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); + vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); - // Calculate the projected size of the hemisphere - vec2 rayRadiusUV = 0.5 * R * FocalLen / -P.z; - float rayRadiusPix = rayRadiusUV.x * AORes.x; + // Calculate the projected size of the hemisphere + float w = P.z * projMatrix[2][3] + projMatrix[3][3]; + vec2 rayRadiusUV = (0.5 * R * vec2(projMatrix[0][0], projMatrix[1][1]) / w); // [-1,1] -> [0,1] uv + float rayRadiusPix = rayRadiusUV.x * renderTargetRes.x; float ao = 1.0; // Make sure the radius of the evaluated hemisphere is more than a pixel - if(rayRadiusPix > 1.0){ - ao = 0.0; - float numSteps; - vec2 stepSizeUV; + if(rayRadiusPix > 1.0) { + ao = 0.0; + float numSteps; + vec2 stepSizeUV; - // Compute the number of steps - ComputeSteps(stepSizeUV, numSteps, rayRadiusPix, random.z); + // Compute the number of steps + ComputeSteps(stepSizeUV, numSteps, rayRadiusPix, random.z); - float alpha = 2.0 * PI / numDirections; + float alpha = 2.0 * PI / numDirections; - // Calculate the horizon occlusion of each direction - for(float d = 0; d < numDirections; ++d){ - float theta = alpha * d; + // Calculate the horizon occlusion of each direction + for(float d = 0; d < numDirections; ++d) { + float theta = alpha * d; - // Apply noise to the direction - vec2 dir = RotateDirections(vec2(cos(theta), sin(theta)), random.xy); - vec2 deltaUV = dir * stepSizeUV; + // Apply noise to the direction + vec2 dir = RotateDirections(vec2(cos(theta), sin(theta)), random.xy); + vec2 deltaUV = dir * stepSizeUV; - // Sample the pixels along the direction - ao += HorizonOcclusion( deltaUV, - P, - dPdu, - dPdv, - random.z, - numSteps); - } + // Sample the pixels along the direction + ao += HorizonOcclusion( deltaUV, + P, + dPdu, + dPdv, + random.z, + numSteps); + } + + // Average the results and produce the final AO + ao = 1.0 - ao / numDirections * AOStrength; + } - // Average the results and produce the final AO - ao = 1.0 - ao / numDirections * AOStrength; - } outFragColor = vec4(vec3(ao), 1.0); -} \ No newline at end of file +} diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 0c1b92b559..28b4344bf1 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -161,8 +161,5 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContex batch.draw(gpu::TRIANGLES, 24, 0); } - // Before rendering the batch make sure we re in sync with gl state - args->_context->syncCache(); - renderContext->args->_context->syncCache(); - args->_context->render((batch)); + args->_context->render(batch); } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index ff2cf6ff41..0754d81bde 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -258,6 +258,9 @@ void DrawBackground::run(const SceneContextPointer& sceneContext, const RenderCo } RenderArgs* args = renderContext->args; gpu::Batch batch; + batch.enableSkybox(true); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); args->_batch = &batch; glm::mat4 projMat; @@ -271,9 +274,6 @@ void DrawBackground::run(const SceneContextPointer& sceneContext, const RenderCo renderItems(sceneContext, renderContext, inItems); args->_context->render((*args->_batch)); args->_batch = nullptr; - - // Force the context sync - args->_context->syncCache(); } void ItemMaterialBucketMap::insert(const ItemID& id, const model::MaterialKey& key) { diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 6bb53389af..670a13f081 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -3,7 +3,7 @@ set(TARGET_NAME script-engine) setup_memory_debugger() # use setup_hifi_library macro to setup our project and link appropriate Qt modules -setup_hifi_library(Gui Network Script Widgets) +setup_hifi_library(Gui Network Script WebSockets Widgets) add_dependency_external_projects(glm) find_package(GLM REQUIRED) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index cfd6cda56b..b42daa710a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -38,6 +38,7 @@ #include "ScriptEngine.h" #include "TypedArrays.h" #include "XMLHttpRequestClass.h" +#include "WebSocketClass.h" #include "SceneScriptingInterface.h" @@ -343,6 +344,9 @@ void ScriptEngine::init() { QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor); globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); + QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor); + globalObject().setProperty("WebSocket", webSocketConstructorValue); + QScriptValue printConstructorValue = newFunction(debugPrint); globalObject().setProperty("print", printConstructorValue); @@ -353,6 +357,9 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue); qScriptRegisterMetaType(this, avatarDataToScriptValue, avatarDataFromScriptValue); qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue); + qScriptRegisterMetaType(this, webSocketToScriptValue, webSocketFromScriptValue); + qScriptRegisterMetaType(this, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue); + qScriptRegisterMetaType(this, wscReadyStateToScriptValue, wscReadyStateFromScriptValue); registerGlobalObject("Script", this); registerGlobalObject("Audio", &AudioScriptingInterface::getInstance()); diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp new file mode 100644 index 0000000000..c844dc3582 --- /dev/null +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -0,0 +1,127 @@ +// +// WebSocketClass.cpp +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/4/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// This class is an implementation of the WebSocket object for scripting use. It provides a near-complete implementation +// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngine.h" +#include "WebSocketClass.h" + +WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) : + _engine(engine), + _webSocket(new QWebSocket()) +{ + initialize(); + _webSocket->open(url); +} + +WebSocketClass::WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket) : + _engine(engine), + _webSocket(qWebSocket) +{ + initialize(); +} + +void WebSocketClass::initialize() { + connect(_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose); + connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); + connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); + connect(_webSocket, static_cast(&QWebSocket::error), this, + &WebSocketClass::handleOnError); + _binaryType = QStringLiteral("blob"); +} + +QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) { + QString url; + if (context->argumentCount() > 0) { + url = context->argument(0).toString(); + } + return engine->newQObject(new WebSocketClass(engine, url), QScriptEngine::ScriptOwnership); +} + +WebSocketClass::~WebSocketClass() { + _webSocket->deleteLater(); +} + +void WebSocketClass::send(QScriptValue message) { + _webSocket->sendTextMessage(message.toString()); +} + +void WebSocketClass::close() { + this->close(QWebSocketProtocol::CloseCodeNormal); +} + +void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode) { + this->close(closeCode, QStringLiteral("")); +} + +void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reason) { + _webSocket->close(closeCode, reason); +} + +void WebSocketClass::handleOnClose() { + bool hasError = (_webSocket->error() != QAbstractSocket::UnknownSocketError); + if (_onCloseEvent.isFunction()) { + QScriptValueList args; + QScriptValue arg = _engine->newObject(); + arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCodeAbnormalDisconnection : _webSocket->closeCode()); + arg.setProperty("reason", _webSocket->closeReason()); + arg.setProperty("wasClean", !hasError); + args << arg; + _onCloseEvent.call(QScriptValue(), args); + } +} + +void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) { + if (_onErrorEvent.isFunction()) { + _onErrorEvent.call(); + } +} + +void WebSocketClass::handleOnMessage(const QString& message) { + if (_onMessageEvent.isFunction()) { + QScriptValueList args; + QScriptValue arg = _engine->newObject(); + arg.setProperty("data", message); + args << arg; + _onMessageEvent.call(QScriptValue(), args); + } +} + +void WebSocketClass::handleOnOpen() { + if (_onOpenEvent.isFunction()) { + _onOpenEvent.call(); + } +} + +QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode &closeCode) { + return closeCode; +} + +void qWSCloseCodeFromScriptValue(const QScriptValue &object, QWebSocketProtocol::CloseCode &closeCode) { + closeCode = (QWebSocketProtocol::CloseCode)object.toUInt16(); +} + +QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in) { + return engine->newQObject(in, QScriptEngine::ScriptOwnership); +} + +void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) { + out = qobject_cast(object.toQObject()); +} + +QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState) { + return readyState; +} + +void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState) { + readyState = (WebSocketClass::ReadyState)object.toUInt16(); +} diff --git a/libraries/script-engine/src/WebSocketClass.h b/libraries/script-engine/src/WebSocketClass.h new file mode 100644 index 0000000000..8ba8ecf362 --- /dev/null +++ b/libraries/script-engine/src/WebSocketClass.h @@ -0,0 +1,140 @@ +// +// WebSocketClass.h +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/4/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// 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_WebSocketClass_h +#define hifi_WebSocketClass_h + +#include +#include +#include + +class WebSocketClass : public QObject { + Q_OBJECT + Q_PROPERTY(QString binaryType READ getBinaryType WRITE setBinaryType) + Q_PROPERTY(ulong bufferedAmount READ getBufferedAmount) + Q_PROPERTY(QString extensions READ getExtensions) + + Q_PROPERTY(QScriptValue onclose READ getOnClose WRITE setOnClose) + Q_PROPERTY(QScriptValue onerror READ getOnError WRITE setOnError) + Q_PROPERTY(QScriptValue onmessage READ getOnMessage WRITE setOnMessage) + Q_PROPERTY(QScriptValue onopen READ getOnOpen WRITE setOnOpen) + + Q_PROPERTY(QString protocol READ getProtocol) + Q_PROPERTY(WebSocketClass::ReadyState readyState READ getReadyState) + Q_PROPERTY(QString url READ getURL) + + Q_PROPERTY(WebSocketClass::ReadyState CONNECTING READ getConnecting CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState OPEN READ getOpen CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState CLOSING READ getClosing CONSTANT) + Q_PROPERTY(WebSocketClass::ReadyState CLOSED READ getClosed CONSTANT) + +public: + WebSocketClass(QScriptEngine* engine, QString url); + WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket); + ~WebSocketClass(); + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + + enum ReadyState { + CONNECTING = 0, + OPEN, + CLOSING, + CLOSED + }; + + QWebSocket* getWebSocket() { return _webSocket; } + + ReadyState getConnecting() const { return CONNECTING; }; + ReadyState getOpen() const { return OPEN; }; + ReadyState getClosing() const { return CLOSING; }; + ReadyState getClosed() const { return CLOSED; }; + + void setBinaryType(QString binaryType) { _binaryType = binaryType; } + QString getBinaryType() { return _binaryType; } + + // extensions is a empty string until supported in QT WebSockets + QString getExtensions() { return QString(); } + + // protocol is a empty string until supported in QT WebSockets + QString getProtocol() { return QString(); } + + ulong getBufferedAmount() { return 0; } + + QString getURL() { return _webSocket->requestUrl().toDisplayString(); } + + ReadyState getReadyState() { + switch (_webSocket->state()) { + case QAbstractSocket::SocketState::HostLookupState: + case QAbstractSocket::SocketState::ConnectingState: + return CONNECTING; + case QAbstractSocket::SocketState::ConnectedState: + case QAbstractSocket::SocketState::BoundState: + case QAbstractSocket::SocketState::ListeningState: + return OPEN; + case QAbstractSocket::SocketState::ClosingState: + return CLOSING; + } + return CLOSED; + } + + void setOnClose(QScriptValue eventFunction) { _onCloseEvent = eventFunction; } + QScriptValue getOnClose() { return _onCloseEvent; } + + void setOnError(QScriptValue eventFunction) { _onErrorEvent = eventFunction; } + QScriptValue getOnError() { return _onErrorEvent; } + + void setOnMessage(QScriptValue eventFunction) { _onMessageEvent = eventFunction; } + QScriptValue getOnMessage() { return _onMessageEvent; } + + void setOnOpen(QScriptValue eventFunction) { _onOpenEvent = eventFunction; } + QScriptValue getOnOpen() { return _onOpenEvent; } + +public slots: + void send(QScriptValue message); + + void close(); + void close(QWebSocketProtocol::CloseCode closeCode); + void close(QWebSocketProtocol::CloseCode closeCode, QString reason); + +private: + QWebSocket* _webSocket; + QScriptEngine* _engine; + + QScriptValue _onCloseEvent; + QScriptValue _onErrorEvent; + QScriptValue _onMessageEvent; + QScriptValue _onOpenEvent; + + QString _binaryType; + + void initialize(); + +private slots: + void handleOnClose(); + void handleOnError(QAbstractSocket::SocketError error); + void handleOnMessage(const QString& message); + void handleOnOpen(); + +}; + +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode); +Q_DECLARE_METATYPE(WebSocketClass::ReadyState); + +QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode& closeCode); +void qWSCloseCodeFromScriptValue(const QScriptValue& object, QWebSocketProtocol::CloseCode& closeCode); + +QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in); +void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out); + +QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState); +void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState); + +#endif // hifi_WebSocketClass_h diff --git a/libraries/script-engine/src/WebSocketServerClass.cpp b/libraries/script-engine/src/WebSocketServerClass.cpp new file mode 100644 index 0000000000..3b3a02a7c9 --- /dev/null +++ b/libraries/script-engine/src/WebSocketServerClass.cpp @@ -0,0 +1,69 @@ +// +// WebSocketServerClass.cpp +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/10/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// Making WebSocketServer accessible through scripting. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngine.h" +#include "WebSocketServerClass.h" + +WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port) : + _engine(engine), + _webSocketServer(serverName, QWebSocketServer::SslMode::NonSecureMode) +{ + if (_webSocketServer.listen(QHostAddress::Any, port)) { + connect(&_webSocketServer, &QWebSocketServer::newConnection, this, &WebSocketServerClass::onNewConnection); + } +} + +QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine) { + // the serverName is used in handshakes + QString serverName = QStringLiteral("HighFidelity - Scripted WebSocket Listener"); + // port 0 will auto-assign a free port + quint16 port = 0; + QScriptValue callee = context->callee(); + if (context->argumentCount() > 0) { + QScriptValue options = context->argument(0); + QScriptValue portOption = options.property(QStringLiteral("port")); + if (portOption.isValid() && portOption.isNumber()) { + port = portOption.toNumber(); + } + QScriptValue serverNameOption = options.property(QStringLiteral("serverName")); + if (serverNameOption.isValid() && serverNameOption.isString()) { + serverName = serverNameOption.toString(); + } + } + return engine->newQObject(new WebSocketServerClass(engine, serverName, port), QScriptEngine::ScriptOwnership); +} + +WebSocketServerClass::~WebSocketServerClass() { + if (_webSocketServer.isListening()) { + close(); + } + _clients.empty(); +} + +void WebSocketServerClass::onNewConnection() { + WebSocketClass* newClient = new WebSocketClass(_engine, _webSocketServer.nextPendingConnection()); + _clients << newClient; + connect(newClient->getWebSocket(), &QWebSocket::disconnected, [newClient, this]() { + _clients.removeOne(newClient); + }); + emit newConnection(newClient); +} + +void WebSocketServerClass::close() { + foreach(WebSocketClass* client, _clients) { + if (client->getReadyState() != WebSocketClass::ReadyState::CLOSED) { + client->close(QWebSocketProtocol::CloseCode::CloseCodeGoingAway, "Server closing."); + } + } + _webSocketServer.close(); +} diff --git a/libraries/script-engine/src/WebSocketServerClass.h b/libraries/script-engine/src/WebSocketServerClass.h new file mode 100644 index 0000000000..972bf9c032 --- /dev/null +++ b/libraries/script-engine/src/WebSocketServerClass.h @@ -0,0 +1,52 @@ +// +// WebSocketServerClass.h +// libraries/script-engine/src/ +// +// Created by Thijs Wenker on 8/10/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// 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_WebSocketServerClass_h +#define hifi_WebSocketServerClass_h + +#include +#include +#include +#include "WebSocketClass.h" + +class WebSocketServerClass : public QObject { + Q_OBJECT + Q_PROPERTY(QString url READ getURL) + Q_PROPERTY(quint16 port READ getPort) + Q_PROPERTY(bool listening READ isListening) + +public: + WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port); + ~WebSocketServerClass(); + + QString getURL() { return _webSocketServer.serverUrl().toDisplayString(); } + quint16 getPort() { return _webSocketServer.serverPort(); } + bool isListening() { return _webSocketServer.isListening(); } + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + +public slots: + void close(); + +private: + QWebSocketServer _webSocketServer; + QScriptEngine* _engine; + QList _clients; + +private slots: + void onNewConnection(); + +signals: + void newConnection(WebSocketClass* client); + +}; + +#endif // hifi_WebSocketServerClass_h