diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 779307c19d..cc6c4930ff 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -369,14 +369,6 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
             reverbTime = _zoneReverbSettings[i].reverbTime;
             wetLevel = _zoneReverbSettings[i].wetLevel;
 
-            // Modulate wet level with distance to wall
-            float MIN_ATTENUATION_DISTANCE = 2.0f;
-            float MAX_ATTENUATION = -12; // dB
-            glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter());
-            float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z);
-            if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) {
-                wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE);
-            }
             break;
         }
     }
diff --git a/cmake/externals/openvr/CMakeLists.txt b/cmake/externals/openvr/CMakeLists.txt
index 3fe7df44d0..930a339d12 100644
--- a/cmake/externals/openvr/CMakeLists.txt
+++ b/cmake/externals/openvr/CMakeLists.txt
@@ -7,8 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
 
 ExternalProject_Add(
   ${EXTERNAL_NAME}
-  URL https://github.com/ValveSoftware/openvr/archive/v0.9.15.zip
-  URL_MD5 0ff8560b49b6da1150fcc47360e8ceca
+  URL https://github.com/ValveSoftware/openvr/archive/v0.9.19.zip
+  URL_MD5 843f9dde488584d8af1f3ecf2252b4e0
   CONFIGURE_COMMAND ""
   BUILD_COMMAND ""
   INSTALL_COMMAND ""
diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 80ee32efa1..44a1796a8d 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -308,7 +308,7 @@
           "name": "reverb",
           "type": "table",
           "label": "Reverb Settings",
-          "help": "In this table you can set reverb levels for audio zones.  For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet level of -10 db. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet level of -5 db.",
+          "help": "In this table you can set reverb levels for audio zones.  For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
           "numbered": true,
           "columns": [
             {
@@ -325,9 +325,9 @@
             },
             {
               "name": "wet_level",
-              "label": "Wet Level",
+              "label": "Wet/Dry Mix",
               "can_set": true,
-              "placeholder": "(in db)"
+              "placeholder": "(in percent)"
             }
           ]
         }
diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index e3eb19dc4a..225a1d7957 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -451,6 +451,37 @@
 
                 var elPreviewCameraButton = document.getElementById("preview-camera-button");
     
+                var urlUpdaters = document.getElementsByClassName("update-url-version");
+                var PARAM_REGEXP = /(?:\?)(\S+)/; // Check if this has any parameters.
+                var TIMESTAMP_REGEXP = /(&?HFTime=\d+)/;
+
+                var refreshEvent = function(event){
+                    var urlElement = event.target.parentElement.getElementsByClassName("url")[0];
+                    var content = urlElement.value;
+                    var date = new Date();
+                    var timeStamp = date.getTime();
+
+                    if(content.length > 0){
+                        if(PARAM_REGEXP.test(content)){
+                        // Has params, so lets remove existing definition and append again.
+                            content = content.replace(TIMESTAMP_REGEXP,"") + "&";
+                        }else{
+                            content += "?";
+                        }
+                        content = content.replace("?&","?");
+                        urlElement.value = content + "HFTime=" + timeStamp;
+                    }
+
+                    var evt = document.createEvent("HTMLEvents");
+                    evt.initEvent("change", true, true );
+                    urlElement.dispatchEvent(evt);
+                };
+
+                for(var index = 0; index < urlUpdaters.length; index++){
+                    var urlUpdater = urlUpdaters[index];
+                    urlUpdater.addEventListener("click", refreshEvent);
+                }
+
                 if (window.EventBridge !== undefined) {
                     var properties;
                     EventBridge.scriptEventReceived.connect(function(data) {
@@ -1185,6 +1216,7 @@
             <div class="label">Ambient URL</div>
             <div class="value">
                 <input type="text" id="property-zone-key-ambient-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1262,6 +1294,7 @@
             <div class="label">Skybox URL</div>
             <div class="value">
                 <input type="text" id="property-zone-skybox-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1273,6 +1306,7 @@
             <div class="label">Source URL</div>
             <div class="value">
                 <input type="text" id="property-web-source-url" class="url">
+                <div class="update-url-version"></div>
 
             </div>
         </div>
@@ -1286,12 +1320,14 @@
             <div class="label">Href - Hifi://address</div>
             <div class="value">
                 <input id="property-hyperlink-href" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
         <div class="hyperlink-section property">
             <div class="label">Description</div>
             <div class="value">
                 <input id="property-hyperlink-description" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1375,16 +1411,19 @@
             <div class="label">X-axis Texture URL</div>
             <div class="value">
               <input type="text" id="property-x-texture-url" class="url">
+              <div class="update-url-version"></div>
             </div>
 
             <div class="label">Y-axis Texture URL</div>
             <div class="value">
               <input type="text" id="property-y-texture-url" class="url">
+              <div class="update-url-version"></div>
             </div>
 
             <div class="label">Z-axis Texture URL</div>
             <div class="value">
               <input type="text" id="property-z-texture-url" class="url">
+              <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1566,6 +1605,7 @@
             <div class="label">Collision Sound URL</div>
             <div class="value">
                 <input id="property-collision-sound-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1583,6 +1623,7 @@
             </div>
             <div class="value">
                 <input id="property-script-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1595,6 +1636,7 @@
             <div class="label">Model URL</div>
             <div class="value">
                 <input type="text" id="property-model-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
 
@@ -1613,12 +1655,14 @@
             <div class="label">Compound Shape URL</div>
             <div class="value">
                 <input type="text" id="property-compound-shape-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
         <div class="model-section property">
             <div class="label">Animation URL</div>
             <div class="value">
                 <input type="text" id="property-model-animation-url" class="url">
+                <div class="update-url-version"></div>
             </div>
         </div>
         <div class="model-section property">
diff --git a/examples/html/eventBridgeLoader.js b/examples/html/eventBridgeLoader.js
index ebfb6dc740..de6553ee1c 100644
--- a/examples/html/eventBridgeLoader.js
+++ b/examples/html/eventBridgeLoader.js
@@ -10,50 +10,11 @@
 
 var EventBridge;
 
-EventBridgeConnectionProxy = function(parent) {
-    this.parent = parent;
-    this.realSignal = this.parent.realBridge.scriptEventReceived
-    this.webWindowId = this.parent.webWindow.windowId;
-}
-
-EventBridgeConnectionProxy.prototype.connect = function(callback) {
-    var that = this;
-    this.realSignal.connect(function(id, message) {
-        if (id === that.webWindowId) { callback(message); }
+openEventBridge = function(callback) {
+    new QWebChannel(qt.webChannelTransport, function(channel) {
+        console.log("uid " + EventBridgeUid);
+        EventBridge = channel.objects[EventBridgeUid];
+        callback(EventBridge);
     });
 }
 
-EventBridgeProxy = function(webWindow) {
-    this.webWindow = webWindow;
-    this.realBridge = this.webWindow.eventBridge;  
-    this.scriptEventReceived = new EventBridgeConnectionProxy(this);
-}
-
-EventBridgeProxy.prototype.emitWebEvent = function(data) {
-    this.realBridge.emitWebEvent(data);
-}
-
-openEventBridge = function(callback) {
-    EVENT_BRIDGE_URI = "ws://localhost:51016";
-    socket = new WebSocket(this.EVENT_BRIDGE_URI);
-    
-    socket.onclose = function() { 
-        console.error("web channel closed"); 
-    };
-
-    socket.onerror = function(error) { 
-        console.error("web channel error: " + error); 
-    };
-
-    socket.onopen = function() {
-        channel = new QWebChannel(socket, function(channel) {
-            console.log("Document url is " + document.URL);
-            var webWindow = channel.objects[document.URL.toLowerCase()];
-            console.log("WebWindow is " + webWindow)
-            eventBridgeProxy = new EventBridgeProxy(webWindow);
-            EventBridge = eventBridgeProxy; 
-            if (callback) {  callback(eventBridgeProxy); }
-        });
-    }
-}
-
diff --git a/examples/html/qmlWebTest.html b/examples/html/qmlWebTest.html
index e59535701d..553ce83417 100644
--- a/examples/html/qmlWebTest.html
+++ b/examples/html/qmlWebTest.html
@@ -4,21 +4,17 @@
 <script type="text/javascript" src="jquery-2.1.4.min.js"></script>
 <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
 <script type="text/javascript" src="eventBridgeLoader.js"></script>
-
 <script>
-    var myBridge;
-
     window.onload = function() { 
-        openEventBridge(function(eventBridge) {
-            myBridge = eventBridge;
-            myBridge.scriptEventReceived.connect(function(message) {
+        openEventBridge(function() {
+            EventBridge.scriptEventReceived.connect(function(message) {
                 console.log("HTML side received message: " + message); 
             });
         });
     }
 
     testClick = function() {
-        myBridge.emitWebEvent("HTML side sending message - button click");
+        EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]);
     }
 </script>
 </head>
diff --git a/examples/html/style.css b/examples/html/style.css
index 83982dab15..3ae1c1739b 100644
--- a/examples/html/style.css
+++ b/examples/html/style.css
@@ -134,8 +134,18 @@ textarea {
     resize: vertical;
 }
 
+.update-url-version{
+    width:17px;
+    height:17px;
+    float:right;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAQAAACRZI9xAAABAUlEQVQoz33RvyvEARjH8SdlUX6UbhFJfgwmE3aTxWEiitxqN/gH7IxCKTcymLiJFIOJsihiUKcrCYnuXoZv+F45n2d4lvfz6fPpCSFs6BO1JllvLmXk5F37dC0vJ5NGmlBxoFp7Rn6RDpRRtG5KtynrijhLoBBGQcllKkFWAXsyCbKEFxeGqmJmFZFLkE639vX+12hCSVft0nUR8RbLcRN/aSHGIqL2tVYPconL32qLtaiL88Rl0qtXc+pTDv1OPGMlidtgS8Wp2RR05FEFM98/GlRQ9mTHvB7jVt2rKGPAT9xh2z6qfnToHV1SjZpN23Tl051di1oco9W/pdttaBRfEhFXOZV7vEsAAAAASUVORK5CYII=);
+    padding:0 !important;
+    margin:0 2px 0 0 !important;
+}
+
 input.url {
-    width: 100%;
+    width:85%;
+    padding-right: 20px;
 }
 
 input.coord {
diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js
index 5faa68668d..d29f2ba002 100644
--- a/examples/tests/qmlWebTest.js
+++ b/examples/tests/qmlWebTest.js
@@ -8,26 +8,14 @@ webWindow.eventBridge.webEventReceived.connect(function(data) {
     print("JS Side event received: " + data);
 });
 
-var titles = ["A", "B", "C"];
-var titleIndex = 0;
-
 Script.setInterval(function() {
-    webWindow.eventBridge.emitScriptEvent("JS Event sent");
-    var size = webWindow.size;
-    var position = webWindow.position;
-    print("Window url: " + webWindow.url)
-    print("Window visible: " + webWindow.visible)
-    print("Window size:  " + size.x + "x" + size.y)
-    print("Window pos:   " + position.x + "x" + position.y)
-    webWindow.setVisible(!webWindow.visible);
-    webWindow.setTitle(titles[titleIndex]);
-    webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
-    titleIndex += 1;
-    titleIndex %= titles.length;
-}, 2 * 1000);
+    var message = [ Math.random(), Math.random() ];
+    print("JS Side sending: " + message); 
+    webWindow.emitScriptEvent(message);
+}, 5 * 1000);
 
-Script.setTimeout(function() {
-    print("Closing script");
+Script.scriptEnding.connect(function(){
     webWindow.close();
-    Script.stop();
-}, 15 * 1000)
+    webWindow.deleteLater();
+});
+
diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml
index fd4e629568..0058770462 100644
--- a/interface/resources/qml/QmlWebWindow.qml
+++ b/interface/resources/qml/QmlWebWindow.qml
@@ -1,6 +1,7 @@
 import QtQuick 2.3
 import QtQuick.Controls 1.2
 import QtWebEngine 1.1
+import QtWebChannel 1.0
 
 import "windows" as Windows
 import "controls" as Controls
@@ -15,11 +16,22 @@ Windows.Window {
     // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
     destroyOnCloseButton: false
     property alias source: webview.url
+    property alias webChannel: webview.webChannel
+    // A unique identifier to let the HTML JS find the event bridge 
+    // object (our C++ wrapper)
+    property string uid;
+
+    // This is for JS/QML communication, which is unused in a WebWindow,
+    // but not having this here results in spurious warnings about a 
+    // missing signal
+    signal sendToScript(var message);
 
     Controls.WebView {
         id: webview
         url: "about:blank"
         anchors.fill: parent
         focus: true
+        onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
+        Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
     }
 } // dialog
diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml
index 75aa50aa34..5313ca23e9 100644
--- a/interface/resources/qml/ToolWindow.qml
+++ b/interface/resources/qml/ToolWindow.qml
@@ -37,14 +37,33 @@ Windows.Window {
         Repeater {
             model: 4
             Tab {
+                // Force loading of the content even if the tab is not visible
+                // (required for letting the C++ code access the webview)
                 active: true
-                enabled: false;
-                // we need to store the original url here for future identification
+                enabled: false
                 property string originalUrl: "";
-                onEnabledChanged: toolWindow.updateVisiblity();
+
                 Controls.WebView {
                     id: webView;
+                    // we need to store the original url here for future identification
+                    // A unique identifier to let the HTML JS find the event bridge 
+                    // object (our C++ wrapper)
+                    property string uid;
                     anchors.fill: parent
+                    enabled: false
+
+                    // This is for JS/QML communication, which is unused in a WebWindow,
+                    // but not having this here results in spurious warnings about a 
+                    // missing signal
+                    signal sendToScript(var message);
+                    
+                    onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
+                    onEnabledChanged: toolWindow.updateVisiblity();
+                    onLoadingChanged: {
+                        if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
+                            webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
+                        }
+                    }
                 }
             }
         }
@@ -113,20 +132,23 @@ Windows.Window {
 
         var tab = tabView.getTab(index);
         tab.title = "";
-        tab.originalUrl = "";
         tab.enabled = false;
+        tab.originalUrl = "";
+        tab.item.url = "about:blank";
+        tab.item.enabled = false;
     }
 
     function addWebTab(properties) {
         if (!properties.source) {
-            console.warn("Attempted to open Web Tool Pane without URL")
+            console.warn("Attempted to open Web Tool Pane without URL");
             return;
         }
 
         var existingTabIndex = findIndexForUrl(properties.source);
         if (existingTabIndex >= 0) {
-            console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
-            return tabView.getTab(existingTabIndex);
+            console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source);
+            var tab = tabView.getTab(existingTabIndex);
+            return tab.item;
         }
 
         var freeTabIndex = findFreeTab();
@@ -135,25 +157,22 @@ Windows.Window {
             return;
         }
 
-        var newTab = tabView.getTab(freeTabIndex);
-        newTab.title = properties.title || "Unknown";
-        newTab.originalUrl = properties.source;
-        newTab.item.url = properties.source;
-        newTab.active = true;
-
         if (properties.width) {
-            tabView.width = Math.min(Math.max(tabView.width, properties.width),
-                                        toolWindow.maxSize.x);
+            tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
         }
 
         if (properties.height) {
-            tabView.height = Math.min(Math.max(tabView.height, properties.height),
-                                        toolWindow.maxSize.y);
+            tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y);
         }
 
-        console.log("Updating visibility based on child tab added");
-        newTab.enabledChanged.connect(updateVisiblity)
-        updateVisiblity();
-        return newTab
+        var tab = tabView.getTab(freeTabIndex);
+        tab.title = properties.title || "Unknown";
+        tab.enabled = true;
+        tab.originalUrl = properties.source;
+
+        var result = tab.item;
+        result.enabled = true;
+        result.url = properties.source;
+        return result;
     }
 }
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 18080cd448..1361e6e322 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -59,6 +59,7 @@ WebEngineView {
             request.openIn(newWindow.webView)
     }
 
-
-    profile: desktop.browserProfile
+    // This breaks the webchannel used for passing messages.  Fixed in Qt 5.6
+    // See https://bugreports.qt.io/browse/QTBUG-49521
+    //profile: desktop.browserProfile
 }
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index e7bccae892..b9effa444b 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1415,7 +1415,7 @@ void Application::paintGL() {
         _lastFramesPerSecondUpdate = now;
     }
 
-    PROFILE_RANGE(__FUNCTION__);
+    PROFILE_RANGE_EX(__FUNCTION__, 0xff0000ff, (uint64_t)_frameCount);
     PerformanceTimer perfTimer("paintGL");
 
     if (nullptr == _displayPlugin) {
@@ -2554,11 +2554,12 @@ void Application::idle(uint64_t now) {
         return;
     }
 
+    PROFILE_RANGE(__FUNCTION__);
+
     // We're going to execute idle processing, so restart the last idle timer
     _lastTimeUpdated.start();
 
     {
-        PROFILE_RANGE(__FUNCTION__);
         static uint64_t lastIdleStart{ now };
         uint64_t idleStartToStartDuration = now - lastIdleStart;
         if (idleStartToStartDuration != 0) {
@@ -3146,6 +3147,9 @@ void Application::updateDialogs(float deltaTime) {
 }
 
 void Application::update(float deltaTime) {
+
+    PROFILE_RANGE_EX(__FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1);
+
     bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
     PerformanceWarning warn(showWarnings, "Application::update()");
 
@@ -3246,9 +3250,13 @@ void Application::update(float deltaTime) {
     QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
 
     if (_physicsEnabled) {
+        PROFILE_RANGE_EX("Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
+
         PerformanceTimer perfTimer("physics");
 
         {
+            PROFILE_RANGE_EX("UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
+
             PerformanceTimer perfTimer("updateStates)");
             static VectorOfMotionStates motionStates;
             _entitySimulation.getObjectsToRemoveFromPhysics(motionStates);
@@ -3281,12 +3289,14 @@ void Application::update(float deltaTime) {
             });
         }
         {
+            PROFILE_RANGE_EX("StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount());
             PerformanceTimer perfTimer("stepSimulation");
             getEntities()->getTree()->withWriteLock([&] {
                 _physicsEngine->stepSimulation();
             });
         }
         {
+            PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
             PerformanceTimer perfTimer("havestChanges");
             if (_physicsEngine->hasOutgoingChanges()) {
                 getEntities()->getTree()->withWriteLock([&] {
@@ -3321,17 +3331,24 @@ void Application::update(float deltaTime) {
 
         qApp->setAvatarSimrateSample(1.0f / deltaTime);
 
-        avatarManager->updateOtherAvatars(deltaTime);
+        {
+            PROFILE_RANGE_EX("OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
+            avatarManager->updateOtherAvatars(deltaTime);
+        }
 
         qApp->updateMyAvatarLookAtPosition();
 
         // update sensorToWorldMatrix for camera and hand controllers
         myAvatar->updateSensorToWorldMatrix();
 
-        avatarManager->updateMyAvatar(deltaTime);
+        {
+            PROFILE_RANGE_EX("MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
+            avatarManager->updateMyAvatar(deltaTime);
+        }
     }
 
     {
+        PROFILE_RANGE_EX("Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
         PerformanceTimer perfTimer("overlays");
         _overlays.update(deltaTime);
     }
@@ -3351,6 +3368,7 @@ void Application::update(float deltaTime) {
 
     // Update my voxel servers with my current voxel query...
     {
+        PROFILE_RANGE_EX("QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
         PerformanceTimer perfTimer("queryOctree");
         quint64 sinceLastQuery = now - _lastQueriedTime;
         const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
@@ -4677,13 +4695,18 @@ qreal Application::getDevicePixelRatio() {
 }
 
 DisplayPlugin* Application::getActiveDisplayPlugin() {
-    std::unique_lock<std::recursive_mutex> lock(_displayPluginLock);
-    if (nullptr == _displayPlugin && QThread::currentThread() == thread()) {
-        updateDisplayMode();
-        Q_ASSERT(_displayPlugin);
+    DisplayPlugin* result = nullptr;
+    if (QThread::currentThread() == thread()) {
+        if (nullptr == _displayPlugin) {
+            updateDisplayMode();
+            Q_ASSERT(_displayPlugin);
+        }
+        result = _displayPlugin.get();
+    } else {
+        std::unique_lock<std::mutex> lock(_displayPluginLock);
+        result = _displayPlugin.get();
     }
-    
-    return _displayPlugin.get();
+    return result;
 }
 
 const DisplayPlugin* Application::getActiveDisplayPlugin() const {
@@ -4801,20 +4824,26 @@ void Application::updateDisplayMode() {
         return;
     }
 
-    if (_displayPlugin) {
-        _displayPlugin->deactivate();
-    }
-
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
 
-    // FIXME probably excessive and useless context switching
-    _offscreenContext->makeCurrent();
-    newDisplayPlugin->activate();
-    _offscreenContext->makeCurrent();
-    offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
-    _offscreenContext->makeCurrent();
-    getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
-    _displayPlugin = newDisplayPlugin;
+    // Make the switch atomic from the perspective of other threads
+    {
+        std::unique_lock<std::mutex> lock(_displayPluginLock);
+
+        if (_displayPlugin) {
+            _displayPlugin->deactivate();
+        }
+
+        // FIXME probably excessive and useless context switching
+        _offscreenContext->makeCurrent();
+        newDisplayPlugin->activate();
+        _offscreenContext->makeCurrent();
+        offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
+        _offscreenContext->makeCurrent();
+        getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
+        _displayPlugin = newDisplayPlugin;
+    }
+
 
     emit activeDisplayPluginChanged();
 
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 695d30998a..d21e647bc7 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -383,7 +383,7 @@ private:
 
     OffscreenGLCanvas* _offscreenContext { nullptr };
     DisplayPluginPointer _displayPlugin;
-    std::recursive_mutex _displayPluginLock;
+    std::mutex _displayPluginLock;
     InputPluginList _activeInputPlugins;
 
     bool _activatingDisplayPlugin { false };
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 35bc2aa696..63c67f6b9f 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -107,7 +107,7 @@ Menu::Menu() {
 
     auto scriptEngines = DependencyManager::get<ScriptEngines>();
     // Edit > Stop All Scripts... [advanced]
-    addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0, 
+    addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0,
         scriptEngines.data(), SLOT(stopAllScripts()),
         QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
 
@@ -140,7 +140,7 @@ Menu::Menu() {
     // Edit > Reload All Content [advanced]
     addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()),
         QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
-    
+
 
     // Edit > Package Model... [advanced]
     addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
@@ -153,7 +153,7 @@ Menu::Menu() {
     auto audioIO = DependencyManager::get<AudioClient>();
 
     // Audio > Mute
-    addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, 
+    addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false,
         audioIO.data(), SLOT(toggleMute()));
 
     // Audio > Show Level Meter
@@ -458,7 +458,7 @@ Menu::Menu() {
         avatar, SLOT(setEnableMeshVisible(bool)));
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false);
-    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, false,
+    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true,
         avatar, SLOT(setUseAnimPreAndPostRotations(bool)));
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true,
         avatar, SLOT(setEnableInverseKinematics(bool)));
@@ -534,7 +534,7 @@ Menu::Menu() {
 
     // Developer > Audio >>>
     MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio");
-    addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, 
+    addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true,
         audioIO.data(), SLOT(toggleAudioNoiseReduction()));
     addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false,
         audioIO.data(), SLOT(toggleServerEcho()));
@@ -617,7 +617,7 @@ Menu::Menu() {
                                   QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
 
 
-    addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, 
+    addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true,
                 NULL, NULL, UNSPECIFIED_POSITION, "Advanced");
 #endif
 }
@@ -651,7 +651,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
         } else if (properties.isCheckable) {
             menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName,
                                                                     properties.shortcutKeySequence, properties.isChecked,
-                                                                    MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), 
+                                                                    MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()),
                                                                     requestedPosition, properties.grouping);
         } else {
             menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence,
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 778f5af4c8..056b5b76c4 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -187,7 +187,7 @@ void Avatar::simulate(float deltaTime) {
 
     // simple frustum check
     float boundingRadius = getBoundingRadius();
-    bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
+    bool inView = qApp->getDisplayViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
 
     if (_shouldAnimate && !_shouldSkipRender && inView) {
         {
diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp
index e8f429f22c..a5747e4f96 100644
--- a/libraries/animation/src/AnimClip.cpp
+++ b/libraries/animation/src/AnimClip.cpp
@@ -13,7 +13,7 @@
 #include "AnimationLogging.h"
 #include "AnimUtil.h"
 
-bool AnimClip::usePreAndPostPoseFromAnim = false;
+bool AnimClip::usePreAndPostPoseFromAnim = true;
 
 AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) :
     AnimNode(AnimNode::Type::Clip, id),
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index ae9adc71c2..a2b664d064 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -20,6 +20,7 @@
 #include <GeometryUtil.h>
 #include <NumericalConstants.h>
 #include <DebugDraw.h>
+#include <shared/NsightHelpers.h>
 
 #include "AnimationLogging.h"
 #include "AnimClip.h"
@@ -852,6 +853,8 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh
 
 void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
 
+    PROFILE_RANGE_EX(__FUNCTION__, 0xffff00ff, 0);
+
     setModelOffset(rootTransform);
 
     if (_animNode) {
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 4d44a771f7..7e01196dc7 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -565,10 +565,10 @@ void AudioClient::updateReverbOptions() {
             _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime());
             reverbChanged = true;
         }
-        //if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) {
-        //    _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel());
-        //    reverbChanged = true;
-        //}
+        if (_zoneReverbOptions.getWetDryMix() != _receivedAudioStream.getWetLevel()) {
+            _zoneReverbOptions.setWetDryMix(_receivedAudioStream.getWetLevel());
+            reverbChanged = true;
+        }
 
         if (_reverbOptions != &_zoneReverbOptions) {
             _reverbOptions = &_zoneReverbOptions;
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
index ca36ab35f0..d842dc553b 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
@@ -21,7 +21,7 @@
 #include <gl/GLWidget.h>
 #include <NumericalConstants.h>
 #include <DependencyManager.h>
-
+#include <shared/NsightHelpers.h>
 #include <plugins/PluginContainer.h>
 #include <gl/Config.h>
 #include <gl/GLEscrow.h>
@@ -404,7 +404,11 @@ void OpenGLDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overla
 
 void OpenGLDisplayPlugin::updateTextures() {
     // FIXME intrduce a GPU wait instead of a CPU/GPU sync point?
+#if THREADED_PRESENT
     if (_sceneTextureEscrow.fetchSignaledAndRelease(_currentSceneTexture)) {
+#else
+    if (_sceneTextureEscrow.fetchAndReleaseWithGpuWait(_currentSceneTexture)) {
+#endif
         updateFrameData();
     }
 
@@ -527,6 +531,9 @@ void OpenGLDisplayPlugin::internalPresent() {
 
 void OpenGLDisplayPlugin::present() {
     incrementPresentCount();
+
+    PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
+
     updateTextures();
     if (_currentSceneTexture) {
         // Write all layers to a local framebuffer
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
index 501232f7e7..7295b07ad3 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
@@ -17,9 +17,9 @@
 #include <GLMHelpers.h>
 #include <SimpleMovingAverage.h>
 #include <gl/OglplusHelpers.h>
-#include <gl/GLEscrow.h>
 
 #define THREADED_PRESENT 1
+#include <gl/GLEscrow.h>
 
 class OpenGLDisplayPlugin : public DisplayPlugin {
 protected:
diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp
index c3782b907f..b022b10887 100644
--- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp
@@ -19,6 +19,7 @@
 #include <gpu/GLBackend.h>
 #include <CursorManager.h>
 #include <gl/GLWidget.h>
+#include <shared/NsightHelpers.h>
 
 #include "../Logging.h"
 #include "../CompositorHelper.h"
@@ -106,6 +107,9 @@ void HmdDisplayPlugin::compositePointer() {
 }
 
 void HmdDisplayPlugin::internalPresent() {
+
+    PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
+
     // Composite together the scene, overlay and mouse cursor
     hmdPresent();
 
@@ -149,6 +153,8 @@ void HmdDisplayPlugin::internalPresent() {
         });
         swapBuffers();
     }
+
+    postPreview();
 }
 
 void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {
diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
index 659a3a16fa..fede16c3a5 100644
--- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
@@ -31,6 +31,7 @@ public:
 protected:
     virtual void hmdPresent() = 0;
     virtual bool isHmdMounted() const = 0;
+    virtual void postPreview() {};
 
     void internalActivate() override;
     void compositeOverlay() override;
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index d6fbdd5229..d6835502d0 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -113,16 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
         return _originalTextures;
     }
 
-    // TODO: Remove this line and enforce passing a texturemap as stringified JSON
-    QString jsonTextures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}";
+    // Legacy: a ,\n-delimited list of filename:"texturepath"
+    if (*textures.cbegin() != '{') {
+        textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}";
+    }
+
     QJsonParseError error;
-    QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error);
+    QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
     if (error.error != QJsonParseError::NoError) {
         qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
         return _originalTextures;
     }
-    QJsonObject texturesAsJsonObject = texturesAsJson.object();
-    return texturesAsJsonObject.toVariantMap();
+    return texturesJson.object().toVariantMap();
 }
 
 void RenderableModelEntityItem::remapTextures() {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 58a82d5f11..28f4882b86 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -320,13 +320,12 @@ void ImageReader::run() {
     }
 
     QMetaObject::invokeMethod(texture.data(), "setImage", 
-        Q_ARG(const QImage&, image),
         Q_ARG(void*, theTexture),
         Q_ARG(int, originalWidth), Q_ARG(int, originalHeight));
     QThread::currentThread()->setPriority(originalPriority);
 }
 
-void NetworkTexture::setImage(const QImage& image, void* voidTexture, int originalWidth,
+void NetworkTexture::setImage(void* voidTexture, int originalWidth,
                               int originalHeight) {
     _originalWidth = originalWidth;
     _originalHeight = originalHeight;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 6fb0cc3177..858a40de36 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -136,7 +136,7 @@ protected:
           
     Q_INVOKABLE void loadContent(const QByteArray& content);
     // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on...
-    Q_INVOKABLE void setImage(const QImage& image, void* texture, int originalWidth, int originalHeight);
+    Q_INVOKABLE void setImage(void* texture, int originalWidth, int originalHeight);
 
 
 private:
diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp
index 1d1a6628fe..d30be2c139 100644
--- a/libraries/networking/src/udt/CongestionControl.cpp
+++ b/libraries/networking/src/udt/CongestionControl.cpp
@@ -201,7 +201,7 @@ void DefaultCC::onTimeout() {
 
 void DefaultCC::stopSlowStart() {
     _slowStart = false;
-    
+
     if (_receiveRate > 0) {
         // Set the sending rate to the receiving rate.
         setPacketSendPeriod(USECS_PER_SECOND / _receiveRate);
diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp
index e5f3508b81..af70295840 100644
--- a/libraries/networking/src/udt/Connection.cpp
+++ b/libraries/networking/src/udt/Connection.cpp
@@ -103,6 +103,7 @@ SendQueue& Connection::getSendQueue() {
         QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission);
         QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive);
         QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout);
+        QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss);
         
         // set defaults on the send queue from our congestion control object and estimatedTimeout()
         _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod);
@@ -140,6 +141,12 @@ void Connection::queueTimeout() {
     });
 }
 
+void Connection::queueShortCircuitLoss(quint32 sequenceNumber) {
+    updateCongestionControlAndSendQueue([this, sequenceNumber]{
+        _congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber });
+    });
+}
+
 void Connection::sendReliablePacket(std::unique_ptr<Packet> packet) {
     Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably.");
     getSendQueue().queuePacket(std::move(packet));
diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h
index 4f5a8793e7..08a2df9b97 100644
--- a/libraries/networking/src/udt/Connection.h
+++ b/libraries/networking/src/udt/Connection.h
@@ -87,6 +87,7 @@ private slots:
     void recordRetransmission();
     void queueInactive();
     void queueTimeout();
+    void queueShortCircuitLoss(quint32 sequenceNumber);
     
 private:
     void sendACK(bool wasCausedBySyncTimeout = true);
diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp
index 9701561ec7..2ffa42cb82 100644
--- a/libraries/networking/src/udt/SendQueue.cpp
+++ b/libraries/networking/src/udt/SendQueue.cpp
@@ -128,13 +128,13 @@ void SendQueue::stop() {
     _emptyCondition.notify_one();
 }
     
-void SendQueue::sendPacket(const Packet& packet) {
-    _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination);
+int SendQueue::sendPacket(const Packet& packet) {
+    return _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination);
 }
     
 void SendQueue::ack(SequenceNumber ack) {
     // this is a response from the client, re-set our timeout expiry and our last response time
-    _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
+    _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
     
     if (_lastACKSequenceNumber == (uint32_t) ack) {
         return;
@@ -164,7 +164,7 @@ void SendQueue::ack(SequenceNumber ack) {
 
 void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
     // this is a response from the client, re-set our timeout expiry
-    _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
+    _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
     
     {
         std::lock_guard<std::mutex> nakLocker(_naksLock);
@@ -177,8 +177,8 @@ void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
 
 void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) {
     // this is a response from the client, re-set our timeout expiry
-    _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
-    
+    _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
+
     {
         std::lock_guard<std::mutex> nakLocker(_naksLock);
         _naks.clear();
@@ -232,15 +232,16 @@ SequenceNumber SendQueue::getNextSequenceNumber() {
     return _currentSequenceNumber;
 }
 
-void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) {
+bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) {
     // write the sequence number and send the packet
     newPacket->writeSequenceNumber(sequenceNumber);
-    sendPacket(*newPacket);
-    
+
     // Save packet/payload size before we move it
     auto packetSize = newPacket->getDataSize();
     auto payloadSize = newPacket->getPayloadSize();
     
+    auto bytesWritten = sendPacket(*newPacket);
+
     {
         // Insert the packet we have just sent in the sent list
         QWriteLocker locker(&_sentLock);
@@ -249,8 +250,24 @@ void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket,
         entry.second.swap(newPacket);
     }
     Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list");
-    
+
     emit packetSent(packetSize, payloadSize);
+
+    if (bytesWritten < 0) {
+        // this is a short-circuit loss - we failed to put this packet on the wire
+        // so immediately add it to the loss list
+
+        {
+            std::lock_guard<std::mutex> nakLocker(_naksLock);
+            _naks.append(sequenceNumber);
+        }
+
+        emit shortCircuitLoss(quint32(sequenceNumber));
+
+        return false;
+    } else {
+        return true;
+    }
 }
 
 void SendQueue::run() {
@@ -285,12 +302,14 @@ void SendQueue::run() {
     auto nextPacketTimestamp = p_high_resolution_clock::now();
 
     while (_state == State::Running) {
-        bool sentAPacket = maybeResendPacket();
+        bool attemptedToSendPacket = maybeResendPacket();
         
         // if we didn't find a packet to re-send AND we think we can fit a new packet on the wire
         // (this is according to the current flow window size) then we send out a new packet
-        if (!sentAPacket) {
-            sentAPacket = maybeSendNewPacket();
+        auto newPacketCount = 0;
+        if (!attemptedToSendPacket) {
+            newPacketCount = maybeSendNewPacket();
+            attemptedToSendPacket = (newPacketCount > 0);
         }
         
         // since we're a while loop, give the thread a chance to process events
@@ -300,12 +319,13 @@ void SendQueue::run() {
         // If the send queue has been innactive, skip the sleep for
         // Either _isRunning will have been set to false and we'll break
         // Or something happened and we'll keep going
-        if (_state != State::Running || isInactive(sentAPacket)) {
+        if (_state != State::Running || isInactive(attemptedToSendPacket)) {
             return;
         }
 
         // push the next packet timestamp forwards by the current packet send period
-        nextPacketTimestamp += std::chrono::microseconds(_packetSendPeriod);
+        auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod;
+        nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
 
         // sleep as long as we need until next packet send, if we can
         const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now());
@@ -314,7 +334,7 @@ void SendQueue::run() {
     }
 }
 
-bool SendQueue::maybeSendNewPacket() {
+int SendQueue::maybeSendNewPacket() {
     if (!isFlowWindowFull()) {
         // we didn't re-send a packet, so time to send a new one
         
@@ -324,38 +344,43 @@ bool SendQueue::maybeSendNewPacket() {
             // grab the first packet we will send
             std::unique_ptr<Packet> firstPacket = _packets.takePacket();
             Q_ASSERT(firstPacket);
-            
-            std::unique_ptr<Packet> secondPacket;
-            bool shouldSendPairTail = false;
-            
-            if (((uint32_t) nextNumber & 0xF) == 0) {
-                // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
-                // pull off a second packet if we can before we unlock
-                shouldSendPairTail = true;
-                
-                secondPacket = _packets.takePacket();
+
+
+            // attempt to send the first packet
+            if (sendNewPacketAndAddToSentList(move(firstPacket), nextNumber)) {
+                std::unique_ptr<Packet> secondPacket;
+                bool shouldSendPairTail = false;
+
+                if (((uint32_t) nextNumber & 0xF) == 0) {
+                    // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
+                    // pull off a second packet if we can before we unlock
+                    shouldSendPairTail = true;
+
+                    secondPacket = _packets.takePacket();
+                }
+
+                // do we have a second in a pair to send as well?
+                if (secondPacket) {
+                    sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
+                } else if (shouldSendPairTail) {
+                    // we didn't get a second packet to send in the probe pair
+                    // send a control packet of type ProbePairTail so the receiver can still do
+                    // proper bandwidth estimation
+                    static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
+                    _socket->writeBasePacket(*pairTailPacket, _destination);
+                }
+
+                // we attempted to send two packets, return 2
+                return 2;
+            } else {
+                // we attempted to send a single packet, return 1
+                return 1;
             }
-            
-            // definitely send the first packet
-            sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
-            
-            // do we have a second in a pair to send as well?
-            if (secondPacket) {
-                sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
-            } else if (shouldSendPairTail) {
-                // we didn't get a second packet to send in the probe pair
-                // send a control packet of type ProbePairTail so the receiver can still do
-                // proper bandwidth estimation
-                static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
-                _socket->writeBasePacket(*pairTailPacket, _destination);
-            }
-            
-            // We sent our packet(s), return here
-            return true;
         }
     }
+    
     // No packets were sent
-    return false;
+    return 0;
 }
 
 bool SendQueue::maybeResendPacket() {
@@ -375,8 +400,9 @@ bool SendQueue::maybeResendPacket() {
             
             // see if we can find the packet to re-send
             auto it = _sentPackets.find(resendNumber);
-            
+
             if (it != _sentPackets.end()) {
+
                 auto& entry = it->second;
                 // we found the packet - grab it
                 auto& resendPacket = *(entry.second);
@@ -437,7 +463,7 @@ bool SendQueue::maybeResendPacket() {
     return false;
 }
 
-bool SendQueue::isInactive(bool sentAPacket) {
+bool SendQueue::isInactive(bool attemptedToSendPacket) {
     // check for connection timeout first
 
     // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been
@@ -447,7 +473,8 @@ bool SendQueue::isInactive(bool sentAPacket) {
 
     auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse);
 
-    if (sinceLastResponse >= quint64(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) &&
+    if (sinceLastResponse > 0 &&
+        sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) &&
         _lastReceiverResponse > 0 &&
         sinceLastResponse > MIN_MS_BEFORE_INACTIVE) {
         // If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
@@ -462,7 +489,7 @@ bool SendQueue::isInactive(bool sentAPacket) {
         return true;
     }
 
-    if (!sentAPacket) {
+    if (!attemptedToSendPacket) {
         // During our processing above we didn't send any packets
         
         // If that is still the case we should use a condition_variable_any to sleep until we have data to handle.
diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h
index 9400ae8352..21f6141c3c 100644
--- a/libraries/networking/src/udt/SendQueue.h
+++ b/libraries/networking/src/udt/SendQueue.h
@@ -79,6 +79,7 @@ signals:
     
     void queueInactive();
 
+    void shortCircuitLoss(quint32 sequenceNumber);
     void timeout();
     
 private slots:
@@ -91,13 +92,13 @@ private:
     
     void sendHandshake();
     
-    void sendPacket(const Packet& packet);
-    void sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
+    int sendPacket(const Packet& packet);
+    bool sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
     
-    bool maybeSendNewPacket(); // Figures out what packet to send next
+    int maybeSendNewPacket(); // Figures out what packet to send next
     bool maybeResendPacket(); // Determines whether to resend a packet and which one
     
-    bool isInactive(bool sentAPacket);
+    bool isInactive(bool attemptedToSendPacket);
     void deactivate(); // makes the queue inactive and cleans it up
 
     bool isFlowWindowFull() const;
@@ -122,7 +123,7 @@ private:
     
     std::atomic<int> _estimatedTimeout { 0 }; // Estimated timeout, set from CC
     std::atomic<int> _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC
-    std::atomic<uint64_t> _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK)
+    std::atomic<int64_t> _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK)
     
     std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC
     
diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp
index 484f049944..56805e8f83 100644
--- a/libraries/render/src/render/CullTask.cpp
+++ b/libraries/render/src/render/CullTask.cpp
@@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render
 void CullSpatialSelection::configure(const Config& config) {
     _justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
     _freezeFrustum = config.freezeFrustum;
+    _skipCulling = config.skipCulling;
 }
 
 void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
@@ -191,60 +192,112 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
     // visibility cull if partially selected ( octree cell contianing it was partial)
     // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
 
-    // inside & fit items: easy, just filter
-    {
-        PerformanceTimer perfTimer("insideFitItems");
-        for (auto id : inSelection.insideItems) {
-            auto& item = scene->getItem(id);
-            if (_filter.test(item.getKey())) {
-                ItemBound itemBound(id, item.getBound());
-                outItems.emplace_back(itemBound);
-            }
-        }
-    }
-
-    // inside & subcell items: filter & distance cull
-    {
-        PerformanceTimer perfTimer("insideSmallItems");
-        for (auto id : inSelection.insideSubcellItems) {
-            auto& item = scene->getItem(id);
-            if (_filter.test(item.getKey())) {
-                ItemBound itemBound(id, item.getBound());
-                if (test.solidAngleTest(itemBound.bound)) {
+    if (_skipCulling) {
+        // inside & fit items: filter only, culling is disabled
+        {
+            PerformanceTimer perfTimer("insideFitItems");
+            for (auto id : inSelection.insideItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
                     outItems.emplace_back(itemBound);
                 }
             }
         }
-    }
 
-    // partial & fit items: filter & frustum cull
-    {
-        PerformanceTimer perfTimer("partialFitItems");
-        for (auto id : inSelection.partialItems) {
-            auto& item = scene->getItem(id);
-            if (_filter.test(item.getKey())) {
-                ItemBound itemBound(id, item.getBound());
-                if (test.frustumTest(itemBound.bound)) {
+        // inside & subcell items: filter only, culling is disabled
+        {
+            PerformanceTimer perfTimer("insideSmallItems");
+            for (auto id : inSelection.insideSubcellItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
                     outItems.emplace_back(itemBound);
                 }
             }
         }
-    }
 
-    // partial & subcell items:: filter & frutum cull & solidangle cull
-    {
-        PerformanceTimer perfTimer("partialSmallItems");
-        for (auto id : inSelection.partialSubcellItems) {
-            auto& item = scene->getItem(id);
-            if (_filter.test(item.getKey())) {
-                ItemBound itemBound(id, item.getBound());
-                if (test.frustumTest(itemBound.bound)) {
+        // partial & fit items: filter only, culling is disabled
+        {
+            PerformanceTimer perfTimer("partialFitItems");
+            for (auto id : inSelection.partialItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
+                    outItems.emplace_back(itemBound);
+                }
+            }
+        }
+
+        // partial & subcell items: filter only, culling is disabled
+        {
+            PerformanceTimer perfTimer("partialSmallItems");
+            for (auto id : inSelection.partialSubcellItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
+                    outItems.emplace_back(itemBound);
+                }
+            }
+        }
+
+    } else {
+
+        // inside & fit items: easy, just filter
+        {
+            PerformanceTimer perfTimer("insideFitItems");
+            for (auto id : inSelection.insideItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
+                    outItems.emplace_back(itemBound);
+                }
+            }
+        }
+
+        // inside & subcell items: filter & distance cull
+        {
+            PerformanceTimer perfTimer("insideSmallItems");
+            for (auto id : inSelection.insideSubcellItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
                     if (test.solidAngleTest(itemBound.bound)) {
                         outItems.emplace_back(itemBound);
                     }
                 }
             }
         }
+
+        // partial & fit items: filter & frustum cull
+        {
+            PerformanceTimer perfTimer("partialFitItems");
+            for (auto id : inSelection.partialItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
+                    if (test.frustumTest(itemBound.bound)) {
+                        outItems.emplace_back(itemBound);
+                    }
+                }
+            }
+        }
+
+        // partial & subcell items:: filter & frutum cull & solidangle cull
+        {
+            PerformanceTimer perfTimer("partialSmallItems");
+            for (auto id : inSelection.partialSubcellItems) {
+                auto& item = scene->getItem(id);
+                if (_filter.test(item.getKey())) {
+                    ItemBound itemBound(id, item.getBound());
+                    if (test.frustumTest(itemBound.bound)) {
+                        if (test.solidAngleTest(itemBound.bound)) {
+                            outItems.emplace_back(itemBound);
+                        }
+                    }
+                }
+            }
+        }
     }
 
     details._rendered += (int)outItems.size();
diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h
index a6a32e4561..e84f018e91 100644
--- a/libraries/render/src/render/CullTask.h
+++ b/libraries/render/src/render/CullTask.h
@@ -70,14 +70,16 @@ namespace render {
         Q_OBJECT
         Q_PROPERTY(int numItems READ getNumItems)
         Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
+        Q_PROPERTY(bool skipCulling MEMBER skipCulling WRITE setSkipCulling)
     public:
         int numItems{ 0 };
         int getNumItems() { return numItems; }
 
         bool freezeFrustum{ false };
+        bool skipCulling{ false };
     public slots:
         void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
-
+        void setSkipCulling(bool enabled) { skipCulling = enabled; emit dirty(); }
     signals:
         void dirty();
     };
@@ -85,6 +87,7 @@ namespace render {
     class CullSpatialSelection {
         bool _freezeFrustum{ false }; // initialized by Config
         bool _justFrozeFrustum{ false };
+        bool _skipCulling{ false };
         ViewFrustum _frozenFrutstum;
     public:
         using Config = CullSpatialSelectionConfig;
diff --git a/libraries/shared/src/shared/NsightHelpers.cpp b/libraries/shared/src/shared/NsightHelpers.cpp
index e48e228588..2539ff8864 100644
--- a/libraries/shared/src/shared/NsightHelpers.cpp
+++ b/libraries/shared/src/shared/NsightHelpers.cpp
@@ -8,6 +8,7 @@
 
 #include "NsightHelpers.h"
 
+#ifdef _WIN32
 #if defined(NSIGHT_FOUND)
 #include "nvToolsExt.h"
 
@@ -15,8 +16,28 @@ ProfileRange::ProfileRange(const char *name) {
     nvtxRangePush(name);
 }
 
+ProfileRange::ProfileRange(const char *name, uint32_t argbColor, uint64_t payload) {
+
+    nvtxEventAttributes_t eventAttrib = {0};
+    eventAttrib.version = NVTX_VERSION;
+    eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
+    eventAttrib.colorType = NVTX_COLOR_ARGB;
+    eventAttrib.color = argbColor;
+    eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII;
+    eventAttrib.message.ascii = name;
+    eventAttrib.payload.llValue = payload;
+    eventAttrib.payloadType = NVTX_PAYLOAD_TYPE_UNSIGNED_INT64;
+
+    nvtxRangePushEx(&eventAttrib);
+}
+
 ProfileRange::~ProfileRange() {
     nvtxRangePop();
 }
 
+#else
+ProfileRange::ProfileRange(const char *name) {}
+ProfileRange::ProfileRange(const char *name, uint32_t argbColor, uint64_t payload) {}
+ProfileRange::~ProfileRange() {}
 #endif
+#endif // _WIN32
diff --git a/libraries/shared/src/shared/NsightHelpers.h b/libraries/shared/src/shared/NsightHelpers.h
index 3acdf14411..9853171b34 100644
--- a/libraries/shared/src/shared/NsightHelpers.h
+++ b/libraries/shared/src/shared/NsightHelpers.h
@@ -9,16 +9,21 @@
 #ifndef hifi_gl_NsightHelpers_h
 #define hifi_gl_NsightHelpers_h
 
-#if defined(NSIGHT_FOUND)
-    class ProfileRange {
-    public:
-        ProfileRange(const char *name);
-        ~ProfileRange();
-    };
+#ifdef _WIN32
+#include <stdint.h>
+
+class ProfileRange {
+public:
+    ProfileRange(const char *name);
+    ProfileRange(const char *name, uint32_t argbColor, uint64_t payload);
+    ~ProfileRange();
+};
+
 #define PROFILE_RANGE(name) ProfileRange profileRangeThis(name);
+#define PROFILE_RANGE_EX(name, argbColor, payload) ProfileRange profileRangeThis(name, argbColor, payload);
 #else
 #define PROFILE_RANGE(name)
+#define PROFILE_RANGE_EX(name, argbColor, payload)
 #endif
 
-
-#endif
\ No newline at end of file
+#endif
diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp
index f12fb51b19..43fd5a64df 100644
--- a/libraries/ui/src/QmlWebWindowClass.cpp
+++ b/libraries/ui/src/QmlWebWindowClass.cpp
@@ -14,6 +14,8 @@
 
 #include <QtQml/QQmlContext>
 
+#include <QtWebChannel/QWebChannel>
+
 #include <QtScript/QScriptContext>
 #include <QtScript/QScriptEngine>
 
@@ -31,19 +33,39 @@ static const char* const URL_PROPERTY = "source";
 // Method called by Qt scripts to create a new web window in the overlay
 QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
     return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
-        [&](QObject* object) { return new QmlWebWindowClass(object); });
+        [&](QObject* object) {  return new QmlWebWindowClass(object);  });
 }
 
 QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
+    _uid = QUuid::createUuid().toString();
+    asQuickItem()->setProperty("uid", _uid);
+    auto webchannelVar = qmlWindow->property("webChannel");
+    _webchannel = qvariant_cast<QWebChannel*>(webchannelVar);
+    Q_ASSERT(_webchannel);
+    _webchannel->registerObject(_uid, this);
 }
 
+void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
+    } else {
+        emit scriptEventReceived(scriptMessage);
+    }
+}
 
-// FIXME remove.
-void QmlWebWindowClass::handleNavigation(const QString& url) {
+void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
+    } else {
+        emit webEventReceived(webMessage);
+    }
 }
 
 QString QmlWebWindowClass::getURL() const {
     QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
+        if (_qmlWindow.isNull()) {
+            return QVariant();
+        }
         return _qmlWindow->property(URL_PROPERTY);
     });
     return result.toString();
@@ -54,6 +76,8 @@ extern QString fixupHifiUrl(const QString& urlString);
 
 void QmlWebWindowClass::setURL(const QString& urlString) {
     DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        _qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString));
+        if (!_qmlWindow.isNull()) {
+            _qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString));
+        }
     });
 }
diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h
index 14e533c7b4..35322ef0ea 100644
--- a/libraries/ui/src/QmlWebWindowClass.h
+++ b/libraries/ui/src/QmlWebWindowClass.h
@@ -11,10 +11,13 @@
 
 #include "QmlWindowClass.h"
 
+class QWebChannel;
+
 // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping 
 class QmlWebWindowClass : public QmlWindowClass {
     Q_OBJECT
     Q_PROPERTY(QString url READ getURL CONSTANT)
+    Q_PROPERTY(QString uid READ getUid CONSTANT)
 
 public:
     static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
@@ -23,12 +26,18 @@ public:
 public slots:
     QString getURL() const;
     void setURL(const QString& url);
+    const QString& getUid() const { return _uid; }
+    void emitScriptEvent(const QVariant& scriptMessage);
+    void emitWebEvent(const QVariant& webMessage);
 
 signals:
     void urlChanged();
+    void scriptEventReceived(const QVariant& message);
+    void webEventReceived(const QVariant& message);
 
-private slots:
-    void handleNavigation(const QString& url);
+private:
+    QString _uid;
+    QWebChannel* _webchannel { nullptr };
 };
 
 #endif
diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp
index 679e86f4ae..37d461acd0 100644
--- a/libraries/ui/src/QmlWindowClass.cpp
+++ b/libraries/ui/src/QmlWindowClass.cpp
@@ -26,10 +26,6 @@
 
 #include "OffscreenUi.h"
 
-QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
-static QWebChannel webChannel;
-static const uint16_t WEB_CHANNEL_PORT = 51016;
-static std::atomic<int> nextWindowId;
 static const char* const SOURCE_PROPERTY = "source";
 static const char* const TITLE_PROPERTY = "title";
 static const char* const WIDTH_PROPERTY = "width";
@@ -37,54 +33,6 @@ static const char* const HEIGHT_PROPERTY = "height";
 static const char* const VISIBILE_PROPERTY = "visible";
 static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
 
-void QmlScriptEventBridge::emitWebEvent(const QString& data) {
-    QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
-}
-
-void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
-    QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, 
-        Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
-}
-
-class QmlWebTransport : public QWebChannelAbstractTransport {
-    Q_OBJECT
-public:
-    QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
-        // Translate from the websocket layer to the webchannel layer
-        connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
-            QJsonParseError error;
-            QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
-            if (error.error || !document.isObject()) {
-                qWarning() << "Unable to parse incoming JSON message" << message;
-                return;
-            }
-            emit messageReceived(document.object(), this);
-        });
-    }
-
-    virtual void sendMessage(const QJsonObject &message) override {
-        // Translate from the webchannel layer to the websocket layer
-        _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
-    }
-
-private:
-    QWebSocket* const _webSocket;
-};
-
-
-void QmlWindowClass::setupServer() {
-    if (!_webChannelServer) {
-        _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
-        if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
-            qFatal("Failed to open web socket server.");
-        }
-
-        QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
-            webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
-        });
-    }
-}
-
 QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, 
     QScriptContext* context, QScriptEngine* engine, 
     std::function<QmlWindowClass*(QObject*)> builder) 
@@ -168,10 +116,8 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
         }
 
         offscreenUi->returnFromUiThread([&] {
-            setupServer();
             retVal = builder(newTab);
             retVal->_toolWindow = true;
-            registerObject(url.toLower(), retVal);
             return QVariant();
         });
     } else {
@@ -179,10 +125,8 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
         QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
             Q_ARG(const QString&, qmlSource),
             Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
-            setupServer();
             retVal = builder(object);
             context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
-            registerObject(url.toLower(), retVal);
             if (!title.isEmpty()) {
                 retVal->setTitle(title);
             }
@@ -209,12 +153,9 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine*
     });
 }
 
-QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
-    : _windowId(++nextWindowId), _qmlWindow(qmlWindow)
-{
-    qDebug() << "Created window with ID " << _windowId;
+QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) {
     Q_ASSERT(_qmlWindow);
-    Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
+    Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
     // Forward messages received from QML on to the script
     connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
 }
@@ -228,19 +169,11 @@ QmlWindowClass::~QmlWindowClass() {
     close();
 }
 
-void QmlWindowClass::registerObject(const QString& name, QObject* object) {
-    webChannel.registerObject(name, object);
-}
-
-void QmlWindowClass::deregisterObject(QObject* object) {
-    webChannel.deregisterObject(object);
-}
-
 QQuickItem* QmlWindowClass::asQuickItem() const {
     if (_toolWindow) {
         return DependencyManager::get<OffscreenUi>()->getToolWindow();
     }
-    return dynamic_cast<QQuickItem*>(_qmlWindow);
+    return _qmlWindow.isNull() ? nullptr : dynamic_cast<QQuickItem*>(_qmlWindow.data());
 }
 
 void QmlWindowClass::setVisible(bool visible) {
@@ -260,32 +193,34 @@ void QmlWindowClass::setVisible(bool visible) {
 
 bool QmlWindowClass::isVisible() const {
     // The tool window itself has special logic based on whether any tabs are enabled
-    if (_toolWindow) {
-        auto targetTab = dynamic_cast<QQuickItem*>(_qmlWindow);
-        return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
-            return QVariant::fromValue(targetTab->isEnabled());
-        }).toBool();
-    } else {
-        QQuickItem* targetWindow = asQuickItem();
-        return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
-            return QVariant::fromValue(targetWindow->isVisible());
-        }).toBool();
-    }
+    return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
+        if (_qmlWindow.isNull()) {
+            return QVariant::fromValue(false);
+        }
+        if (_toolWindow) {
+            return QVariant::fromValue(dynamic_cast<QQuickItem*>(_qmlWindow.data())->isEnabled());
+        } else {
+            return QVariant::fromValue(asQuickItem()->isVisible());
+        }
+    }).toBool();
 }
 
 glm::vec2 QmlWindowClass::getPosition() const {
-    QQuickItem* targetWindow = asQuickItem();
     QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
-        return targetWindow->position();
+        if (_qmlWindow.isNull()) {
+            return QVariant(QPointF(0, 0));
+        }
+        return asQuickItem()->position();
     });
     return toGlm(result.toPointF());
 }
 
 
 void QmlWindowClass::setPosition(const glm::vec2& position) {
-    QQuickItem* targetWindow = asQuickItem();
     DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        targetWindow->setPosition(QPointF(position.x, position.y));
+        if (!_qmlWindow.isNull()) {
+            asQuickItem()->setPosition(QPointF(position.x, position.y));
+        }
     });
 }
 
@@ -299,17 +234,21 @@ glm::vec2 toGlm(const QSizeF& size) {
 }
 
 glm::vec2 QmlWindowClass::getSize() const {
-    QQuickItem* targetWindow = asQuickItem();
     QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
+        if (_qmlWindow.isNull()) {
+            return QVariant(QSizeF(0, 0));
+        }
+        QQuickItem* targetWindow = asQuickItem();
         return QSizeF(targetWindow->width(), targetWindow->height());
     });
     return toGlm(result.toSizeF());
 }
 
 void QmlWindowClass::setSize(const glm::vec2& size) {
-    QQuickItem* targetWindow = asQuickItem();
     DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        targetWindow->setSize(QSizeF(size.x, size.y));
+        if (!_qmlWindow.isNull()) {
+            asQuickItem()->setSize(QSizeF(size.x, size.y));
+        }
     });
 }
 
@@ -318,9 +257,10 @@ void QmlWindowClass::setSize(int width, int height) {
 }
 
 void QmlWindowClass::setTitle(const QString& title) {
-    QQuickItem* targetWindow = asQuickItem();
     DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        targetWindow->setProperty(TITLE_PROPERTY, title);
+        if (!_qmlWindow.isNull()) {
+            asQuickItem()->setProperty(TITLE_PROPERTY, title);
+        }
     });
 }
 
@@ -345,7 +285,12 @@ void QmlWindowClass::hasClosed() {
 }
 
 void QmlWindowClass::raise() {
-    QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::QueuedConnection);
+    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+    offscreenUi->executeOnUiThread([=] {
+        if (!_qmlWindow.isNull()) {
+            QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection);
+        }
+    });
 }
 
 #include "QmlWindowClass.moc"
diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h
index 26152b1f24..eda6ce674c 100644
--- a/libraries/ui/src/QmlWindowClass.h
+++ b/libraries/ui/src/QmlWindowClass.h
@@ -10,42 +10,21 @@
 #define hifi_ui_QmlWindowClass_h
 
 #include <QtCore/QObject>
-#include <GLMHelpers.h>
+#include <QtCore/QPointer>
 #include <QtScript/QScriptValue>
 #include <QtQuick/QQuickItem>
-#include <QtWebChannel/QWebChannelAbstractTransport>
+
+#include <GLMHelpers.h>
 
 class QScriptEngine;
 class QScriptContext;
-class QmlWindowClass;
-class QWebSocketServer;
-class QWebSocket;
-
-class QmlScriptEventBridge : public QObject {
-    Q_OBJECT
-public:
-    QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {}
-
-public slots :
-    void emitWebEvent(const QString& data);
-    void emitScriptEvent(const QString& data);
-
-signals:
-    void webEventReceived(const QString& data);
-    void scriptEventReceived(int windowId, const QString& data);
-
-private:
-    const QmlWindowClass* _webWindow { nullptr };
-    QWebSocket *_socket { nullptr };
-};
 
 // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping 
 class QmlWindowClass : public QObject {
     Q_OBJECT
     Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
-    Q_PROPERTY(int windowId READ getWindowId CONSTANT)
-    Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
-    Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
+    Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged)
+    Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
     Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
 
 public:
@@ -64,21 +43,19 @@ public slots:
     glm::vec2 getSize() const;
     void setSize(const glm::vec2& size);
     void setSize(int width, int height);
-
     void setTitle(const QString& title);
 
-
-    // Ugh.... do not want to do
     Q_INVOKABLE void raise();
     Q_INVOKABLE void close();
-    Q_INVOKABLE int getWindowId() const { return _windowId; };
-    Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
+    Q_INVOKABLE QObject* getEventBridge() { return this; };
 
     // Scripts can use this to send a message to the QML object
     void sendToQml(const QVariant& message);
 
 signals:
     void visibilityChanged(bool visible);  // Tool window
+    void positionChanged();
+    void sizeChanged();
     void moved(glm::vec2 position);
     void resized(QSizeF size);
     void closed();
@@ -92,19 +69,13 @@ protected:
     static QScriptValue internalConstructor(const QString& qmlSource, 
         QScriptContext* context, QScriptEngine* engine, 
         std::function<QmlWindowClass*(QObject*)> function);
-    static void setupServer();
-    static void registerObject(const QString& name, QObject* object);
-    static void deregisterObject(QObject* object);
-    static QWebSocketServer* _webChannelServer;
 
     QQuickItem* asQuickItem() const;
-    QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
 
     // FIXME needs to be initialized in the ctor once we have support
     // for tool window panes in QML
     bool _toolWindow { false };
-    const int _windowId;
-    QObject* _qmlWindow;
+    QPointer<QObject> _qmlWindow;
     QString _source;
 };
 
diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp
index 5ab56e1659..71a858e1e8 100644
--- a/plugins/oculus/src/OculusDisplayPlugin.cpp
+++ b/plugins/oculus/src/OculusDisplayPlugin.cpp
@@ -6,6 +6,7 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 #include "OculusDisplayPlugin.h"
+#include <shared/NsightHelpers.h>
 #include "OculusHelpers.h"
 
 const QString OculusDisplayPlugin::NAME("Oculus Rift");
@@ -54,6 +55,9 @@ void OculusDisplayPlugin::updateFrameData() {
 }
 
 void OculusDisplayPlugin::hmdPresent() {
+
+    PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex)
+
     if (!_currentSceneTexture) {
         return;
     }
diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp
index 0cd9bac15f..0e7541066e 100644
--- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp
+++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp
@@ -21,7 +21,7 @@
 #include <PerfStat.h>
 #include <plugins/PluginContainer.h>
 #include <ViewFrustum.h>
-
+#include <shared/NsightHelpers.h>
 #include "OpenVrHelpers.h"
 
 Q_DECLARE_LOGGING_CATEGORY(displayplugins)
@@ -69,6 +69,9 @@ void OpenVrDisplayPlugin::internalActivate() {
     _compositor = vr::VRCompositor();
     Q_ASSERT(_compositor);
 
+    // enable async time warp
+    // _compositor->ForceInterleavedReprojectionOn(true);
+
     // set up default sensor space such that the UI overlay will align with the front of the room.
     auto chaperone = vr::VRChaperone();
     if (chaperone) {
@@ -119,14 +122,11 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) {
     float vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float);
 
 #if THREADED_PRESENT
-    // TODO: this seems awfuly long, 44ms total, but it produced the best results.
+    // 3 frames of prediction + vsyncToPhotons = 44ms total
     const float NUM_PREDICTION_FRAMES = 3.0f;
     float predictedSecondsFromNow = NUM_PREDICTION_FRAMES * frameDuration + vsyncToPhotons;
 #else
-    uint64_t frameCounter;
-    float timeSinceLastVsync;
-    _system->GetTimeSinceLastVsync(&timeSinceLastVsync, &frameCounter);
-    float predictedSecondsFromNow = 3.0f * frameDuration - timeSinceLastVsync + vsyncToPhotons;
+    float predictedSecondsFromNow = frameDuration + vsyncToPhotons;
 #endif
 
     vr::TrackedDevicePose_t predictedTrackedDevicePose[vr::k_unMaxTrackedDeviceCount];
@@ -144,6 +144,9 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) {
 }
 
 void OpenVrDisplayPlugin::hmdPresent() {
+
+    PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex)
+
     // Flip y-axis since GL UV coords are backwards.
     static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 };
     static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 };
@@ -152,6 +155,10 @@ void OpenVrDisplayPlugin::hmdPresent() {
 
     _compositor->Submit(vr::Eye_Left, &texture, &leftBounds);
     _compositor->Submit(vr::Eye_Right, &texture, &rightBounds);
+}
+
+void OpenVrDisplayPlugin::postPreview() {
+    PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex)
 
     vr::TrackedDevicePose_t currentTrackedDevicePose[vr::k_unMaxTrackedDeviceCount];
     _compositor->WaitGetPoses(currentTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0);
diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h
index caaf75a4d0..78b76cb78d 100644
--- a/plugins/openvr/src/OpenVrDisplayPlugin.h
+++ b/plugins/openvr/src/OpenVrDisplayPlugin.h
@@ -35,6 +35,7 @@ protected:
 
     void hmdPresent() override;
     bool isHmdMounted() const override;
+    void postPreview() override;
 
 private:
     vr::IVRSystem* _system { nullptr };
diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp
index d2c3649933..8ddf028dd2 100644
--- a/plugins/openvr/src/OpenVrHelpers.cpp
+++ b/plugins/openvr/src/OpenVrHelpers.cpp
@@ -51,7 +51,7 @@ vr::IVRSystem* acquireOpenVrSystem() {
         if (!activeHmd) {
             qCDebug(displayplugins) << "openvr: No vr::IVRSystem instance active, building";
             vr::EVRInitError eError = vr::VRInitError_None;
-            activeHmd = vr::VR_Init(&eError);
+            activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene);
             qCDebug(displayplugins) << "openvr display: HMD is " << activeHmd << " error is " << eError;
         }
         if (activeHmd) {
diff --git a/server-console/src/log.js b/server-console/src/log.js
index e45848e5a5..3634eaeaa7 100644
--- a/server-console/src/log.js
+++ b/server-console/src/log.js
@@ -44,6 +44,14 @@ ready = function() {
     var domainServer = remote.getGlobal('domainServer');
     var acMonitor = remote.getGlobal('acMonitor');
 
+    var pendingLines = {
+        'ds': new Array(),
+        'ac': new Array()
+    };
+
+    var UPDATE_INTERVAL = 16; // Update log at ~60 fps
+    var interval = setInterval(flushPendingLines, UPDATE_INTERVAL);
+
     var logWatchers = {
         'ds': {
         },
@@ -83,7 +91,7 @@ ready = function() {
             var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 });
 
             logTail.on('line', function(msg) {
-                appendLogMessage(msg, stream);
+                pendingLines[stream].push(msg);
             });
 
             logTail.on('error', function(error) {
@@ -107,6 +115,7 @@ ready = function() {
     }
 
     window.onbeforeunload = function(e) {
+        clearInterval(interval);
         domainServer.removeListener('logs-updated', updateLogFiles);
         acMonitor.removeListener('logs-updated', updateLogFiles);
     };
@@ -164,14 +173,23 @@ ready = function() {
         return !filter || message.toLowerCase().indexOf(filter) >= 0;
     }
 
-    function appendLogMessage(msg, name) {
+    function appendLogMessages(name) {
+        var array = pendingLines[name];
+        if (array.length === 0) {
+            return;
+        }
+        if (array.length > maxLogLines) {
+            array = array.slice(-maxLogLines);
+        }
+
+        console.log(name, array.length);
+
         var id = name == "ds" ? "domain-server" : "assignment-client";
         var $pidLog = $('#' + id);
 
-        var size = ++tabStates[id].size;
+        var size = tabStates[id].size + array.length;
         if (size > maxLogLines) {
-            $pidLog.find('div.log-line:first').remove();
-            removed = true;
+            $pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove();
         }
 
         var wasAtBottom = false;
@@ -179,17 +197,25 @@ ready = function() {
             wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height());
         }
 
-        var $logLine = $('<div class="log-line">').text(msg);
-        if (!shouldDisplayLogMessage(msg)) {
-            $logLine.hide();
+        for (line in array) {
+            var $logLine = $('<div class="log-line">').text(array[line]);
+            if (!shouldDisplayLogMessage(array[line])) {
+                $logLine.hide();
+            }
+
+            $pidLog.append($logLine);
         }
 
-        $pidLog.append($logLine);
+        delete pendingLines[name];
+        pendingLines[name] = new Array();
 
         if (wasAtBottom) {
             $pidLog.scrollTop($pidLog[0].scrollHeight);
         }
-
+    }
+    function flushPendingLines() {
+        appendLogMessages('ds');
+        appendLogMessages('ac');
     }
 
     // handle filtering of table rows on input change
diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp
index 533e6371e9..2b5e306b09 100644
--- a/tools/udt-test/src/UDTTest.cpp
+++ b/tools/udt-test/src/UDTTest.cpp
@@ -77,7 +77,7 @@ UDTTest::UDTTest(int& argc, char** argv) :
     
     // randomize the seed for packet size randomization
     srand(time(NULL));
-    
+
     _socket.bind(QHostAddress::AnyIPv4, _argumentParser.value(PORT_OPTION).toUInt());
     qDebug() << "Test socket is listening on" << _socket.localPort();