diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 969a05a792..6d13305af7 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2382,6 +2382,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         checkLoginTimer->start();
     }
 #endif
+
+    const QString SPLASH_SKYBOX { "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
+    _splashScreen->parse(SPLASH_SKYBOX);
 }
 
 void Application::updateVerboseLogging() {
@@ -2862,8 +2865,6 @@ void Application::initializeGL() {
     DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
 }
 
-static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
-
 void Application::initializeDisplayPlugins() {
     auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
     Setting::Handle<QString> activeDisplayPluginSetting{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() };
@@ -2899,45 +2900,6 @@ void Application::initializeDisplayPlugins() {
 
     // Submit a default frame to render until the engine starts up
     updateRenderArgs(0.0f);
-
-#define ENABLE_SPLASH_FRAME 0
-#if ENABLE_SPLASH_FRAME
-    {
-        QMutexLocker viewLocker(&_renderArgsMutex);
-
-        if (_appRenderArgs._isStereo) {
-            _gpuContext->enableStereo(true);
-            _gpuContext->setStereoProjections(_appRenderArgs._eyeProjections);
-            _gpuContext->setStereoViews(_appRenderArgs._eyeOffsets);
-        }
-
-        // Frame resources
-        auto framebufferCache = DependencyManager::get<FramebufferCache>();
-        gpu::FramebufferPointer finalFramebuffer = framebufferCache->getFramebuffer();
-        std::shared_ptr<ProceduralSkybox> procedural = std::make_shared<ProceduralSkybox>();
-        procedural->parse(SPLASH_SKYBOX);
-
-        _gpuContext->beginFrame(_appRenderArgs._view, _appRenderArgs._headPose);
-        gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
-            batch.resetStages();
-            batch.enableStereo(false);
-            batch.setFramebuffer(finalFramebuffer);
-            batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, { 0, 0, 0, 1 });
-            batch.enableSkybox(true);
-            batch.enableStereo(_appRenderArgs._isStereo);
-            batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
-            procedural->render(batch, _appRenderArgs._renderArgs.getViewFrustum());
-        });
-        auto frame = _gpuContext->endFrame();
-        frame->frameIndex = 0;
-        frame->framebuffer = finalFramebuffer;
-        frame->pose = _appRenderArgs._headPose;
-        frame->framebufferRecycler = [framebufferCache, procedural](const gpu::FramebufferPointer& framebuffer) {
-            framebufferCache->releaseFramebuffer(framebuffer);
-        };
-        _displayPlugin->submitFrame(frame);
-    }
-#endif
 }
 
 void Application::initializeRenderEngine() {
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 9b8aac425a..fb1c916c36 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -793,5 +793,7 @@ private:
 
     bool _showTrackedObjects { false };
     bool _prevShowTrackedObjects { false };
+
+    std::shared_ptr<ProceduralSkybox> _splashScreen { std::make_shared<ProceduralSkybox>() };
 };
 #endif // hifi_Application_h
diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp
index a87caeb9a8..47cde80ab3 100644
--- a/interface/src/Application_render.cpp
+++ b/interface/src/Application_render.cpp
@@ -62,6 +62,7 @@ void Application::paintGL() {
     glm::mat4  HMDSensorPose;
     glm::mat4  eyeToWorld;
     glm::mat4  sensorToWorld;
+    ViewFrustum viewFrustum;
 
     bool isStereo;
     glm::mat4  stereoEyeOffsets[2];
@@ -84,6 +85,7 @@ void Application::paintGL() {
             stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];
             stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[eye];
         });
+        viewFrustum = _appRenderArgs._renderArgs.getViewFrustum();
     }
 
     {
@@ -94,21 +96,12 @@ void Application::paintGL() {
         gpu::doInBatch("Application_render::gpuContextReset", _gpuContext, [&](gpu::Batch& batch) {
             batch.resetStages();
         });
-    }
 
-
-    {
-        PROFILE_RANGE(render, "/renderOverlay");
-        PerformanceTimer perfTimer("renderOverlay");
-        // NOTE: There is no batch associated with this renderArgs
-        // the ApplicationOverlay class assumes it's viewport is setup to be the device size
-        renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize());
-        _applicationOverlay.renderOverlay(&renderArgs);
-    }
-
-    {
-        PROFILE_RANGE(render, "/updateCompositor");
-        getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
+        if (isStereo) {
+            renderArgs._context->enableStereo(true);
+            renderArgs._context->setStereoProjections(stereoEyeProjections);
+            renderArgs._context->setStereoViews(stereoEyeOffsets);
+        }
     }
 
     gpu::FramebufferPointer finalFramebuffer;
@@ -122,17 +115,38 @@ void Application::paintGL() {
         finalFramebuffer = framebufferCache->getFramebuffer();
     }
 
-    {
-        if (isStereo) {
-            renderArgs._context->enableStereo(true);
-            renderArgs._context->setStereoProjections(stereoEyeProjections);
-            renderArgs._context->setStereoViews(stereoEyeOffsets);
+    if (!displayPlugin->areAllProgramsLoaded()) {
+        gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
+            batch.enableStereo(false);
+            batch.setFramebuffer(finalFramebuffer);
+            batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, { 0, 0, 0, 1 });
+            batch.enableSkybox(true);
+            batch.enableStereo(isStereo);
+            batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
+            _splashScreen->render(batch, viewFrustum);
+        });
+    } else {
+        {
+            PROFILE_RANGE(render, "/renderOverlay");
+            PerformanceTimer perfTimer("renderOverlay");
+            // NOTE: There is no batch associated with this renderArgs
+            // the ApplicationOverlay class assumes it's viewport is setup to be the device size
+            renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize());
+            _applicationOverlay.renderOverlay(&renderArgs);
         }
 
-        renderArgs._hudOperator = displayPlugin->getHUDOperator();
-        renderArgs._hudTexture = _applicationOverlay.getOverlayTexture();
-        renderArgs._blitFramebuffer = finalFramebuffer;
-        runRenderFrame(&renderArgs);
+        {
+            PROFILE_RANGE(render, "/updateCompositor");
+            getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
+        }
+
+        {
+            PROFILE_RANGE(render, "/runRenderFrame");
+            renderArgs._hudOperator = displayPlugin->getHUDOperator();
+            renderArgs._hudTexture = _applicationOverlay.getOverlayTexture();
+            renderArgs._blitFramebuffer = finalFramebuffer;
+            runRenderFrame(&renderArgs);
+        }
     }
 
     auto frame = _gpuContext->endFrame();
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
index 190d4d4104..f562efbe22 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
@@ -31,6 +31,7 @@
 
 #include <gpu/Texture.h>
 #include <shaders/Shaders.h>
+#include <gpu/gl/GLShader.h>
 #include <gpu/gl/GLShared.h>
 #include <gpu/gl/GLBackend.h>
 #include <GeometryCache.h>
@@ -637,6 +638,17 @@ void OpenGLDisplayPlugin::present() {
     auto frameId = (uint64_t)presentCount();
     PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId)
     uint64_t startPresent = usecTimestampNow();
+
+    if (!_allProgramsLoaded) {
+        const auto& programIDs = shader::allPrograms();
+        if (_currentLoadingProgramIndex < programIDs.size()) {
+            auto shader = gpu::Shader::createProgram(programIDs.at(_currentLoadingProgramIndex++));
+            gpu::gl::GLShader::sync(*getGLBackend(), *shader);
+        } else {
+            _allProgramsLoaded = true;
+        }
+    }
+
     {
         PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId)
         updateFrameData();
@@ -829,6 +841,9 @@ void OpenGLDisplayPlugin::render(std::function<void(gpu::Batch& batch)> f) {
     _gpuContext->executeBatch(batch);
 }
 
+OpenGLDisplayPlugin::OpenGLDisplayPlugin() : DisplayPlugin() {
+    _allProgramsLoaded = false;
+}
 
 OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
 }
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
index 1cc060161b..1e3983d366 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
@@ -38,6 +38,7 @@ protected:
     using Lock = std::unique_lock<Mutex>;
     using Condition = std::condition_variable;
 public:
+    OpenGLDisplayPlugin();
     ~OpenGLDisplayPlugin();
     // These must be final to ensure proper ordering of operations
     // between the main thread and the presentation thread
@@ -180,5 +181,7 @@ protected:
     // be serialized through this mutex
     mutable Mutex _presentMutex;
     float _hudAlpha{ 1.0f };
+
+    size_t _currentLoadingProgramIndex { 0 };
 };
 
diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp
index 44d2bd6ca0..cf123a3f66 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp
@@ -37,6 +37,7 @@ GLShader* GLShader::sync(GLBackend& backend, const Shader& shader, const Shader:
     if (object) {
         return object;
     }
+    PROFILE_RANGE(render, "/GLShader::sync");
     // need to have a gpu object?
     if (shader.isProgram()) {
         GLShader* tempObject = backend.compileBackendProgram(shader, handler);
diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h
index ad49ceafe6..e925309042 100644
--- a/libraries/plugins/src/plugins/DisplayPlugin.h
+++ b/libraries/plugins/src/plugins/DisplayPlugin.h
@@ -217,6 +217,8 @@ public:
 
     static const QString& MENU_PATH();
 
+    bool areAllProgramsLoaded() { return _allProgramsLoaded; }
+
 signals:
     void recommendedFramebufferSizeChanged(const QSize& size);
     void resetSensorsRequested();
@@ -233,6 +235,8 @@ protected:
 
     float _renderResolutionScale { 1.0f };
 
+    std::atomic<bool> _allProgramsLoaded { true };
+
 private:
     QMutex _presentMutex;
     QWaitCondition _presentCondition;