Migrate composition of the overlay and pointer to the present thread

This commit is contained in:
Brad Davis 2016-02-24 17:38:01 -08:00
parent dfe0575237
commit 28103cf5ef
59 changed files with 1874 additions and 1736 deletions

View file

@ -120,6 +120,7 @@
#include <recording/Recorder.h>
#include <QmlWebWindowClass.h>
#include <Preferences.h>
#include <display-plugins/CompositorHelper.h>
#include "AnimDebugDraw.h"
#include "AudioClient.h"
@ -376,6 +377,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
DependencyManager::set<InterfaceParentFinder>();
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
DependencyManager::set<CompositorHelper>();
return true;
}
@ -770,7 +772,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
}
if (action == controller::toInt(controller::Action::RETICLE_CLICK)) {
auto reticlePos = _compositor.getReticlePosition();
auto reticlePos = getApplicationCompositor().getReticlePosition();
QPoint globalPos(reticlePos.x, reticlePos.y);
// FIXME - it would be nice if this was self contained in the _compositor or Reticle class
@ -793,14 +795,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
cycleCamera();
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
auto reticlePosition = _compositor.getReticlePosition();
auto reticlePosition = getApplicationCompositor().getReticlePosition();
offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
auto oldPos = _compositor.getReticlePosition();
_compositor.setReticlePosition({ oldPos.x + state, oldPos.y });
auto oldPos = getApplicationCompositor().getReticlePosition();
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
auto oldPos = _compositor.getReticlePosition();
_compositor.setReticlePosition({ oldPos.x, oldPos.y + state });
auto oldPos = getApplicationCompositor().getReticlePosition();
getApplicationCompositor().setReticlePosition({ oldPos.x, oldPos.y + state });
} else if (action == controller::toInt(controller::Action::TOGGLE_OVERLAY)) {
toggleOverlays();
}
@ -1253,17 +1255,17 @@ void Application::initializeUi() {
rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
rootContext->setContextProperty("Render", _renderEngine->getConfiguration().get());
rootContext->setContextProperty("Reticle", _compositor.getReticleInterface());
rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
rootContext->setContextProperty("ApplicationCompositor", &_compositor);
rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
_glWidget->installEventFilter(offscreenUi.data());
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
QPointF result = pt;
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin->isHmd()) {
_compositor.handleRealMouseMoveEvent(false);
auto resultVec = _compositor.getReticlePosition();
getApplicationCompositor().handleRealMouseMoveEvent(false);
auto resultVec = getApplicationCompositor().getReticlePosition();
result = QPointF(resultVec.x, resultVec.y);
}
return result.toPoint();
@ -1287,7 +1289,15 @@ void Application::initializeUi() {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
}
Menu::setInstance();
updateInputModes();
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, [=] {
if (isHMDMode()) {
showCursor(compositorHelper->getAllowMouseCapture() ? Qt::BlankCursor : Qt::ArrowCursor);
}
});
}
void Application::paintGL() {
@ -1381,9 +1391,10 @@ void Application::paintGL() {
QSize size = getDeviceSize();
renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height());
_applicationOverlay.renderOverlay(&renderArgs);
gpu::FramebufferPointer overlayFramebuffer = _applicationOverlay.getOverlayFramebuffer();
auto overlayTexture = _applicationOverlay.acquireOverlay();
if (overlayTexture) {
displayPlugin->submitOverlayTexture(overlayTexture);
}
}
glm::vec3 boomOffset;
@ -1484,6 +1495,8 @@ void Application::paintGL() {
myAvatar->endCapture();
}
getApplicationCompositor().setFrameInfo(_frameCount, _myCamera.getTransform());
// Primary rendering pass
auto framebufferCache = DependencyManager::get<FramebufferCache>();
const QSize size = framebufferCache->getFrameBufferSize();
@ -1536,7 +1549,7 @@ void Application::paintGL() {
mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale);
eyeOffsets[eye] = eyeOffsetTransform;
displayPlugin->setEyeRenderPose(_frameCount, eye, headPose);
displayPlugin->setEyeRenderPose(_frameCount, eye, headPose * glm::inverse(eyeOffsetTransform));
eyeProjections[eye] = displayPlugin->getEyeProjection(eye, baseProjection);
});
@ -1550,44 +1563,12 @@ void Application::paintGL() {
renderArgs._context->enableStereo(false);
}
// Overlay Composition, needs to occur after screen space effects have completed
// FIXME migrate composition into the display plugins
{
PROFILE_RANGE(__FUNCTION__ "/compositor");
PerformanceTimer perfTimer("compositor");
auto primaryFbo = finalFramebuffer;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFbo));
if (displayPlugin->isStereo()) {
QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height()));
glClear(GL_DEPTH_BUFFER_BIT);
for_each_eye([&](Eye eye) {
renderArgs._viewport = toGlm(currentViewport);
if (displayPlugin->isHmd()) {
_compositor.displayOverlayTextureHmd(&renderArgs, eye);
} else {
_compositor.displayOverlayTexture(&renderArgs);
}
}, [&] {
currentViewport.moveLeft(currentViewport.width());
});
} else {
glViewport(0, 0, size.width(), size.height());
_compositor.displayOverlayTexture(&renderArgs);
}
}
// deliver final composited scene to the display plugin
{
PROFILE_RANGE(__FUNCTION__ "/pluginOutput");
PerformanceTimer perfTimer("pluginOutput");
auto finalTexturePointer = finalFramebuffer->getRenderBuffer(0);
GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer);
Q_ASSERT(0 != finalTexture);
auto finalTexture = finalFramebuffer->getRenderBuffer(0);
Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture));
_lockedFramebufferMap[finalTexture] = finalFramebuffer;
@ -1595,10 +1576,9 @@ void Application::paintGL() {
{
PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene");
PerformanceTimer perfTimer("pluginSubmitScene");
displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size));
displayPlugin->submitSceneTexture(_frameCount, finalTexture);
}
Q_ASSERT(isCurrentContext(_offscreenContext->getContext()));
}
{
@ -1795,7 +1775,7 @@ bool Application::event(QEvent* event) {
bool Application::eventFilter(QObject* object, QEvent* event) {
if (event->type() == QEvent::Leave) {
_compositor.handleLeaveEvent();
getApplicationCompositor().handleLeaveEvent();
}
if (event->type() == QEvent::ShortcutOverride) {
@ -2083,7 +2063,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
void Application::keyReleaseEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto reticlePosition = _compositor.getReticlePosition();
auto reticlePosition = getApplicationCompositor().getReticlePosition();
offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
}
@ -2172,13 +2152,6 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) {
#endif
}
/// called by ApplicationCompositor when in HMD mode and we're faking our mouse movement
void Application::fakeMouseEvent(QMouseEvent* event) {
_fakedMouseEvent = true;
sendEvent(_glWidget, event);
_fakedMouseEvent = false;
}
void Application::mouseMoveEvent(QMouseEvent* event) {
PROFILE_RANGE(__FUNCTION__);
@ -2190,15 +2163,15 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
// if this is a real mouse event, and we're in HMD mode, then we should use it to move the
// compositor reticle
if (!_fakedMouseEvent) {
if (event->spontaneous()) {
// handleRealMouseMoveEvent() will return true, if we shouldn't process the event further
if (_compositor.handleRealMouseMoveEvent()) {
if (getApplicationCompositor().handleRealMouseMoveEvent()) {
return; // bail
}
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = _compositor.getMouseEventPosition(event);
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
auto button = event->button();
auto buttons = event->buttons();
@ -2240,7 +2213,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
// will consume all keyboard events.
offscreenUi->unfocusWindows();
auto eventPosition = _compositor.getMouseEventPosition(event);
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
QMouseEvent mappedEvent(event->type(),
transformedPos,
@ -2286,7 +2259,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
void Application::mouseReleaseEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = _compositor.getMouseEventPosition(event);
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
QMouseEvent mappedEvent(event->type(),
transformedPos,
@ -2569,7 +2542,7 @@ void Application::setLowVelocityFilter(bool lowVelocityFilter) {
}
ivec2 Application::getMouse() {
auto reticlePosition = _compositor.getReticlePosition();
auto reticlePosition = getApplicationCompositor().getReticlePosition();
// in the HMD, the reticlePosition is the mouse position
if (isHMDMode()) {
@ -4208,7 +4181,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get());
scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
scriptEngine->registerGlobalObject("Reticle", _compositor.getReticleInterface());
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
}
bool Application::canAcceptURL(const QString& urlString) const {
@ -4709,7 +4682,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
auto action = menu->addActionToQMenuAndActionHash(parent,
name, 0, qApp,
SLOT(updateDisplayMode()),
QAction::NoRole, UNSPECIFIED_POSITION, groupingMenu);
QAction::NoRole, Menu::UNSPECIFIED_POSITION, groupingMenu);
action->setCheckable(true);
action->setChecked(active);
@ -4814,6 +4787,7 @@ void Application::updateDisplayMode() {
oldDisplayPlugin = _displayPlugin;
_displayPlugin = newDisplayPlugin;
getApplicationCompositor().setDisplayPlugin(_displayPlugin);
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
@ -5031,3 +5005,7 @@ void Application::showDesktop() {
_overlayConductor.setEnabled(true);
}
}
CompositorHelper& Application::getApplicationCompositor() const {
return *DependencyManager::get<CompositorHelper>();
}

View file

@ -51,7 +51,6 @@
#include "render/Engine.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/DialogsManagerScriptingInterface.h"
#include "ui/ApplicationCompositor.h"
#include "ui/ApplicationOverlay.h"
#include "ui/AudioStatsDialog.h"
#include "ui/BandwidthDialog.h"
@ -67,6 +66,7 @@ class GLCanvas;
class FaceTracker;
class MainWindow;
class AssetUpload;
class CompositorHelper;
namespace controller {
class StateController;
@ -125,6 +125,7 @@ public:
bool isThrottleRendering() const;
Camera* getCamera() { return &_myCamera; }
const Camera* getCamera() const { return &_myCamera; }
// Represents the current view frustum of the avatar.
ViewFrustum* getViewFrustum();
const ViewFrustum* getViewFrustum() const;
@ -149,8 +150,7 @@ public:
ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
ApplicationCompositor& getApplicationCompositor() { return _compositor; }
const ApplicationCompositor& getApplicationCompositor() const { return _compositor; }
CompositorHelper& getApplicationCompositor() const;
Overlays& getOverlays() { return _overlays; }
@ -221,8 +221,6 @@ public:
float getAverageSimsPerSecond();
void fakeMouseEvent(QMouseEvent* event);
signals:
void svoImportRequested(const QString& url);
@ -390,7 +388,7 @@ private:
InputPluginList _activeInputPlugins;
bool _activatingDisplayPlugin { false };
QMap<uint32_t, gpu::FramebufferPointer> _lockedFramebufferMap;
QMap<gpu::TexturePointer, gpu::FramebufferPointer> _lockedFramebufferMap;
MainWindow* _window;
@ -484,7 +482,6 @@ private:
Overlays _overlays;
ApplicationOverlay _applicationOverlay;
ApplicationCompositor _compositor;
OverlayConductor _overlayConductor;
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();

View file

@ -21,6 +21,7 @@
#include <QSettings>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <QtCore/QUrl>
#include "Menu.h"

View file

@ -22,6 +22,7 @@
#include <UserActivityLogger.h>
#include <VrMenu.h>
#include <ScriptEngines.h>
#include <MenuItemProperties.h>
#include "Application.h"
#include "AccountManager.h"
@ -45,11 +46,15 @@
#include "Menu.h"
// Fixme make static member of Menu
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
void Menu::setInstance() {
globalInstance<Menu>(MENU_PROPERTY_NAME);
}
Menu* Menu::getInstance() {
static Menu* instance = globalInstance<Menu>(MENU_PROPERTY_NAME);
return instance;
return static_cast<Menu*>(ui::Menu::getInstance());
}
Menu::Menu() {
@ -621,418 +626,6 @@ Menu::Menu() {
#endif
}
void Menu::toggleAdvancedMenus() {
setGroupingIsVisible("Advanced", !getGroupingIsVisible("Advanced"));
}
void Menu::toggleDeveloperMenus() {
setGroupingIsVisible("Developer", !getGroupingIsVisible("Developer"));
}
void Menu::loadSettings() {
scanMenuBar(&Menu::loadAction);
}
void Menu::saveSettings() {
scanMenuBar(&Menu::saveAction);
}
void Menu::loadAction(Settings& settings, QAction& action) {
if (action.isChecked() != settings.value(action.text(), action.isChecked()).toBool()) {
action.trigger();
}
}
void Menu::saveAction(Settings& settings, QAction& action) {
settings.setValue(action.text(), action.isChecked());
}
void Menu::scanMenuBar(settingsAction modifySetting) {
Settings settings;
foreach (QMenu* menu, findChildren<QMenu*>()) {
scanMenu(*menu, modifySetting, settings);
}
}
void Menu::scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings) {
settings.beginGroup(menu.title());
foreach (QAction* action, menu.actions()) {
if (action->menu()) {
scanMenu(*action->menu(), modifySetting, settings);
} else if (action->isCheckable()) {
modifySetting(settings, *action);
}
}
settings.endGroup();
}
void Menu::addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName,
int menuItemLocation, const QString& grouping) {
QAction* actionBefore = NULL;
QAction* separator;
QAction* separatorText;
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
actionBefore = destinationMenu->actions()[menuItemLocation];
}
if (actionBefore) {
separator = new QAction("",destinationMenu);
destinationMenu->insertAction(actionBefore, separator);
separator->setSeparator(true);
separatorText = new QAction(actionName,destinationMenu);
separatorText->setEnabled(false);
destinationMenu->insertAction(actionBefore, separatorText);
} else {
separator = destinationMenu->addSeparator();
separatorText = destinationMenu->addAction(actionName);
separatorText->setEnabled(false);
}
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << separator;
_groupingActions[grouping] << separatorText;
bool isVisible = getGroupingIsVisible(grouping);
separator->setVisible(isVisible);
separatorText->setVisible(isVisible);
}
}
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut,
const QObject* receiver,
const char* member,
QAction::MenuRole role,
int menuItemLocation,
const QString& grouping) {
QAction* action = NULL;
QAction* actionBefore = NULL;
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
actionBefore = destinationMenu->actions()[menuItemLocation];
}
if (!actionBefore) {
if (receiver && member) {
action = destinationMenu->addAction(actionName, receiver, member, shortcut);
} else {
action = destinationMenu->addAction(actionName);
action->setShortcut(shortcut);
}
} else {
action = new QAction(actionName, destinationMenu);
action->setShortcut(shortcut);
destinationMenu->insertAction(actionBefore, action);
if (receiver && member) {
connect(action, SIGNAL(triggered()), receiver, member);
}
}
action->setMenuRole(role);
_actionHash.insert(actionName, action);
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
return action;
}
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
QAction* action,
const QString& actionName,
const QKeySequence& shortcut,
QAction::MenuRole role,
int menuItemLocation,
const QString& grouping) {
QAction* actionBefore = NULL;
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
actionBefore = destinationMenu->actions()[menuItemLocation];
}
if (!actionName.isEmpty()) {
action->setText(actionName);
}
if (shortcut != 0) {
action->setShortcut(shortcut);
}
if (role != QAction::NoRole) {
action->setMenuRole(role);
}
if (!actionBefore) {
destinationMenu->addAction(action);
} else {
destinationMenu->insertAction(actionBefore, action);
}
_actionHash.insert(action->text(), action);
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
return action;
}
QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut,
const bool checked,
const QObject* receiver,
const char* member,
int menuItemLocation,
const QString& grouping) {
QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member,
QAction::NoRole, menuItemLocation);
action->setCheckable(true);
action->setChecked(checked);
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
return action;
}
void Menu::removeAction(MenuWrapper* menu, const QString& actionName) {
auto action = _actionHash.value(actionName);
menu->removeAction(action);
_actionHash.remove(actionName);
for (auto& grouping : _groupingActions) {
grouping.remove(action);
}
}
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, menuOption),
Q_ARG(bool, isChecked));
return;
}
QAction* menu = _actionHash.value(menuOption);
if (menu) {
menu->setChecked(isChecked);
}
}
bool Menu::isOptionChecked(const QString& menuOption) const {
const QAction* menu = _actionHash.value(menuOption);
if (menu) {
return menu->isChecked();
}
return false;
}
void Menu::triggerOption(const QString& menuOption) {
QAction* action = _actionHash.value(menuOption);
if (action) {
action->trigger();
} else {
qCDebug(interfaceapp) << "NULL Action for menuOption '" << menuOption << "'";
}
}
QAction* Menu::getActionForOption(const QString& menuOption) {
return _actionHash.value(menuOption);
}
QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) {
QList<QAction*> menuActions;
if (menu) {
menuActions = menu->actions();
} else {
menuActions = actions();
}
foreach (QAction* menuAction, menuActions) {
QString actionText = menuAction->text();
if (menuName == menuAction->text()) {
return menuAction;
}
}
return NULL;
}
MenuWrapper* Menu::getSubMenuFromName(const QString& menuName, MenuWrapper* menu) {
QAction* action = getActionFromName(menuName, menu);
if (action) {
return MenuWrapper::fromMenu(action->menu());
}
return NULL;
}
MenuWrapper* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) {
QStringList menuTree = menuName.split(">");
MenuWrapper* parent = NULL;
MenuWrapper* menu = NULL;
foreach (QString menuTreePart, menuTree) {
parent = menu;
finalMenuPart = menuTreePart.trimmed();
menu = getSubMenuFromName(finalMenuPart, parent);
if (!menu) {
break;
}
}
return parent;
}
MenuWrapper* Menu::getMenu(const QString& menuName) {
QStringList menuTree = menuName.split(">");
MenuWrapper* parent = NULL;
MenuWrapper* menu = NULL;
int item = 0;
foreach (QString menuTreePart, menuTree) {
menu = getSubMenuFromName(menuTreePart.trimmed(), parent);
if (!menu) {
break;
}
parent = menu;
item++;
}
return menu;
}
QAction* Menu::getMenuAction(const QString& menuName) {
QStringList menuTree = menuName.split(">");
MenuWrapper* parent = NULL;
QAction* action = NULL;
foreach (QString menuTreePart, menuTree) {
action = getActionFromName(menuTreePart.trimmed(), parent);
if (!action) {
break;
}
parent = MenuWrapper::fromMenu(action->menu());
}
return action;
}
int Menu::findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem) {
int position = 0;
foreach(QAction* action, menu->actions()) {
if (action->text() == searchMenuItem) {
return position;
}
position++;
}
return UNSPECIFIED_POSITION; // not found
}
int Menu::positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition) {
QList<QAction*> menuActions = menu->actions();
if (requestedPosition > 1 && requestedPosition < menuActions.size()) {
QAction* beforeRequested = menuActions[requestedPosition - 1];
if (beforeRequested->isSeparator()) {
requestedPosition--;
}
}
return requestedPosition;
}
bool Menu::_isSomeSubmenuShown = false;
MenuWrapper* Menu::addMenu(const QString& menuName, const QString& grouping) {
QStringList menuTree = menuName.split(">");
MenuWrapper* addTo = NULL;
MenuWrapper* menu = NULL;
foreach (QString menuTreePart, menuTree) {
menu = getSubMenuFromName(menuTreePart.trimmed(), addTo);
if (!menu) {
if (!addTo) {
menu = new MenuWrapper(QMenuBar::addMenu(menuTreePart.trimmed()));
} else {
menu = addTo->addMenu(menuTreePart.trimmed());
}
}
addTo = menu;
}
if (isValidGrouping(grouping)) {
auto action = getMenuAction(menuName);
if (action) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
}
QMenuBar::repaint();
// hook our show/hide for popup menus, so we can keep track of whether or not one
// of our submenus is currently showing.
connect(menu->_realMenu, &QMenu::aboutToShow, []() { _isSomeSubmenuShown = true; });
connect(menu->_realMenu, &QMenu::aboutToHide, []() { _isSomeSubmenuShown = false; });
return menu;
}
void Menu::removeMenu(const QString& menuName) {
QAction* action = getMenuAction(menuName);
// only proceed if the menu actually exists
if (action) {
QString finalMenuPart;
MenuWrapper* parent = getMenuParent(menuName, finalMenuPart);
if (parent) {
parent->removeAction(action);
} else {
QMenuBar::removeAction(action);
}
QMenuBar::repaint();
}
}
bool Menu::menuExists(const QString& menuName) {
QAction* action = getMenuAction(menuName);
// only proceed if the menu actually exists
if (action) {
return true;
}
return false;
}
void Menu::addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping) {
MenuWrapper* menuObj = getMenu(menuName);
if (menuObj) {
addDisabledActionAndSeparator(menuObj, separatorName);
}
}
void Menu::removeSeparator(const QString& menuName, const QString& separatorName) {
MenuWrapper* menu = getMenu(menuName);
bool separatorRemoved = false;
if (menu) {
int textAt = findPositionOfMenuItem(menu, separatorName);
QList<QAction*> menuActions = menu->actions();
QAction* separatorText = menuActions[textAt];
if (textAt > 0 && textAt < menuActions.size()) {
QAction* separatorLine = menuActions[textAt - 1];
if (separatorLine) {
if (separatorLine->isSeparator()) {
menu->removeAction(separatorText);
menu->removeAction(separatorLine);
separatorRemoved = true;
}
}
}
}
if (separatorRemoved) {
QMenuBar::repaint();
}
}
void Menu::addMenuItem(const MenuItemProperties& properties) {
MenuWrapper* menuObj = getMenu(properties.menuName);
if (menuObj) {
@ -1075,126 +668,3 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
QMenuBar::repaint();
}
}
void Menu::removeMenuItem(const QString& menu, const QString& menuitem) {
MenuWrapper* menuObj = getMenu(menu);
if (menuObj) {
removeAction(menuObj, menuitem);
QMenuBar::repaint();
}
}
bool Menu::menuItemExists(const QString& menu, const QString& menuitem) {
QAction* menuItemAction = _actionHash.value(menuitem);
if (menuItemAction) {
return (getMenu(menu) != NULL);
}
return false;
}
bool Menu::getGroupingIsVisible(const QString& grouping) {
if (grouping.isEmpty() || grouping.isNull()) {
return true;
}
if (_groupingVisible.contains(grouping)) {
return _groupingVisible[grouping];
}
return false;
}
void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
// NOTE: Default grouping always visible
if (grouping.isEmpty() || grouping.isNull()) {
return;
}
_groupingVisible[grouping] = isVisible;
for (auto action: _groupingActions[grouping]) {
action->setVisible(isVisible);
}
QMenuBar::repaint();
}
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected) {
auto menu = addMenu(groupName);
QActionGroup* actionGroup = new QActionGroup(menu);
actionGroup->setExclusive(true);
auto menuScriptingInterface = MenuScriptingInterface::getInstance();
for (auto action : actionList) {
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected,
menuScriptingInterface,
SLOT(menuItemTriggered()));
actionGroup->addAction(item);
}
QMenuBar::repaint();
}
void Menu::removeActionGroup(const QString& groupName) {
removeMenu(groupName);
}
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addMenu(menu);
});
_backMap[menu] = this;
}
QList<QAction*> MenuWrapper::actions() {
return _realMenu->actions();
}
MenuWrapper* MenuWrapper::addMenu(const QString& menuName) {
return new MenuWrapper(_realMenu->addMenu(menuName));
}
void MenuWrapper::setEnabled(bool enabled) {
_realMenu->setEnabled(enabled);
}
QAction* MenuWrapper::addSeparator() {
return _realMenu->addSeparator();
}
void MenuWrapper::addAction(QAction* action) {
_realMenu->addAction(action);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addAction(_realMenu, action);
});
}
QAction* MenuWrapper::addAction(const QString& menuName) {
QAction* action = _realMenu->addAction(menuName);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addAction(_realMenu, action);
});
return action;
}
QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) {
QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addAction(_realMenu, action);
});
return action;
}
void MenuWrapper::removeAction(QAction* action) {
_realMenu->removeAction(action);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->removeAction(action);
});
}
void MenuWrapper::insertAction(QAction* before, QAction* action) {
_realMenu->insertAction(before, action);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->insertAction(before, action);
});
}
QHash<QMenu*, MenuWrapper*> MenuWrapper::_backMap;

View file

@ -9,144 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <ui/Menu.h>
#ifndef hifi_Menu_h
#define hifi_Menu_h
#include <QDir>
#include <QMenuBar>
#include <QHash>
#include <QKeySequence>
#include <QPointer>
#include <QStandardPaths>
class MenuItemProperties;
#include <MenuItemProperties.h>
#include "DiscoverabilityManager.h"
class Settings;
class MenuWrapper : public QObject {
public:
QList<QAction*> actions();
MenuWrapper* addMenu(const QString& menuName);
void setEnabled(bool enabled = true);
QAction* addSeparator();
void addAction(QAction* action);
QAction* addAction(const QString& menuName);
void insertAction(QAction* before, QAction* menuName);
QAction* addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut = 0);
void removeAction(QAction* action);
QAction* newAction() {
return new QAction(_realMenu);
}
private:
MenuWrapper(QMenu* menu);
static MenuWrapper* fromMenu(QMenu* menu) {
return _backMap[menu];
}
QMenu* const _realMenu;
static QHash<QMenu*, MenuWrapper*> _backMap;
friend class Menu;
};
class Menu : public QMenuBar {
class Menu : public ui::Menu {
Q_OBJECT
public:
Menu();
static void setInstance();
static Menu* getInstance();
void loadSettings();
void saveSettings();
MenuWrapper* getMenu(const QString& menuName);
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
void triggerOption(const QString& menuOption);
QAction* getActionForOption(const QString& menuOption);
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut = 0,
const QObject* receiver = NULL,
const char* member = NULL,
QAction::MenuRole role = QAction::NoRole,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
QAction* action,
const QString& actionName = QString(),
const QKeySequence& shortcut = 0,
QAction::MenuRole role = QAction::NoRole,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut = 0,
const bool checked = false,
const QObject* receiver = NULL,
const char* member = NULL,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
void removeAction(MenuWrapper* menu, const QString& actionName);
public slots:
MenuWrapper* addMenu(const QString& menuName, const QString& grouping = QString());
void removeMenu(const QString& menuName);
bool menuExists(const QString& menuName);
void addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping = QString());
void removeSeparator(const QString& menuName, const QString& separatorName);
void addMenuItem(const MenuItemProperties& properties);
void removeMenuItem(const QString& menuName, const QString& menuitem);
bool menuItemExists(const QString& menuName, const QString& menuitem);
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString());
void removeActionGroup(const QString& groupName);
bool isOptionChecked(const QString& menuOption) const;
void setIsOptionChecked(const QString& menuOption, bool isChecked);
bool getGroupingIsVisible(const QString& grouping);
void setGroupingIsVisible(const QString& grouping, bool isVisible); /// NOTE: the "" grouping is always visible
void toggleDeveloperMenus();
void toggleAdvancedMenus();
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }
private:
typedef void(*settingsAction)(Settings&, QAction&);
static void loadAction(Settings& settings, QAction& action);
static void saveAction(Settings& settings, QAction& action);
void scanMenuBar(settingsAction modifySetting);
void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings);
/// helper method to have separators with labels that are also compatible with OS X
void addDisabledActionAndSeparator(MenuWrapper* destinationMenu,
const QString& actionName,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
QAction* getActionFromName(const QString& menuName, MenuWrapper* menu);
MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart);
QAction* getMenuAction(const QString& menuName);
int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem);
int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition);
QHash<QString, QAction*> _actionHash;
bool isValidGrouping(const QString& grouping) const { return grouping == "Advanced" || grouping == "Developer"; }
QHash<QString, bool> _groupingVisible;
QHash<QString, QSet<QAction*>> _groupingActions;
static bool _isSomeSubmenuShown;
Menu();
Q_INVOKABLE void addMenuItem(const MenuItemProperties& properties);
};
namespace MenuOption {
@ -302,3 +179,4 @@ namespace MenuOption {
}
#endif // hifi_Menu_h

View file

@ -170,7 +170,7 @@ bool PluginContainerProxy::makeRenderingContextCurrent() {
return qApp->_offscreenContext->makeCurrent();
}
void PluginContainerProxy::releaseSceneTexture(uint32_t texture) {
void PluginContainerProxy::releaseSceneTexture(const gpu::TexturePointer& texture) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
auto& framebufferMap = qApp->_lockedFramebufferMap;
Q_ASSERT(framebufferMap.contains(texture));
@ -180,7 +180,7 @@ void PluginContainerProxy::releaseSceneTexture(uint32_t texture) {
framebufferCache->releaseFramebuffer(framebufferPointer);
}
void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) {
void PluginContainerProxy::releaseOverlayTexture(const gpu::TexturePointer& texture) {
// FIXME implement present thread compositing
}

View file

@ -25,8 +25,8 @@ class PluginContainerProxy : public QObject, PluginContainer {
virtual void showDisplayPluginsTools() override;
virtual void requestReset() override;
virtual bool makeRenderingContextCurrent() override;
virtual void releaseSceneTexture(uint32_t texture) override;
virtual void releaseOverlayTexture(uint32_t texture) override;
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override;
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override;
virtual GLWidget* getPrimaryWidget() override;
virtual QWindow* getPrimaryWindow() override;
virtual QOpenGLContext* getPrimaryContext() override;

View file

@ -13,7 +13,8 @@
#include <QtScript/QScriptContext>
#include "display-plugins/DisplayPlugin.h"
#include <display-plugins/DisplayPlugin.h>
#include <display-plugins/CompositorHelper.h>
#include <avatar/AvatarManager.h>
#include "Application.h"

View file

@ -86,10 +86,13 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected) {
static const char* slot = SLOT(menuItemTriggered());
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
Q_ARG(const QString&, groupName),
Q_ARG(const QStringList&, actionList),
Q_ARG(const QString&, selected));
Q_ARG(const QString&, selected),
Q_ARG(QObject*, this),
Q_ARG(const char*, slot));
}
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {

View file

@ -1,704 +0,0 @@
//
// ApplicationCompositor.cpp
// interface/src/ui/overlays
//
// Created by Benjamin Arnold on 5/27/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ApplicationCompositor.h"
#include <memory>
#include <QPropertyAnimation>
#include <glm/gtc/type_ptr.hpp>
#include <display-plugins/DisplayPlugin.h>
#include <avatar/AvatarManager.h>
#include <gpu/GLBackend.h>
#include <NumericalConstants.h>
#include "CursorManager.h"
#include "Tooltip.h"
#include "Application.h"
#include <controllers/InputDevice.h>
// Used to animate the magnification windows
static const quint64 MSECS_TO_USECS = 1000ULL;
static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
static const float reticleSize = TWO_PI / 100.0f;
static const float CURSOR_PIXEL_SIZE = 32.0f;
static gpu::BufferPointer _hemiVertices;
static gpu::BufferPointer _hemiIndices;
static int _hemiIndexCount{ 0 };
EntityItemID ApplicationCompositor::_noItemId;
static QString _tooltipId;
// Return a point's cartesian coordinates on a sphere from pitch and yaw
glm::vec3 getPoint(float yaw, float pitch) {
return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)),
glm::sin(-pitch),
glm::cos(-pitch) * (-glm::cos(yaw)));
}
//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should
//be multiplied by dir and added to origin to get the location of the collision
bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result)
{
//Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
//Compute A, B and C coefficients
float a = glm::dot(dir, dir);
float b = 2 * glm::dot(dir, origin);
float c = glm::dot(origin, origin) - (r * r);
//Find discriminant
float disc = b * b - 4 * a * c;
// if discriminant is negative there are no real roots, so return
// false as ray misses sphere
if (disc < 0) {
return false;
}
// compute q as described above
float distSqrt = sqrtf(disc);
float q;
if (b < 0) {
q = (-b - distSqrt) / 2.0f;
} else {
q = (-b + distSqrt) / 2.0f;
}
// compute t0 and t1
float t0 = q / a;
float t1 = c / q;
// make sure t0 is smaller than t1
if (t0 > t1) {
// if t0 is bigger than t1 swap them around
float temp = t0;
t0 = t1;
t1 = temp;
}
// if t1 is less than zero, the object is in the ray's negative direction
// and consequently the ray misses the sphere
if (t1 < 0) {
return false;
}
// if t0 is less than zero, the intersection point is at t1
if (t0 < 0) {
*result = t1;
return true;
} else { // else the intersection point is at t0
*result = t0;
return true;
}
}
ApplicationCompositor::ApplicationCompositor() :
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")),
_reticleInterface(new ReticleInterface(this))
{
auto geometryCache = DependencyManager::get<GeometryCache>();
_reticleQuad = geometryCache->allocateID();
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
if (_hoverItemId != entityItemID) {
_hoverItemId = entityItemID;
_hoverItemEnterUsecs = usecTimestampNow();
auto properties = entityScriptingInterface->getEntityProperties(_hoverItemId);
// check the format of this href string before we parse it
QString hrefString = properties.getHref();
auto cursor = Cursor::Manager::instance().getCursor();
if (!hrefString.isEmpty()) {
if (!hrefString.startsWith("hifi:")) {
hrefString.prepend("hifi://");
}
// parse out a QUrl from the hrefString
QUrl href = QUrl(hrefString);
_hoverItemTitle = href.host();
_hoverItemDescription = properties.getDescription();
cursor->setIcon(Cursor::Icon::LINK);
} else {
_hoverItemTitle.clear();
_hoverItemDescription.clear();
cursor->setIcon(Cursor::Icon::DEFAULT);
}
}
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
if (_hoverItemId == entityItemID) {
_hoverItemId = _noItemId;
_hoverItemTitle.clear();
_hoverItemDescription.clear();
auto cursor = Cursor::Manager::instance().getCursor();
cursor->setIcon(Cursor::Icon::DEFAULT);
if (!_tooltipId.isEmpty()) {
qDebug() << "Closing tooltip " << _tooltipId;
Tooltip::closeTip(_tooltipId);
_tooltipId.clear();
}
}
});
_alphaPropertyAnimation.reset(new QPropertyAnimation(this, "alpha"));
}
ApplicationCompositor::~ApplicationCompositor() {
}
void ApplicationCompositor::bindCursorTexture(gpu::Batch& batch, uint8_t cursorIndex) {
auto& cursorManager = Cursor::Manager::instance();
auto cursor = cursorManager.getCursor(cursorIndex);
auto iconId = cursor->getIcon();
if (!_cursors.count(iconId)) {
auto iconPath = cursorManager.getIconImage(cursor->getIcon());
_cursors[iconId] = DependencyManager::get<TextureCache>()->
getImageTexture(iconPath);
}
batch.setResourceTexture(0, _cursors[iconId]);
}
// Draws the FBO texture for the screen
void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) {
PROFILE_RANGE(__FUNCTION__);
if (_alpha <= 0.0f) {
return;
}
gpu::FramebufferPointer overlayFramebuffer = qApp->getApplicationOverlay().getOverlayFramebuffer();
if (!overlayFramebuffer) {
return;
}
updateTooltips();
//Handle fading and deactivation/activation of UI
gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) {
auto geometryCache = DependencyManager::get<GeometryCache>();
geometryCache->useSimpleDrawPipeline(batch);
batch.setViewportTransform(renderArgs->_viewport);
batch.setModelTransform(Transform());
batch.setViewTransform(Transform());
batch.setProjectionTransform(mat4());
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha));
//draw the mouse pointer
if (getReticleVisible()) {
// Get the mouse coordinates and convert to NDC [-1, 1]
vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas...
vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize);
// Invert the Y axis
mousePosition.y *= -1.0f;
Transform model;
model.setTranslation(vec3(mousePosition, 0));
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
model.setScale(vec3(mouseSize, 1.0f));
batch.setModelTransform(model);
bindCursorTexture(batch);
geometryCache->renderUnitQuad(batch, vec4(1));
}
});
}
// Draws the FBO texture for Oculus rift.
void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int eye) {
PROFILE_RANGE(__FUNCTION__);
if (_alpha <= 0.0f) {
return;
}
gpu::FramebufferPointer overlayFramebuffer = qApp->getApplicationOverlay().getOverlayFramebuffer();
if (!overlayFramebuffer) {
return;
}
updateTooltips();
glm::uvec2 screenSize = qApp->getUiSize(); // HMD use virtual screen size
vec2 canvasSize = screenSize;
_textureAspectRatio = aspect(canvasSize);
auto geometryCache = DependencyManager::get<GeometryCache>();
gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) {
geometryCache->useSimpleDrawPipeline(batch);
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
mat4 camMat;
_cameraBaseTransform.getMatrix(camMat);
auto displayPlugin = qApp->getActiveDisplayPlugin();
auto headPose = qApp->getHMDSensorPose();
auto eyeToHead = displayPlugin->getEyeToHeadTransform((Eye)eye);
camMat = (headPose * eyeToHead) * camMat; // FIXME - why are not all transforms are doing this aditioanl eyeToHead
batch.setViewportTransform(renderArgs->_viewport);
batch.setViewTransform(camMat);
batch.setProjectionTransform(qApp->getEyeProjection(eye));
#ifdef DEBUG_OVERLAY
{
batch.setModelTransform(glm::translate(mat4(), vec3(0, 0, -2)));
geometryCache->renderUnitQuad(batch, glm::vec4(1));
}
#else
{
batch.setModelTransform(_modelTransform);
drawSphereSection(batch);
}
#endif
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
bindCursorTexture(batch);
//Mouse Pointer
if (getReticleVisible()) {
if (getReticleDepth() != 1.0f) {
// calculate the "apparent location" based on the depth and the current ray
glm::vec3 origin, direction;
auto reticlePosition = getReticlePosition();
computeHmdPickRay(reticlePosition, origin, direction);
auto apparentPosition = origin + (direction * getReticleDepth());
// same code as used to render for apparent location
auto myCamera = qApp->getCamera();
mat4 cameraMat = myCamera->getTransform();
auto UITransform = cameraMat * glm::inverse(headPose);
auto relativePosition4 = glm::inverse(UITransform) * vec4(apparentPosition, 1);
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto relativeDistance = glm::length(relativePosition);
// look at borrowed from overlays
float elevation = -asinf(relativePosition.y / glm::length(relativePosition));
float azimuth = atan2f(relativePosition.x, relativePosition.z);
glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, -PI, 0)); // this extra *quat(vec3(0,-PI,0)) was required to get the quad to flip this seems like we could optimize
Transform transform;
transform.setTranslation(relativePosition);
transform.setScale(reticleScale);
transform.postScale(relativeDistance); // scale not quite working, distant things too large
transform.setRotation(faceCamera);
batch.setModelTransform(transform);
} else {
glm::mat4 overlayXfm;
_modelTransform.getMatrix(overlayXfm);
auto reticlePosition = getReticlePosition();
glm::vec2 projection = overlayToSpherical(reticlePosition);
mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
mat4 reticleXfm = overlayXfm * pointerXfm;
reticleXfm = glm::scale(reticleXfm, reticleScale);
batch.setModelTransform(reticleXfm);
}
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
}
});
}
QPointF ApplicationCompositor::getMouseEventPosition(QMouseEvent* event) {
if (qApp->isHMDMode()) {
QMutexLocker locker(&_reticleLock);
return QPointF(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
}
return event->localPos();
}
bool ApplicationCompositor::shouldCaptureMouse() const {
// if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu
return _allowMouseCapture && qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown();
}
void ApplicationCompositor::setAllowMouseCapture(bool capture) {
if (qApp->isHMDMode()) {
if (capture) {
qApp->showCursor(Qt::BlankCursor);
} else {
qApp->showCursor(Qt::ArrowCursor);
}
}
_allowMouseCapture = capture;
}
void ApplicationCompositor::handleLeaveEvent() {
if (shouldCaptureMouse()) {
QWidget* mainWidget = (QWidget*)qApp->getWindow();
QRect mainWidgetFrame = qApp->getRenderingGeometry();
QRect uncoveredRect = mainWidgetFrame;
foreach(QWidget* widget, QApplication::topLevelWidgets()) {
if (widget->isWindow() && widget->isVisible() && widget != mainWidget) {
QRect widgetFrame = widget->frameGeometry();
if (widgetFrame.intersects(uncoveredRect)) {
QRect intersection = uncoveredRect & widgetFrame;
if (intersection.top() > uncoveredRect.top()) {
uncoveredRect.setBottom(intersection.top() - 1);
} else if (intersection.bottom() < uncoveredRect.bottom()) {
uncoveredRect.setTop(intersection.bottom() + 1);
}
if (intersection.left() > uncoveredRect.left()) {
uncoveredRect.setRight(intersection.left() - 1);
} else if (intersection.right() < uncoveredRect.right()) {
uncoveredRect.setLeft(intersection.right() + 1);
}
}
}
}
_ignoreMouseMove = true;
auto sendToPos = uncoveredRect.center();
QCursor::setPos(sendToPos);
_lastKnownRealMouse = sendToPos;
}
}
bool ApplicationCompositor::handleRealMouseMoveEvent(bool sendFakeEvent) {
// If the mouse move came from a capture mouse related move, we completely ignore it.
if (_ignoreMouseMove) {
_ignoreMouseMove = false;
return true; // swallow the event
}
// If we're in HMD mode
if (shouldCaptureMouse()) {
QMutexLocker locker(&_reticleLock);
auto newPosition = QCursor::pos();
auto changeInRealMouse = newPosition - _lastKnownRealMouse;
auto newReticlePosition = _reticlePositionInHMD + toGlm(changeInRealMouse);
setReticlePosition(newReticlePosition, sendFakeEvent);
_ignoreMouseMove = true;
QCursor::setPos(QPoint(_lastKnownRealMouse.x(), _lastKnownRealMouse.y())); // move cursor back to where it was
return true; // swallow the event
} else {
_lastKnownRealMouse = QCursor::pos();
}
return false; // let the caller know to process the event
}
glm::vec2 ApplicationCompositor::getReticlePosition() const {
if (qApp->isHMDMode()) {
QMutexLocker locker(&_reticleLock);
return _reticlePositionInHMD;
}
return toGlm(QCursor::pos());
}
bool ApplicationCompositor::getReticleOverDesktop() const {
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
// but... if we're outside of the overlay area, we also want to call ourselves
// as being over the desktop.
if (qApp->isHMDMode()) {
QMutexLocker locker(&_reticleLock);
glm::vec2 maxOverlayPosition = qApp->getUiSize();
if (_reticlePositionInHMD.x < 0 || _reticlePositionInHMD.y < 0 ||
_reticlePositionInHMD.x > maxOverlayPosition.x || _reticlePositionInHMD.y > maxOverlayPosition.y) {
return true; // we are outside the overlay area, consider ourselves over the desktop
}
}
return _isOverDesktop;
}
void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFakeEvent) {
if (qApp->isHMDMode()) {
QMutexLocker locker(&_reticleLock);
const float MOUSE_EXTENTS_VERT_ANGULAR_SIZE = 170.0f; // 5deg from poles
const float MOUSE_EXTENTS_VERT_PIXELS = VIRTUAL_SCREEN_SIZE_Y * (MOUSE_EXTENTS_VERT_ANGULAR_SIZE / DEFAULT_HMD_UI_VERT_ANGULAR_SIZE);
const float MOUSE_EXTENTS_HORZ_ANGULAR_SIZE = 360.0f; // full sphere
const float MOUSE_EXTENTS_HORZ_PIXELS = VIRTUAL_SCREEN_SIZE_X * (MOUSE_EXTENTS_HORZ_ANGULAR_SIZE / DEFAULT_HMD_UI_HORZ_ANGULAR_SIZE);
glm::vec2 maxOverlayPosition = qApp->getUiSize();
float extaPixelsX = (MOUSE_EXTENTS_HORZ_PIXELS - maxOverlayPosition.x) / 2.0f;
float extaPixelsY = (MOUSE_EXTENTS_VERT_PIXELS - maxOverlayPosition.y) / 2.0f;
glm::vec2 mouseExtra { extaPixelsX, extaPixelsY };
glm::vec2 minMouse = vec2(0) - mouseExtra;
glm::vec2 maxMouse = maxOverlayPosition + mouseExtra;
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
if (sendFakeEvent) {
// in HMD mode we need to fake our mouse moves...
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
auto button = Qt::NoButton;
auto buttons = QApplication::mouseButtons();
auto modifiers = QApplication::keyboardModifiers();
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
qApp->fakeMouseEvent(&event);
}
} else {
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPos = toGlm(QCursor::pos());
auto distance = glm::distance(oldPos, position);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPos << " newPos:" << position;
}
QCursor::setPos(position.x, position.y);
}
}
#include <QDesktopWidget>
glm::vec2 ApplicationCompositor::getReticleMaximumPosition() const {
glm::vec2 result;
if (qApp->isHMDMode()) {
result = glm::vec2(VIRTUAL_SCREEN_SIZE_X, VIRTUAL_SCREEN_SIZE_Y);
} else {
QRect rec = QApplication::desktop()->screenGeometry();
result = glm::vec2(rec.right(), rec.bottom());
}
return result;
}
void ApplicationCompositor::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const {
auto surfacePointAt = sphereSurfaceFromOverlay(cursorPos); // in world space
glm::vec3 worldSpaceCameraPosition = qApp->getCamera()->getPosition();
origin = worldSpaceCameraPosition;
direction = glm::normalize(surfacePointAt - worldSpaceCameraPosition);
}
//Finds the collision point of a world space ray
bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const {
auto headPose = qApp->getHMDSensorPose();
auto myCamera = qApp->getCamera();
mat4 cameraMat = myCamera->getTransform();
auto UITransform = cameraMat * glm::inverse(headPose);
auto relativePosition4 = glm::inverse(UITransform) * vec4(position, 1);
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
float instersectionDistance;
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
result = position + glm::normalize(direction) * instersectionDistance;
return true;
}
return false;
}
void ApplicationCompositor::buildHemiVertices(
const float fov, const float aspectRatio, const int slices, const int stacks) {
static float textureFOV = 0.0f, textureAspectRatio = 1.0f;
if (textureFOV == fov && textureAspectRatio == aspectRatio) {
return;
}
textureFOV = fov;
textureAspectRatio = aspectRatio;
auto geometryCache = DependencyManager::get<GeometryCache>();
_hemiVertices = std::make_shared<gpu::Buffer>();
_hemiIndices = std::make_shared<gpu::Buffer>();
if (fov >= PI) {
qDebug() << "TexturedHemisphere::buildVBO(): FOV greater or equal than Pi will create issues";
}
//UV mapping source: http://www.mvps.org/directx/articles/spheremap.htm
vec3 pos;
vec2 uv;
// Compute vertices positions and texture UV coordinate
// Create and write to buffer
for (int i = 0; i < stacks; i++) {
uv.y = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f
// abs(theta) <= fov / 2.0f
float pitch = -fov * (uv.y - 0.5f);
for (int j = 0; j < slices; j++) {
uv.x = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f
// abs(phi) <= fov * aspectRatio / 2.0f
float yaw = -fov * aspectRatio * (uv.x - 0.5f);
pos = getPoint(yaw, pitch);
static const vec4 color(1);
_hemiVertices->append(sizeof(pos), (gpu::Byte*)&pos);
_hemiVertices->append(sizeof(vec2), (gpu::Byte*)&uv);
_hemiVertices->append(sizeof(vec4), (gpu::Byte*)&color);
}
}
// Compute number of indices needed
static const int VERTEX_PER_TRANGLE = 3;
static const int TRIANGLE_PER_RECTANGLE = 2;
int numberOfRectangles = (slices - 1) * (stacks - 1);
_hemiIndexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE;
// Compute indices order
std::vector<GLushort> indices;
for (int i = 0; i < stacks - 1; i++) {
for (int j = 0; j < slices - 1; j++) {
GLushort bottomLeftIndex = i * slices + j;
GLushort bottomRightIndex = bottomLeftIndex + 1;
GLushort topLeftIndex = bottomLeftIndex + slices;
GLushort topRightIndex = topLeftIndex + 1;
// FIXME make a z-order curve for better vertex cache locality
indices.push_back(topLeftIndex);
indices.push_back(bottomLeftIndex);
indices.push_back(topRightIndex);
indices.push_back(topRightIndex);
indices.push_back(bottomLeftIndex);
indices.push_back(bottomRightIndex);
}
}
_hemiIndices->append(sizeof(GLushort) * indices.size(), (gpu::Byte*)&indices[0]);
}
void ApplicationCompositor::drawSphereSection(gpu::Batch& batch) {
buildHemiVertices(_textureFov, _textureAspectRatio, 80, 80);
static const int VERTEX_DATA_SLOT = 0;
static const int TEXTURE_DATA_SLOT = 1;
static const int COLOR_DATA_SLOT = 2;
auto streamFormat = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
streamFormat->setAttribute(gpu::Stream::POSITION, VERTEX_DATA_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
streamFormat->setAttribute(gpu::Stream::TEXCOORD, TEXTURE_DATA_SLOT, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_DATA_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA));
batch.setInputFormat(streamFormat);
static const int VERTEX_STRIDE = sizeof(vec3) + sizeof(vec2) + sizeof(vec4);
if (_prevAlpha != _alpha) {
// adjust alpha by munging vertex color alpha.
// FIXME we should probably just use a uniform for this.
float* floatPtr = reinterpret_cast<float*>(_hemiVertices->editData());
const auto ALPHA_FLOAT_OFFSET = (sizeof(vec3) + sizeof(vec2) + sizeof(vec3)) / sizeof(float);
const auto VERTEX_FLOAT_STRIDE = (sizeof(vec3) + sizeof(vec2) + sizeof(vec4)) / sizeof(float);
const auto NUM_VERTS = _hemiVertices->getSize() / VERTEX_STRIDE;
for (size_t i = 0; i < NUM_VERTS; i++) {
floatPtr[i * VERTEX_FLOAT_STRIDE + ALPHA_FLOAT_OFFSET] = _alpha;
}
}
gpu::BufferView posView(_hemiVertices, 0, _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::POSITION)._element);
gpu::BufferView uvView(_hemiVertices, sizeof(vec3), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::TEXCOORD)._element);
gpu::BufferView colView(_hemiVertices, sizeof(vec3) + sizeof(vec2), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::COLOR)._element);
batch.setInputBuffer(VERTEX_DATA_SLOT, posView);
batch.setInputBuffer(TEXTURE_DATA_SLOT, uvView);
batch.setInputBuffer(COLOR_DATA_SLOT, colView);
batch.setIndexBuffer(gpu::UINT16, _hemiIndices, 0);
batch.drawIndexed(gpu::TRIANGLES, _hemiIndexCount);
}
glm::vec2 ApplicationCompositor::sphericalToOverlay(const glm::vec2& sphericalPos) const {
glm::vec2 result = sphericalPos;
result.x *= -1.0f;
result /= _textureFov;
result.x /= _textureAspectRatio;
result += 0.5f;
result *= qApp->getUiSize();
return result;
}
glm::vec2 ApplicationCompositor::overlayToSpherical(const glm::vec2& overlayPos) const {
glm::vec2 result = overlayPos;
result /= qApp->getUiSize();
result -= 0.5f;
result *= _textureFov;
result.x *= _textureAspectRatio;
result.x *= -1.0f;
return result;
}
glm::vec2 ApplicationCompositor::overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const {
auto headPose = qApp->getHMDSensorPose();
auto myCamera = qApp->getCamera();
mat4 cameraMat = myCamera->getTransform();
auto UITransform = cameraMat * glm::inverse(headPose);
auto relativePosition4 = glm::inverse(UITransform) * vec4(sphereSurfacePoint, 1);
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto center = vec3(0); // center of HUD in HUD space
auto direction = relativePosition - center; // direction to relative position in HUD space
glm::vec2 polar = glm::vec2(glm::atan(direction.x, -direction.z), glm::asin(direction.y)) * -1.0f;
auto overlayPos = sphericalToOverlay(polar);
return overlayPos;
}
glm::vec3 ApplicationCompositor::sphereSurfaceFromOverlay(const glm::vec2& overlay) const {
auto spherical = overlayToSpherical(overlay);
auto sphereSurfacePoint = getPoint(spherical.x, spherical.y);
auto headPose = qApp->getHMDSensorPose();
auto myCamera = qApp->getCamera();
mat4 cameraMat = myCamera->getTransform();
auto UITransform = cameraMat * glm::inverse(headPose);
auto position4 = UITransform * vec4(sphereSurfacePoint, 1);
auto position = vec3(position4) / position4.w;
return position;
}
void ApplicationCompositor::updateTooltips() {
if (_hoverItemId != _noItemId) {
quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs;
if (_hoverItemEnterUsecs != UINT64_MAX && !_hoverItemTitle.isEmpty() && hoverDuration > TOOLTIP_DELAY) {
// TODO Enable and position the tooltip
_hoverItemEnterUsecs = UINT64_MAX;
_tooltipId = Tooltip::showTip(_hoverItemTitle, _hoverItemDescription);
}
}
}
static const float FADE_DURATION = 500.0f;
void ApplicationCompositor::fadeIn() {
_fadeInAlpha = true;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(1.0f);
_alphaPropertyAnimation->start();
}
void ApplicationCompositor::fadeOut() {
_fadeInAlpha = false;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(0.0f);
_alphaPropertyAnimation->start();
}
void ApplicationCompositor::toggle() {
if (_fadeInAlpha) {
fadeOut();
} else {
fadeIn();
}
}

View file

@ -244,34 +244,69 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder(RenderArgs* renderAr
}
}
static const auto COLOR_FORMAT = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
static const auto DEFAULT_SAMPLER = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);
std::mutex _textureGuard;
using Lock = std::unique_lock<std::mutex>;
std::queue<gpu::TexturePointer> _availableTextures;
void ApplicationOverlay::buildFramebufferObject() {
PROFILE_RANGE(__FUNCTION__);
auto uiSize = qApp->getUiSize();
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
}
if (_overlayFramebuffer && uiSize == _overlayFramebuffer->getSize()) {
// Already built
return;
auto width = uiSize.x;
auto height = uiSize.y;
if (!_overlayFramebuffer->getDepthStencilBuffer()) {
auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(DEPTH_FORMAT, width, height, DEFAULT_SAMPLER));
_overlayFramebuffer->setDepthStencilBuffer(overlayDepthTexture, DEPTH_FORMAT);
}
if (!_overlayFramebuffer->getRenderBuffer(0)) {
gpu::TexturePointer newColorAttachment;
{
Lock lock(_textureGuard);
if (!_availableTextures.empty()) {
newColorAttachment = _availableTextures.front();
_availableTextures.pop();
}
}
if (newColorAttachment) {
newColorAttachment->resize2D(width, height, newColorAttachment->getNumSamples());
_overlayFramebuffer->setRenderBuffer(0, newColorAttachment);
}
}
if (_overlayFramebuffer) {
_overlayFramebuffer.reset();
_overlayDepthTexture.reset();
_overlayColorTexture.reset();
// If the overlay framebuffer still has no color attachment, no textures were available for rendering, so build a new one
if (!_overlayFramebuffer->getRenderBuffer(0)) {
auto colorBuffer = gpu::TexturePointer(gpu::Texture::create2D(COLOR_FORMAT, width, height, DEFAULT_SAMPLER));
_overlayFramebuffer->setRenderBuffer(0, colorBuffer);
}
}
gpu::TexturePointer ApplicationOverlay::acquireOverlay() {
if (!_overlayFramebuffer) {
return gpu::TexturePointer();
}
auto result = _overlayFramebuffer->getRenderBuffer(0);
auto textureId = gpu::GLBackend::getTextureID(result, false);
if (!textureId) {
qDebug() << "Missing texture";
}
_overlayFramebuffer->setRenderBuffer(0, gpu::TexturePointer());
return result;
}
void ApplicationOverlay::releaseOverlay(gpu::TexturePointer texture) {
if (texture) {
Lock lock(_textureGuard);
_availableTextures.push(texture);
} else {
qWarning() << "Attempted to release null texture";
}
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
auto width = uiSize.x;
auto height = uiSize.y;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
_overlayColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler));
_overlayFramebuffer->setRenderBuffer(0, _overlayColorTexture);
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);
_overlayDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler));
_overlayFramebuffer->setDepthStencilBuffer(_overlayDepthTexture, depthFormat);
}

View file

@ -26,7 +26,8 @@ public:
void renderOverlay(RenderArgs* renderArgs);
gpu::FramebufferPointer getOverlayFramebuffer() const { return _overlayFramebuffer; }
gpu::TexturePointer acquireOverlay();
void releaseOverlay(gpu::TexturePointer pointer);
private:
void renderStatsAndLogs(RenderArgs* renderArgs);

View file

@ -9,6 +9,7 @@
//
#include <OffscreenUi.h>
#include <display-plugins/CompositorHelper.h>
#include "Application.h"
#include "avatar/AvatarManager.h"

View file

@ -16,6 +16,7 @@
#include <ScriptEngines.h>
#include <OffscreenUi.h>
#include <Preferences.h>
#include <display-plugins/CompositorHelper.h>
#include "Application.h"
#include "DialogsManager.h"

View file

@ -1,16 +1,9 @@
set(TARGET_NAME display-plugins)
setup_hifi_library(OpenGL)
link_hifi_libraries(shared plugins gl)
link_hifi_libraries(shared plugins gl ui)
target_opengl()
GroupSources("src/display-plugins")
target_oglplus()
if (WIN32)
add_dependency_external_projects(OpenVR)
find_package(OpenVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
endif()

View file

@ -35,9 +35,9 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() {
updateFramerate();
}
void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) {
void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) {
_wantVsync = true; // always
WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture, sceneSize);
WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture);
}
void Basic2DWindowOpenGLDisplayPlugin::internalPresent() {

View file

@ -24,7 +24,7 @@ public:
virtual void activate() override;
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override;
virtual void internalPresent() override;

View file

@ -0,0 +1,485 @@
//
// Created by Benjamin Arnold on 5/27/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CompositorHelper.h"
#include <memory>
#include <math.h>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <glm/gtc/type_ptr.hpp>
#include <ui/Menu.h>
#include <NumericalConstants.h>
#include <DependencyManager.h>
#include <plugins/PluginManager.h>
#include <plugins/PluginContainer.h>
#include <CursorManager.h>
#include <gl/GLWidget.h>
// Used to animate the magnification windows
static const quint64 MSECS_TO_USECS = 1000ULL;
//static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
static const float reticleSize = TWO_PI / 100.0f;
//EntityItemID CompositorHelper::_noItemId;
static QString _tooltipId;
const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(3960, 1188); // ~10% more pixel density than old version, 72dx240d FOV
const float CompositorHelper::VIRTUAL_UI_ASPECT_RATIO = (float)VIRTUAL_SCREEN_SIZE.x / (float)VIRTUAL_SCREEN_SIZE.y;
const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI * 3.0f / 2.0f, PI * 3.0f / 2.0f / VIRTUAL_UI_ASPECT_RATIO);
const vec2 CompositorHelper::MOUSE_EXTENTS_ANGULAR_SIZE = vec2(PI * 2.0f, PI * 0.95f); // horizontal: full sphere, vertical: ~5deg from poles
const vec2 CompositorHelper::MOUSE_EXTENTS_PIXELS = vec2(VIRTUAL_SCREEN_SIZE) * (MOUSE_EXTENTS_ANGULAR_SIZE / VIRTUAL_UI_TARGET_FOV);
// Return a point's cartesian coordinates on a sphere from pitch and yaw
glm::vec3 getPoint(float yaw, float pitch) {
return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)),
glm::sin(-pitch),
glm::cos(-pitch) * (-glm::cos(yaw)));
}
// FIXME move to GLMHelpers
//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should
//be multiplied by dir and added to origin to get the location of the collision
bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result)
{
//Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
//Compute A, B and C coefficients
float a = glm::dot(dir, dir);
float b = 2 * glm::dot(dir, origin);
float c = glm::dot(origin, origin) - (r * r);
//Find discriminant
float disc = b * b - 4 * a * c;
// if discriminant is negative there are no real roots, so return
// false as ray misses sphere
if (disc < 0) {
return false;
}
// compute q as described above
float distSqrt = sqrtf(disc);
float q;
if (b < 0) {
q = (-b - distSqrt) / 2.0f;
} else {
q = (-b + distSqrt) / 2.0f;
}
// compute t0 and t1
float t0 = q / a;
float t1 = c / q;
// make sure t0 is smaller than t1
if (t0 > t1) {
// if t0 is bigger than t1 swap them around
float temp = t0;
t0 = t1;
t1 = temp;
}
// if t1 is less than zero, the object is in the ray's negative direction
// and consequently the ray misses the sphere
if (t1 < 0) {
return false;
}
// if t0 is less than zero, the intersection point is at t1
if (t0 < 0) {
*result = t1;
return true;
} else { // else the intersection point is at t0
*result = t0;
return true;
}
}
CompositorHelper::CompositorHelper() :
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")),
_reticleInterface(new ReticleInterface(this))
{
// FIX in a separate PR addressing the current mouse over entity bug
//auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
//connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
// if (_hoverItemId != entityItemID) {
// _hoverItemId = entityItemID;
// _hoverItemEnterUsecs = usecTimestampNow();
// auto properties = entityScriptingInterface->getEntityProperties(_hoverItemId);
// // check the format of this href string before we parse it
// QString hrefString = properties.getHref();
// auto cursor = Cursor::Manager::instance().getCursor();
// if (!hrefString.isEmpty()) {
// if (!hrefString.startsWith("hifi:")) {
// hrefString.prepend("hifi://");
// }
// // parse out a QUrl from the hrefString
// QUrl href = QUrl(hrefString);
// _hoverItemTitle = href.host();
// _hoverItemDescription = properties.getDescription();
// cursor->setIcon(Cursor::Icon::LINK);
// } else {
// _hoverItemTitle.clear();
// _hoverItemDescription.clear();
// cursor->setIcon(Cursor::Icon::DEFAULT);
// }
// }
//});
//connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
// if (_hoverItemId == entityItemID) {
// _hoverItemId = _noItemId;
// _hoverItemTitle.clear();
// _hoverItemDescription.clear();
// auto cursor = Cursor::Manager::instance().getCursor();
// cursor->setIcon(Cursor::Icon::DEFAULT);
// if (!_tooltipId.isEmpty()) {
// qDebug() << "Closing tooltip " << _tooltipId;
// Tooltip::closeTip(_tooltipId);
// _tooltipId.clear();
// }
// }
//});
}
bool CompositorHelper::isHMD() const {
return _currentDisplayPlugin && _currentDisplayPlugin->isHmd();
}
QPointF CompositorHelper::getMouseEventPosition(QMouseEvent* event) {
if (isHMD()) {
QMutexLocker locker(&_reticleLock);
return QPointF(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
}
return event->localPos();
}
bool CompositorHelper::shouldCaptureMouse() const {
// if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu
return _allowMouseCapture && isHMD() && QApplication::activeWindow() && !ui::Menu::isSomeSubmenuShown();
}
void CompositorHelper::setAllowMouseCapture(bool capture) {
if (capture != _allowMouseCapture) {
_allowMouseCapture = capture;
emit allowMouseCaptureChanged();
}
_allowMouseCapture = capture;
}
void CompositorHelper::handleLeaveEvent() {
if (shouldCaptureMouse()) {
//QWidget* mainWidget = (QWidget*)qApp->getWindow();
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
static QWidget* mainWidget = nullptr;
if (mainWidget == nullptr) {
mainWidget = renderingWidget->parentWidget();
}
QRect mainWidgetFrame;
{
mainWidgetFrame = renderingWidget->geometry();
auto topLeft = mainWidgetFrame.topLeft();
auto topLeftScreen = renderingWidget->mapToGlobal(topLeft);
mainWidgetFrame.moveTopLeft(topLeftScreen);
}
QRect uncoveredRect = mainWidgetFrame;
foreach(QWidget* widget, QApplication::topLevelWidgets()) {
if (widget->isWindow() && widget->isVisible() && widget != mainWidget) {
QRect widgetFrame = widget->frameGeometry();
if (widgetFrame.intersects(uncoveredRect)) {
QRect intersection = uncoveredRect & widgetFrame;
if (intersection.top() > uncoveredRect.top()) {
uncoveredRect.setBottom(intersection.top() - 1);
} else if (intersection.bottom() < uncoveredRect.bottom()) {
uncoveredRect.setTop(intersection.bottom() + 1);
}
if (intersection.left() > uncoveredRect.left()) {
uncoveredRect.setRight(intersection.left() - 1);
} else if (intersection.right() < uncoveredRect.right()) {
uncoveredRect.setLeft(intersection.right() + 1);
}
}
}
}
_ignoreMouseMove = true;
auto sendToPos = uncoveredRect.center();
QCursor::setPos(sendToPos);
_lastKnownRealMouse = sendToPos;
}
}
bool CompositorHelper::handleRealMouseMoveEvent(bool sendFakeEvent) {
// If the mouse move came from a capture mouse related move, we completely ignore it.
if (_ignoreMouseMove) {
_ignoreMouseMove = false;
return true; // swallow the event
}
// If we're in HMD mode
if (shouldCaptureMouse()) {
QMutexLocker locker(&_reticleLock);
auto newPosition = QCursor::pos();
auto changeInRealMouse = newPosition - _lastKnownRealMouse;
auto newReticlePosition = _reticlePositionInHMD + toGlm(changeInRealMouse);
setReticlePosition(newReticlePosition, sendFakeEvent);
_ignoreMouseMove = true;
QCursor::setPos(QPoint(_lastKnownRealMouse.x(), _lastKnownRealMouse.y())); // move cursor back to where it was
return true; // swallow the event
} else {
_lastKnownRealMouse = QCursor::pos();
}
return false; // let the caller know to process the event
}
glm::vec2 CompositorHelper::getReticlePosition() const {
if (isHMD()) {
QMutexLocker locker(&_reticleLock);
return _reticlePositionInHMD;
}
return toGlm(QCursor::pos());
}
bool CompositorHelper::getReticleOverDesktop() const {
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
// but... if we're outside of the overlay area, we also want to call ourselves
// as being over the desktop.
if (isHMD()) {
QMutexLocker locker(&_reticleLock);
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
static const glm::vec2 minOverlayPosition;
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
return true;
}
}
return _isOverDesktop;
}
glm::vec2 CompositorHelper::getReticleMaximumPosition() const {
glm::vec2 result;
if (isHMD()) {
result = VIRTUAL_SCREEN_SIZE;
} else {
QRect rec = QApplication::desktop()->screenGeometry();
result = glm::vec2(rec.right(), rec.bottom());
}
return result;
}
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
if (isHMD()) {
QMutexLocker locker(&_reticleLock);
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
// FIXME don't allow negative mouseExtra
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
glm::vec2 minMouse = vec2(0) - mouseExtra;
glm::vec2 maxMouse = maxOverlayPosition + mouseExtra;
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
if (sendFakeEvent) {
// in HMD mode we need to fake our mouse moves...
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
auto button = Qt::NoButton;
auto buttons = QApplication::mouseButtons();
auto modifiers = QApplication::keyboardModifiers();
if (qApp->thread() == QThread::currentThread()) {
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
qApp->sendEvent(qApp, &event);
} else {
qApp->postEvent(qApp, new QMouseEvent(QEvent::MouseMove, globalPos, button, buttons, modifiers));
}
}
} else {
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPos = toGlm(QCursor::pos());
auto distance = glm::distance(oldPos, position);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" <<
distance << " oldPos:" << oldPos.x << "," << oldPos.y << " newPos:" << position.x << "," << position.y;
}
QCursor::setPos(position.x, position.y);
}
}
void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& origin, glm::vec3& direction) const {
auto surfacePointAt = sphereSurfaceFromOverlay(cursorPos); // in world space
origin = vec3(_currentCamera[3]);
direction = glm::normalize(surfacePointAt - origin);
}
glm::mat4 CompositorHelper::getUiTransform() const {
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose(_currentFrame));
}
//Finds the collision point of a world space ray
bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const {
auto UITransform = getUiTransform();
auto relativePosition4 = glm::inverse(UITransform) * vec4(position, 1);
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
float instersectionDistance;
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
result = position + glm::normalize(direction) * instersectionDistance;
return true;
}
return false;
}
glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const {
glm::vec2 result = sphericalPos;
result.x *= -1.0f;
result /= _textureFov;
result.x /= _textureAspectRatio;
result += 0.5f;
result *= _currentDisplayPlugin->getRecommendedUiSize();
return result;
}
glm::vec2 CompositorHelper::overlayToSpherical(const glm::vec2& overlayPos) const {
glm::vec2 result = overlayPos;
result /= _currentDisplayPlugin->getRecommendedUiSize();
result -= 0.5f;
result *= _textureFov;
result.x *= _textureAspectRatio;
result.x *= -1.0f;
return result;
}
glm::vec2 CompositorHelper::overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const {
auto UITransform = getUiTransform();
auto relativePosition4 = glm::inverse(UITransform) * vec4(sphereSurfacePoint, 1);
auto direction = vec3(relativePosition4) / relativePosition4.w;
// FIXME use a GLMHelper cartesianToSpherical after fixing the rotation signs.
glm::vec2 polar = glm::vec2(glm::atan(direction.x, -direction.z), glm::asin(direction.y)) * -1.0f;
auto overlayPos = sphericalToOverlay(polar);
return overlayPos;
}
glm::vec3 CompositorHelper::sphereSurfaceFromOverlay(const glm::vec2& overlay) const {
auto spherical = overlayToSpherical(overlay);
// FIXME use a GLMHelper sphericalToCartesian after fixing the rotation signs.
auto sphereSurfacePoint = getPoint(spherical.x, spherical.y);
auto UITransform = getUiTransform();
auto position4 = UITransform * vec4(sphereSurfacePoint, 1);
return vec3(position4) / position4.w;
}
void CompositorHelper::updateTooltips() {
//if (_hoverItemId != _noItemId) {
// quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs;
// if (_hoverItemEnterUsecs != UINT64_MAX && !_hoverItemTitle.isEmpty() && hoverDuration > TOOLTIP_DELAY) {
// // TODO Enable and position the tooltip
// _hoverItemEnterUsecs = UINT64_MAX;
// _tooltipId = Tooltip::showTip(_hoverItemTitle, _hoverItemDescription);
// }
//}
}
static const float FADE_DURATION = 500.0f;
void CompositorHelper::fadeIn() {
_fadeInAlpha = true;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(1.0f);
_alphaPropertyAnimation->start();
}
void CompositorHelper::fadeOut() {
_fadeInAlpha = false;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(0.0f);
_alphaPropertyAnimation->start();
}
void CompositorHelper::toggle() {
if (_fadeInAlpha) {
fadeOut();
} else {
fadeIn();
}
}
glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::mat4& headPose) const {
glm::mat4 result;
if (isHMD()) {
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
auto reticlePosition = getReticlePosition();
if (getReticleDepth() != 1.0f) {
auto spherical = overlayToSpherical(reticlePosition);
auto sphereSurfacePoint = getPoint(spherical.x, spherical.y);
auto origin = vec3(headPose[3]);
auto direction = glm::normalize(sphereSurfacePoint - origin);
auto apparentPosition = (direction * getReticleDepth());
// same code as used to render for apparent location
auto relativePosition4 = glm::inverse(eyePose) * vec4(apparentPosition, 1);
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto relativeDistance = glm::length(relativePosition);
// look at borrowed from overlays
float elevation = -asinf(relativePosition.y / glm::length(relativePosition));
float azimuth = atan2f(relativePosition.x, relativePosition.z);
glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, -PI, 0)); // this extra *quat(vec3(0,-PI,0)) was required to get the quad to flip this seems like we could optimize
Transform transform;
transform.setTranslation(relativePosition);
transform.setScale(reticleScale);
transform.postScale(relativeDistance);
transform.setRotation(faceCamera);
transform.getMatrix(result);
} else {
glm::mat4 overlayXfm;
_modelTransform.getMatrix(overlayXfm);
glm::vec2 projection = overlayToSpherical(reticlePosition);
mat4 pointerXfm = glm::inverse(eyePose) * glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
mat4 reticleXfm = overlayXfm * pointerXfm;
result = glm::scale(reticleXfm, reticleScale);
}
} else {
static const float CURSOR_PIXEL_SIZE = 32.0f;
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
const auto canvasSize = vec2(toGlm(renderingWidget->size()));;
vec2 mousePosition = toGlm(renderingWidget->mapFromGlobal(QCursor::pos()));
mousePosition /= canvasSize;
mousePosition *= 2.0;
mousePosition -= 1.0;
mousePosition.y *= -1.0f;
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
return glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f));
}
return result;
}

View file

@ -6,59 +6,54 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ApplicationCompositor_h
#define hifi_ApplicationCompositor_h
#ifndef hifi_display_plugins_Compositor_h
#define hifi_display_plugins_Compositor_h
#include <atomic>
#include <cstdint>
#include <QCursor>
#include <QMouseEvent>
#include <QObject>
#include <QPropertyAnimation>
#include <QtCore/QObject>
#include <QtCore/QMutex>
#include <QtCore/QPropertyAnimation>
#include <QtGui/QCursor>
#include <QtGui/QMouseEvent>
#include <EntityItemID.h>
#include <GeometryCache.h>
#include <GLMHelpers.h>
#include <gpu/Batch.h>
#include <gpu/Texture.h>
#include <Transform.h>
#include <DependencyManager.h>
#include "DisplayPlugin.h"
class Camera;
class PalmData;
class RenderArgs;
class ReticleInterface;
const float DEFAULT_RETICLE_DEPTH = 1.0f; // FIXME - probably should be based on UI radius
const float MAGNIFY_WIDTH = 220.0f;
const float MAGNIFY_HEIGHT = 100.0f;
const float MAGNIFY_MULT = 2.0f;
const int VIRTUAL_SCREEN_SIZE_X = 3960; // ~10% more pixel density than old version, 72dx240d FOV
const int VIRTUAL_SCREEN_SIZE_Y = 1188; // ~10% more pixel density than old version, 72dx240d FOV
const float DEFAULT_HMD_UI_HORZ_ANGULAR_SIZE = 240.0f;
const float DEFAULT_HMD_UI_VERT_ANGULAR_SIZE = DEFAULT_HMD_UI_HORZ_ANGULAR_SIZE * (float)VIRTUAL_SCREEN_SIZE_Y / (float)VIRTUAL_SCREEN_SIZE_X;
// Handles the drawing of the overlays to the screen
// TODO, move divide up the rendering, displaying and input handling
// facilities of this class
class ApplicationCompositor : public QObject {
class CompositorHelper : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha)
Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop)
public:
ApplicationCompositor();
~ApplicationCompositor();
static const uvec2 VIRTUAL_SCREEN_SIZE;
static const float VIRTUAL_UI_ASPECT_RATIO;
static const vec2 VIRTUAL_UI_TARGET_FOV;
static const vec2 MOUSE_EXTENTS_ANGULAR_SIZE;
static const vec2 MOUSE_EXTENTS_PIXELS;
void displayOverlayTexture(RenderArgs* renderArgs);
void displayOverlayTextureHmd(RenderArgs* renderArgs, int eye);
CompositorHelper();
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
float getHmdUIAngularSize() const { return _hmdUIAngularSize; }
void setHmdUIAngularSize(float hmdUIAngularSize) { _hmdUIAngularSize = hmdUIAngularSize; }
bool isHMD() const;
// Converter from one frame of reference to another.
// Frame of reference:
@ -66,8 +61,7 @@ public:
// Overlay: Position on the overlay (x,y)
glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
void computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const;
uint32_t getOverlayTexture() const;
void computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& origin, glm::vec3& direction) const;
glm::vec2 overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const;
glm::vec3 sphereSurfaceFromOverlay(const glm::vec2& overlay) const;
@ -93,10 +87,12 @@ public:
void resetReticleDepth() { _reticleDepth = DEFAULT_RETICLE_DEPTH; }
glm::vec2 getReticlePosition() const;
void setReticlePosition(glm::vec2 position, bool sendFakeEvent = true);
void setReticlePosition(const glm::vec2& position, bool sendFakeEvent = true);
glm::vec2 getReticleMaximumPosition() const;
glm::mat4 getReticleTransform(const glm::mat4& eyePose = glm::mat4(), const glm::mat4& headPose = glm::mat4()) const;
ReticleInterface* getReticleInterface() { return _reticleInterface; }
/// return value - true means the caller should not process the event further
@ -113,34 +109,38 @@ public:
bool getReticleOverDesktop() const;
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
private:
bool _isOverDesktop { true };
void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; }
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; }
void displayOverlayTextureStereo(RenderArgs* renderArgs, float aspectRatio, float fov);
void bindCursorTexture(gpu::Batch& batch, uint8_t cursorId = 0);
void buildHemiVertices(const float fov, const float aspectRatio, const int slices, const int stacks);
void drawSphereSection(gpu::Batch& batch);
signals:
void allowMouseCaptureChanged();
private:
glm::mat4 getUiTransform() const;
void updateTooltips();
// Support for hovering and tooltips
static EntityItemID _noItemId;
EntityItemID _hoverItemId { _noItemId };
QString _hoverItemTitle;
QString _hoverItemDescription;
quint64 _hoverItemEnterUsecs { 0 };
DisplayPluginPointer _currentDisplayPlugin;
glm::mat4 _currentCamera;
uint32_t _currentFrame { 0 };
float _hmdUIAngularSize { DEFAULT_HMD_UI_VERT_ANGULAR_SIZE };
float _textureFov { glm::radians(DEFAULT_HMD_UI_VERT_ANGULAR_SIZE) };
float _textureAspectRatio { 1.0f };
int _hemiVerticesID { GeometryCache::UNKNOWN_ID };
//// Support for hovering and tooltips
//static EntityItemID _noItemId;
//EntityItemID _hoverItemId { _noItemId };
float _alpha { 0.0f }; // hidden by default
//QString _hoverItemTitle;
//QString _hoverItemDescription;
//quint64 _hoverItemEnterUsecs { 0 };
bool _isOverDesktop { true };
float _hmdUIAngularSize { glm::degrees(VIRTUAL_UI_TARGET_FOV.y) };
float _textureFov { VIRTUAL_UI_TARGET_FOV.y };
float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO };
float _alpha { 1.0f };
float _prevAlpha { 1.0f };
float _fadeInAlpha { true };
float _oculusUIRadius { 1.0f };
QMap<uint16_t, gpu::TexturePointer> _cursors;
int _reticleQuad;
int _previousBorderWidth { -1 };
@ -182,7 +182,7 @@ class ReticleInterface : public QObject {
Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay)
public:
ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {}
ReticleInterface(CompositorHelper* outer) : QObject(outer), _compositor(outer) {}
Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); }
@ -203,9 +203,7 @@ public:
Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); }
private:
ApplicationCompositor* _compositor;
CompositorHelper* _compositor;
};
#endif // hifi_ApplicationCompositor_h
#endif // hifi_CompositorHelper_h

View file

@ -22,11 +22,11 @@ bool NullDisplayPlugin::hasFocus() const {
return false;
}
void NullDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) {
void NullDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) {
_container->releaseSceneTexture(sceneTexture);
}
void NullDisplayPlugin::submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) {
void NullDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overlayTexture) {
_container->releaseOverlayTexture(overlayTexture);
}

View file

@ -20,8 +20,8 @@ public:
virtual glm::uvec2 getRecommendedRenderSize() const override;
virtual bool hasFocus() const override;
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;
virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override;
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override;
virtual void submitOverlayTexture(const gpu::TexturePointer& overlayTexture) override;
virtual QImage getScreenshot() const override;
private:
static const QString NAME;

View file

@ -26,6 +26,9 @@
#include <gl/Config.h>
#include <gl/GLEscrow.h>
#include <GLMHelpers.h>
#include <gpu/GLBackend.h>
#include <CursorManager.h>
#include "CompositorHelper.h"
#if THREADED_PRESENT
@ -184,13 +187,16 @@ private:
#endif
OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
_sceneTextureEscrow.setRecycler([this](GLuint texture){
_sceneTextureEscrow.setRecycler([this](const gpu::TexturePointer& texture){
cleanupForSceneTexture(texture);
_container->releaseSceneTexture(texture);
});
_overlayTextureEscrow.setRecycler([this](const gpu::TexturePointer& texture) {
_container->releaseOverlayTexture(texture);
});
}
void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) {
void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) {
Lock lock(_mutex);
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
_sceneTextureToFrameIndexMap.remove(sceneTexture);
@ -198,15 +204,26 @@ void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) {
void OpenGLDisplayPlugin::activate() {
_vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported();
if (!_cursorsData.size()) {
auto& cursorManager = Cursor::Manager::instance();
for (const auto iconId : cursorManager.registeredIcons()) {
auto& cursorData = _cursorsData[iconId];
auto iconPath = cursorManager.getIconImage(iconId);
auto image = QImage(iconPath);
image = image.mirrored();
image = image.convertToFormat(QImage::Format_RGBA8888);
cursorData.image = image;
cursorData.size = toGlm(image.size());
cursorData.hotSpot = vec2(0.5f);
}
}
_vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported();
#if THREADED_PRESENT
// Start the present thread if necessary
auto presentThread = DependencyManager::get<PresentThread>();
if (!presentThread) {
auto widget = _container->getPrimaryWidget();
DependencyManager::set<PresentThread>();
presentThread = DependencyManager::get<PresentThread>();
presentThread->setObjectName("Presentation Thread");
@ -242,6 +259,7 @@ void OpenGLDisplayPlugin::deactivate() {
DisplayPlugin::deactivate();
}
void OpenGLDisplayPlugin::customizeContext() {
#if THREADED_PRESENT
_uncustomized = false;
@ -250,6 +268,20 @@ void OpenGLDisplayPlugin::customizeContext() {
#endif
enableVsync();
for (auto& cursorValue : _cursorsData) {
auto& cursorData = cursorValue.second;
if (!cursorData.texture) {
const auto& image = cursorData.image;
glGenTextures(1, &cursorData.texture);
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
using namespace oglplus;
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
Context::Disable(Capability::Blend);
@ -257,10 +289,26 @@ void OpenGLDisplayPlugin::customizeContext() {
Context::Disable(Capability::CullFace);
_program = loadDefaultShader();
auto uniforms = _program->ActiveUniforms();
while (!uniforms.Empty()) {
auto uniform = uniforms.Front();
if (uniform.Name() == "Projection") {
_projectionUniform = uniform.Index();
} else if (uniform.Name() == "ModelView") {
_modelViewUniform = uniform.Index();
}
uniforms.Next();
}
_plane = loadPlane(_program);
_compositeFramebuffer = std::make_shared<BasicFramebufferWrapper>();
_compositeFramebuffer->Init(getRecommendedRenderSize());
}
void OpenGLDisplayPlugin::uncustomizeContext() {
_compositeFramebuffer.reset();
_program.reset();
_plane.reset();
}
@ -308,7 +356,7 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
return false;
}
void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) {
void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) {
{
Lock lock(_mutex);
_sceneTextureToFrameIndexMap[sceneTexture] = frameIndex;
@ -326,15 +374,27 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t scene
#endif
}
void OpenGLDisplayPlugin::submitOverlayTexture(GLuint overlayTexture, const glm::uvec2& overlaySize) {
void OpenGLDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overlayTexture) {
// Submit it to the presentation thread via escrow
_currentOverlayTexture = overlayTexture;
_overlayTextureEscrow.submit(overlayTexture);
}
void OpenGLDisplayPlugin::updateTextures() {
auto oldSceneTexture = _currentSceneTexture;
_currentSceneTexture = _sceneTextureEscrow.fetchAndRelease(_currentSceneTexture);
if (oldSceneTexture != _currentSceneTexture) {
updateFrameData();
}
_currentOverlayTexture = _overlayTextureEscrow.fetchAndRelease(_currentOverlayTexture);
}
void OpenGLDisplayPlugin::updateFrameData() {
Lock lock(_mutex);
_currentRenderFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
}
void OpenGLDisplayPlugin::updateFramerate() {
uint64_t now = usecTimestampNow();
static uint64_t lastSwapEnd { now };
@ -346,14 +406,85 @@ void OpenGLDisplayPlugin::updateFramerate() {
}
}
void OpenGLDisplayPlugin::compositeOverlay() {
using namespace oglplus;
// Overlay draw
if (isStereo()) {
Uniform<glm::mat4>(*_program, _projectionUniform).Set(mat4());
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(mat4());
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
});
} else {
// Overlay draw
Uniform<glm::mat4>(*_program, _projectionUniform).Set(mat4());
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(mat4());
drawUnitQuad();
}
}
void OpenGLDisplayPlugin::compositePointer() {
using namespace oglplus;
auto compositorHelper = DependencyManager::get<CompositorHelper>();
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
if (isStereo()) {
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
});
} else {
drawUnitQuad();
}
Uniform<glm::mat4>(*_program, _projectionUniform).Set(mat4());
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(mat4());
}
void OpenGLDisplayPlugin::compositeLayers() {
using namespace oglplus;
auto targetRenderSize = getRecommendedRenderSize();
if (!_compositeFramebuffer || _compositeFramebuffer->size != targetRenderSize) {
_compositeFramebuffer = std::make_shared<BasicFramebufferWrapper>();
_compositeFramebuffer->Init(targetRenderSize);
}
_compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] {
Context::Viewport(targetRenderSize.x, targetRenderSize.y);
Context::Clear().DepthBuffer();
glBindTexture(GL_TEXTURE_2D, getSceneTextureId());
_program->Bind();
Uniform<glm::mat4>(*_program, _projectionUniform).Set(mat4());
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(mat4());
drawUnitQuad();
auto overlayTextureId = getOverlayTextureId();
if (overlayTextureId) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
compositeOverlay();
auto compositorHelper = DependencyManager::get<CompositorHelper>();
if (compositorHelper->getReticleVisible()) {
auto& cursorManager = Cursor::Manager::instance();
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
compositePointer();
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_BLEND);
}
});
}
void OpenGLDisplayPlugin::internalPresent() {
using namespace oglplus;
uvec2 size = getSurfacePixels();
Context::Viewport(size.x, size.y);
Context::Clear().DepthBuffer();
glBindTexture(GL_TEXTURE_2D, _currentSceneTexture);
drawUnitQuad();
const uvec2& srcSize = _compositeFramebuffer->size;
uvec2 dstSize = getSurfacePixels();
_compositeFramebuffer->Bound(FramebufferTarget::Read, [&] {
Context::BlitFramebuffer(
0, 0, srcSize.x, srcSize.y,
0, 0, dstSize.x, dstSize.y,
BufferSelectBit::ColorBuffer, BlitFilter::Nearest);
});
swapBuffers();
}
@ -361,6 +492,9 @@ void OpenGLDisplayPlugin::present() {
incrementPresentCount();
updateTextures();
if (_currentSceneTexture) {
// Write all layers to a local framebuffer
compositeLayers();
// Take the composite framebuffer and send it to the output device
internalPresent();
updateFramerate();
}
@ -379,6 +513,7 @@ float OpenGLDisplayPlugin::presentRate() {
void OpenGLDisplayPlugin::drawUnitQuad() {
_program->Bind();
_plane->Use();
_plane->Draw();
}
@ -436,3 +571,28 @@ void OpenGLDisplayPlugin::enableDeactivate() {
_deactivateWait.notify_one();
}
#endif
uint32_t OpenGLDisplayPlugin::getSceneTextureId() const {
if (!_currentSceneTexture) {
return 0;
}
return gpu::GLBackend::getTextureID(_currentSceneTexture, false);
}
uint32_t OpenGLDisplayPlugin::getOverlayTextureId() const {
if (!_currentOverlayTexture) {
return 0;
}
return gpu::GLBackend::getTextureID(_currentOverlayTexture, false);
}
void OpenGLDisplayPlugin::eyeViewport(Eye eye) const {
using namespace oglplus;
uvec2 vpSize = _compositeFramebuffer->size;
vpSize.x /= 2;
uvec2 vpPos;
if (eye == Eye::Right) {
vpPos.x = vpSize.x;
}
Context::Viewport(vpPos.x, vpPos.y, vpSize.x, vpSize.y);
}

View file

@ -12,6 +12,7 @@
#include <condition_variable>
#include <QtCore/QTimer>
#include <QtGui/QImage>
#include <GLMHelpers.h>
#include <SimpleMovingAverage.h>
@ -25,6 +26,7 @@ protected:
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
using Condition = std::condition_variable;
using TextureEscrow = GLEscrow<gpu::TexturePointer>;
public:
OpenGLDisplayPlugin();
virtual void activate() override;
@ -32,8 +34,8 @@ public:
virtual void stop() override;
virtual bool eventFilter(QObject* receiver, QEvent* event) override;
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;
virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override;
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override;
virtual void submitOverlayTexture(const gpu::TexturePointer& overlayTexture) override;
virtual float presentRate() override;
virtual glm::uvec2 getRecommendedRenderSize() const override {
@ -50,7 +52,13 @@ protected:
#if THREADED_PRESENT
friend class PresentThread;
#endif
uint32_t getSceneTextureId() const;
uint32_t getOverlayTextureId() const;
void compositeLayers();
virtual void compositeOverlay();
virtual void compositePointer();
virtual glm::uvec2 getSurfaceSize() const = 0;
virtual glm::uvec2 getSurfacePixels() const = 0;
@ -61,32 +69,49 @@ protected:
// These functions must only be called on the presentation thread
virtual void customizeContext();
virtual void uncustomizeContext();
virtual void cleanupForSceneTexture(uint32_t sceneTexture);
void withMainThreadContext(std::function<void()> f) const;
virtual void cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture);
// Plugin specific functionality to send the composed scene to the output window or device
virtual void internalPresent();
void withMainThreadContext(std::function<void()> f) const;
void present();
void updateTextures();
void updateFramerate();
void drawUnitQuad();
void swapBuffers();
// Plugin specific functionality to composite the scene and overlay and present the result
virtual void internalPresent();
void eyeViewport(Eye eye) const;
virtual void updateFrameData();
ProgramPtr _program;
int32_t _modelViewUniform { -1 };
int32_t _projectionUniform { -1 };
ShapeWrapperPtr _plane;
mutable Mutex _mutex;
SimpleMovingAverage _usecsPerFrame { 10 };
QMap<uint32_t, uint32_t> _sceneTextureToFrameIndexMap;
QMap<gpu::TexturePointer, uint32_t> _sceneTextureToFrameIndexMap;
uint32_t _currentRenderFrameIndex { 0 };
GLuint _currentSceneTexture { 0 };
GLuint _currentOverlayTexture { 0 };
gpu::TexturePointer _currentSceneTexture;
gpu::TexturePointer _currentOverlayTexture;
GLTextureEscrow _sceneTextureEscrow;
TextureEscrow _sceneTextureEscrow;
TextureEscrow _overlayTextureEscrow;
bool _vsyncSupported { false };
struct CursorData {
QImage image;
vec2 hotSpot;
uvec2 size;
uint32_t texture { 0 };
};
std::map<uint16_t, CursorData> _cursorsData;
BasicFramebufferWrapperPtr _compositeFramebuffer;
private:
#if THREADED_PRESENT
void enableDeactivate();

View file

@ -11,16 +11,25 @@
#include <glm/gtc/matrix_transform.hpp>
#include <QtCore/QLoggingCategory>
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <GLMHelpers.h>
#include <plugins/PluginContainer.h>
#include <gpu/GLBackend.h>
#include <CursorManager.h>
#include "../Logging.h"
#include "../CompositorHelper.h"
static const QString MONO_PREVIEW = "Mono Preview";
static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate";
static const bool DEFAULT_MONO_VIEW = true;
glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const {
return CompositorHelper::VIRTUAL_SCREEN_SIZE;
}
void HmdDisplayPlugin::activate() {
_monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW);
@ -30,21 +39,60 @@ void HmdDisplayPlugin::activate() {
_container->setBoolSetting("monoPreview", _monoPreview);
}, true, _monoPreview);
_container->removeMenu(FRAMERATE);
WindowOpenGLDisplayPlugin::activate();
Parent::activate();
}
void HmdDisplayPlugin::deactivate() {
WindowOpenGLDisplayPlugin::deactivate();
Parent::deactivate();
}
void HmdDisplayPlugin::customizeContext() {
WindowOpenGLDisplayPlugin::customizeContext();
Parent::customizeContext();
// Only enable mirroring if we know vsync is disabled
enableVsync(false);
_enablePreview = !isVsyncEnabled();
_sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO);
}
void HmdDisplayPlugin::uncustomizeContext() {
_sphereSection.reset();
_compositeFramebuffer.reset();
Parent::uncustomizeContext();
}
void HmdDisplayPlugin::compositeOverlay() {
using namespace oglplus;
_sphereSection->Use();
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto modelView = glm::inverse(_currentRenderEyePoses[eye]); // *glm::translate(mat4(), vec3(0, 0, -1));
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(modelView);
Uniform<glm::mat4>(*_program, _projectionUniform).Set(_eyeProjections[eye]);
_sphereSection->Draw();
});
}
void HmdDisplayPlugin::compositePointer() {
//Mouse Pointer
auto compositorHelper = DependencyManager::get<CompositorHelper>();
_plane->Use();
// Reconstruct the headpose from the eye poses
auto currentHeadPose = _currentRenderEyePoses[Left];
currentHeadPose[3] = vec4((vec3(_currentRenderEyePoses[Left][3]) + vec3(_currentRenderEyePoses[Right][3])) / 2.0f, 1.0f);
for_each_eye([&](Eye eye) {
using namespace oglplus;
eyeViewport(eye);
auto reticleTransform = compositorHelper->getReticleTransform(_currentRenderEyePoses[eye], currentHeadPose);
Uniform<glm::mat4>(*_program, _modelViewUniform).Set(reticleTransform);
Uniform<glm::mat4>(*_program, _projectionUniform).Set(_eyeProjections[eye]);
_plane->Draw();
});
}
void HmdDisplayPlugin::internalPresent() {
// Composite together the scene, overlay and mouse cursor
hmdPresent();
// screen preview mirroring
if (_enablePreview) {
auto windowSize = toGlm(_window->size());
@ -69,19 +117,30 @@ void HmdDisplayPlugin::internalPresent() {
targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2;
}
glClear(GL_COLOR_BUFFER_BIT);
glViewport(
targetViewportPosition.x, targetViewportPosition.y,
targetViewportSize.x * (_monoPreview ? 2 : 1), targetViewportSize.y);
glEnable(GL_SCISSOR_TEST);
glScissor(
targetViewportPosition.x, targetViewportPosition.y,
targetViewportSize.x, targetViewportSize.y);
glBindTexture(GL_TEXTURE_2D, _currentSceneTexture);
GLenum err = glGetError();
Q_ASSERT(0 == err);
drawUnitQuad();
glDisable(GL_SCISSOR_TEST);
using namespace oglplus;
Context::Clear().ColorBuffer();
auto sourceSize = _compositeFramebuffer->size;
if (_monoPreview) {
sourceSize.x /= 2;
}
_compositeFramebuffer->Bound(Framebuffer::Target::Read, [&] {
Context::BlitFramebuffer(
0, 0, sourceSize.x, sourceSize.y,
targetViewportPosition.x, targetViewportPosition.y,
targetViewportPosition.x + targetViewportSize.x, targetViewportPosition.y + targetViewportSize.y,
BufferSelectBit::ColorBuffer, BlitFilter::Nearest);
});
swapBuffers();
}
}
void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {
Lock lock(_mutex);
_renderEyePoses[frameIndex][eye] = pose;
}
void HmdDisplayPlugin::updateFrameData() {
Parent::updateFrameData();
Lock lock(_mutex);
_currentRenderEyePoses = _renderEyePoses[_currentRenderFrameIndex];
}

View file

@ -12,34 +12,41 @@
#include "../WindowOpenGLDisplayPlugin.h"
class HmdDisplayPlugin : public WindowOpenGLDisplayPlugin {
using Parent = WindowOpenGLDisplayPlugin;
public:
bool isHmd() const override final { return true; }
float getIPD() const override final { return _ipd; }
glm::mat4 getEyeToHeadTransform(Eye eye) const override final { return _eyeOffsets[eye]; }
glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override final { return _eyeProjections[eye]; }
glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override final { return _cullingProjection; }
glm::uvec2 getRecommendedUiSize() const override final {
// FIXME - would be good to have these values sync with ApplicationCompositor in a better way.
const int VIRTUAL_SCREEN_SIZE_X = 3960; // ~10% more pixel density than old version, 72dx240d FOV
const int VIRTUAL_SCREEN_SIZE_Y = 1188; // ~10% more pixel density than old version, 72dx240d FOV
auto result = uvec2(VIRTUAL_SCREEN_SIZE_X, VIRTUAL_SCREEN_SIZE_Y);
return result;
}
glm::uvec2 getRecommendedUiSize() const override final;
glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; }
void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final;
void activate() override;
void deactivate() override;
protected:
virtual void hmdPresent() = 0;
void compositeOverlay() override;
void compositePointer() override;
void internalPresent() override;
void customizeContext() override;
void uncustomizeContext() override;
void updateFrameData() override;
std::array<glm::mat4, 2> _eyeOffsets;
std::array<glm::mat4, 2> _eyeProjections;
glm::mat4 _cullingProjection;
glm::uvec2 _renderTargetSize;
float _ipd { 0.064f };
using EyePoses = std::array<glm::mat4, 2>;
QMap<uint32_t, EyePoses> _renderEyePoses;
EyePoses _currentRenderEyePoses;
private:
bool _enablePreview { false };
bool _monoPreview { true };
ShapeWrapperPtr _sphereSection;
};

View file

@ -46,13 +46,15 @@ void main() {
const QString InterleavedStereoDisplayPlugin::NAME("3D TV - Interleaved");
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);
compileProgram(_interleavedProgram, INTERLEAVED_TEXTURED_VS, INTERLEAVED_TEXTURED_FS);
}
void InterleavedStereoDisplayPlugin::uncustomizeContext() {
_interleavedProgram.reset();
StereoDisplayPlugin::uncustomizeContext();
}
glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const {
@ -64,8 +66,14 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const {
void InterleavedStereoDisplayPlugin::internalPresent() {
using namespace oglplus;
_program->Bind();
auto sceneSize = getRecommendedRenderSize();
Uniform<ivec2>(*_program, "textureSize").SetValue(sceneSize);
WindowOpenGLDisplayPlugin::internalPresent();
_interleavedProgram->Bind();
Uniform<ivec2>(*_interleavedProgram, "textureSize").SetValue(sceneSize);
auto surfaceSize = getSurfacePixels();
Context::Viewport(0, 0, surfaceSize.x, surfaceSize.y);
glBindTexture(GL_TEXTURE_2D, GetName(_compositeFramebuffer->color));
_plane->Use();
_plane->Draw();
swapBuffers();
}

View file

@ -12,17 +12,17 @@
class InterleavedStereoDisplayPlugin : public StereoDisplayPlugin {
Q_OBJECT
public:
InterleavedStereoDisplayPlugin();
virtual const QString& getName() const override { return NAME; }
virtual grouping getGrouping() const override { return ADVANCED; }
const QString& getName() const override { return NAME; }
grouping getGrouping() const override { return ADVANCED; }
glm::uvec2 getRecommendedRenderSize() const override;
protected:
// initialize OpenGL context settings needed by the plugin
virtual void customizeContext() override;
virtual glm::uvec2 getRecommendedRenderSize() const override;
void customizeContext() override;
void uncustomizeContext() override;
void internalPresent() override;
private:
ProgramPtr _interleavedProgram;
static const QString NAME;
};

View file

@ -8,6 +8,10 @@
#include "SideBySideStereoDisplayPlugin.h"
#include <GLMHelpers.h>
#include <CursorManager.h>
#include <plugins/PluginContainer.h>
#include <gl/GLWidget.h>
#include "../CompositorHelper.h"
const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo");
@ -19,5 +23,3 @@ glm::uvec2 SideBySideStereoDisplayPlugin::getRecommendedRenderSize() const {
result.x *= 2;
return result;
}

View file

@ -18,6 +18,7 @@ public:
virtual const QString& getName() const override { return NAME; }
virtual grouping getGrouping() const override { return ADVANCED; }
virtual glm::uvec2 getRecommendedRenderSize() const override;
private:
static const QString NAME;
};

View file

@ -8,19 +8,18 @@
#include "StereoDisplayPlugin.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QAction>
#include <QtGui/QScreen>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <gpu/GLBackend.h>
#include <ViewFrustum.h>
#include <MatrixStack.h>
#include <plugins/PluginContainer.h>
#include <QGuiApplication>
#include <QScreen>
StereoDisplayPlugin::StereoDisplayPlugin() {
}
#include <gl/GLWidget.h>
#include <CursorManager.h>
#include "../CompositorHelper.h"
bool StereoDisplayPlugin::isSupported() const {
// FIXME this should attempt to do a scan for supported 3D output
@ -76,14 +75,16 @@ void StereoDisplayPlugin::activate() {
_container->removeMenu(FRAMERATE);
_container->setFullscreen(qApp->primaryScreen());
_screen = qApp->primaryScreen();
_container->setFullscreen(_screen);
WindowOpenGLDisplayPlugin::activate();
}
void StereoDisplayPlugin::updateScreen() {
for (uint32_t i = 0; i < _screenActions.size(); ++i) {
if (_screenActions[i]->isChecked()) {
_container->setFullscreen(qApp->screens().at(i));
_screen = qApp->screens().at(i);
_container->setFullscreen(_screen);
break;
}
}
@ -100,3 +101,4 @@ void StereoDisplayPlugin::deactivate() {
float StereoDisplayPlugin::getRecommendedAspectRatio() const {
return aspect(WindowOpenGLDisplayPlugin::getRecommendedRenderSize());
}

View file

@ -8,11 +8,12 @@
#pragma once
#include "../WindowOpenGLDisplayPlugin.h"
class QScreen;
class StereoDisplayPlugin : public WindowOpenGLDisplayPlugin {
Q_OBJECT
using Parent = WindowOpenGLDisplayPlugin;
public:
StereoDisplayPlugin();
virtual bool isStereo() const override final { return true; }
virtual bool isSupported() const override final;
@ -33,4 +34,5 @@ public:
protected:
void updateScreen();
float _ipd{ 0.064f };
QScreen* _screen;
};

View file

@ -42,13 +42,19 @@
// in use by the GPU. Fence sync objects are used to moderate the actual release of
// resources in either direction.
template <
typename T,
// Only accept numeric types
typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type
typename T
//,
//// Only accept numeric types
//typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type
>
class GLEscrow {
public:
static const uint64_t MAX_UNSIGNALED_TIME = USECS_PER_SECOND / 2;
const T& invalid() const {
static const T INVALID_RESULT;
return INVALID_RESULT;
}
struct Item {
const T _value;
@ -133,7 +139,7 @@ public:
// or if none is available (which could mean either the submission
// list is empty or that the first item on the list isn't yet signaled
T fetch() {
T result{0};
T result = invalid();
// On the one hand using try_lock() reduces the chance of blocking the consumer thread,
// but if the produce thread is going fast enough, it could effectively
// starve the consumer out of ever actually getting resources.
@ -151,7 +157,7 @@ public:
// or if none is available (which could mean either the submission
// list is empty or that the first item on the list isn't yet signaled
// Also releases any previous texture held by the caller
T fetchAndRelease(T oldValue) {
T fetchAndRelease(const T& oldValue) {
T result = fetch();
if (!result) {
return oldValue;
@ -164,7 +170,7 @@ public:
// If fetch returns a non-zero value, it's the responsibility of the
// client to release it at some point
void release(T t, GLsync readSync = 0) {
void release(const T& t, GLsync readSync = 0) {
if (!readSync) {
// FIXME should the release and submit actually force the creation of a fence?
readSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
@ -240,7 +246,7 @@ private:
// inside the locked sections, so it cannot have any latency
if (item.signaled()) {
// if the sync is signaled, queue it for deletion
_trash.push_front(Item(0, item._sync));
_trash.push_front(Item(invalid(), item._sync));
// And change the stored value to 0 so we don't check it again
item._sync = 0;
return true;
@ -259,6 +265,12 @@ private:
List _trash;
};
template<>
inline const GLuint& GLEscrow<GLuint>::invalid() const {
static const GLuint INVALID_RESULT { 0 };
return INVALID_RESULT;
}
using GLTextureEscrow = GLEscrow<GLuint>;
#endif

View file

@ -485,7 +485,9 @@ void OffscreenQmlSurface::updateQuick() {
if (_render) {
QMutexLocker lock(&(_renderer->_mutex));
_renderer->post(RENDER);
_renderer->_cond.wait(&(_renderer->_mutex));
while (!_renderer->_cond.wait(&(_renderer->_mutex), 100)) {
qApp->processEvents();
}
_render = false;
}

View file

@ -15,14 +15,17 @@ using namespace oglplus::shapes;
static const char * SIMPLE_TEXTURED_VS = R"VS(#version 410 core
#pragma line __LINE__
uniform mat4 Projection = mat4(1);
uniform mat4 ModelView = mat4(1);
in vec3 Position;
in vec2 TexCoord;
out vec2 vTexCoord;
void main() {
gl_Position = vec4(Position, 1);
vTexCoord = TexCoord;
gl_Position = Projection * ModelView * vec4(Position, 1);
vTexCoord = TexCoord ;
}
)VS";

View file

@ -83,7 +83,7 @@ public:
~GLTexture();
};
static GLTexture* syncGPUObject(const Texture& texture);
static GLuint getTextureID(const TexturePointer& texture);
static GLuint getTextureID(const TexturePointer& texture, bool sync = true);
// very specific for now
static void syncSampler(const Sampler& sampler, Texture::Type type, GLTexture* object);

View file

@ -578,11 +578,16 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) {
GLuint GLBackend::getTextureID(const TexturePointer& texture) {
GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) {
if (!texture) {
return 0;
}
GLTexture* object = GLBackend::syncGPUObject(*texture);
GLTexture* object { nullptr };
if (sync) {
object = GLBackend::syncGPUObject(*texture);
} else {
object = Backend::getGPUObject<GLBackend::GLTexture>(*texture);
}
if (object) {
return object->_texture;
} else {

View file

View file

@ -50,6 +50,11 @@ class QWindow;
#define AVERAGE_HUMAN_IPD 0.064f
namespace gpu {
class Texture;
using TexturePointer = std::shared_ptr<Texture>;
}
class DisplayPlugin : public Plugin {
Q_OBJECT
public:
@ -70,12 +75,12 @@ public:
/**
* Sends the scene texture to the display plugin.
*/
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) = 0;
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) = 0;
/**
* Sends the scene texture to the display plugin.
*/
virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) = 0;
virtual void submitOverlayTexture(const gpu::TexturePointer& overlayTexture) = 0;
// Does the rendering surface have current focus?
virtual bool hasFocus() const = 0;

View file

@ -23,6 +23,11 @@ class QWindow;
class DisplayPlugin;
namespace gpu {
class Texture;
using TexturePointer = std::shared_ptr<Texture>;
}
class PluginContainer {
public:
static PluginContainer& getInstance();
@ -39,8 +44,8 @@ public:
virtual void showDisplayPluginsTools() = 0;
virtual void requestReset() = 0;
virtual bool makeRenderingContextCurrent() = 0;
virtual void releaseSceneTexture(uint32_t texture) = 0;
virtual void releaseOverlayTexture(uint32_t texture) = 0;
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) = 0;
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) = 0;
virtual GLWidget* getPrimaryWidget() = 0;
virtual QWindow* getPrimaryWindow() = 0;
virtual QOpenGLContext* getPrimaryContext() = 0;

View file

@ -1,3 +1,3 @@
set(TARGET_NAME script-engine)
setup_hifi_library(Gui Network Script WebSockets Widgets)
link_hifi_libraries(shared networking octree gpu procedural model model-networking recording avatars fbx entities controllers animation audio physics)
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics)

View file

@ -13,20 +13,7 @@
#include <RegisteredMetaTypes.h>
#include "MenuItemProperties.h"
MenuItemProperties::MenuItemProperties() :
menuName(""),
menuItemName(""),
shortcutKey(""),
shortcutKeyEvent(),
shortcutKeySequence(),
position(UNSPECIFIED_POSITION),
beforeItem(""),
afterItem(""),
isCheckable(false),
isChecked(false),
isSeparator(false)
{
};
MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& menuItemName,
const QString& shortcutKey, bool checkable, bool checked, bool separator) :
@ -35,9 +22,6 @@ MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& m
shortcutKey(shortcutKey),
shortcutKeyEvent(),
shortcutKeySequence(shortcutKey),
position(UNSPECIFIED_POSITION),
beforeItem(""),
afterItem(""),
isCheckable(checkable),
isChecked(checked),
isSeparator(separator)
@ -48,12 +32,8 @@ MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& m
const KeyEvent& shortcutKeyEvent, bool checkable, bool checked, bool separator) :
menuName(menuName),
menuItemName(menuItemName),
shortcutKey(""),
shortcutKeyEvent(shortcutKeyEvent),
shortcutKeySequence(shortcutKeyEvent),
position(UNSPECIFIED_POSITION),
beforeItem(""),
afterItem(""),
isCheckable(checkable),
isChecked(checked),
isSeparator(separator)

View file

@ -14,35 +14,35 @@
#include <QtScript/QScriptEngine>
#include <ui/Menu.h>
#include "KeyEvent.h"
const int UNSPECIFIED_POSITION = -1;
class MenuItemProperties {
public:
MenuItemProperties();
MenuItemProperties(const QString& menuName, const QString& menuItemName,
MenuItemProperties() {}
MenuItemProperties(const QString& menuName, const QString& menuItemName,
const QString& shortcutKey = QString(""), bool checkable = false, bool checked = false, bool separator = false);
MenuItemProperties(const QString& menuName, const QString& menuItemName,
MenuItemProperties(const QString& menuName, const QString& menuItemName,
const KeyEvent& shortcutKeyEvent, bool checkable = false, bool checked = false, bool separator = false);
QString menuName;
QString menuItemName;
// Shortcut key items: in order of priority
QString shortcutKey;
KeyEvent shortcutKeyEvent;
QKeySequence shortcutKeySequence; // this is what we actually use, it's set from one of the above
// location related items: in order of priority
int position;
int position { ui::Menu::UNSPECIFIED_POSITION };
QString beforeItem;
QString afterItem;
// other properties
bool isCheckable;
bool isChecked;
bool isSeparator;
bool isCheckable { false };
bool isChecked { false };
bool isSeparator { false };
QString grouping; /// Either: "", "Advanced", or "Developer"
};

View file

@ -44,6 +44,10 @@ namespace Cursor {
return instance;
}
QList<uint16_t> Manager::registeredIcons() const {
return ICONS.keys();
}
uint8_t Manager::getCount() {
return 1;
}

View file

@ -47,6 +47,7 @@ namespace Cursor {
void setScale(float scale);
Instance* getCursor(uint8_t index = 0);
uint16_t registerIcon(const QString& path);
QList<uint16_t> registeredIcons() const;
const QString& getIconImage(uint16_t icon);
private:
float _scale{ 1.0f };

View file

@ -0,0 +1,11 @@
//
// Created by Bradley Austin Davis 2016/03/01
// 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 "Logging.h"
Q_LOGGING_CATEGORY(uiLogging, "hifi.ui")

View file

@ -0,0 +1,16 @@
//
// Created by Bradley Austin Davis 2015/10/11
// 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
//
#ifndef hifi_Controllers_Logging_h
#define hifi_Controllers_Logging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(uiLogging)
#endif

View file

@ -0,0 +1,561 @@
//
// Created by Stephen Birarda on 8/12/13.
// Copyright 2013 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 "Menu.h"
#include <QtCore/QThread>
#include <QtCore/QDebug>
#include <QtWidgets/QShortcut>
#include <SettingHandle.h>
#include "../VrMenu.h"
#include "Logging.h"
using namespace ui;
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
Menu* Menu::getInstance() {
static Menu* instance = globalInstance<Menu>(MENU_PROPERTY_NAME);
return instance;
}
Menu::Menu() {
}
void Menu::toggleAdvancedMenus() {
setGroupingIsVisible("Advanced", !getGroupingIsVisible("Advanced"));
}
void Menu::toggleDeveloperMenus() {
setGroupingIsVisible("Developer", !getGroupingIsVisible("Developer"));
}
void Menu::loadSettings() {
scanMenuBar(&Menu::loadAction);
}
void Menu::saveSettings() {
scanMenuBar(&Menu::saveAction);
}
void Menu::loadAction(Settings& settings, QAction& action) {
if (action.isChecked() != settings.value(action.text(), action.isChecked()).toBool()) {
action.trigger();
}
}
void Menu::saveAction(Settings& settings, QAction& action) {
settings.setValue(action.text(), action.isChecked());
}
void Menu::scanMenuBar(settingsAction modifySetting) {
Settings settings;
foreach (QMenu* menu, findChildren<QMenu*>()) {
scanMenu(*menu, modifySetting, settings);
}
}
void Menu::scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings) {
settings.beginGroup(menu.title());
foreach (QAction* action, menu.actions()) {
if (action->menu()) {
scanMenu(*action->menu(), modifySetting, settings);
} else if (action->isCheckable()) {
modifySetting(settings, *action);
}
}
settings.endGroup();
}
void Menu::addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName,
int menuItemLocation, const QString& grouping) {
QAction* actionBefore = NULL;
QAction* separator;
QAction* separatorText;
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
actionBefore = destinationMenu->actions()[menuItemLocation];
}
if (actionBefore) {
separator = new QAction("",destinationMenu);
destinationMenu->insertAction(actionBefore, separator);
separator->setSeparator(true);
separatorText = new QAction(actionName,destinationMenu);
separatorText->setEnabled(false);
destinationMenu->insertAction(actionBefore, separatorText);
} else {
separator = destinationMenu->addSeparator();
separatorText = destinationMenu->addAction(actionName);
separatorText->setEnabled(false);
}
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << separator;
_groupingActions[grouping] << separatorText;
bool isVisible = getGroupingIsVisible(grouping);
separator->setVisible(isVisible);
separatorText->setVisible(isVisible);
}
}
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut,
const QObject* receiver,
const char* member,
QAction::MenuRole role,
int menuItemLocation,
const QString& grouping) {
QAction* action = NULL;
QAction* actionBefore = NULL;
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
actionBefore = destinationMenu->actions()[menuItemLocation];
}
if (!actionBefore) {
if (receiver && member) {
action = destinationMenu->addAction(actionName, receiver, member, shortcut);
} else {
action = destinationMenu->addAction(actionName);
action->setShortcut(shortcut);
}
} else {
action = new QAction(actionName, destinationMenu);
action->setShortcut(shortcut);
destinationMenu->insertAction(actionBefore, action);
if (receiver && member) {
connect(action, SIGNAL(triggered()), receiver, member);
}
}
action->setMenuRole(role);
_actionHash.insert(actionName, action);
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
return action;
}
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
QAction* action,
const QString& actionName,
const QKeySequence& shortcut,
QAction::MenuRole role,
int menuItemLocation,
const QString& grouping) {
QAction* actionBefore = NULL;
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
actionBefore = destinationMenu->actions()[menuItemLocation];
}
if (!actionName.isEmpty()) {
action->setText(actionName);
}
if (shortcut != 0) {
action->setShortcut(shortcut);
}
if (role != QAction::NoRole) {
action->setMenuRole(role);
}
if (!actionBefore) {
destinationMenu->addAction(action);
} else {
destinationMenu->insertAction(actionBefore, action);
}
_actionHash.insert(action->text(), action);
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
return action;
}
QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut,
const bool checked,
const QObject* receiver,
const char* member,
int menuItemLocation,
const QString& grouping) {
QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member,
QAction::NoRole, menuItemLocation);
action->setCheckable(true);
action->setChecked(checked);
if (isValidGrouping(grouping)) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
return action;
}
void Menu::removeAction(MenuWrapper* menu, const QString& actionName) {
auto action = _actionHash.value(actionName);
menu->removeAction(action);
_actionHash.remove(actionName);
for (auto& grouping : _groupingActions) {
grouping.remove(action);
}
}
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, menuOption),
Q_ARG(bool, isChecked));
return;
}
QAction* menu = _actionHash.value(menuOption);
if (menu) {
menu->setChecked(isChecked);
}
}
bool Menu::isOptionChecked(const QString& menuOption) const {
const QAction* menu = _actionHash.value(menuOption);
if (menu) {
return menu->isChecked();
}
return false;
}
void Menu::triggerOption(const QString& menuOption) {
QAction* action = _actionHash.value(menuOption);
if (action) {
action->trigger();
} else {
qCDebug(uiLogging) << "NULL Action for menuOption '" << menuOption << "'";
}
}
QAction* Menu::getActionForOption(const QString& menuOption) {
return _actionHash.value(menuOption);
}
QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) {
QList<QAction*> menuActions;
if (menu) {
menuActions = menu->actions();
} else {
menuActions = actions();
}
foreach (QAction* menuAction, menuActions) {
QString actionText = menuAction->text();
if (menuName == menuAction->text()) {
return menuAction;
}
}
return NULL;
}
MenuWrapper* Menu::getSubMenuFromName(const QString& menuName, MenuWrapper* menu) {
QAction* action = getActionFromName(menuName, menu);
if (action) {
return MenuWrapper::fromMenu(action->menu());
}
return NULL;
}
MenuWrapper* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) {
QStringList menuTree = menuName.split(">");
MenuWrapper* parent = NULL;
MenuWrapper* menu = NULL;
foreach (QString menuTreePart, menuTree) {
parent = menu;
finalMenuPart = menuTreePart.trimmed();
menu = getSubMenuFromName(finalMenuPart, parent);
if (!menu) {
break;
}
}
return parent;
}
MenuWrapper* Menu::getMenu(const QString& menuName) {
QStringList menuTree = menuName.split(">");
MenuWrapper* parent = NULL;
MenuWrapper* menu = NULL;
int item = 0;
foreach (QString menuTreePart, menuTree) {
menu = getSubMenuFromName(menuTreePart.trimmed(), parent);
if (!menu) {
break;
}
parent = menu;
item++;
}
return menu;
}
QAction* Menu::getMenuAction(const QString& menuName) {
QStringList menuTree = menuName.split(">");
MenuWrapper* parent = NULL;
QAction* action = NULL;
foreach (QString menuTreePart, menuTree) {
action = getActionFromName(menuTreePart.trimmed(), parent);
if (!action) {
break;
}
parent = MenuWrapper::fromMenu(action->menu());
}
return action;
}
int Menu::findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem) {
int position = 0;
foreach(QAction* action, menu->actions()) {
if (action->text() == searchMenuItem) {
return position;
}
position++;
}
return UNSPECIFIED_POSITION; // not found
}
int Menu::positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition) {
QList<QAction*> menuActions = menu->actions();
if (requestedPosition > 1 && requestedPosition < menuActions.size()) {
QAction* beforeRequested = menuActions[requestedPosition - 1];
if (beforeRequested->isSeparator()) {
requestedPosition--;
}
}
return requestedPosition;
}
bool Menu::_isSomeSubmenuShown = false;
MenuWrapper* Menu::addMenu(const QString& menuName, const QString& grouping) {
QStringList menuTree = menuName.split(">");
MenuWrapper* addTo = NULL;
MenuWrapper* menu = NULL;
foreach (QString menuTreePart, menuTree) {
menu = getSubMenuFromName(menuTreePart.trimmed(), addTo);
if (!menu) {
if (!addTo) {
menu = new MenuWrapper(QMenuBar::addMenu(menuTreePart.trimmed()));
} else {
menu = addTo->addMenu(menuTreePart.trimmed());
}
}
addTo = menu;
}
if (isValidGrouping(grouping)) {
auto action = getMenuAction(menuName);
if (action) {
_groupingActions[grouping] << action;
action->setVisible(getGroupingIsVisible(grouping));
}
}
QMenuBar::repaint();
// hook our show/hide for popup menus, so we can keep track of whether or not one
// of our submenus is currently showing.
connect(menu->_realMenu, &QMenu::aboutToShow, []() { _isSomeSubmenuShown = true; });
connect(menu->_realMenu, &QMenu::aboutToHide, []() { _isSomeSubmenuShown = false; });
return menu;
}
void Menu::removeMenu(const QString& menuName) {
QAction* action = getMenuAction(menuName);
// only proceed if the menu actually exists
if (action) {
QString finalMenuPart;
MenuWrapper* parent = getMenuParent(menuName, finalMenuPart);
if (parent) {
parent->removeAction(action);
} else {
QMenuBar::removeAction(action);
}
QMenuBar::repaint();
}
}
bool Menu::menuExists(const QString& menuName) {
QAction* action = getMenuAction(menuName);
// only proceed if the menu actually exists
if (action) {
return true;
}
return false;
}
void Menu::addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping) {
MenuWrapper* menuObj = getMenu(menuName);
if (menuObj) {
addDisabledActionAndSeparator(menuObj, separatorName);
}
}
void Menu::removeSeparator(const QString& menuName, const QString& separatorName) {
MenuWrapper* menu = getMenu(menuName);
bool separatorRemoved = false;
if (menu) {
int textAt = findPositionOfMenuItem(menu, separatorName);
QList<QAction*> menuActions = menu->actions();
QAction* separatorText = menuActions[textAt];
if (textAt > 0 && textAt < menuActions.size()) {
QAction* separatorLine = menuActions[textAt - 1];
if (separatorLine) {
if (separatorLine->isSeparator()) {
menu->removeAction(separatorText);
menu->removeAction(separatorLine);
separatorRemoved = true;
}
}
}
}
if (separatorRemoved) {
QMenuBar::repaint();
}
}
void Menu::removeMenuItem(const QString& menu, const QString& menuitem) {
MenuWrapper* menuObj = getMenu(menu);
if (menuObj) {
removeAction(menuObj, menuitem);
QMenuBar::repaint();
}
}
bool Menu::menuItemExists(const QString& menu, const QString& menuitem) {
QAction* menuItemAction = _actionHash.value(menuitem);
if (menuItemAction) {
return (getMenu(menu) != NULL);
}
return false;
}
bool Menu::getGroupingIsVisible(const QString& grouping) {
if (grouping.isEmpty() || grouping.isNull()) {
return true;
}
if (_groupingVisible.contains(grouping)) {
return _groupingVisible[grouping];
}
return false;
}
void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
// NOTE: Default grouping always visible
if (grouping.isEmpty() || grouping.isNull()) {
return;
}
_groupingVisible[grouping] = isVisible;
for (auto action: _groupingActions[grouping]) {
action->setVisible(isVisible);
}
QMenuBar::repaint();
}
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected, QObject* receiver, const char* slot) {
auto menu = addMenu(groupName);
QActionGroup* actionGroup = new QActionGroup(menu);
actionGroup->setExclusive(true);
for (auto action : actionList) {
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected, receiver, slot);
actionGroup->addAction(item);
}
QMenuBar::repaint();
}
void Menu::removeActionGroup(const QString& groupName) {
removeMenu(groupName);
}
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addMenu(menu);
});
_backMap[menu] = this;
}
QList<QAction*> MenuWrapper::actions() {
return _realMenu->actions();
}
MenuWrapper* MenuWrapper::addMenu(const QString& menuName) {
return new MenuWrapper(_realMenu->addMenu(menuName));
}
void MenuWrapper::setEnabled(bool enabled) {
_realMenu->setEnabled(enabled);
}
QAction* MenuWrapper::addSeparator() {
return _realMenu->addSeparator();
}
void MenuWrapper::addAction(QAction* action) {
_realMenu->addAction(action);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addAction(_realMenu, action);
});
}
QAction* MenuWrapper::addAction(const QString& menuName) {
QAction* action = _realMenu->addAction(menuName);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addAction(_realMenu, action);
});
return action;
}
QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) {
QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addAction(_realMenu, action);
});
return action;
}
void MenuWrapper::removeAction(QAction* action) {
_realMenu->removeAction(action);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->removeAction(action);
});
}
void MenuWrapper::insertAction(QAction* before, QAction* action) {
_realMenu->insertAction(before, action);
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->insertAction(before, action);
});
}
QHash<QMenu*, MenuWrapper*> MenuWrapper::_backMap;

155
libraries/ui/src/ui/Menu.h Normal file
View file

@ -0,0 +1,155 @@
//
// Created by Stephen Birarda on 8/12/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ui_Menu_h
#define hifi_ui_Menu_h
#include <QtCore/QDir>
#include <QtCore/QPointer>
#include <QtCore/QStandardPaths>
#include <QtCore/QHash>
#include <QtGui/QKeySequence>
#include <QtWidgets/QMenuBar>
class Settings;
namespace ui {
class Menu;
}
class MenuWrapper : public QObject {
public:
QList<QAction*> actions();
MenuWrapper* addMenu(const QString& menuName);
void setEnabled(bool enabled = true);
QAction* addSeparator();
void addAction(QAction* action);
QAction* addAction(const QString& menuName);
void insertAction(QAction* before, QAction* menuName);
QAction* addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut = 0);
void removeAction(QAction* action);
QAction* newAction() {
return new QAction(_realMenu);
}
private:
MenuWrapper(QMenu* menu);
static MenuWrapper* fromMenu(QMenu* menu) {
return _backMap[menu];
}
QMenu* const _realMenu;
static QHash<QMenu*, MenuWrapper*> _backMap;
friend class ui::Menu;
};
namespace ui {
class Menu : public QMenuBar {
Q_OBJECT
public:
static const int UNSPECIFIED_POSITION = -1;
Menu();
static Menu* getInstance();
void loadSettings();
void saveSettings();
MenuWrapper* getMenu(const QString& menuName);
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
void triggerOption(const QString& menuOption);
QAction* getActionForOption(const QString& menuOption);
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut = 0,
const QObject* receiver = NULL,
const char* member = NULL,
QAction::MenuRole role = QAction::NoRole,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
QAction* action,
const QString& actionName = QString(),
const QKeySequence& shortcut = 0,
QAction::MenuRole role = QAction::NoRole,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut = 0,
const bool checked = false,
const QObject* receiver = NULL,
const char* member = NULL,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
void removeAction(MenuWrapper* menu, const QString& actionName);
public slots:
MenuWrapper* addMenu(const QString& menuName, const QString& grouping = QString());
void removeMenu(const QString& menuName);
bool menuExists(const QString& menuName);
void addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping = QString());
void removeSeparator(const QString& menuName, const QString& separatorName);
void removeMenuItem(const QString& menuName, const QString& menuitem);
bool menuItemExists(const QString& menuName, const QString& menuitem);
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString(),
QObject* receiver = nullptr, const char* slot = nullptr);
void removeActionGroup(const QString& groupName);
bool isOptionChecked(const QString& menuOption) const;
void setIsOptionChecked(const QString& menuOption, bool isChecked);
bool getGroupingIsVisible(const QString& grouping);
void setGroupingIsVisible(const QString& grouping, bool isVisible); /// NOTE: the "" grouping is always visible
void toggleDeveloperMenus();
void toggleAdvancedMenus();
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }
protected:
typedef void(*settingsAction)(Settings&, QAction&);
static void loadAction(Settings& settings, QAction& action);
static void saveAction(Settings& settings, QAction& action);
void scanMenuBar(settingsAction modifySetting);
void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings);
/// helper method to have separators with labels that are also compatible with OS X
void addDisabledActionAndSeparator(MenuWrapper* destinationMenu,
const QString& actionName,
int menuItemLocation = UNSPECIFIED_POSITION,
const QString& grouping = QString());
QAction* getActionFromName(const QString& menuName, MenuWrapper* menu);
MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart);
QAction* getMenuAction(const QString& menuName);
int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem);
int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition);
QHash<QString, QAction*> _actionHash;
bool isValidGrouping(const QString& grouping) const { return grouping == "Advanced" || grouping == "Developer"; }
QHash<QString, bool> _groupingVisible;
QHash<QString, QSet<QAction*>> _groupingActions;
static bool _isSomeSubmenuShown;
};
} // namespace ui
#endif // hifi_Menu_h

View file

@ -13,7 +13,7 @@ if (WIN32)
set(TARGET_NAME oculus)
setup_hifi_plugin()
link_hifi_libraries(shared gl plugins display-plugins)
link_hifi_libraries(shared gl plugins gpu display-plugins input-plugins)
include_hifi_library_headers(octree)

View file

@ -76,8 +76,6 @@ void OculusBaseDisplayPlugin::activate() {
qFatal("Failed to acquire HMD");
}
HmdDisplayPlugin::activate();
_hmdDesc = ovr_GetHmdDesc(_session);
_ipd = ovr_GetFloat(_session, OVR_KEY_IPD, _ipd);
@ -123,6 +121,11 @@ void OculusBaseDisplayPlugin::activate() {
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
qFatal("Could not attach to sensor device");
}
// This must come after the initialization, so that the values calculated
// above are available during the customizeContext call (when not running
// in threaded present mode)
HmdDisplayPlugin::activate();
}
void OculusBaseDisplayPlugin::deactivate() {

View file

@ -19,8 +19,3 @@ bool OculusDebugDisplayPlugin::isSupported() const {
}
return OculusBaseDisplayPlugin::isSupported();
}
void OculusDebugDisplayPlugin::customizeContext() {
OculusBaseDisplayPlugin::customizeContext();
enableVsync(false);
}

View file

@ -11,12 +11,12 @@
class OculusDebugDisplayPlugin : public OculusBaseDisplayPlugin {
public:
virtual const QString& getName() const override { return NAME; }
virtual grouping getGrouping() const override { return DEVELOPER; }
virtual bool isSupported() const override;
const QString& getName() const override { return NAME; }
grouping getGrouping() const override { return DEVELOPER; }
bool isSupported() const override;
protected:
virtual void customizeContext() override;
void hmdPresent() override {}
private:
static const QString NAME;

View file

@ -37,57 +37,38 @@ void OculusDisplayPlugin::uncustomizeContext() {
OculusBaseDisplayPlugin::uncustomizeContext();
}
void OculusDisplayPlugin::internalPresent() {
template <typename SrcFbo, typename DstFbo>
void blit(const SrcFbo& srcFbo, const DstFbo& dstFbo) {
using namespace oglplus;
srcFbo->Bound(FramebufferTarget::Read, [&] {
dstFbo->Bound(FramebufferTarget::Draw, [&] {
Context::BlitFramebuffer(
0, 0, srcFbo->size.x, srcFbo->size.y,
0, 0, dstFbo->size.x, dstFbo->size.y,
BufferSelectBit::ColorBuffer, BlitFilter::Linear);
});
});
}
void OculusDisplayPlugin::updateFrameData() {
Parent::updateFrameData();
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentRenderEyePoses[Left]);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentRenderEyePoses[Right]);
}
void OculusDisplayPlugin::hmdPresent() {
if (!_currentSceneTexture) {
return;
}
using namespace oglplus;
const auto& size = _sceneFbo->size;
_sceneFbo->Bound([&] {
Context::Viewport(size.x, size.y);
glBindTexture(GL_TEXTURE_2D, _currentSceneTexture);
//glEnable(GL_FRAMEBUFFER_SRGB);
GLenum err = glGetError();
drawUnitQuad();
//glDisable(GL_FRAMEBUFFER_SRGB);
});
uint32_t frameIndex { 0 };
EyePoses eyePoses;
blit(_compositeFramebuffer, _sceneFbo);
{
Lock lock(_mutex);
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(_currentSceneTexture));
frameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
Q_ASSERT(_frameEyePoses.contains(frameIndex));
eyePoses = _frameEyePoses[frameIndex];
}
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = eyePoses.first;
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = eyePoses.second;
{
ovrLayerHeader* layers = &_sceneLayer.Header;
ovrResult result = ovr_SubmitFrame(_session, frameIndex, &_viewScaleDesc, &layers, 1);
ovrResult result = ovr_SubmitFrame(_session, _currentRenderFrameIndex, &_viewScaleDesc, &layers, 1);
if (!OVR_SUCCESS(result)) {
qDebug() << result;
}
}
_sceneFbo->Increment();
// Handle mirroring to screen in base class
HmdDisplayPlugin::internalPresent();
}
void OculusDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {
auto ovrPose = ovrPoseFromGlm(pose);
{
Lock lock(_mutex);
if (eye == Eye::Left) {
_frameEyePoses[frameIndex].first = ovrPose;
} else {
_frameEyePoses[frameIndex].second = ovrPose;
}
}
}

View file

@ -15,24 +15,23 @@ using SwapFboPtr = QSharedPointer<SwapFramebufferWrapper>;
const float TARGET_RATE_Oculus = 75.0f;
class OculusDisplayPlugin : public OculusBaseDisplayPlugin {
using Parent = OculusBaseDisplayPlugin;
public:
virtual void activate() override;
virtual const QString& getName() const override { return NAME; }
virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final;
void activate() override;
const QString& getName() const override { return NAME; }
virtual float getTargetFrameRate() override { return TARGET_RATE_Oculus; }
float getTargetFrameRate() override { return TARGET_RATE_Oculus; }
protected:
virtual void internalPresent() override;
virtual void customizeContext() override;
virtual void uncustomizeContext() override;
void hmdPresent() override;
void customizeContext() override;
void uncustomizeContext() override;
void updateFrameData() override;
private:
using EyePoses = std::pair<ovrPosef, ovrPosef>;
static const QString NAME;
bool _enablePreview { false };
bool _monoPreview { true };
QMap<uint32_t, EyePoses> _frameEyePoses;
SwapFboPtr _sceneFbo;
};

View file

@ -10,7 +10,7 @@ if (NOT WIN32)
set(TARGET_NAME oculusLegacy)
setup_hifi_plugin()
link_hifi_libraries(shared gl plugins display-plugins)
link_hifi_libraries(shared gl gpu plugins display-plugins input-plugins)
include_hifi_library_headers(octree)

View file

@ -34,6 +34,7 @@ public:
protected:
virtual void customizeContext() override;
void hmdPresent() override {}
#if 0
virtual void uncustomizeContext() override;
virtual void internalPresent() override;

View file

@ -21,7 +21,6 @@
#include <PerfStat.h>
#include <plugins/PluginContainer.h>
#include <ViewFrustum.h>
#include <GLMHelpers.h>
#include "OpenVrHelpers.h"
@ -128,19 +127,16 @@ glm::mat4 OpenVrDisplayPlugin::getHeadPose(uint32_t frameIndex) const {
return _trackedDevicePoseMat4[0];
}
void OpenVrDisplayPlugin::internalPresent() {
void OpenVrDisplayPlugin::hmdPresent() {
// Flip y-axis since GL UV coords are backwards.
static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 };
static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 };
vr::Texture_t texture{ (void*)_currentSceneTexture, vr::API_OpenGL, vr::ColorSpace_Auto };
vr::Texture_t texture { (void*)oglplus::GetName(_compositeFramebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto };
_compositor->Submit(vr::Eye_Left, &texture, &leftBounds);
_compositor->Submit(vr::Eye_Right, &texture, &rightBounds);
vr::TrackedDevicePose_t currentTrackedDevicePose[vr::k_unMaxTrackedDeviceCount];
_compositor->WaitGetPoses(currentTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0);
// Handle the mirroring in the base class
HmdDisplayPlugin::internalPresent();
}

View file

@ -32,11 +32,10 @@ public:
virtual glm::mat4 getHeadPose(uint32_t frameIndex) const override;
protected:
virtual void internalPresent() override;
void hmdPresent() override;
private:
vr::IVRSystem* _system { nullptr };
static const QString NAME;
mutable Mutex _poseMutex;
};

View file

@ -92,8 +92,8 @@ public:
virtual void showDisplayPluginsTools() override {}
virtual void requestReset() override {}
virtual bool makeRenderingContextCurrent() override { return true; }
virtual void releaseSceneTexture(uint32_t texture) override {}
virtual void releaseOverlayTexture(uint32_t texture) override {}
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override {}
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override {}
virtual GLWidget* getPrimaryWidget() override { return nullptr; }
virtual QWindow* getPrimaryWindow() override { return nullptr; }
virtual QOpenGLContext* getPrimaryContext() override { return nullptr; }