mirror of
https://github.com/overte-org/overte.git
synced 2025-07-14 09:16:37 +02:00
430 lines
17 KiB
C++
430 lines
17 KiB
C++
//
|
|
// Application_Plugins.cpp
|
|
// interface/src
|
|
//
|
|
// Split from Application.cpp by HifiExperiments on 3/30/24
|
|
// Created by Andrzej Kapolka on 5/10/13.
|
|
// Copyright 2013 High Fidelity, Inc.
|
|
// Copyright 2020 Vircadia contributors.
|
|
// Copyright 2022-2023 Overte e.V.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
#include "Application.h"
|
|
|
|
#include <QtCore/QCommandLineParser>
|
|
|
|
#include <input-plugins/InputPlugin.h>
|
|
#include <display-plugins/DisplayPlugin.h>
|
|
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
|
#include <OffscreenUi.h>
|
|
#include <plugins/PluginManager.h>
|
|
#include <plugins/PluginUtils.h>
|
|
#include <scripting/HMDScriptingInterface.h>
|
|
#include <UserActivityLogger.h>
|
|
|
|
#include "AudioClient.h"
|
|
#include "InterfaceLogging.h"
|
|
#include "Menu.h"
|
|
|
|
static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds
|
|
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
|
|
static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
|
|
|
|
// Statically provided display and input plugins
|
|
extern DisplayPluginList getDisplayPlugins();
|
|
extern InputPluginList getInputPlugins();
|
|
extern void saveInputPluginSettings(const InputPluginList& plugins);
|
|
|
|
void Application::initializePluginManager(const QCommandLineParser& parser) {
|
|
DependencyManager::set<PluginManager>();
|
|
auto pluginManager = PluginManager::getInstance();
|
|
|
|
// To avoid any confusion: the getInputPlugins and getDisplayPlugins are not the ones
|
|
// from PluginManager, but functions exported by input-plugins/InputPlugin.cpp and
|
|
// display-plugins/DisplayPlugin.cpp.
|
|
//
|
|
// These functions provide the plugin manager with static default plugins.
|
|
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
|
|
pluginManager->setDisplayPluginProvider([] { return getDisplayPlugins(); });
|
|
pluginManager->setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); });
|
|
|
|
// This must be a member function -- PluginManager must exist, and for that
|
|
// QApplication must exist, or it can't find the plugin path, as QCoreApplication:applicationDirPath
|
|
// won't work yet.
|
|
|
|
if (parser.isSet("display")) {
|
|
auto preferredDisplays = parser.value("display").split(',', Qt::SkipEmptyParts);
|
|
qInfo() << "Setting prefered display plugins:" << preferredDisplays;
|
|
PluginManager::getInstance()->setPreferredDisplayPlugins(preferredDisplays);
|
|
}
|
|
|
|
if (parser.isSet("disableDisplayPlugins")) {
|
|
auto disabledDisplays = parser.value("disableDisplayPlugins").split(',', Qt::SkipEmptyParts);
|
|
qInfo() << "Disabling following display plugins:" << disabledDisplays;
|
|
PluginManager::getInstance()->disableDisplays(disabledDisplays);
|
|
}
|
|
|
|
if (parser.isSet("disableInputPlugins")) {
|
|
auto disabledInputs = parser.value("disableInputPlugins").split(',', Qt::SkipEmptyParts);
|
|
qInfo() << "Disabling following input plugins:" << disabledInputs;
|
|
PluginManager::getInstance()->disableInputs(disabledInputs);
|
|
}
|
|
|
|
if (parser.isSet("useExperimentalXR")) {
|
|
auto pluginNames = QStringList();
|
|
pluginNames.push_back("OpenVR (Vive)");
|
|
PluginManager::getInstance()->disableDisplays(pluginNames);
|
|
PluginManager::getInstance()->disableInputs(pluginNames);
|
|
} else {
|
|
auto pluginNames = QStringList();
|
|
pluginNames.push_back("OpenXR");
|
|
PluginManager::getInstance()->disableDisplays(pluginNames);
|
|
PluginManager::getInstance()->disableInputs(pluginNames);
|
|
}
|
|
}
|
|
|
|
void Application::shutdownPlugins() {}
|
|
|
|
void Application::initializeDisplayPlugins() {
|
|
const auto& displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
|
|
Setting::Handle<QString> activeDisplayPluginSetting { ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() };
|
|
auto lastActiveDisplayPluginName = activeDisplayPluginSetting.get();
|
|
|
|
auto defaultDisplayPlugin = displayPlugins.at(0);
|
|
// One time initialization code
|
|
DisplayPluginPointer targetDisplayPlugin;
|
|
for(const auto& displayPlugin : displayPlugins) {
|
|
displayPlugin->setContext(_graphicsEngine->getGPUContext());
|
|
if (displayPlugin->getName() == lastActiveDisplayPluginName) {
|
|
targetDisplayPlugin = displayPlugin;
|
|
}
|
|
|
|
if (!_autoSwitchDisplayModeSupportedHMDPlugin) {
|
|
if (displayPlugin->isHmd() && displayPlugin->getSupportsAutoSwitch()) {
|
|
_autoSwitchDisplayModeSupportedHMDPlugin = displayPlugin;
|
|
_autoSwitchDisplayModeSupportedHMDPluginName = _autoSwitchDisplayModeSupportedHMDPlugin->getName();
|
|
_previousHMDWornStatus = _autoSwitchDisplayModeSupportedHMDPlugin->isDisplayVisible() && _autoSwitchDisplayModeSupportedHMDPlugin->isActive();
|
|
}
|
|
}
|
|
|
|
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged,
|
|
[this](const QSize& size) { resizeGL(); });
|
|
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
|
|
|
|
if (displayPlugin->isHmd()) {
|
|
auto hmdDisplayPlugin = dynamic_cast<HmdDisplayPlugin*>(displayPlugin.get());
|
|
QObject::connect(hmdDisplayPlugin, &HmdDisplayPlugin::hmdMountedChanged,
|
|
DependencyManager::get<HMDScriptingInterface>().data(), &HMDScriptingInterface::mountedChanged);
|
|
QObject::connect(hmdDisplayPlugin, &HmdDisplayPlugin::hmdVisibleChanged, this, &Application::hmdVisibleChanged);
|
|
}
|
|
}
|
|
|
|
// The default display plugin needs to be activated first, otherwise the display plugin thread
|
|
// may be launched by an external plugin, which is bad
|
|
setDisplayPlugin(defaultDisplayPlugin);
|
|
|
|
// Now set the desired plugin if it's not the same as the default plugin
|
|
if (targetDisplayPlugin && (targetDisplayPlugin != defaultDisplayPlugin)) {
|
|
setDisplayPlugin(targetDisplayPlugin);
|
|
}
|
|
|
|
if (_autoSwitchDisplayModeSupportedHMDPlugin) {
|
|
if (getActiveDisplayPlugin() != _autoSwitchDisplayModeSupportedHMDPlugin && !_autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) {
|
|
startHMDStandBySession();
|
|
}
|
|
// Poll periodically to check whether the user has worn HMD or not. Switch Display mode accordingly.
|
|
// If the user wears HMD then switch to VR mode. If the user removes HMD then switch to Desktop mode.
|
|
QTimer* autoSwitchDisplayModeTimer = new QTimer(this);
|
|
connect(autoSwitchDisplayModeTimer, SIGNAL(timeout()), this, SLOT(switchDisplayMode()));
|
|
autoSwitchDisplayModeTimer->start(INTERVAL_TO_CHECK_HMD_WORN_STATUS);
|
|
}
|
|
|
|
// Submit a default frame to render until the engine starts up
|
|
updateRenderArgs(0.0f);
|
|
}
|
|
|
|
DisplayPluginPointer Application::getActiveDisplayPlugin() const {
|
|
if (QThread::currentThread() != thread()) {
|
|
std::unique_lock<std::mutex> lock(_displayPluginLock);
|
|
return _displayPlugin;
|
|
}
|
|
|
|
if (!_aboutToQuit && !_displayPlugin) {
|
|
const_cast<Application*>(this)->updateDisplayMode();
|
|
Q_ASSERT(_displayPlugin);
|
|
}
|
|
return _displayPlugin;
|
|
}
|
|
|
|
void Application::setActiveDisplayPlugin(const QString& pluginName) {
|
|
DisplayPluginPointer newDisplayPlugin;
|
|
for (const DisplayPluginPointer& displayPlugin : PluginManager::getInstance()->getDisplayPlugins()) {
|
|
QString name = displayPlugin->getName();
|
|
if (pluginName == name) {
|
|
newDisplayPlugin = displayPlugin;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newDisplayPlugin) {
|
|
setDisplayPlugin(newDisplayPlugin);
|
|
}
|
|
}
|
|
|
|
glm::uvec2 Application::getUiSize() const {
|
|
static const uint MIN_SIZE = 1;
|
|
glm::uvec2 result(MIN_SIZE);
|
|
if (_displayPlugin) {
|
|
result = getActiveDisplayPlugin()->getRecommendedUiSize();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QRect Application::getRecommendedHUDRect() const {
|
|
auto uiSize = getUiSize();
|
|
QRect result(0, 0, uiSize.x, uiSize.y);
|
|
if (_displayPlugin) {
|
|
result = getActiveDisplayPlugin()->getRecommendedHUDRect();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
glm::vec2 Application::getDeviceSize() const {
|
|
static const int MIN_SIZE = 1;
|
|
glm::vec2 result(MIN_SIZE);
|
|
if (_displayPlugin) {
|
|
result = getActiveDisplayPlugin()->getRecommendedRenderSize();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Application::isThrottleRendering() const {
|
|
if (_displayPlugin) {
|
|
return getActiveDisplayPlugin()->isThrottled();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
float Application::getTargetRenderFrameRate() const {
|
|
return getActiveDisplayPlugin()->getTargetFrameRate();
|
|
}
|
|
|
|
bool Application::hasRiftControllers() {
|
|
return PluginUtils::isOculusTouchControllerAvailable();
|
|
}
|
|
|
|
bool Application::hasViveControllers() {
|
|
return PluginUtils::isViveControllerAvailable();
|
|
}
|
|
|
|
bool Application::isHMDMode() const {
|
|
return getActiveDisplayPlugin()->isHmd();
|
|
}
|
|
|
|
mat4 Application::getHMDSensorPose() const {
|
|
if (isHMDMode()) {
|
|
return getActiveDisplayPlugin()->getHeadPose();
|
|
}
|
|
return mat4();
|
|
}
|
|
|
|
mat4 Application::getEyeOffset(int eye) const {
|
|
// FIXME invert?
|
|
return getActiveDisplayPlugin()->getEyeToHeadTransform((Eye)eye);
|
|
}
|
|
|
|
mat4 Application::getEyeProjection(int eye) const {
|
|
QMutexLocker viewLocker(&_viewMutex);
|
|
if (isHMDMode()) {
|
|
return getActiveDisplayPlugin()->getEyeProjection((Eye)eye, _viewFrustum.getProjection());
|
|
}
|
|
return _viewFrustum.getProjection();
|
|
}
|
|
|
|
// resentSensors() is a bit of vestigial feature. It used to be used for Oculus DK2 to recenter the view around
|
|
// the current head orientation. With the introduction of "room scale" tracking we no longer need that particular
|
|
// feature. However, we still use this to reset face trackers, eye trackers, audio and to optionally re-load the avatar
|
|
// rig and animations from scratch.
|
|
void Application::resetSensors(bool andReload) {
|
|
_overlayConductor.centerUI();
|
|
getActiveDisplayPlugin()->resetSensors();
|
|
getMyAvatar()->reset(true, andReload);
|
|
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
|
|
}
|
|
|
|
void Application::updateDisplayMode() {
|
|
// Unsafe to call this method from anything but the main thread
|
|
if (QThread::currentThread() != thread()) {
|
|
qFatal("Attempted to switch display plugins from a non-main thread");
|
|
}
|
|
|
|
// Once time initialization code that depends on the UI being available
|
|
const auto& displayPlugins = getDisplayPlugins();
|
|
|
|
// Default to the first item on the list, in case none of the menu items match
|
|
|
|
DisplayPluginPointer newDisplayPlugin = displayPlugins.at(0);
|
|
auto menu = getPrimaryMenu();
|
|
if (menu) {
|
|
for (const auto& displayPlugin : PluginManager::getInstance()->getDisplayPlugins()) {
|
|
QString name = displayPlugin->getName();
|
|
QAction* action = menu->getActionForOption(name);
|
|
// Menu might have been removed if the display plugin lost
|
|
if (!action) {
|
|
continue;
|
|
}
|
|
if (action->isChecked()) {
|
|
newDisplayPlugin = displayPlugin;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newDisplayPlugin == _displayPlugin) {
|
|
return;
|
|
}
|
|
|
|
setDisplayPlugin(newDisplayPlugin);
|
|
}
|
|
|
|
void Application::switchDisplayMode() {
|
|
if (!_autoSwitchDisplayModeSupportedHMDPlugin) {
|
|
return;
|
|
}
|
|
|
|
bool currentHMDWornStatus = _autoSwitchDisplayModeSupportedHMDPlugin->isDisplayVisible();
|
|
if (currentHMDWornStatus != _previousHMDWornStatus) {
|
|
// Switch to respective mode as soon as currentHMDWornStatus changes
|
|
if (currentHMDWornStatus) {
|
|
qCDebug(interfaceapp) << "Switching from Desktop to HMD mode";
|
|
endHMDSession();
|
|
setActiveDisplayPlugin(_autoSwitchDisplayModeSupportedHMDPluginName);
|
|
} else {
|
|
qCDebug(interfaceapp) << "Switching from HMD to desktop mode";
|
|
setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME);
|
|
startHMDStandBySession();
|
|
}
|
|
}
|
|
_previousHMDWornStatus = currentHMDWornStatus;
|
|
}
|
|
|
|
void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
|
|
if (newDisplayPlugin == _displayPlugin) {
|
|
return;
|
|
}
|
|
|
|
// FIXME don't have the application directly set the state of the UI,
|
|
// instead emit a signal that the display plugin is changing and let
|
|
// the desktop lock itself. Reduces coupling between the UI and display
|
|
// plugins
|
|
auto offscreenUi = getOffscreenUI();
|
|
auto desktop = offscreenUi ? offscreenUi->getDesktop() : nullptr;
|
|
auto menu = Menu::getInstance();
|
|
|
|
// Make the switch atomic from the perspective of other threads
|
|
{
|
|
std::unique_lock<std::mutex> lock(_displayPluginLock);
|
|
bool wasRepositionLocked = false;
|
|
if (desktop) {
|
|
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
|
|
wasRepositionLocked = desktop->property("repositionLocked").toBool();
|
|
desktop->setProperty("repositionLocked", true);
|
|
}
|
|
|
|
if (_displayPlugin) {
|
|
disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
|
|
_displayPlugin->deactivate();
|
|
}
|
|
|
|
auto oldDisplayPlugin = _displayPlugin;
|
|
bool active = newDisplayPlugin->activate();
|
|
|
|
if (!active) {
|
|
const DisplayPluginList& displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
|
|
|
|
// If the new plugin fails to activate, fallback to last display
|
|
qWarning() << "Failed to activate display: " << newDisplayPlugin->getName();
|
|
newDisplayPlugin = oldDisplayPlugin;
|
|
|
|
if (newDisplayPlugin) {
|
|
qWarning() << "Falling back to last display: " << newDisplayPlugin->getName();
|
|
active = newDisplayPlugin->activate();
|
|
}
|
|
|
|
// If there is no last display, or
|
|
// If the last display fails to activate, fallback to desktop
|
|
if (!active) {
|
|
newDisplayPlugin = displayPlugins.at(0);
|
|
qWarning() << "Falling back to display: " << newDisplayPlugin->getName();
|
|
active = newDisplayPlugin->activate();
|
|
}
|
|
|
|
if (!active) {
|
|
qFatal("Failed to activate fallback plugin");
|
|
}
|
|
}
|
|
|
|
if (offscreenUi) {
|
|
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
|
|
}
|
|
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
|
|
_displayPlugin = newDisplayPlugin;
|
|
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);
|
|
if (desktop) {
|
|
desktop->setProperty("repositionLocked", wasRepositionLocked);
|
|
}
|
|
|
|
RefreshRateManager& refreshRateManager = getRefreshRateManager();
|
|
refreshRateManager.setRefreshRateOperator(OpenGLDisplayPlugin::getRefreshRateOperator());
|
|
bool isHmd = newDisplayPlugin->isHmd();
|
|
RefreshRateManager::UXMode uxMode = isHmd ? RefreshRateManager::UXMode::VR :
|
|
RefreshRateManager::UXMode::DESKTOP;
|
|
|
|
refreshRateManager.setUXMode(uxMode);
|
|
}
|
|
|
|
bool isHmd = _displayPlugin->isHmd();
|
|
qCDebug(interfaceapp) << "Entering into" << (isHmd ? "HMD" : "Desktop") << "Mode";
|
|
|
|
// Only log/emit after a successful change
|
|
UserActivityLogger::getInstance().logAction("changed_display_mode", {
|
|
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
|
|
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" },
|
|
{ "hmd", isHmd }
|
|
});
|
|
emit activeDisplayPluginChanged();
|
|
|
|
// reset the avatar, to set head and hand palms back to a reasonable default pose.
|
|
getMyAvatar()->reset(false);
|
|
|
|
// switch to first person if entering hmd and setting is checked
|
|
if (menu) {
|
|
QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
|
|
if (action) {
|
|
action->setChecked(true);
|
|
}
|
|
|
|
if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
|
|
menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, true);
|
|
cameraMenuChanged();
|
|
}
|
|
|
|
// Remove the selfie camera options from menu if in HMD mode
|
|
auto selfieAction = menu->getActionForOption(MenuOption::SelfieCamera);
|
|
selfieAction->setVisible(!isHmd);
|
|
}
|
|
|
|
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
|
|
}
|
|
|
|
void Application::startHMDStandBySession() {
|
|
_autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession();
|
|
}
|
|
|
|
void Application::endHMDSession() {
|
|
_autoSwitchDisplayModeSupportedHMDPlugin->endSession();
|
|
}
|