diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 28fa760231..83d6bc1b28 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1266,9 +1266,32 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     // Make sure the window is set to the correct size by processing the pending events
     QCoreApplication::processEvents();
     _glWidget->createContext();
-    _glWidget->makeCurrent();
 
+    // Create the main thread context, the GPU backend, and the display plugins
     initializeGL();
+    qCDebug(interfaceapp, "Initialized Display.");
+    // Create the rendering engine.  This can be slow on some machines due to lots of 
+    // GPU pipeline creation.
+    initializeRenderEngine();
+    qCDebug(interfaceapp, "Initialized Render Engine.");
+
+    // Initialize the user interface and menu system
+    // Needs to happen AFTER the render engine initialization to access its configuration
+    initializeUi();
+
+    init();
+    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);
+
+    _idleLoopStdev.reset();
+
+    // update before the first render
+    update(0);
+
     // Make sure we don't time out during slow operations at startup
     updateHeartbeat();
 
@@ -2430,48 +2453,47 @@ void Application::initializeGL() {
         _isGLInitialized = true;
     }
 
-    // Build a shared canvas / context for the Chromium processes
     _glWidget->makeCurrent();
+    glClearColor(0.2f, 0.2f, 0.2f, 1);
+    glClear(GL_COLOR_BUFFER_BIT);
+    _glWidget->swapBuffers();
 
-#if !defined(DISABLE_QML)
-    // Chromium rendering uses some GL functions that prevent nSight from capturing
-    // frames, so we only create the shared context if nsight is NOT active.
-    if (!nsightActive()) {
-        _chromiumShareContext = new OffscreenGLCanvas();
-        _chromiumShareContext->setObjectName("ChromiumShareContext");
-        _chromiumShareContext->create(_glWidget->qglContext());
-        _chromiumShareContext->makeCurrent();
-        if (!_chromiumShareContext->makeCurrent()) {
-            qCWarning(interfaceapp, "Unable to make chromium shared context current");
-        }
-        qt_gl_set_global_share_context(_chromiumShareContext->getContext());
-    } else {
-        qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
+    // Build an offscreen GL context for the main thread.
+    _offscreenContext = new OffscreenGLCanvas();
+    _offscreenContext->setObjectName("MainThreadContext");
+    _offscreenContext->create(_glWidget->qglContext());
+    if (!_offscreenContext->makeCurrent()) {
+        qFatal("Unable to make offscreen context current");
     }
-#endif
+    _offscreenContext->doneCurrent();
+    _offscreenContext->setThreadContext();
 
-    // Build a shared canvas / context for the QML rendering
-    _glWidget->makeCurrent();
-    _qmlShareContext = new OffscreenGLCanvas();
-    _qmlShareContext->setObjectName("QmlShareContext");
-    _qmlShareContext->create(_glWidget->qglContext());
-    if (!_qmlShareContext->makeCurrent()) {
-        qCWarning(interfaceapp, "Unable to make QML shared context current");
+    // Move the GL widget context to the render event handler thread
+    _renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
+    if (!_offscreenContext->makeCurrent()) {
+        qFatal("Unable to make offscreen context current");
     }
-    OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
-    _qmlShareContext->doneCurrent();
 
+    // Create the GPU backend
+
+    // Requires the window context, because that's what's used in the actual rendering
+    // and the GPU backend will make things like the VAO which cannot be shared across 
+    // contexts
     _glWidget->makeCurrent();
     gpu::Context::init<gpu::gl::GLBackend>();
     qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
         QVariant::fromValue((void*)(&gpu::gl::GLBackend::makeProgram)));
-    _gpuContext = std::make_shared<gpu::Context>();
-    // The gpu context can make child contexts for transfers, so
-    // we need to restore primary rendering context
     _glWidget->makeCurrent();
+    _gpuContext = std::make_shared<gpu::Context>();
 
-    initDisplay();
-    qCDebug(interfaceapp, "Initialized Display.");
+    // Restore the default main thread context
+    _offscreenContext->makeCurrent();
+
+    updateDisplayMode();
+}
+
+void Application::initializeRenderEngine() {
+    _offscreenContext->makeCurrent();
 
     // FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
     DeadlockWatchdogThread::withPause([&] {
@@ -2488,66 +2510,44 @@ void Application::initializeGL() {
         // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
         DependencyManager::get<GeometryCache>()->initializeShapePipelines();
     });
-
-    _offscreenContext = new OffscreenGLCanvas();
-    _offscreenContext->setObjectName("MainThreadContext");
-    _offscreenContext->create(_glWidget->qglContext());
-    if (!_offscreenContext->makeCurrent()) {
-        qFatal("Unable to make offscreen context current");
-    }
-    _offscreenContext->doneCurrent();
-    _offscreenContext->setThreadContext();
-    _renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
-
-    // The UI can't be created until the primary OpenGL
-    // context is created, because it needs to share
-    // texture resources
-    // Needs to happen AFTER the render engine initialization to access its configuration
-    if (!_offscreenContext->makeCurrent()) {
-        qFatal("Unable to make offscreen context current");
-    }
-
-    initializeUi();
-    qCDebug(interfaceapp, "Initialized Offscreen UI.");
-
-    if (!_offscreenContext->makeCurrent()) {
-        qFatal("Unable to make offscreen context current");
-    }
-    init();
-    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);
-
-    _idleLoopStdev.reset();
-
-
-    // Restore the primary GL content for the main thread
-    if (!_offscreenContext->makeCurrent()) {
-        qFatal("Unable to make offscreen context current");
-    }
-
-    // update before the first render
-    update(0);
 }
 
 extern void setupPreferences();
 
 void Application::initializeUi() {
+    // Build a shared canvas / context for the Chromium processes
+#if !defined(DISABLE_QML)
+    // Chromium rendering uses some GL functions that prevent nSight from capturing
+    // frames, so we only create the shared context if nsight is NOT active.
+    if (!nsightActive()) {
+        _chromiumShareContext = new OffscreenGLCanvas();
+        _chromiumShareContext->setObjectName("ChromiumShareContext");
+        _chromiumShareContext->create(_offscreenContext->getContext());
+        if (!_chromiumShareContext->makeCurrent()) {
+            qCWarning(interfaceapp, "Unable to make chromium shared context current");
+        }
+        qt_gl_set_global_share_context(_chromiumShareContext->getContext());
+        _chromiumShareContext->doneCurrent();
+        // Restore the GL widget context
+        _offscreenContext->makeCurrent();
+    } else {
+        qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
+    }
+#endif
+
+    // Build a shared canvas / context for the QML rendering
+    _qmlShareContext = new OffscreenGLCanvas();
+    _qmlShareContext->setObjectName("QmlShareContext");
+    _qmlShareContext->create(_offscreenContext->getContext());
+    if (!_qmlShareContext->makeCurrent()) {
+        qCWarning(interfaceapp, "Unable to make QML shared context current");
+    }
+    OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
+    _qmlShareContext->doneCurrent();
+    // Restore the GL widget context
+    _offscreenContext->makeCurrent();
     // Make sure all QML surfaces share the main thread GL context
     OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
-    OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" },
-        [](QQmlContext* context) {
-        qDebug() << "Whitelist OverlayWindow worked";
-        context->setContextProperty("OverlayWindowTestString", "TestWorked");
-    });
-    OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" },
-        [](QQmlContext* context) {
-        qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked";
-    });
-
 
     AddressBarDialog::registerType();
     ErrorDialog::registerType();
@@ -2650,6 +2650,10 @@ void Application::initializeUi() {
     auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
     offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
     offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
+
+    // Now that the menu is instantiated, ensure the display plugin menu is properly updated
+    updateDisplayMode();
+    flushMenuUpdates();
 }
 
 
@@ -4611,11 +4615,8 @@ QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) {
     return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z);
 }
 
-void Application::initDisplay() {
-}
-
 void Application::init() {
-
+    _offscreenContext->makeCurrent();
     // Make sure Login state is up to date
     DependencyManager::get<DialogsManager>()->toggleLoginDialog();
     if (!DISABLE_DEFERRED) {
@@ -7509,21 +7510,34 @@ void Application::updateDisplayMode() {
         qFatal("Attempted to switch display plugins from a non-main thread");
     }
 
-    auto menu = Menu::getInstance();
     auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
 
+    // Once time initialization code 
     static std::once_flag once;
     std::call_once(once, [&] {
-        bool first = true;
-
-        // first sort the plugins into groupings: standard, advanced, developer
-        DisplayPluginList standard;
-        DisplayPluginList advanced;
-        DisplayPluginList developer;
         foreach(auto displayPlugin, displayPlugins) {
             displayPlugin->setContext(_gpuContext);
-            auto grouping = displayPlugin->getGrouping();
-            switch (grouping) {
+            QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged,
+                [this](const QSize& size) { resizeGL(); });
+            QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
+        }
+    });
+
+    // Once time initialization code that depends on the UI being available
+    auto menu = Menu::getInstance();
+    if (menu) {
+        static std::once_flag onceUi;
+        std::call_once(onceUi, [&] {
+            bool first = true;
+
+            // first sort the plugins into groupings: standard, advanced, developer
+            DisplayPluginList standard;
+            DisplayPluginList advanced;
+            DisplayPluginList developer;
+            foreach(auto displayPlugin, displayPlugins) {
+                displayPlugin->setContext(_gpuContext);
+                auto grouping = displayPlugin->getGrouping();
+                switch (grouping) {
                 case Plugin::ADVANCED:
                     advanced.push_back(displayPlugin);
                     break;
@@ -7533,42 +7547,40 @@ void Application::updateDisplayMode() {
                 default:
                     standard.push_back(displayPlugin);
                     break;
+                }
             }
-        }
 
-        // concatenate the groupings into a single list in the order: standard, advanced, developer
-        standard.insert(std::end(standard), std::begin(advanced), std::end(advanced));
-        standard.insert(std::end(standard), std::begin(developer), std::end(developer));
+            // concatenate the groupings into a single list in the order: standard, advanced, developer
+            standard.insert(std::end(standard), std::begin(advanced), std::end(advanced));
+            standard.insert(std::end(standard), std::begin(developer), std::end(developer));
 
-        foreach(auto displayPlugin, standard) {
-            addDisplayPluginToMenu(displayPlugin, first);
-            auto displayPluginName = displayPlugin->getName();
-            QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
-                resizeGL();
-            });
-            QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
-            first = false;
-        }
+            foreach(auto displayPlugin, standard) {
+                addDisplayPluginToMenu(displayPlugin, first);
+                first = false;
+            }
 
-        // after all plugins have been added to the menu, add a separator to the menu
-        auto menu = Menu::getInstance();
-        auto parent = menu->getMenu(MenuOption::OutputMenu);
-        parent->addSeparator();
-    });
+            // after all plugins have been added to the menu, add a separator to the menu
+            auto parent = menu->getMenu(MenuOption::OutputMenu);
+            parent->addSeparator();
+        });
+
+    }
 
 
     // Default to the first item on the list, in case none of the menu items match
     DisplayPluginPointer newDisplayPlugin = displayPlugins.at(0);
-    foreach(DisplayPluginPointer 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 (menu) {
+        foreach(DisplayPluginPointer 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;
+            }
         }
     }
 
@@ -7576,8 +7588,13 @@ void Application::updateDisplayMode() {
         return;
     }
 
+    setDisplayPlugin(newDisplayPlugin);
+}
+
+void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
     auto desktop = offscreenUi->getDesktop();
+    auto menu = Menu::getInstance();
 
     // Make the switch atomic from the perspective of other threads
     {
@@ -7598,6 +7615,8 @@ void Application::updateDisplayMode() {
         bool active = newDisplayPlugin->activate();
 
         if (!active) {
+            auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
+
             // If the new plugin fails to activate, fallback to last display
             qWarning() << "Failed to activate display: " << newDisplayPlugin->getName();
             newDisplayPlugin = oldDisplayPlugin;
@@ -7618,13 +7637,6 @@ void Application::updateDisplayMode() {
             if (!active) {
                 qFatal("Failed to activate fallback plugin");
             }
-
-            // We've changed the selection - it should be reflected in the menu
-            QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
-            if (!action) {
-                qFatal("Failed to find activated plugin");
-            }
-            action->setChecked(true);
         }
 
         offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
@@ -7651,14 +7663,21 @@ void Application::updateDisplayMode() {
     getMyAvatar()->reset(false);
 
     // switch to first person if entering hmd and setting is checked
-    if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
-        menu->setIsOptionChecked(MenuOption::FirstPerson, true);
-        cameraMenuChanged();
-    }
+    if (menu) {
+        QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
+        if (action) {
+            action->setChecked(true);
+        }
 
-    // Remove the mirror camera option from menu if in HMD mode
-    auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
-    mirrorAction->setVisible(!isHmd);
+        if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
+            menu->setIsOptionChecked(MenuOption::FirstPerson, true);
+            cameraMenuChanged();
+        }
+
+        // Remove the mirror camera option from menu if in HMD mode
+        auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
+        mirrorAction->setVisible(!isHmd);
+    }
 
     Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
 }
@@ -7734,15 +7753,18 @@ void Application::unresponsiveApplication() {
 }
 
 void Application::setActiveDisplayPlugin(const QString& pluginName) {
-    auto menu = Menu::getInstance();
-    foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
+    DisplayPluginPointer newDisplayPlugin;
+    for (DisplayPluginPointer displayPlugin : PluginManager::getInstance()->getDisplayPlugins()) {
         QString name = displayPlugin->getName();
-        QAction* action = menu->getActionForOption(name);
         if (pluginName == name) {
-            action->setChecked(true);
+            newDisplayPlugin = displayPlugin;
+            break;
         }
     }
-    updateDisplayMode();
+
+    if (newDisplayPlugin) {
+        setDisplayPlugin(newDisplayPlugin);
+    }
 }
 
 void Application::handleLocalServerConnection() const {
diff --git a/interface/src/Application.h b/interface/src/Application.h
index d7fbb48a58..769658b0d6 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -145,6 +145,7 @@ public:
     Q_INVOKABLE QString getUserAgent();
 
     void initializeGL();
+    void initializeRenderEngine();
     void initializeUi();
 
     void updateCamera(RenderArgs& renderArgs, float deltaTime);
@@ -437,6 +438,7 @@ private slots:
     static void packetSent(quint64 length);
     static void addingEntityWithCertificate(const QString& certificateID, const QString& placeName);
     void updateDisplayMode();
+    void setDisplayPlugin(DisplayPluginPointer newPlugin);
     void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);
 
     void addAssetToWorldCheckModelSize();
@@ -449,7 +451,6 @@ private slots:
     void switchDisplayMode();
 
 private:
-    static void initDisplay();
     void init();
     bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event);
     bool handleFileOpenEvent(QFileOpenEvent* event);
diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp
index cfdfb1fc21..cb69d3a514 100644
--- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp
@@ -56,10 +56,8 @@ glm::mat4 StereoDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& basePr
 
 static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate";
 
-std::vector<QAction*> _screenActions;
 bool StereoDisplayPlugin::internalActivate() {
     auto screens = qApp->screens();
-    _screenActions.resize(screens.size());
     for (int i = 0; i < screens.size(); ++i) {
         auto screen = screens.at(i);
         QString name = QString("Screen %1: %2").arg(i + 1).arg(screen->name());
@@ -67,9 +65,9 @@ bool StereoDisplayPlugin::internalActivate() {
         if (screen == qApp->primaryScreen()) {
             checked = true;
         }
-        auto action = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name,
-            [this](bool clicked) { updateScreen(); }, true, checked, "Screens");
-        _screenActions[i] = action;
+        const uint32_t screenIndex = i;
+        _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name,
+            [=](bool clicked) { updateScreen(screenIndex); }, true, checked, "Screens");
     }
 
     _container->removeMenu(FRAMERATE);
@@ -80,18 +78,12 @@ bool StereoDisplayPlugin::internalActivate() {
     return Parent::internalActivate();
 }
 
-void StereoDisplayPlugin::updateScreen() {
-    for (uint32_t i = 0; i < _screenActions.size(); ++i) {
-        if (_screenActions[i]->isChecked()) {
-            _screen = qApp->screens().at(i);
-            _container->setFullscreen(_screen);
-            break;
-        }
-    }
+void StereoDisplayPlugin::updateScreen(uint32_t i) {
+    _screen = qApp->screens().at(i);
+    _container->setFullscreen(_screen);
 }
 
 void StereoDisplayPlugin::internalDeactivate() {
-    _screenActions.clear();
     _container->unsetFullscreen();
     Parent::internalDeactivate();
 }
diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h
index c4205ea1db..5a7ca24059 100644
--- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h
@@ -31,7 +31,7 @@ public:
 protected:
     virtual bool internalActivate() override;
     virtual void internalDeactivate() override;
-    void updateScreen();
+    void updateScreen(uint32_t i);
 
     float _ipd{ 0.064f };
     QScreen* _screen;
diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp
index c1d049f7f3..1c0ad1a85e 100644
--- a/libraries/gl/src/gl/GLWidget.cpp
+++ b/libraries/gl/src/gl/GLWidget.cpp
@@ -72,6 +72,10 @@ void GLWidget::createContext() {
     _context->doneCurrent();
 }
 
+void GLWidget::swapBuffers() {
+    _context->swapBuffers();
+}
+
 bool GLWidget::makeCurrent() {
     gl::Context::makeCurrent(_context->qglContext(), windowHandle());
     return _context->makeCurrent();
diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h
index 21dffc1b75..a0bf8ea0b0 100644
--- a/libraries/gl/src/gl/GLWidget.h
+++ b/libraries/gl/src/gl/GLWidget.h
@@ -32,6 +32,7 @@ public:
     void createContext();
     bool makeCurrent();
     void doneCurrent();
+    void swapBuffers();
     gl::Context* context() { return _context; }
     QOpenGLContext* qglContext();
 
diff --git a/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp
index 6d54351743..58dc971cb9 100644
--- a/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp
+++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp
@@ -34,48 +34,140 @@ PluginContainer::~PluginContainer() {
     INSTANCE = nullptr;
 };
 
+struct MenuCache {
+    QSet<QString> menus;
+    struct Item {
+        QString path;
+        std::function<void(bool)> onClicked;
+        bool checkable;
+        bool checked;
+        QString groupName;
+    };
+    QHash<QString, Item> items;
+    std::map<QString, QActionGroup*> _exclusiveGroups;
+
+    void addMenu(ui::Menu* menu, const QString& menuName) {
+        if (!menu) {
+            menus.insert(menuName);
+            return;
+        }
+
+        flushCache(menu);
+        menu->addMenu(menuName);
+    }
+
+    void removeMenu(ui::Menu* menu, const QString& menuName) {
+        if (!menu) {
+            menus.remove(menuName);
+            return;
+        }
+        flushCache(menu);
+        menu->removeMenu(menuName);
+    }
+
+    void addMenuItem(ui::Menu* menu, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
+        if (!menu) {
+            items[name] = Item{ path, onClicked, checkable, checked, groupName };
+            return;
+        }
+        flushCache(menu);
+        MenuWrapper* parentItem = menu->getMenu(path);
+        QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name);
+        if (!groupName.isEmpty()) {
+            QActionGroup* group{ nullptr };
+            if (!_exclusiveGroups.count(groupName)) {
+                group = _exclusiveGroups[groupName] = new QActionGroup(menu);
+                group->setExclusive(true);
+            } else {
+                group = _exclusiveGroups[groupName];
+            }
+            group->addAction(action);
+        }
+        QObject::connect(action, &QAction::triggered, [=] {
+            onClicked(action->isChecked());
+        });
+        action->setCheckable(checkable);
+        action->setChecked(checked);
+    }
+    void removeMenuItem(ui::Menu* menu, const QString& menuName, const QString& menuItemName) {
+        if (!menu) {
+            items.remove(menuItemName);
+            return;
+        }
+        flushCache(menu);
+        menu->removeMenuItem(menuName, menuItemName);
+    }
+
+    bool isOptionChecked(ui::Menu* menu, const QString& name) {
+        if (!menu) {
+            return items.contains(name) && items[name].checked;
+        }
+        flushCache(menu);
+        return menu->isOptionChecked(name);
+    }
+
+    void setIsOptionChecked(ui::Menu* menu, const QString& name, bool checked) {
+        if (!menu) {
+            if (items.contains(name)) {
+                items[name].checked = checked;
+            }
+            return;
+        }
+        flushCache(menu);
+
+    }
+
+    void flushCache(ui::Menu* menu) {
+        if (!menu) {
+            return;
+        }
+        static bool flushed = false;
+        if (flushed) {
+            return;
+        }
+        flushed = true;
+        for (const auto& menuName : menus) {
+            addMenu(menu, menuName);
+        }
+        menus.clear();
+
+        for (const auto& menuItemName : items.keys()) {
+            const auto menuItem = items[menuItemName];
+            addMenuItem(menu, menuItem.path, menuItemName, menuItem.onClicked, menuItem.checkable, menuItem.checked, menuItem.groupName);
+        }
+        items.clear();
+    }
+};
+
+
+static MenuCache& getMenuCache() {
+    static MenuCache cache;
+    return cache;
+}
 
 void PluginContainer::addMenu(const QString& menuName) {
-    getPrimaryMenu()->addMenu(menuName);
+    getMenuCache().addMenu(getPrimaryMenu(), menuName);
 }
 
 void PluginContainer::removeMenu(const QString& menuName) {
-    getPrimaryMenu()->removeMenu(menuName);
+    getMenuCache().removeMenu(getPrimaryMenu(), menuName);
 }
 
-QAction* PluginContainer::addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
-    auto menu = getPrimaryMenu();
-    MenuWrapper* parentItem = menu->getMenu(path);
-    QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name);
-    if (!groupName.isEmpty()) {
-        QActionGroup* group { nullptr };
-        if (!_exclusiveGroups.count(groupName)) {
-            group = _exclusiveGroups[groupName] = new QActionGroup(menu);
-            group->setExclusive(true);
-        } else {
-            group = _exclusiveGroups[groupName];
-        }
-        group->addAction(action);
-    }
-    QObject::connect(action, &QAction::triggered, [=] {
-        onClicked(action->isChecked());
-    });
-    action->setCheckable(checkable);
-    action->setChecked(checked);
+void PluginContainer::addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
+    getMenuCache().addMenuItem(getPrimaryMenu(), path, name, onClicked, checkable, checked, groupName);
     if (type == PluginType::DISPLAY_PLUGIN) {
         _currentDisplayPluginActions.push_back({ path, name });
     } else {
         _currentInputPluginActions.push_back({ path, name });
     }
-    return action;
 }
 
 void PluginContainer::removeMenuItem(const QString& menuName, const QString& menuItem) {
-    getPrimaryMenu()->removeMenuItem(menuName, menuItem);
+    getMenuCache().removeMenuItem(getPrimaryMenu(), menuName, menuItem);
 }
 
 bool PluginContainer::isOptionChecked(const QString& name) {
-    return getPrimaryMenu()->isOptionChecked(name);
+    return getMenuCache().isOptionChecked(getPrimaryMenu(), name);
 }
 
 void PluginContainer::setIsOptionChecked(const QString& path, bool checked) {
@@ -161,3 +253,7 @@ void PluginContainer::setBoolSetting(const QString& settingName, bool value) {
     Setting::Handle<bool> settingValue(settingName, value);
     return settingValue.set(value);
 }
+
+void PluginContainer::flushMenuUpdates() {
+    getMenuCache().flushCache(getPrimaryMenu());
+}
diff --git a/libraries/ui-plugins/src/ui-plugins/PluginContainer.h b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h
index 167af100b3..da9ea46cf4 100644
--- a/libraries/ui-plugins/src/ui-plugins/PluginContainer.h
+++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h
@@ -46,7 +46,7 @@ public:
 
     void addMenu(const QString& menuName);
     void removeMenu(const QString& menuName);
-    QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "");
+    void addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "");
     void removeMenuItem(const QString& menuName, const QString& menuItem);
     bool isOptionChecked(const QString& name);
     void setIsOptionChecked(const QString& path, bool checked);
@@ -77,9 +77,9 @@ public:
     }
 
 protected:
+    void flushMenuUpdates();
     QVector<QPair<QString, QString>> _currentDisplayPluginActions;
     QVector<QPair<QString, QString>> _currentInputPluginActions;
-    std::map<QString, QActionGroup*> _exclusiveGroups;
     QRect _savedGeometry { 10, 120, 800, 600 };
 };