diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index a9e4bedbbd..1e6e945f5f 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -724,8 +724,8 @@ extern DisplayPluginList getDisplayPlugins();
 extern InputPluginList getInputPlugins();
 extern void saveInputPluginSettings(const InputPluginList& plugins);
 
-bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, bool runningMarkerExisted) {
-    qInstallMessageHandler(messageHandler);
+bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted) {
+
 
 
     const int listenPort = parser.isSet("listenPort") ? parser.value("listenPort").toInt() : INVALID_PORT;
@@ -743,6 +743,7 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b
 
     bool previousSessionCrashed { false };
     if (!inTestMode) {
+        // TODO: FIX
         previousSessionCrashed = CrashRecoveryHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
     }
 
@@ -763,13 +764,12 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b
         }
     }
 
-    // Tell the plugin manager about our statically linked plugins
+
+
     DependencyManager::set<ScriptInitializers>();
-    DependencyManager::set<PluginManager>();
+
+    // Tell the plugin manager about our statically linked plugins
     auto pluginManager = PluginManager::getInstance();
-    pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
-    pluginManager->setDisplayPluginProvider([] { return getDisplayPlugins(); });
-    pluginManager->setInputPluginSettingsPersister([](const InputPluginList& plugins) { saveInputPluginSettings(plugins); });
     if (auto steamClient = pluginManager->getSteamClientPlugin()) {
         steamClient->init();
     }
@@ -777,6 +777,7 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b
         oculusPlatform->init();
     }
 
+
     PROFILE_SET_THREAD_NAME("Main Thread");
 
 #if defined(Q_OS_WIN)
@@ -993,8 +994,7 @@ bool Application::initMenu() {
 Application::Application(
     int& argc, char** argv,
     const QCommandLineParser& parser,
-    QElapsedTimer& startupTimer,
-    bool runningMarkerExisted
+    QElapsedTimer& startupTimer
 ) :
     QApplication(argc, argv),
     _window(new MainWindow(desktop())),
@@ -1004,10 +1004,7 @@ Application::Application(
 #ifndef Q_OS_ANDROID
     _logger(new FileLogger(this)),
 #endif
-    _previousSessionCrashed(setupEssentials(argc, argv, parser, runningMarkerExisted)),
-    _entitySimulation(std::make_shared<PhysicalEntitySimulation>()),
-    _physicsEngine(std::make_shared<PhysicsEngine>(Vectors::ZERO)),
-    _entityClipboard(std::make_shared<EntityTree>()),
+    _previousSessionCrashed(false), //setupEssentials(parser, false)),
     _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION),
     _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES),
     _hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
@@ -1032,12 +1029,72 @@ Application::Application(
     _snapshotSound(nullptr),
     _sampleSound(nullptr)
 {
-    auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
-    setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
     setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
 
     LogHandler::getInstance().moveToThread(thread());
     LogHandler::getInstance().setupRepeatedMessageFlusher();
+    qInstallMessageHandler(messageHandler);
+
+    DependencyManager::set<PathUtils>();
+}
+
+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);
+    }
+
+}
+
+void Application::initialize(const QCommandLineParser &parser) {
+
+    //qCDebug(interfaceapp) << "Setting up essentials";
+    setupEssentials(parser, _previousSessionCrashed);
+    qCDebug(interfaceapp) << "Initializing application";
+
+    _entitySimulation = std::make_shared<PhysicalEntitySimulation>();
+    _physicsEngine = std::make_shared<PhysicsEngine>(Vectors::ZERO);
+    _entityClipboard = std::make_shared<EntityTree>();
+    _octreeProcessor = std::make_shared<OctreePacketProcessor>();
+    _entityEditSender = std::make_shared<EntityEditPacketSender>();
+    _graphicsEngine = std::make_shared<GraphicsEngine>();
+    _applicationOverlay = std::make_shared<ApplicationOverlay>();
+
+
+
+    auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
+    setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
+
 
     {
         if (parser.isSet("testScript")) {
@@ -1405,7 +1462,7 @@ Application::Application(
     connect(myAvatar.get(), &MyAvatar::positionGoneTo, this, [this] {
         if (!_physicsEnabled) {
             // when we arrive somewhere without physics enabled --> startSafeLanding
-            _octreeProcessor.startSafeLanding();
+            _octreeProcessor->startSafeLanding();
         }
     }, Qt::QueuedConnection);
 
@@ -1578,9 +1635,9 @@ Application::Application(
     qCDebug(interfaceapp, "init() complete.");
 
     // create thread for parsing of octree data independent of the main network and rendering threads
-    _octreeProcessor.initialize(_enableProcessOctreeThread);
-    connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
-    _entityEditSender.initialize(_enableProcessOctreeThread);
+    _octreeProcessor->initialize(_enableProcessOctreeThread);
+    connect(_octreeProcessor.get(), &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
+    _entityEditSender->initialize(_enableProcessOctreeThread);
 
     _idleLoopStdev.reset();
 
@@ -1698,7 +1755,7 @@ Application::Application(
         userActivityLogger.logAction("launch", properties);
     }
 
-    _entityEditSender.setMyAvatar(myAvatar.get());
+    _entityEditSender->setMyAvatar(myAvatar.get());
 
     // The entity octree will have to know about MyAvatar for the parentJointName import
     getEntities()->getTree()->setMyAvatar(myAvatar);
@@ -1707,7 +1764,7 @@ Application::Application(
     // For now we're going to set the PPS for outbound packets to be super high, this is
     // probably not the right long term solution. But for now, we're going to do this to
     // allow you to move an entity around in your hand
-    _entityEditSender.setPacketsPerSecond(3000); // super high!!
+    _entityEditSender->setPacketsPerSecond(3000); // super high!!
 
     // Make sure we don't time out during slow operations at startup
     updateHeartbeat();
@@ -2375,7 +2432,7 @@ Application::Application(
 
     connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
     connect(_window, SIGNAL(windowMinimizedChanged(bool)), this, SLOT(windowMinimizedChanged(bool)));
-    qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
+    qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)_sessionRunTimer.elapsed() / 1000.0);
 
     EntityTreeRenderer::setEntitiesShouldFadeFunction([this]() {
         SharedNodePointer entityServerNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::EntityServer);
@@ -2572,7 +2629,7 @@ Application::Application(
     }
 
     _pendingIdleEvent = false;
-    _graphicsEngine.startup();
+    _graphicsEngine->startup();
 
     qCDebug(interfaceapp) << "Directory Service session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
 
@@ -2879,43 +2936,59 @@ void Application::cleanupBeforeQuit() {
 
 Application::~Application() {
     // remove avatars from physics engine
-    auto avatarManager = DependencyManager::get<AvatarManager>();
-    avatarManager->clearOtherAvatars();
-    auto myCharacterController = getMyAvatar()->getCharacterController();
-    myCharacterController->clearDetailedMotionStates();
+    if (auto avatarManager = DependencyManager::get<AvatarManager>()) {
+        // AvatarManager may not yet exist in case of an early exit
 
-    PhysicsEngine::Transaction transaction;
-    avatarManager->buildPhysicsTransaction(transaction);
-    _physicsEngine->processTransaction(transaction);
-    avatarManager->handleProcessedPhysicsTransaction(transaction);
-    avatarManager->deleteAllAvatars();
+        avatarManager->clearOtherAvatars();
+        auto myCharacterController = getMyAvatar()->getCharacterController();
+        myCharacterController->clearDetailedMotionStates();
 
-    _physicsEngine->setCharacterController(nullptr);
+        PhysicsEngine::Transaction transaction;
+        avatarManager->buildPhysicsTransaction(transaction);
+        _physicsEngine->processTransaction(transaction);
+        avatarManager->handleProcessedPhysicsTransaction(transaction);
+        avatarManager->deleteAllAvatars();
+    }
+
+    if (_physicsEngine) {
+        _physicsEngine->setCharacterController(nullptr);
+    }
 
     // the _shapeManager should have zero references
     _shapeManager.collectGarbage();
     assert(_shapeManager.getNumShapes() == 0);
 
-    // shutdown graphics engine
-    _graphicsEngine.shutdown();
+    if (_graphicsEngine) {
+        // shutdown graphics engine
+        _graphicsEngine->shutdown();
+    }
 
     _gameWorkload.shutdown();
 
     DependencyManager::destroy<Preferences>();
     PlatformHelper::shutdown();
 
-    _entityClipboard->eraseAllOctreeElements();
-    _entityClipboard.reset();
-
-    _octreeProcessor.terminate();
-    _entityEditSender.terminate();
-
-    if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
-        steamClient->shutdown();
+    if (_entityClipboard) {
+        _entityClipboard->eraseAllOctreeElements();
+        _entityClipboard.reset();
     }
 
-    if (auto oculusPlatform = PluginManager::getInstance()->getOculusPlatformPlugin()) {
-        oculusPlatform->shutdown();
+    if (_octreeProcessor) {
+        _octreeProcessor->terminate();
+    }
+
+    if (_entityEditSender) {
+        _entityEditSender->terminate();
+    }
+
+    if (auto pluginManager = PluginManager::getInstance()) {
+        if (auto steamClient = pluginManager->getSteamClientPlugin()) {
+            steamClient->shutdown();
+        }
+
+        if (auto oculusPlatform = pluginManager->getOculusPlatformPlugin()) {
+            oculusPlatform->shutdown();
+        }
     }
 
     DependencyManager::destroy<PluginManager>();
@@ -2943,7 +3016,9 @@ Application::~Application() {
     DependencyManager::destroy<GeometryCache>();
     DependencyManager::destroy<ScreenshareScriptingInterface>();
 
-    DependencyManager::get<ResourceManager>()->cleanup();
+    if (auto resourceManager = DependencyManager::get<ResourceManager>()) {
+        resourceManager->cleanup();
+    }
 
     // remove the NodeList from the DependencyManager
     DependencyManager::destroy<NodeList>();
@@ -2957,13 +3032,14 @@ Application::~Application() {
     _window->deleteLater();
 
     // make sure that the quit event has finished sending before we take the application down
-    auto closeEventSender = DependencyManager::get<CloseEventSender>();
-    while (!closeEventSender->hasFinishedQuitEvent() && !closeEventSender->hasTimedOutQuitEvent()) {
-        // sleep a little so we're not spinning at 100%
-        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    if (auto closeEventSender = DependencyManager::get<CloseEventSender>()) {
+        while (!closeEventSender->hasFinishedQuitEvent() && !closeEventSender->hasTimedOutQuitEvent()) {
+            // sleep a little so we're not spinning at 100%
+            std::this_thread::sleep_for(std::chrono::milliseconds(10));
+        }
+        // quit the thread used by the closure event sender
+        closeEventSender->thread()->quit();
     }
-    // quit the thread used by the closure event sender
-    closeEventSender->thread()->quit();
 
     // Can't log to file past this point, FileLogger about to be deleted
     qInstallMessageHandler(LogHandler::verboseMessageHandler);
@@ -3103,7 +3179,7 @@ void Application::initializeGL() {
     glClear(GL_COLOR_BUFFER_BIT);
     _glWidget->swapBuffers();
 
-    _graphicsEngine.initializeGPU(_glWidget);
+    _graphicsEngine->initializeGPU(_glWidget);
 }
 
 void Application::initializeDisplayPlugins() {
@@ -3115,7 +3191,7 @@ void Application::initializeDisplayPlugins() {
     // Once time initialization code
     DisplayPluginPointer targetDisplayPlugin;
     for(const auto& displayPlugin : displayPlugins) {
-        displayPlugin->setContext(_graphicsEngine.getGPUContext());
+        displayPlugin->setContext(_graphicsEngine->getGPUContext());
         if (displayPlugin->getName() == lastActiveDisplayPluginName) {
             targetDisplayPlugin = displayPlugin;
         }
@@ -3167,7 +3243,7 @@ void Application::initializeDisplayPlugins() {
 void Application::initializeRenderEngine() {
     // FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
     DeadlockWatchdogThread::withPause([&] {
-        _graphicsEngine.initializeRender();
+        _graphicsEngine->initializeRender();
         DependencyManager::get<Keyboard>()->registerKeyboardHighlighting();
     });
 }
@@ -3424,7 +3500,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
     surfaceContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
     surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
     surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
-    surfaceContext->setContextProperty("FrameTimings", &_graphicsEngine._frameTimingsScriptingInterface);
+    surfaceContext->setContextProperty("FrameTimings", &_graphicsEngine->_frameTimingsScriptingInterface);
     surfaceContext->setContextProperty("Rates", new RatesScriptingInterface(this));
 
     surfaceContext->setContextProperty("TREE_SCALE", TREE_SCALE);
@@ -4060,7 +4136,7 @@ std::map<QString, QString> Application::prepareServerlessDomainContents(QUrl dom
     bool success = tmpTree->readFromByteArray(domainURL.toString(), data);
     if (success) {
         tmpTree->reaverageOctreeElements();
-        tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), "domain", 0, 0, 0);
+        tmpTree->sendEntities(_entityEditSender.get(), getEntities()->getTree(), "domain", 0, 0, 0);
     }
     std::map<QString, QString> namedPaths = tmpTree->getNamedPaths();
 
@@ -4130,8 +4206,8 @@ void Application::onPresent(quint32 frameCount) {
         postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority);
     }
     expected = false;
-    if (_graphicsEngine.checkPendingRenderEvent() && !isAboutToQuit()) {
-        postEvent(_graphicsEngine._renderEventHandler, new QEvent((QEvent::Type)ApplicationEvent::Render));
+    if (_graphicsEngine->checkPendingRenderEvent() && !isAboutToQuit()) {
+        postEvent(_graphicsEngine->_renderEventHandler, new QEvent((QEvent::Type)ApplicationEvent::Render));
     }
 }
 
@@ -4201,7 +4277,9 @@ bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) {
 }
 
 bool Application::notify(QObject * object, QEvent * event) {
-    if (thread() == QThread::currentThread()) {
+    if (thread() == QThread::currentThread() && _profilingInitialized ) {
+        // _profilingInitialized gets set once we're reading for profiling.
+        // this prevents a deadlock due to profiling not working yet
         PROFILE_RANGE_IF_LONGER(app, "notify", 2)
         return QApplication::notify(object, event);
     }
@@ -5246,8 +5324,8 @@ void Application::idle() {
     PROFILE_COUNTER_IF_CHANGED(app, "pendingDownloads", uint32_t, ResourceCache::getPendingRequestCount());
     PROFILE_COUNTER_IF_CHANGED(app, "currentProcessing", int, DependencyManager::get<StatTracker>()->getStat("Processing").toInt());
     PROFILE_COUNTER_IF_CHANGED(app, "pendingProcessing", int, DependencyManager::get<StatTracker>()->getStat("PendingProcessing").toInt());
-    auto renderConfig = _graphicsEngine.getRenderEngine()->getConfiguration();
-    PROFILE_COUNTER_IF_CHANGED(render, "gpuTime", float, (float)_graphicsEngine.getGPUContext()->getFrameTimerGPUAverage());
+    auto renderConfig = _graphicsEngine->getRenderEngine()->getConfiguration();
+    PROFILE_COUNTER_IF_CHANGED(render, "gpuTime", float, (float)_graphicsEngine->getGPUContext()->getFrameTimerGPUAverage());
 
     PROFILE_RANGE(app, __FUNCTION__);
 
@@ -5614,7 +5692,7 @@ bool Application::importEntities(const QString& urlOrFilename, const bool isObse
 }
 
 QVector<EntityItemID> Application::pasteEntities(const QString& entityHostType, float x, float y, float z) {
-    return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), entityHostType, x, y, z);
+    return _entityClipboard->sendEntities(_entityEditSender.get(), getEntities()->getTree(), entityHostType, x, y, z);
 }
 
 void Application::init() {
@@ -5664,7 +5742,7 @@ void Application::init() {
     _physicsEngine->init();
 
     EntityTreePointer tree = getEntities()->getTree();
-    _entitySimulation->init(tree, _physicsEngine, &_entityEditSender);
+    _entitySimulation->init(tree, _physicsEngine, _entityEditSender.get());
     tree->setSimulation(_entitySimulation);
 
     auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
@@ -5688,7 +5766,7 @@ void Application::init() {
         }
     }, Qt::QueuedConnection);
 
-    _gameWorkload.startup(getEntities()->getWorkloadSpace(), _graphicsEngine.getRenderScene(), _entitySimulation);
+    _gameWorkload.startup(getEntities()->getWorkloadSpace(), _graphicsEngine->getRenderScene(), _entitySimulation);
     _entitySimulation->setWorkloadSpace(getEntities()->getWorkloadSpace());
 }
 
@@ -5860,7 +5938,7 @@ void Application::updateLOD(float deltaTime) const {
     // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode
     if (!isThrottleRendering()) {
         float presentTime = getActiveDisplayPlugin()->getAveragePresentTime();
-        float engineRunTime = (float)(_graphicsEngine.getRenderEngine()->getConfiguration().get()->getCPURunTime());
+        float engineRunTime = (float)(_graphicsEngine->getRenderEngine()->getConfiguration().get()->getCPURunTime());
         float gpuTime = getGPUContext()->getFrameTimerGPUAverage();
         float batchTime = getGPUContext()->getFrameTimerBatchAverage();
         auto lodManager = DependencyManager::get<LODManager>();
@@ -5896,8 +5974,8 @@ void Application::updateThreads(float deltaTime) {
 
     // parse voxel packets
     if (!_enableProcessOctreeThread) {
-        _octreeProcessor.threadRoutine();
-        _entityEditSender.threadRoutine();
+        _octreeProcessor->threadRoutine();
+        _entityEditSender->threadRoutine();
     }
 }
 
@@ -6020,7 +6098,7 @@ void Application::resetPhysicsReadyInformation() {
     _gpuTextureMemSizeStabilityCount = 0;
     _gpuTextureMemSizeAtLastCheck = 0;
     _physicsEnabled = false;
-    _octreeProcessor.stopSafeLanding();
+    _octreeProcessor->stopSafeLanding();
 }
 
 void Application::reloadResourceCaches() {
@@ -6165,7 +6243,7 @@ void Application::updateSecondaryCameraViewFrustum() {
     // camera should be.
 
     // Code based on SecondaryCameraJob
-    auto renderConfig = _graphicsEngine.getRenderEngine()->getConfiguration();
+    auto renderConfig = _graphicsEngine->getRenderEngine()->getConfiguration();
     assert(renderConfig);
     auto camera = dynamic_cast<SecondaryCameraJobConfig*>(renderConfig->getConfig("SecondaryCamera"));
 
@@ -6283,7 +6361,7 @@ void Application::tryToEnablePhysics() {
         auto myAvatar = getMyAvatar();
         if (myAvatar->isReadyForPhysics()) {
             myAvatar->getCharacterController()->setPhysicsEngine(_physicsEngine);
-            _octreeProcessor.resetSafeLanding();
+            _octreeProcessor->resetSafeLanding();
             _physicsEnabled = true;
             setIsInterstitialMode(false);
             myAvatar->updateMotionBehaviorFromMenu();
@@ -6292,7 +6370,7 @@ void Application::tryToEnablePhysics() {
 }
 
 void Application::update(float deltaTime) {
-    PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_graphicsEngine._renderFrameCount + 1);
+    PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_graphicsEngine->_renderFrameCount + 1);
 
     if (_aboutToQuit) {
         return;
@@ -6309,12 +6387,12 @@ void Application::update(float deltaTime) {
         if (isServerlessMode()) {
             tryToEnablePhysics();
         } else if (_failedToConnectToEntityServer) {
-            if (_octreeProcessor.safeLandingIsActive()) {
-                _octreeProcessor.stopSafeLanding();
+            if (_octreeProcessor->safeLandingIsActive()) {
+                _octreeProcessor->stopSafeLanding();
             }
         } else {
-            _octreeProcessor.updateSafeLanding();
-            if (_octreeProcessor.safeLandingIsComplete()) {
+            _octreeProcessor->updateSafeLanding();
+            if (_octreeProcessor->safeLandingIsComplete()) {
                 tryToEnablePhysics();
             }
         }
@@ -6801,7 +6879,7 @@ void Application::update(float deltaTime) {
 }
 
 void Application::updateRenderArgs(float deltaTime) {
-    _graphicsEngine.editRenderArgs([this, deltaTime](AppRenderArgs& appRenderArgs) {
+    _graphicsEngine->editRenderArgs([this, deltaTime](AppRenderArgs& appRenderArgs) {
         PerformanceTimer perfTimer("editRenderArgs");
         appRenderArgs._headPose = getHMDSensorPose();
 
@@ -6830,7 +6908,7 @@ void Application::updateRenderArgs(float deltaTime) {
                 _viewFrustum.setProjection(adjustedProjection);
                 _viewFrustum.calculate();
             }
-            appRenderArgs._renderArgs = RenderArgs(_graphicsEngine.getGPUContext(), lodManager->getVisibilityDistance(),
+            appRenderArgs._renderArgs = RenderArgs(_graphicsEngine->getGPUContext(), lodManager->getVisibilityDistance(),
                 lodManager->getBoundaryLevelAdjust(), lodManager->getLODFarHalfAngleTan(), lodManager->getLODNearHalfAngleTan(),
                 lodManager->getLODFarDistance(), lodManager->getLODNearDistance(), RenderArgs::DEFAULT_RENDER_MODE,
                 RenderArgs::MONO, RenderArgs::DEFERRED, RenderArgs::RENDER_DEBUG_NONE);
@@ -6969,7 +7047,7 @@ int Application::sendNackPackets() {
 
             // if there are octree packets from this node that are waiting to be processed,
             // don't send a NACK since the missing packets may be among those waiting packets.
-            if (_octreeProcessor.hasPacketsToProcessFrom(nodeUUID)) {
+            if (_octreeProcessor->hasPacketsToProcessFrom(nodeUUID)) {
                 return;
             }
 
@@ -7011,7 +7089,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
 
     const bool isModifiedQuery = !_physicsEnabled;
     if (isModifiedQuery) {
-        if (!_octreeProcessor.safeLandingIsActive()) {
+        if (!_octreeProcessor->safeLandingIsActive()) {
             // don't send the octreeQuery until SafeLanding knows it has started
             return;
         }
@@ -7280,12 +7358,12 @@ void Application::resettingDomain() {
 void Application::nodeAdded(SharedNodePointer node) {
     if (node->getType() == NodeType::EntityServer) {
         if (_failedToConnectToEntityServer && !_entityServerConnectionTimer.isActive()) {
-            _octreeProcessor.stopSafeLanding();
+            _octreeProcessor->stopSafeLanding();
             _failedToConnectToEntityServer = false;
         } else if (_entityServerConnectionTimer.isActive()) {
             _entityServerConnectionTimer.stop();
         }
-        _octreeProcessor.startSafeLanding();
+        _octreeProcessor->startSafeLanding();
         _entityServerConnectionTimer.setInterval(ENTITY_SERVER_CONNECTION_TIMEOUT);
         _entityServerConnectionTimer.start();
     }
@@ -7357,9 +7435,9 @@ void Application::nodeKilled(SharedNodePointer node) {
     // OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted.
     // This may have to do with GenericThread::threadRoutine() blocking the QThread event loop
 
-    _octreeProcessor.nodeKilled(node);
+    _octreeProcessor->nodeKilled(node);
 
-    _entityEditSender.nodeKilled(node);
+    _entityEditSender->nodeKilled(node);
 
     if (node->getType() == NodeType::AudioMixer) {
         QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled");
@@ -7448,7 +7526,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptManagerPoint
     // setup the packet sender of the script engine's scripting interfaces so
     // we can use the same ones from the application.
     auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
-    entityScriptingInterface->setPacketSender(&_entityEditSender);
+    entityScriptingInterface->setPacketSender(_entityEditSender.get());
     entityScriptingInterface->setEntityTree(getEntities()->getTree());
 
     if (property(hifi::properties::TEST).isValid()) {
@@ -8762,26 +8840,6 @@ void Application::sendLambdaEvent(const std::function<void()>& f) {
     }
 }
 
-void Application::initPlugins(const QCommandLineParser& parser) {
-    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("disable-displays")) {
-        auto disabledDisplays = parser.value("disable-displays").split(',', Qt::SkipEmptyParts);
-        qInfo() << "Disabling following display plugins:"  << disabledDisplays;
-        PluginManager::getInstance()->disableDisplays(disabledDisplays);
-    }
-
-    if (parser.isSet("disable-inputs")) {
-        auto disabledInputs = parser.value("disable-inputs").split(',', Qt::SkipEmptyParts);
-        qInfo() << "Disabling following input plugins:" << disabledInputs;
-        PluginManager::getInstance()->disableInputs(disabledInputs);
-    }
-}
-
 void Application::shutdownPlugins() {
 }
 
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 82b39e868b..63a035dd45 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -123,6 +123,31 @@ class Application : public QApplication,
     friend class OctreePacketProcessor;
 
 public:
+
+    /**
+     * @brief Initialize the plugin manager
+     *
+     * This both does the initial startup and parses arguments. This
+     * is necessary because the plugin manager's options must be set
+     * before any usage of it is made, or they won't apply.
+     *
+     * @param parser
+     */
+    void initializePluginManager(const QCommandLineParser& parser);
+
+    /**
+     * @brief Initialize everything
+     *
+     * This is a QApplication, and for Qt reasons it's desirable to create this object
+     * as early as possible. Without that some Qt functions don't work, like QCoreApplication::applicationDirPath()
+     *
+     * So we keep the constructor as minimal as possible, and do the rest of the work in
+     * this function.
+     */
+    void initialize(const QCommandLineParser &parser);
+
+    void setPreviousSessionCrashed(bool value) { _previousSessionCrashed = value; }
+
     // virtual functions required for PluginContainer
     virtual ui::Menu* getPrimaryMenu() override;
     virtual void requestReset() override { resetSensors(false); }
@@ -135,15 +160,12 @@ public:
 
     virtual DisplayPluginPointer getActiveDisplayPlugin() const override;
 
-    // FIXME? Empty methods, do we still need them?
-    static void initPlugins(const QCommandLineParser& parser);
     static void shutdownPlugins();
 
     Application(
         int& argc, char** argv,
         const QCommandLineParser& parser,
-        QElapsedTimer& startup_time,
-        bool runningMarkerExisted
+        QElapsedTimer& startup_time
     );
     ~Application();
 
@@ -197,16 +219,16 @@ public:
 
     const ConicalViewFrustums& getConicalViews() const override { return _conicalViews; }
 
-    const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
+    const OctreePacketProcessor& getOctreePacketProcessor() const { return *_octreeProcessor; }
     QSharedPointer<EntityTreeRenderer> getEntities() const { return DependencyManager::get<EntityTreeRenderer>(); }
     MainWindow* getWindow() const { return _window; }
     EntityTreePointer getEntityClipboard() const { return _entityClipboard; }
-    EntityEditPacketSender* getEntityEditPacketSender() { return &_entityEditSender; }
+    std::shared_ptr<EntityEditPacketSender> getEntityEditPacketSender() { return _entityEditSender; }
 
     ivec2 getMouse() const;
 
-    ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
-    const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
+    ApplicationOverlay& getApplicationOverlay() { return *_applicationOverlay; }
+    const ApplicationOverlay& getApplicationOverlay() const { return *_applicationOverlay; }
     CompositorHelper& getApplicationCompositor() const;
 
     Overlays& getOverlays() { return _overlays; }
@@ -214,8 +236,8 @@ public:
     PerformanceManager& getPerformanceManager() { return _performanceManager; }
     RefreshRateManager& getRefreshRateManager() { return _refreshRateManager; }
 
-    size_t getRenderFrameCount() const { return _graphicsEngine.getRenderFrameCount(); }
-    float getRenderLoopRate() const { return _graphicsEngine.getRenderLoopRate(); }
+    size_t getRenderFrameCount() const { return _graphicsEngine->getRenderFrameCount(); }
+    float getRenderLoopRate() const { return _graphicsEngine->getRenderLoopRate(); }
     float getNumCollisionObjects() const;
     float getTargetRenderFrameRate() const; // frames/second
 
@@ -293,9 +315,9 @@ public:
     void setMaxOctreePacketsPerSecond(int maxOctreePPS);
     int getMaxOctreePacketsPerSecond() const;
 
-    render::ScenePointer getMain3DScene() override { return _graphicsEngine.getRenderScene(); }
-    render::EnginePointer getRenderEngine() override { return  _graphicsEngine.getRenderEngine(); }
-    gpu::ContextPointer getGPUContext() const { return _graphicsEngine.getGPUContext(); }
+    render::ScenePointer getMain3DScene() override { return _graphicsEngine->getRenderScene(); }
+    render::EnginePointer getRenderEngine() override { return  _graphicsEngine->getRenderEngine(); }
+    gpu::ContextPointer getGPUContext() const { return _graphicsEngine->getGPUContext(); }
 
     const GameWorkload& getGameWorkload() const { return _gameWorkload; }
 
@@ -709,8 +731,8 @@ private:
     bool _enableProcessOctreeThread;
     bool _interstitialMode { false };
 
-    OctreePacketProcessor _octreeProcessor;
-    EntityEditPacketSender _entityEditSender;
+    std::shared_ptr<OctreePacketProcessor> _octreeProcessor;
+    std::shared_ptr<EntityEditPacketSender> _entityEditSender;
 
     StDev _idleLoopStdev;
     float _idleLoopMeasuredJitter;
@@ -757,13 +779,13 @@ private:
 
     GameWorkload _gameWorkload;
 
-    GraphicsEngine _graphicsEngine;
+    std::shared_ptr<GraphicsEngine> _graphicsEngine;
     void updateRenderArgs(float deltaTime);
 
     bool _disableLoginScreen { true };
 
     Overlays _overlays;
-    ApplicationOverlay _applicationOverlay;
+    std::shared_ptr<ApplicationOverlay> _applicationOverlay;
     OverlayConductor _overlayConductor;
 
     DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
@@ -860,5 +882,7 @@ private:
     bool _crashOnShutdown { false };
 
     DiscordPresence* _discordPresence{ nullptr };
+
+    bool _profilingInitialized { false };
 };
 #endif // hifi_Application_h
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 827388aa1c..62100f9cae 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -1035,8 +1035,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
         std::pair<bool, bool> zoneInteractionProperties;
         entityTree->withWriteLock([&] {
             zoneInteractionProperties = entityTreeRenderer->getZoneInteractionProperties();
-            EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
-            entityTree->updateEntityQueryAACube(shared_from_this(), packetSender, false, true);
+            std::shared_ptr<EntityEditPacketSender> packetSender = qApp->getEntityEditPacketSender();
+            entityTree->updateEntityQueryAACube(shared_from_this(), packetSender.get(), false, true);
         });
         bool isPhysicsEnabled = qApp->isPhysicsEnabled();
         bool zoneAllowsFlying = zoneInteractionProperties.first;
@@ -1729,7 +1729,7 @@ void MyAvatar::handleChangedAvatarEntityData() {
     entityTree->deleteEntitiesByID(entitiesToDelete);
 
     // ADD real entities
-    EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
+    auto packetSender = qApp->getEntityEditPacketSender();
     for (const auto& id : entitiesToAdd) {
         bool blobFailed = false;
         EntityItemProperties properties;
@@ -4231,7 +4231,7 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
             _avatarEntitiesLock.withReadLock([&] {
                 avatarEntityIDs = _packedAvatarEntityData.keys();
             });
-            EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
+            auto packetSender = qApp->getEntityEditPacketSender();
             entityTree->withWriteLock([&] {
                 for (const auto& entityID : avatarEntityIDs) {
                     auto entity = entityTree->findEntityByID(entityID);
@@ -6888,7 +6888,7 @@ void MyAvatar::sendPacket(const QUuid& entityID) const {
     if (entityTree) {
         entityTree->withWriteLock([&] {
             // force an update packet
-            EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
+            auto packetSender = qApp->getEntityEditPacketSender();
             packetSender->queueEditAvatarEntityMessage(entityTree, entityID);
         });
     }
diff --git a/interface/src/main.cpp b/interface/src/main.cpp
index 835e4060a7..7e8c1afff3 100644
--- a/interface/src/main.cpp
+++ b/interface/src/main.cpp
@@ -24,6 +24,7 @@
 #include <SharedUtil.h>
 #include <NetworkAccessManager.h>
 #include <gl/GLHelpers.h>
+#include <iostream>
 
 #include "AddressManager.h"
 #include "Application.h"
@@ -33,6 +34,9 @@
 #include "MainWindow.h"
 #include "Profile.h"
 #include "LogHandler.h"
+#include <plugins/PluginManager.h>
+#include <plugins/DisplayPlugin.h>
+#include <plugins/CodecPlugin.h>
 
 #ifdef Q_OS_WIN
 #include <Windows.h>
@@ -63,11 +67,24 @@ int main(int argc, const char* argv[]) {
     }
 #endif
 
+    // Setup QCoreApplication settings, install log message handler
     setupHifiApplication(BuildInfo::INTERFACE_NAME);
 
     // Journald by default in user applications is probably a bit too modern still.
     LogHandler::getInstance().setShouldUseJournald(false);
 
+
+    // Extend argv to enable WebGL rendering
+    std::vector<const char*> argvExtended(&argv[0], &argv[argc]);
+    argvExtended.push_back("--ignore-gpu-blocklist");
+#ifdef Q_OS_ANDROID
+    argvExtended.push_back("--suppress-settings-reset");
+#endif
+    int argcExtended = (int)argvExtended.size();
+
+    QElapsedTimer startupTime;
+    startupTime.start();
+
     QCommandLineParser parser;
     parser.setApplicationDescription("Overte -- A free/libre and open-source virtual worlds client");
     QCommandLineOption helpOption = parser.addHelpOption();
@@ -125,12 +142,12 @@ int main(int argc, const char* argv[]) {
         "displays"
     );
     QCommandLineOption disableDisplaysOption(
-        "disable-displays",
+        "disableDisplayPlugins",
         "Displays to disable. Valid options include \"OpenVR (Vive)\" and \"Oculus Rift\"",
         "string"
     );
     QCommandLineOption disableInputsOption(
-        "disable-inputs",
+        "disableInputPlugins",
         "Inputs to disable. Valid options include \"OpenVR (Vive)\" and \"Oculus Rift\"",
         "string"
     );
@@ -246,6 +263,19 @@ int main(int argc, const char* argv[]) {
         "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald",
         "options"
     );
+    QCommandLineOption getPluginsOption(
+        "getPlugins",
+        "Print out a list of plugins in JSON"
+    );
+    QCommandLineOption abortAfterStartupOption(
+        "abortAfterStartup",
+        "Debug option. Aborts right after startup."
+    );
+    QCommandLineOption abortAfterInitOption(
+        "abortAfterInit",
+        "Debug option. Aborts after initialization, right before the program starts running the event loop."
+    );
+
     // "--qmljsdebugger", which appears in output from "--help-all".
     // Those below don't seem to be optional.
     //     --ignore-gpu-blacklist
@@ -288,6 +318,10 @@ int main(int argc, const char* argv[]) {
     parser.addOption(quitWhenFinishedOption);
     parser.addOption(fastHeartbeatOption);
     parser.addOption(logOption);
+    parser.addOption(abortAfterStartupOption);
+    parser.addOption(abortAfterInitOption);
+    parser.addOption(getPluginsOption);
+
 
     QString applicationPath;
     // A temporary application instance is needed to get the location of the running executable
@@ -310,6 +344,16 @@ int main(int argc, const char* argv[]) {
 #endif
     }
 
+    // TODO: We need settings for Application, but Settings needs an Application
+    // to handle events. Needs splitting into two parts: enough initialization
+    // for Application to work, and then thread start afterwards.
+    Setting::init();
+    Application app(argcExtended, const_cast<char**>(argvExtended.data()), parser, startupTime);
+
+    if (parser.isSet("abortAfterStartup")) {
+        return 99;
+    }
+
     // We want to configure the logging system as early as possible
     auto& logHandler = LogHandler::getInstance();
 
@@ -321,6 +365,75 @@ int main(int argc, const char* argv[]) {
         }
     }
 
+    app.initializePluginManager(parser);
+
+    if (parser.isSet(getPluginsOption)) {
+        auto pluginManager = PluginManager::getInstance();
+
+        QJsonObject pluginsJson;
+        for (const auto &plugin : pluginManager->getPluginInfo()) {
+            QJsonObject data;
+            data["data"] = plugin.metaData;
+            data["loaded"] = plugin.loaded;
+            data["disabled"] = plugin.disabled;
+            data["filteredOut"] = plugin.filteredOut;
+            data["wrongVersion"] = plugin.wrongVersion;
+            pluginsJson[plugin.name] = data;
+        }
+
+        QJsonObject inputJson;
+        for (const auto &plugin : pluginManager->getInputPlugins()) {
+            QJsonObject data;
+            data["subdeviceNames"] = QJsonArray::fromStringList(plugin->getSubdeviceNames());
+            data["deviceName"] = plugin->getDeviceName();
+            data["configurable"] = plugin->configurable();
+            data["isHandController"] = plugin->isHandController();
+            data["isHeadController"] = plugin->isHeadController();
+            data["isActive"] = plugin->isActive();
+            data["isSupported"] = plugin->isSupported();
+
+            inputJson[plugin->getName()] = data;
+        }
+
+        QJsonObject displayJson;
+        for (const auto &plugin : pluginManager->getDisplayPlugins()) {
+            QJsonObject data;
+            data["isHmd"] = plugin->isHmd();
+            data["isStereo"] = plugin->isStereo();
+            data["targetFramerate"] = plugin->getTargetFrameRate();
+            data["hasAsyncReprojection"] = plugin->hasAsyncReprojection();
+            data["isActive"] = plugin->isActive();
+            data["isSupported"] = plugin->isSupported();
+
+            displayJson[plugin->getName()] = data;
+        }
+
+        QJsonObject codecsJson;
+        for (const auto &plugin : pluginManager->getCodecPlugins()) {
+            QJsonObject data;
+            data["isActive"] = plugin->isActive();
+            data["isSupported"] = plugin->isSupported();
+
+            codecsJson[plugin->getName()] = data;
+        }
+
+        QJsonObject platformsJson;
+        platformsJson["steamAvailable"] = (pluginManager->getSteamClientPlugin() != nullptr);
+        platformsJson["oculusAvailable"] = (pluginManager->getOculusPlatformPlugin() != nullptr);
+
+        QJsonObject root;
+        root["plugins"] = pluginsJson;
+        root["inputs"] = inputJson;
+        root["displays"] = displayJson;
+        root["codecs"] = codecsJson;
+        root["platforms"] = platformsJson;
+
+        std::cout << QJsonDocument(root).toJson().toStdString() << "\n";
+
+        return 0;
+    }
+
+
     // Act on arguments for early termination.
     if (parser.isSet(versionOption)) {
         parser.showVersion();
@@ -407,10 +520,9 @@ int main(int argc, const char* argv[]) {
     QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
 #endif
 
-    QElapsedTimer startupTime;
-    startupTime.start();
 
-    Setting::init();
+
+
 
     // Instance UserActivityLogger now that the settings are loaded
     auto& ual = UserActivityLogger::getInstance();
@@ -549,7 +661,7 @@ int main(int argc, const char* argv[]) {
     // Oculus initialization MUST PRECEDE OpenGL context creation.
     // The nature of the Application constructor means this has to be either here,
     // or in the main window ctor, before GL startup.
-    Application::initPlugins(parser);
+    //app.configurePlugins(parser);
 
 #ifdef Q_OS_WIN
     // If we're running in steam mode, we need to do an explicit check to ensure we're up to the required min spec
@@ -587,17 +699,10 @@ int main(int argc, const char* argv[]) {
             SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater);
         }
 
-        // Extend argv to enable WebGL rendering
-        std::vector<const char*> argvExtended(&argv[0], &argv[argc]);
-        argvExtended.push_back("--ignore-gpu-blocklist");
-#ifdef Q_OS_ANDROID
-        argvExtended.push_back("--suppress-settings-reset");
-#endif
-        int argcExtended = (int)argvExtended.size();
-
         PROFILE_SYNC_END(startup, "main startup", "");
         PROFILE_SYNC_BEGIN(startup, "app full ctor", "");
-        Application app(argcExtended, const_cast<char**>(argvExtended.data()), parser, startupTime, runningMarkerExisted);
+        app.setPreviousSessionCrashed(runningMarkerExisted);
+        app.initialize(parser);
         PROFILE_SYNC_END(startup, "app full ctor", "");
 
 #if defined(Q_OS_LINUX)
@@ -665,6 +770,9 @@ int main(int argc, const char* argv[]) {
         translator.load("i18n/interface_en");
         app.installTranslator(&translator);
         qCDebug(interfaceapp, "Created QT Application.");
+        if (parser.isSet("abortAfterInit")) {
+            return 99;
+        }
         exitCode = app.exec();
         server.close();
 
diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp
index c1ca853563..0281a012d2 100644
--- a/libraries/plugins/src/plugins/PluginManager.cpp
+++ b/libraries/plugins/src/plugins/PluginManager.cpp
@@ -72,12 +72,13 @@ int getPluginInterfaceVersionFromMetaData(const QJsonObject& object) {
 QStringList preferredDisplayPlugins;
 QStringList disabledDisplays;
 QStringList disabledInputs;
+std::vector<PluginManager::PluginInfo> pluginInfo;
 
 bool isDisabled(QJsonObject metaData) {
     auto name = getPluginNameFromMetaData(metaData);
     auto iid = getPluginIIDFromMetaData(metaData);
 
-    if (iid == DisplayProvider_iid) {
+    if (iid == DisplayProvider_iid || iid == SteamClientProvider_iid || iid == OculusPlatformProvider_iid) {
         return disabledDisplays.contains(name);
     } else if (iid == InputProvider_iid) {
         return disabledInputs.contains(name);
@@ -126,18 +127,28 @@ int PluginManager::instantiate() {
                 qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin);
                 auto loader = QSharedPointer<QPluginLoader>::create(pluginPath + plugin);
                 const QJsonObject pluginMetaData = loader->metaData();
+
+                PluginInfo info;
+                info.name = plugin;
+                info.metaData = pluginMetaData;
+
 #if defined(HIFI_PLUGINMANAGER_DEBUG)
                 QJsonDocument metaDataDoc(pluginMetaData);
                 qCInfo(plugins) << "Metadata for " << qPrintable(plugin) << ": " << QString(metaDataDoc.toJson());
 #endif
                 if (isDisabled(pluginMetaData)) {
                     qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "is disabled";
+                    info.disabled = true;
+                    pluginInfo.push_back(info);
+
                     // Skip this one, it's disabled
                     continue;
                 }
 
                 if (!_pluginFilter(pluginMetaData)) {
                     qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "doesn't pass provided filter";
+                    info.filteredOut = true;
+                    pluginInfo.push_back(info);
                     continue;
                 }
 
@@ -145,16 +156,22 @@ int PluginManager::instantiate() {
                     qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "interface version doesn't match, not loading:"
                                        << getPluginInterfaceVersionFromMetaData(pluginMetaData)
                                        << "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION;
+
+                    info.wrongVersion = true;
+                    pluginInfo.push_back(info);
                     continue;
                 }
 
                 if (loader->load()) {
                     qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "loaded successfully";
+                    info.loaded = true;
                     loadedPlugins.push_back(loader);
                 } else {
                     qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "failed to load:";
                     qCDebug(plugins) << " " << qPrintable(loader->errorString());
                 }
+
+                pluginInfo.push_back(info);
             }
         } else {
             qWarning() << "pluginPath does not exit..." << pluginDir;
@@ -163,6 +180,11 @@ int PluginManager::instantiate() {
     return loadedPlugins;
 }
 
+std::vector<PluginManager::PluginInfo> PluginManager::getPluginInfo() const {
+    getLoadedPlugins(); // This builds the pluginInfo list
+    return pluginInfo;
+}
+
 const CodecPluginList& PluginManager::getCodecPlugins() {
     static CodecPluginList codecPlugins;
     static std::once_flag once;
@@ -272,14 +294,6 @@ DisplayPluginList PluginManager::getAllDisplayPlugins() {
     return _displayPlugins;
 }
 
-void PluginManager::disableDisplayPlugin(const QString& name) {
-    auto it = std::remove_if(_displayPlugins.begin(), _displayPlugins.end(), [&](const DisplayPluginPointer& plugin){
-        return plugin->getName() == name;
-    });
-    _displayPlugins.erase(it, _displayPlugins.end());
-}
-
-
 const InputPluginList& PluginManager::getInputPlugins() {
     static std::once_flag once;
     static auto deviceAddedCallback = [&](QString deviceName) {
diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h
index 26c98ce5db..b529472e1f 100644
--- a/libraries/plugins/src/plugins/PluginManager.h
+++ b/libraries/plugins/src/plugins/PluginManager.h
@@ -12,54 +12,268 @@
 
 #include <DependencyManager.h>
 #include <SettingHandle.h>
+#include <QJsonDocument>
+#include <QJsonObject>
 
 #include "Forward.h"
 
 class QPluginLoader;
 using PluginManagerPointer = QSharedPointer<PluginManager>;
 
+/**
+ * @brief Manages loadable plugins
+ *
+ * The current implementation does initialization only once, as soon as it's needed.
+ * Once things are initialized the configuration is made permanent.
+ *
+ * Both loadable and statically modules are supported. Static modules have to be provided
+ * with setDisplayPluginProvider, setInputPluginProvider and setCodecPluginProvider.
+ *
+ * @warning Users of the PluginManager must take care to do any configuration very early
+ * on, because changes become impossible once initialization is done. Plugins can't be
+ * added or removed once that happens.
+ *
+ * Initialization is performed in the getDisplayPlugins, getInputPlugins and getCodecPlugins
+ * functions.
+ */
 class PluginManager : public QObject, public Dependency {
     SINGLETON_DEPENDENCY
     Q_OBJECT
 
 public:
+
+    /**
+     * @brief Information about known plugins
+     *
+     */
+    struct PluginInfo {
+        /**
+         * @brief Plugin metadata
+        */
+        QJsonObject metaData;
+
+        /**
+         * @brief Filename
+         *
+         */
+        QString name;
+
+        /**
+         * @brief Whether the plugin has been disabled
+         *
+         */
+        bool disabled = false;
+
+        /**
+         * @brief Whether the plugin has been filtered out by a filter
+         *
+         */
+        bool filteredOut = false;
+
+        /**
+         * @brief Whether the plugin has been not loaded because it's the wrong version
+         *
+         */
+        bool wrongVersion = false;
+
+        /**
+         * @brief Whether the plugin has been loaded successfully
+         *
+         */
+        bool loaded = false;
+    };
+
+
     static PluginManagerPointer getInstance();
 
+    /**
+     * @brief Get the list of display plugins
+     *
+     * @note Calling this function will perform initialization and
+     * connects events to all the known the plugins on the first call.
+     *
+     * @return const DisplayPluginList&
+     */
     const DisplayPluginList& getDisplayPlugins();
+
+    /**
+     * @brief Get the list of input plugins
+     *
+     * @note Calling this function will perform initialization and
+     * connects events to all the known the plugins on the first call.
+     *
+     * @return const InputPluginList&
+     */
     const InputPluginList& getInputPlugins();
+
+    /**
+     * @brief Get the list of audio codec plugins
+     *
+     * @note Calling this function will perform initialization and
+     * connects events to all the known the plugins on the first call.
+     *
+     * @return const CodecPluginList&
+     */
     const CodecPluginList& getCodecPlugins();
+
+    /**
+     * @brief Get the pointer to the Steam client plugin
+     *
+     * This may return a null pointer if Steam support isn't built in.
+     *
+     * @return const SteamClientPluginPointer
+     */
     const SteamClientPluginPointer getSteamClientPlugin();
+
+    /**
+     * @brief Get the pointer to the Oculus Platform Plugin
+     *
+     * This may return a null pointer if Oculus support isn't built in.
+     *
+     * @return const OculusPlatformPluginPointer
+     */
     const OculusPlatformPluginPointer getOculusPlatformPlugin();
 
+    /**
+     * @brief Returns the list of preferred display plugins
+     *
+     * The preferred display plugins are set by setPreferredDisplayPlugins.
+     *
+     * @return DisplayPluginList
+     */
     DisplayPluginList getPreferredDisplayPlugins();
+
+    /**
+     * @brief Sets the list of preferred display plugins
+     *
+     * @note This must be called early, before any call to getPreferredDisplayPlugins.
+     *
+     * @param displays
+     */
     void setPreferredDisplayPlugins(const QStringList& displays);
 
-    void disableDisplayPlugin(const QString& name);
+    /**
+     * @brief Disable a list of displays
+     *
+     * This adds the display to a list of displays not to be used.
+     *
+     * @param displays
+     */
     void disableDisplays(const QStringList& displays);
+
+    /**
+     * @brief Disable a list of inputs
+     *
+     * This adds the input to a list of inputs not to be used.
+     * @param inputs
+     */
     void disableInputs(const QStringList& inputs);
+
+    /**
+     * @brief Save the settings
+     *
+     */
     void saveSettings();
+
+    /**
+     * @brief Set the container for plugins
+     *
+     * This will be passed to all active plugins on initialization.
+     *
+     * @param container
+     */
     void setContainer(PluginContainer* container) { _container = container; }
 
     int instantiate();
     void shutdown();
 
-    // Application that have statically linked plugins can expose them to the plugin manager with these function
+
+    /**
+     * @brief Provide a list of statically linked plugins.
+     *
+     * This is used to provide a list of statically linked plugins to the plugin manager.
+     *
+     * @note This must be called very early on, and only works once. Once the plugin manager
+     * builds its internal list of plugins, the final list becomes set in stone.
+     *
+     * @param provider A std::function that returns a list of display plugins
+     */
     void setDisplayPluginProvider(const DisplayPluginProvider& provider);
+
+    /**
+     * @brief Provide a list of statically linked plugins.
+     *
+     * This is used to provide a list of statically linked plugins to the plugin manager.
+     *
+     * @note This must be called very early on, and only works once. Once the plugin manager
+     * builds its internal list of plugins, the final list becomes set in stone.
+     *
+     * @param provider A std::function that returns a list of input plugins
+     */
     void setInputPluginProvider(const InputPluginProvider& provider);
+
+    /**
+     * @brief Provide a list of statically linked plugins.
+     *
+     * This is used to provide a list of statically linked plugins to the plugin manager.
+     *
+     * @note This must be called very early on, and only works once. Once the plugin manager
+     * builds its internal list of plugins, the final list becomes set in stone.
+     *
+     * @param provider A std::function that returns a list of codec plugins
+     */
     void setCodecPluginProvider(const CodecPluginProvider& provider);
+
+    /**
+     * @brief Set the input plugin persister
+     *
+     * @param persister A std::function that saves input plugin settings
+     */
     void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister);
+
+    /**
+     * @brief Get the list of running input devices
+     *
+     * @return QStringList List of input devices in running state
+     */
     QStringList getRunningInputDeviceNames() const;
 
     using PluginFilter = std::function<bool(const QJsonObject&)>;
+
+    /**
+     * @brief Set the plugin filter that determines whether a plugin will be used or not
+     *
+     * @note This must be called very early on. Once the plugin manager
+     * builds its internal list of plugins, the final list becomes set in stone.
+     *
+     * As of writing, this is used in the audio mixer.
+     *
+     * @param pluginFilter
+     */
     void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; }
+
+    /**
+     * @brief Get a list of all the display plugins
+     *
+     * @return DisplayPluginList List of display plugins
+     */
     Q_INVOKABLE DisplayPluginList getAllDisplayPlugins();
 
     bool getEnableOculusPluginSetting() { return _enableOculusPluginSetting.get(); }
     void setEnableOculusPluginSetting(bool value);
 
+    /**
+     * @brief Returns information about known plugins
+     *
+     * This is a function for informative/debugging purposes.
+     *
+     * @return std::vector<PluginInfo>
+     */
+    std::vector<PluginInfo> getPluginInfo() const;
+
 signals:
     void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices);
-    
+
 private:
     PluginManager() = default;