From a0d80b9508fe4ebd79dc030f832a410d3e25e73f Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Mon, 9 May 2016 16:23:59 -0700
Subject: [PATCH 01/29] Fix hang on new script on shutdown

---
 libraries/script-engine/src/ScriptEngines.cpp | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index c6070e0598..d78fd9fe0c 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -118,9 +118,9 @@ void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
 }
 
 void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
-    _allScriptsMutex.lock();
-    _allKnownScriptEngines.insert(engine);
-    _allScriptsMutex.unlock();
+    if (!_stoppingAllScripts) {
+        _allKnownScriptEngines.insert(engine);
+    }
 }
 
 void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
@@ -128,16 +128,15 @@ void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
     // from the list of running scripts. We don't do this if we're in the process of stopping all scripts
     // because that method removes scripts from its list as it iterates them
     if (!_stoppingAllScripts) {
-        _allScriptsMutex.lock();
+        QMutexLocker locker(&_allScriptsMutex);
         _allKnownScriptEngines.remove(engine);
-        _allScriptsMutex.unlock();
     }
 }
 
 void ScriptEngines::shutdownScripting() {
-    _allScriptsMutex.lock();
     _stoppingAllScripts = true;
     ScriptEngine::_stoppingAllScripts = true;
+    QMutexLocker locker(&_allScriptsMutex);
     qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size();
 
     QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);
@@ -173,8 +172,6 @@ void ScriptEngines::shutdownScripting() {
             i.remove();
         }
     }
-    _stoppingAllScripts = false;
-    _allScriptsMutex.unlock();
     qCDebug(scriptengine) << "DONE Stopping all scripts....";
 }
 

From 694dc1bbf6e0cf4676da89aa7076079cb9c37b99 Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Mon, 9 May 2016 17:28:27 -0700
Subject: [PATCH 02/29] Delay Overlays cleanup for scripting

---
 interface/src/Application.cpp          |  3 +++
 interface/src/ui/overlays/Overlays.cpp | 11 ++---------
 interface/src/ui/overlays/Overlays.h   |  4 ++--
 3 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 5b0d5c65ce..77e266bdb8 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1189,6 +1189,9 @@ void Application::cleanupBeforeQuit() {
     DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
     DependencyManager::destroy<ScriptEngines>();
 
+    // Cleanup all overlays after the scripts, as scripts might add more
+    _overlays.cleanupAllOverlays();
+
     // first stop all timers directly or by invokeMethod
     // depending on what thread they run in
     locationUpdateTimer.stop();
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index 1179fbaa50..9ff7f6268f 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -36,15 +36,8 @@
 #include <QtQuick/QQuickWindow>
 
 
-Overlays::Overlays() : _nextOverlayID(1) {
-    connect(qApp, &Application::beforeAboutToQuit, [=] {
-        cleanupAllOverlays();
-    });
-}
-
-Overlays::~Overlays() {
-}
-
+Overlays::Overlays() :
+    _nextOverlayID(1) {}
 
 void Overlays::cleanupAllOverlays() {
     {
diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h
index 25ba00fdf0..f47f8de153 100644
--- a/interface/src/ui/overlays/Overlays.h
+++ b/interface/src/ui/overlays/Overlays.h
@@ -62,7 +62,6 @@ class Overlays : public QObject {
 
 public:
     Overlays();
-    ~Overlays();
 
     void init();
     void update(float deltatime);
@@ -73,6 +72,8 @@ public:
     Overlay::Pointer getOverlay(unsigned int id) const;
     OverlayPanel::Pointer getPanel(unsigned int id) const { return _panels[id]; }
 
+    void cleanupAllOverlays();
+
 public slots:
     /// adds an overlay with the specific properties
     unsigned int addOverlay(const QString& type, const QVariant& properties);
@@ -145,7 +146,6 @@ signals:
 
 private:
     void cleanupOverlaysToDelete();
-    void cleanupAllOverlays();
 
     QMap<unsigned int, Overlay::Pointer> _overlaysHUD;
     QMap<unsigned int, Overlay::Pointer> _overlaysWorld;

From 2d820221dc83ffb42ac2070793393334e8f28d15 Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Mon, 9 May 2016 17:30:44 -0700
Subject: [PATCH 03/29] Clear queued processing on quit

---
 interface/src/Application.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 77e266bdb8..de955bb5b7 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1185,6 +1185,9 @@ void Application::cleanupBeforeQuit() {
 
     getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
 
+    // Clear any queued processing (I/O, FBX/OBJ/Texture parsing)
+    QThreadPool::globalInstance()->clear();
+
     DependencyManager::get<ScriptEngines>()->saveScripts();
     DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
     DependencyManager::destroy<ScriptEngines>();

From 68731973d8f8288635b2e2aa2bc611bdd74c26e0 Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zmp@umich.edu>
Date: Mon, 9 May 2016 18:04:38 -0700
Subject: [PATCH 04/29] Keep locker when adding script engine

---
 libraries/script-engine/src/ScriptEngines.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index d78fd9fe0c..61220bc00b 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -119,6 +119,7 @@ void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
 
 void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
     if (!_stoppingAllScripts) {
+        QMutexLocker locker(&_allScriptsMutex);
         _allKnownScriptEngines.insert(engine);
     }
 }

From d5a9d39edf87b484a615a1c343530d2a0739d105 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Wed, 11 May 2016 18:01:19 -0700
Subject: [PATCH 05/29] Defaulting the color formats used in the rendering
 pipeline to sRGB

---
 libraries/gpu/src/gpu/GLBackendOutput.cpp       | 2 ++
 libraries/render-utils/src/FramebufferCache.cpp | 8 +++-----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp
index 545d0a8cdb..d40c5f9b97 100755
--- a/libraries/gpu/src/gpu/GLBackendOutput.cpp
+++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp
@@ -209,6 +209,8 @@ void GLBackend::resetOutputStage() {
         _output._drawFBO = 0;
         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
     }
+
+    glEnable(GL_FRAMEBUFFER_SRGB);
 }
 
 void GLBackend::do_setFramebuffer(Batch& batch, size_t paramOffset) {
diff --git a/libraries/render-utils/src/FramebufferCache.cpp b/libraries/render-utils/src/FramebufferCache.cpp
index 22bfbfd869..3223ee5535 100644
--- a/libraries/render-utils/src/FramebufferCache.cpp
+++ b/libraries/render-utils/src/FramebufferCache.cpp
@@ -59,7 +59,8 @@ void FramebufferCache::createPrimaryFramebuffer() {
     _deferredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
     _deferredFramebufferDepthColor = gpu::FramebufferPointer(gpu::Framebuffer::create());
 
-    auto colorFormat = gpu::Element::COLOR_RGBA_32;
+   // auto colorFormat = gpu::Element::COLOR_RGBA_32;
+    auto colorFormat = gpu::Element::COLOR_SRGBA_32;
     auto width = _frameBufferSize.width();
     auto height = _frameBufferSize.height();
 
@@ -95,10 +96,7 @@ void FramebufferCache::createPrimaryFramebuffer() {
 
     auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR);
 
-    // FIXME: Decide on the proper one, let s stick to R11G11B10 for now
-    //_lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, defaultSampler));
     _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler));
-    //_lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC4, gpu::HALF, gpu::RGBA), width, height, defaultSampler));
     _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
     _lightingFramebuffer->setRenderBuffer(0, _lightingTexture);
     _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);
@@ -212,7 +210,7 @@ gpu::TexturePointer FramebufferCache::getLightingTexture() {
 
 gpu::FramebufferPointer FramebufferCache::getFramebuffer() {
     if (_cachedFramebuffers.isEmpty()) {
-        _cachedFramebuffers.push_back(gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, _frameBufferSize.width(), _frameBufferSize.height())));
+        _cachedFramebuffers.push_back(gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_SRGBA_32, _frameBufferSize.width(), _frameBufferSize.height())));
     }
     gpu::FramebufferPointer result = _cachedFramebuffers.front();
     _cachedFramebuffers.pop_front();

From 991f7fe873564c4bbe10db504bcc35bb0478a864 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 13 May 2016 10:20:50 +1200
Subject: [PATCH 06/29] Fix up syntax per JSLint

---
 scripts/system/users.js | 45 ++++++++++++++++++++++++-----------------
 1 file changed, 27 insertions(+), 18 deletions(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index 9612a19eee..55729726f1 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -9,7 +9,7 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-var PopUpMenu = function(properties) {
+var PopUpMenu = function (properties) {
     var value = properties.value,
         promptOverlay,
         valueOverlay,
@@ -21,9 +21,8 @@ var PopUpMenu = function(properties) {
         MIN_MAX_BUTTON_SVG_WIDTH = 17.1,
         MIN_MAX_BUTTON_SVG_HEIGHT = 32.5,
         MIN_MAX_BUTTON_WIDTH = 14,
-        MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH;
-
-    MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg");
+        MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH,
+        MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg");
 
     function positionDisplayOptions() {
         var y,
@@ -218,11 +217,10 @@ var PopUpMenu = function(properties) {
     };
 };
 
-var usersWindow = (function() {
+var usersWindow = (function () {
 
-    var baseURL = Script.resolvePath("assets/images/tools/");
-
-    var WINDOW_WIDTH = 260,
+    var baseURL = Script.resolvePath("assets/images/tools/"),
+        WINDOW_WIDTH = 260,
         WINDOW_MARGIN = 12,
         WINDOW_BASE_MARGIN = 6, // A little less is needed in order look correct
         WINDOW_FONT = {
@@ -383,7 +381,9 @@ var usersWindow = (function() {
         }
 
         // Reserve space for title, friends button, and option controls
-        nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER + windowLineHeight + VISIBILITY_SPACER + windowLineHeight + WINDOW_BASE_MARGIN;
+        nonUsersHeight = WINDOW_MARGIN + windowLineHeight + FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER
+            + windowLineHeight + VISIBILITY_SPACER
+            + windowLineHeight + WINDOW_BASE_MARGIN;
 
         // Limit window to height of viewport minus VU meter and mirror if displayed
         windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight;
@@ -422,12 +422,15 @@ var usersWindow = (function() {
         Overlays.editOverlay(scrollbarBackground, {
             y: scrollbarBackgroundPosition.y
         });
-        scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
+        scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1
+            + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
         Overlays.editOverlay(scrollbarBar, {
             y: scrollbarBarPosition.y
         });
 
-        y = viewportHeight - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER - windowLineHeight - VISIBILITY_SPACER - windowLineHeight - WINDOW_BASE_MARGIN;
+        y = viewportHeight - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER
+            - windowLineHeight - VISIBILITY_SPACER
+            - windowLineHeight - WINDOW_BASE_MARGIN;
         Overlays.editOverlay(friendsButton, {
             y: y
         });
@@ -512,7 +515,7 @@ var usersWindow = (function() {
         usersRequest.send();
     }
 
-    processUsers = function() {
+    processUsers = function () {
         var response,
             myUsername,
             user,
@@ -565,7 +568,7 @@ var usersWindow = (function() {
         }
     };
 
-    pollUsersTimedOut = function() {
+    pollUsersTimedOut = function () {
         print("Error: Request for users status timed out");
         usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
     };
@@ -683,7 +686,8 @@ var usersWindow = (function() {
 
             userClicked = firstUserToDisplay + lineClicked;
 
-            if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) {
+            if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX
+                    && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) {
                 //print("Go to " + usersOnline[linesOfUsers[userClicked]].username);
                 location.goToUser(usersOnline[linesOfUsers[userClicked]].username);
             }
@@ -740,8 +744,12 @@ var usersWindow = (function() {
 
     function onMouseMoveEvent(event) {
         if (isMovingScrollbar) {
-            if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) {
-                scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) / (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
+            if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x
+                    && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN
+                    && scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y
+                    && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) {
+                scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y)
+                    / (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
                 scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0);
                 firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay));
                 updateOverlayPositions();
@@ -773,7 +781,8 @@ var usersWindow = (function() {
         isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM);
         isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM);
 
-        if (viewportHeight !== oldViewportHeight || isMirrorDisplay !== oldIsMirrorDisplay || isFullscreenMirror !== oldIsFullscreenMirror) {
+        if (viewportHeight !== oldViewportHeight || isMirrorDisplay !== oldIsMirrorDisplay
+                || isFullscreenMirror !== oldIsFullscreenMirror) {
             calculateWindowHeight();
             updateUsersDisplay();
             updateOverlayPositions();
@@ -991,4 +1000,4 @@ var usersWindow = (function() {
 
     setUp();
     Script.scriptEnding.connect(tearDown);
-}());
\ No newline at end of file
+}());

From 41dc9a23a84658106de80f705f70e5ca672810a1 Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Thu, 12 May 2016 15:41:15 -0700
Subject: [PATCH 07/29] More buttons to get out of away/pause.

---
 scripts/system/away.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/scripts/system/away.js b/scripts/system/away.js
index 932efd6b60..2b2ea8a42b 100644
--- a/scripts/system/away.js
+++ b/scripts/system/away.js
@@ -262,6 +262,14 @@ eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(goActive);
 eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(goActive);
 eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(goActive);
 eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(goActive);
+eventMapping.from(Controller.Standard.LT).peek().to(goActive);
+eventMapping.from(Controller.Standard.LB).peek().to(goActive);
+eventMapping.from(Controller.Standard.LS).peek().to(goActive);
+eventMapping.from(Controller.Standard.RT).peek().to(goActive);
+eventMapping.from(Controller.Standard.RB).peek().to(goActive);
+eventMapping.from(Controller.Standard.RS).peek().to(goActive);
+eventMapping.from(Controller.Standard.Back).peek().to(goActive);
+eventMapping.from(Controller.Standard.Start).peek().to(goActive);
 Controller.enableMapping(eventMappingName);
 
 Script.scriptEnding.connect(function () {
@@ -270,4 +278,4 @@ Script.scriptEnding.connect(function () {
     Controller.disableMapping(eventMappingName);
     Controller.mousePressEvent.disconnect(goActive);
     Controller.keyPressEvent.disconnect(maybeGoActive);
-});
\ No newline at end of file
+});

From 54d658798aca0021278f368553f927eddfa15ca1 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 13 May 2016 11:22:10 +1200
Subject: [PATCH 08/29] Add border overlay

---
 scripts/system/users.js | 42 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index 55729726f1..6b8040fae8 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -202,7 +202,7 @@ var PopUpMenu = function (properties) {
             width: MIN_MAX_BUTTON_SVG_WIDTH,
             height: MIN_MAX_BUTTON_SVG_HEIGHT / 2
         },
-        color: properties.buttonColor,
+        //color: properties.buttonColor,
         alpha: properties.buttonAlpha,
         visible: properties.visible
     });
@@ -246,6 +246,17 @@ var usersWindow = (function () {
         WINDOW_BACKGROUND_ALPHA = 0.8,
         windowPane,
         windowHeading,
+
+        // Window border is similar to that of edit.js.
+        WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_BASE_MARGIN,
+        WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_BASE_MARGIN,
+        WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_BASE_MARGIN,
+        WINDOW_BORDER_LEFT_MARGIN = WINDOW_BASE_MARGIN,
+        WINDOW_BORDER_RADIUS = 4,
+        WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 },
+        WINDOW_BORDER_ALPHA = 0.5,
+        windowBorder,
+
         MIN_MAX_BUTTON_SVG = baseURL + "min-max-toggle.svg",
         MIN_MAX_BUTTON_SVG_WIDTH = 17.1,
         MIN_MAX_BUTTON_SVG_HEIGHT = 32.5,
@@ -329,6 +340,7 @@ var usersWindow = (function () {
         visibilityControl,
 
         windowHeight,
+        windowBorderHeight,
         windowTextHeight,
         windowLineSpacing,
         windowLineHeight, // = windowTextHeight + windowLineSpacing
@@ -357,6 +369,7 @@ var usersWindow = (function () {
 
         isVisible = true,
         isMinimized = false,
+        isBorderVisible = false,
 
         viewportHeight,
         isMirrorDisplay = false,
@@ -377,6 +390,7 @@ var usersWindow = (function () {
 
         if (isMinimized) {
             windowHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN;
+            windowBorderHeight = windowHeight + WINDOW_BORDER_TOP_MARGIN + WINDOW_BORDER_BOTTOM_MARGIN;
             return;
         }
 
@@ -392,6 +406,7 @@ var usersWindow = (function () {
             maxWindowHeight -= MIRROR_HEIGHT;
         }
         windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight);
+        windowBorderHeight = windowHeight + WINDOW_BORDER_TOP_MARGIN + WINDOW_BORDER_BOTTOM_MARGIN;
 
         // Corresponding number of users to actually display
         numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0);
@@ -407,6 +422,9 @@ var usersWindow = (function () {
     function updateOverlayPositions() {
         var y;
 
+        Overlays.editOverlay(windowBorder, {
+            y: viewportHeight - windowHeight - WINDOW_BORDER_TOP_MARGIN
+        });
         Overlays.editOverlay(windowPane, {
             y: viewportHeight - windowHeight
         });
@@ -490,6 +508,10 @@ var usersWindow = (function () {
             });
         }
 
+        Overlays.editOverlay(windowBorder, {
+            height: windowBorderHeight
+        });
+
         Overlays.editOverlay(windowPane, {
             height: windowHeight,
             text: displayText
@@ -574,6 +596,10 @@ var usersWindow = (function () {
     };
 
     function updateOverlayVisibility() {
+        // TODO
+        //Overlays.editOverlay(windowBorder, {
+        //    visible: isVisible && isBorderVisible
+        //});
         Overlays.editOverlay(windowPane, {
             visible: isVisible
         });
@@ -805,6 +831,19 @@ var usersWindow = (function () {
 
         calculateWindowHeight();
 
+        windowBorder = Overlays.addOverlay("rectangle", {
+            x: -WINDOW_BORDER_LEFT_MARGIN,
+            y: viewportHeight,
+            width: WINDOW_BORDER_WIDTH,
+            height: windowBorderHeight,
+            radius: WINDOW_BORDER_RADIUS,
+            color: WINDOW_BORDER_COLOR,
+            alpha: WINDOW_BORDER_ALPHA,
+            // TODO
+            //visible: isVisible && isBorderVisible
+            visible: true
+        });
+
         windowPane = Overlays.addOverlay("text", {
             x: 0,
             y: viewportHeight, // Start up off-screen
@@ -988,6 +1027,7 @@ var usersWindow = (function () {
         Menu.removeMenuItem(MENU_NAME, MENU_ITEM);
 
         Script.clearTimeout(usersTimer);
+        Overlays.deleteOverlay(windowBorder);
         Overlays.deleteOverlay(windowPane);
         Overlays.deleteOverlay(windowHeading);
         Overlays.deleteOverlay(minimizeButton);

From 40f7212b1e248898560dd1589a1613def415419f Mon Sep 17 00:00:00 2001
From: howard-stearns <howard.stearns@gmail.com>
Date: Thu, 12 May 2016 16:37:03 -0700
Subject: [PATCH 09/29] Make uses of getReticlePosition consistent with respect
 to window offset.

---
 interface/src/Application.cpp | 23 ++++++-----------------
 1 file changed, 6 insertions(+), 17 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 36a12e7b6a..405e30dd6a 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -864,10 +864,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
 
         if (action == controller::toInt(controller::Action::RETICLE_CLICK)) {
             auto reticlePos = getApplicationCompositor().getReticlePosition();
-            QPoint globalPos(reticlePos.x, reticlePos.y);
-
-            // FIXME - it would be nice if this was self contained in the _compositor or Reticle class
-            auto localPos = isHMDMode() ? globalPos : _glWidget->mapFromGlobal(globalPos);
+            QPoint localPos(reticlePos.x, reticlePos.y); // both hmd and desktop already handle this in our coordinates.
             if (state) {
                 QMouseEvent mousePress(QEvent::MouseButtonPress, localPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
                 sendEvent(_glWidget, &mousePress);
@@ -888,15 +885,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
             } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
                 if (!offscreenUi->navigationFocused()) {
                     auto reticlePosition = getApplicationCompositor().getReticlePosition();
-                    offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
+                    offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
                 }
             } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
                 auto reticlePosition = getApplicationCompositor().getReticlePosition();
-                offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
+                offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
             } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
                 if (!offscreenUi->navigationFocused()) {
                     auto reticlePosition = getApplicationCompositor().getReticlePosition();
-                    offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
+                    offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
                 }
             } else if (action == controller::toInt(controller::Action::RETICLE_X)) {
                 auto oldPos = getApplicationCompositor().getReticlePosition();
@@ -2269,7 +2266,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
     if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
         auto offscreenUi = DependencyManager::get<OffscreenUi>();
         auto reticlePosition = getApplicationCompositor().getReticlePosition();
-        offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
+        offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
     }
 
     _keysPressed.remove(event->key());
@@ -2738,15 +2735,7 @@ void Application::setLowVelocityFilter(bool lowVelocityFilter) {
 }
 
 ivec2 Application::getMouse() const {
-    auto reticlePosition = getApplicationCompositor().getReticlePosition();
-
-    // in the HMD, the reticlePosition is the mouse position
-    if (isHMDMode()) {
-        return reticlePosition;
-    }
-
-    // in desktop mode, we need to map from global to widget space
-    return toGlm(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
+    return getApplicationCompositor().getReticlePosition();
 }
 
 FaceTracker* Application::getActiveFaceTracker() {

From aa226739d31e0fc5b216d39e36cd5786227189ae Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Sat, 30 Apr 2016 19:41:04 -0700
Subject: [PATCH 10/29] remove billboard code from Agent

---
 assignment-client/src/Agent.cpp | 20 +-------------------
 assignment-client/src/Agent.h   |  2 --
 2 files changed, 1 insertion(+), 21 deletions(-)

diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index f8f0f7904a..65e193dec6 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -290,7 +290,6 @@ void Agent::executeScript() {
     packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
     packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
     packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
-    packetReceiver.registerListener(PacketType::AvatarBillboard, avatarHashMap.data(), "processAvatarBillboardPacket");
 
     // register ourselves to the script engine
     _scriptEngine->registerGlobalObject("Agent", this);
@@ -341,15 +340,12 @@ void Agent::setIsAvatar(bool isAvatar) {
     if (_isAvatar && !_avatarIdentityTimer) {
         // set up the avatar timers
         _avatarIdentityTimer = new QTimer(this);
-        _avatarBillboardTimer = new QTimer(this);
 
         // connect our slot
         connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
-        connect(_avatarBillboardTimer, &QTimer::timeout, this, &Agent::sendAvatarBillboardPacket);
 
         // start the timers
         _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
-        _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
     }
 
     if (!_isAvatar) {
@@ -359,12 +355,6 @@ void Agent::setIsAvatar(bool isAvatar) {
             delete _avatarIdentityTimer;
             _avatarIdentityTimer = nullptr;
         }
-
-        if (_avatarBillboardTimer) {
-            _avatarBillboardTimer->stop();
-            delete _avatarBillboardTimer;
-            _avatarBillboardTimer = nullptr;
-        }
     }
 }
 
@@ -375,14 +365,6 @@ void Agent::sendAvatarIdentityPacket() {
     }
 }
 
-void Agent::sendAvatarBillboardPacket() {
-    if (_isAvatar) {
-        auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
-        scriptedAvatar->sendBillboardPacket();
-    }
-}
-
-
 void Agent::processAgentAvatarAndAudio(float deltaTime) {
     if (!_scriptEngine->isFinished() && _isAvatar) {
         auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
@@ -491,7 +473,7 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
 }
 
 void Agent::aboutToFinish() {
-    setIsAvatar(false);// will stop timers for sending billboards and identity packets
+    setIsAvatar(false);// will stop timers for sending identity packets
 
     if (_scriptEngine) {
         _scriptEngine->stop();
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index 2b0d22385d..63d4cfa4d6 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -82,7 +82,6 @@ private:
     void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
 
     void sendAvatarIdentityPacket();
-    void sendAvatarBillboardPacket();
 
     QString _scriptContents;
     QTimer* _scriptRequestTimeout { nullptr };
@@ -92,7 +91,6 @@ private:
     int _numAvatarSoundSentBytes = 0;
     bool _isAvatar = false;
     QTimer* _avatarIdentityTimer = nullptr;
-    QTimer* _avatarBillboardTimer = nullptr;
     QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
 
 };

From 2cf943b2b16e9ed618d8e3704b8bde763388b35f Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Sat, 30 Apr 2016 19:44:00 -0700
Subject: [PATCH 11/29] remove billboard code from AvatarData

---
 interface/src/avatar/AvatarManager.cpp  |  1 -
 libraries/avatars/src/AvatarData.cpp    | 50 -------------------------
 libraries/avatars/src/AvatarData.h      | 15 --------
 libraries/avatars/src/AvatarHashMap.cpp | 11 ------
 libraries/avatars/src/AvatarHashMap.h   |  1 -
 5 files changed, 78 deletions(-)

diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 41bcc0332a..ddadcb3909 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -73,7 +73,6 @@ AvatarManager::AvatarManager(QObject* parent) :
     packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
     packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
     packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
-    packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket");
 }
 
 AvatarManager::~AvatarManager() {
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index fd13f8c370..a44d1715c9 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -58,7 +58,6 @@ AvatarData::AvatarData() :
     _headData(NULL),
     _displayNameTargetAlpha(1.0f),
     _displayNameAlpha(1.0f),
-    _billboard(),
     _errorLogExpiry(0),
     _owningAvatarMixer(),
     _targetVelocity(0.0f),
@@ -999,13 +998,6 @@ QByteArray AvatarData::identityByteArray() {
     return identityData;
 }
 
-bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& data) {
-    if (data == _billboard) {
-        return false;
-    }
-    _billboard = data;
-    return true;
-}
 
 void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
     const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
@@ -1103,33 +1095,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
     setAttachmentData(attachmentData);
 }
 
-void AvatarData::setBillboard(const QByteArray& billboard) {
-    _billboard = billboard;
-
-    qCDebug(avatars) << "Changing billboard for avatar.";
-}
-
-void AvatarData::setBillboardFromURL(const QString &billboardURL) {
-    _billboardURL = billboardURL;
-
-
-    qCDebug(avatars) << "Changing billboard for avatar to PNG at" << qPrintable(billboardURL);
-
-    QNetworkRequest billboardRequest;
-    billboardRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
-    billboardRequest.setUrl(QUrl(billboardURL));
-
-    QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
-    QNetworkReply* networkReply = networkAccessManager.get(billboardRequest);
-    connect(networkReply, SIGNAL(finished()), this, SLOT(setBillboardFromNetworkReply()));
-}
-
-void AvatarData::setBillboardFromNetworkReply() {
-    QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
-    setBillboard(networkReply->readAll());
-    networkReply->deleteLater();
-}
-
 void AvatarData::setJointMappingsFromNetworkReply() {
     QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
 
@@ -1204,21 +1169,6 @@ void AvatarData::sendIdentityPacket() {
         });
 }
 
-void AvatarData::sendBillboardPacket() {
-    if (!_billboard.isEmpty()) {
-        auto nodeList = DependencyManager::get<NodeList>();
-
-        // This makes sure the billboard won't be too large to send.
-        // Once more protocol changes are done and we can send blocks of data we can support sending > MTU sized billboards.
-        if (_billboard.size() <= NLPacket::maxPayloadSize(PacketType::AvatarBillboard)) {
-            auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, _billboard.size());
-            billboardPacket->write(_billboard);
-
-            nodeList->broadcastToNodes(std::move(billboardPacket), NodeSet() << NodeType::AvatarMixer);
-        }
-    }
-}
-
 void AvatarData::updateJointMappings() {
     _jointIndices.clear();
     _jointNames.clear();
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 900da38ffa..a7b97ef4c0 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -107,7 +107,6 @@ static const float MIN_AVATAR_SCALE = .005f;
 const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
 
 const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
-const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
 
 // See also static AvatarData::defaultFullAvatarModelUrl().
 const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
@@ -160,7 +159,6 @@ class AvatarData : public QObject, public SpatiallyNestable {
     Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
     Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
     Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
-    Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
 
     Q_PROPERTY(QStringList jointNames READ getJointNames)
 
@@ -285,8 +283,6 @@ public:
     bool hasIdentityChangedAfterParsing(const QByteArray& data);
     QByteArray identityByteArray();
 
-    bool hasBillboardChangedAfterParsing(const QByteArray& data);
-
     const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
     const QString& getDisplayName() const { return _displayName; }
     virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
@@ -304,12 +300,6 @@ public:
     Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString());
     Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString());
 
-    virtual void setBillboard(const QByteArray& billboard);
-    const QByteArray& getBillboard() const { return _billboard; }
-
-    void setBillboardFromURL(const QString& billboardURL);
-    const QString& getBillboardURL() { return _billboardURL; }
-
     QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
     void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
 
@@ -336,9 +326,7 @@ public:
 public slots:
     void sendAvatarDataPacket();
     void sendIdentityPacket();
-    void sendBillboardPacket();
 
-    void setBillboardFromNetworkReply();
     void setJointMappingsFromNetworkReply();
     void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); }
 
@@ -377,9 +365,6 @@ protected:
     float _displayNameTargetAlpha;
     float _displayNameAlpha;
 
-    QByteArray _billboard;
-    QString _billboardURL;
-
     QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
     QStringList _jointNames; ///< in order of depth-first traversal
 
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index 75fb5e6028..f14e2b0ad3 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -136,17 +136,6 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
     }
 }
 
-void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
-    QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
-
-    auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
-
-    QByteArray billboard = message->read(message->getBytesLeftToRead());
-    if (avatar->getBillboard() != billboard) {
-        avatar->setBillboard(billboard);
-    }
-}
-
 void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
     // read the node id
     QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h
index ee1197367c..5f58074427 100644
--- a/libraries/avatars/src/AvatarHashMap.h
+++ b/libraries/avatars/src/AvatarHashMap.h
@@ -53,7 +53,6 @@ private slots:
     
     void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
     void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
-    void processAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
     void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
 
 protected:

From 93ead174aaf04d45e8b11926f4e5172e70859112 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 13 May 2016 12:51:39 +1200
Subject: [PATCH 12/29] Click and drag border to move users window

---
 scripts/system/users.js | 122 +++++++++++++++++++++++++++++-----------
 1 file changed, 90 insertions(+), 32 deletions(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index 6b8040fae8..420f6a6bfe 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -375,6 +375,10 @@ var usersWindow = (function () {
         isMirrorDisplay = false,
         isFullscreenMirror = false,
 
+        windowPosition = { },  // Bottom left corner of window pane.
+        isMovingWindow = false,
+        movingClickOffset = { x: 0, y: 0 },
+
         isUsingScrollbars = false,
         isMovingScrollbar = false,
         scrollbarBackgroundPosition = {},
@@ -420,44 +424,57 @@ var usersWindow = (function () {
     }
 
     function updateOverlayPositions() {
-        var y;
+        // Overlay positions are all relative to windowPosition; windowPosition is the position of the windowPane overlay.
+        var windowLeft = windowPosition.x,
+            windowTop = windowPosition.y - windowHeight,
+            x,
+            y;
 
         Overlays.editOverlay(windowBorder, {
-            y: viewportHeight - windowHeight - WINDOW_BORDER_TOP_MARGIN
+            x: windowPosition.x - WINDOW_BORDER_LEFT_MARGIN,
+            y: windowTop - WINDOW_BORDER_TOP_MARGIN
         });
         Overlays.editOverlay(windowPane, {
-            y: viewportHeight - windowHeight
+            x: windowLeft,
+            y: windowTop
         });
         Overlays.editOverlay(windowHeading, {
-            y: viewportHeight - windowHeight + WINDOW_MARGIN
+            x: windowLeft + WINDOW_MARGIN,
+            y: windowTop + WINDOW_MARGIN
         });
 
         Overlays.editOverlay(minimizeButton, {
-            y: viewportHeight - windowHeight + WINDOW_MARGIN / 2
+            x: windowLeft + WINDOW_WIDTH - WINDOW_MARGIN / 2 - MIN_MAX_BUTTON_WIDTH,
+            y: windowTop + WINDOW_MARGIN / 2
         });
 
-        scrollbarBackgroundPosition.y = viewportHeight - windowHeight + WINDOW_MARGIN + windowTextHeight;
+        scrollbarBackgroundPosition.x = windowLeft + WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH;
+        scrollbarBackgroundPosition.y = windowTop + WINDOW_MARGIN + windowTextHeight;
         Overlays.editOverlay(scrollbarBackground, {
+            x: scrollbarBackgroundPosition.x,
             y: scrollbarBackgroundPosition.y
         });
         scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1
             + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2);
         Overlays.editOverlay(scrollbarBar, {
+            x: scrollbarBackgroundPosition.x + 1,
             y: scrollbarBarPosition.y
         });
 
-        y = viewportHeight - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER
+        x = windowLeft + WINDOW_MARGIN;
+        y = windowPosition.y - FRIENDS_BUTTON_HEIGHT - DISPLAY_SPACER
             - windowLineHeight - VISIBILITY_SPACER
             - windowLineHeight - WINDOW_BASE_MARGIN;
         Overlays.editOverlay(friendsButton, {
+            x: x,
             y: y
         });
 
         y += FRIENDS_BUTTON_HEIGHT + DISPLAY_SPACER;
-        displayControl.updatePosition(WINDOW_MARGIN, y);
+        displayControl.updatePosition(x, y);
 
         y += windowLineHeight + VISIBILITY_SPACER;
-        visibilityControl.updatePosition(WINDOW_MARGIN, y);
+        visibilityControl.updatePosition(x, y);
     }
 
     function updateUsersDisplay() {
@@ -596,10 +613,9 @@ var usersWindow = (function () {
     };
 
     function updateOverlayVisibility() {
-        // TODO
-        //Overlays.editOverlay(windowBorder, {
-        //    visible: isVisible && isBorderVisible
-        //});
+        Overlays.editOverlay(windowBorder, {
+            visible: isVisible && isBorderVisible
+        });
         Overlays.editOverlay(windowPane, {
             visible: isVisible
         });
@@ -765,10 +781,22 @@ var usersWindow = (function () {
             friendsWindow.setURL(FRIENDS_WINDOW_URL);
             friendsWindow.setVisible(true);
             friendsWindow.raise();
+            return;
+        }
+
+        if (clickedOverlay === windowBorder) {
+            movingClickOffset = {
+                x: event.x - windowPosition.x,
+                y: event.y - windowPosition.y
+            };
+
+            isMovingWindow = true;
         }
     }
 
     function onMouseMoveEvent(event) {
+        var isVisible;
+
         if (isMovingScrollbar) {
             if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x
                     && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN
@@ -787,13 +815,44 @@ var usersWindow = (function () {
                 isMovingScrollbar = false;
             }
         }
+
+
+        if (isMovingWindow) {
+            windowPosition = {
+                x: event.x - movingClickOffset.x,
+                y: event.y - movingClickOffset.y
+            };
+            updateOverlayPositions();
+
+        } else {
+
+            isVisible = isBorderVisible;
+            if (isVisible) {
+                isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x
+                    && event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH
+                    && windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y
+                    && event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN;
+            } else {
+                isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH
+                    && windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y;
+            }
+            if (isVisible !== isBorderVisible) {
+                isBorderVisible = isVisible;
+                Overlays.editOverlay(windowBorder, {
+                    visible: isBorderVisible
+                });
+            }
+        }
     }
 
     function onMouseReleaseEvent() {
-        Overlays.editOverlay(scrollbarBar, {
-            backgroundAlpha: SCROLLBAR_BAR_ALPHA
-        });
-        isMovingScrollbar = false;
+        if (isMovingScrollbar) {
+            Overlays.editOverlay(scrollbarBar, {
+                backgroundAlpha: SCROLLBAR_BAR_ALPHA
+            });
+            isMovingScrollbar = false;
+        }
+        isMovingWindow = false;
     }
 
     function onScriptUpdate() {
@@ -828,25 +887,24 @@ var usersWindow = (function () {
         Overlays.deleteOverlay(textSizeOverlay);
 
         viewportHeight = Controller.getViewportDimensions().y;
+        windowPosition = { x: 0, y: viewportHeight };
 
         calculateWindowHeight();
 
         windowBorder = Overlays.addOverlay("rectangle", {
-            x: -WINDOW_BORDER_LEFT_MARGIN,
-            y: viewportHeight,
+            x: 0,
+            y: viewportHeight,   // Start up off-screen
             width: WINDOW_BORDER_WIDTH,
             height: windowBorderHeight,
             radius: WINDOW_BORDER_RADIUS,
             color: WINDOW_BORDER_COLOR,
             alpha: WINDOW_BORDER_ALPHA,
-            // TODO
-            //visible: isVisible && isBorderVisible
-            visible: true
+            visible: isVisible && isBorderVisible
         });
 
         windowPane = Overlays.addOverlay("text", {
             x: 0,
-            y: viewportHeight, // Start up off-screen
+            y: viewportHeight,
             width: WINDOW_WIDTH,
             height: windowHeight,
             topMargin: WINDOW_MARGIN + windowLineHeight,
@@ -861,7 +919,7 @@ var usersWindow = (function () {
         });
 
         windowHeading = Overlays.addOverlay("text", {
-            x: WINDOW_MARGIN,
+            x: 0,
             y: viewportHeight,
             width: WINDOW_WIDTH - 2 * WINDOW_MARGIN,
             height: windowTextHeight,
@@ -876,7 +934,7 @@ var usersWindow = (function () {
         });
 
         minimizeButton = Overlays.addOverlay("image", {
-            x: WINDOW_WIDTH - WINDOW_MARGIN / 2 - MIN_MAX_BUTTON_WIDTH,
+            x: 0,
             y: viewportHeight,
             width: MIN_MAX_BUTTON_WIDTH,
             height: MIN_MAX_BUTTON_HEIGHT,
@@ -893,11 +951,11 @@ var usersWindow = (function () {
         });
 
         scrollbarBackgroundPosition = {
-            x: WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH,
+            x: 0,
             y: viewportHeight
         };
         scrollbarBackground = Overlays.addOverlay("text", {
-            x: scrollbarBackgroundPosition.x,
+            x: 0,
             y: scrollbarBackgroundPosition.y,
             width: SCROLLBAR_BACKGROUND_WIDTH,
             height: windowTextHeight,
@@ -908,11 +966,11 @@ var usersWindow = (function () {
         });
 
         scrollbarBarPosition = {
-            x: WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH + 1,
+            x: 0,
             y: viewportHeight
         };
         scrollbarBar = Overlays.addOverlay("text", {
-            x: scrollbarBarPosition.x,
+            x: 0,
             y: scrollbarBarPosition.y,
             width: SCROLLBAR_BACKGROUND_WIDTH - 2,
             height: windowTextHeight,
@@ -923,7 +981,7 @@ var usersWindow = (function () {
         });
 
         friendsButton = Overlays.addOverlay("image", {
-            x: WINDOW_MARGIN,
+            x: 0,
             y: viewportHeight,
             width: FRIENDS_BUTTON_WIDTH,
             height: FRIENDS_BUTTON_HEIGHT,
@@ -943,7 +1001,7 @@ var usersWindow = (function () {
             value: DISPLAY_VALUES[0],
             values: DISPLAY_VALUES,
             displayValues: DISPLAY_DISPLAY_VALUES,
-            x: WINDOW_MARGIN,
+            x: 0,
             y: viewportHeight,
             width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
             promptWidth: DISPLAY_PROMPT_WIDTH,
@@ -976,7 +1034,7 @@ var usersWindow = (function () {
             value: myVisibility,
             values: VISIBILITY_VALUES,
             displayValues: VISIBILITY_DISPLAY_VALUES,
-            x: WINDOW_MARGIN,
+            x: 0,
             y: viewportHeight,
             width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
             promptWidth: VISIBILITY_PROMPT_WIDTH,

From 8445c9fbc7bef4ffe9fe7d5b3a5f1c5c48214fa7 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 13 May 2016 13:08:45 +1200
Subject: [PATCH 13/29] Update max window height before it scrolls, according
 to its position

---
 scripts/system/users.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index 420f6a6bfe..f3f74e303a 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -403,9 +403,9 @@ var usersWindow = (function () {
             + windowLineHeight + VISIBILITY_SPACER
             + windowLineHeight + WINDOW_BASE_MARGIN;
 
-        // Limit window to height of viewport minus VU meter and mirror if displayed
+        // Limit window to height of viewport above window position minus VU meter and mirror if displayed
         windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight;
-        maxWindowHeight = viewportHeight - AUDIO_METER_HEIGHT;
+        maxWindowHeight = windowPosition.y - AUDIO_METER_HEIGHT;
         if (isMirrorDisplay && !isFullscreenMirror) {
             maxWindowHeight -= MIRROR_HEIGHT;
         }
@@ -715,7 +715,7 @@ var usersWindow = (function () {
         if (clickedOverlay === windowPane) {
 
             overlayX = event.x - WINDOW_MARGIN;
-            overlayY = event.y - viewportHeight + windowHeight - WINDOW_MARGIN - windowLineHeight;
+            overlayY = event.y - windowPosition.y + windowHeight - WINDOW_MARGIN - windowLineHeight;
 
             numLinesBefore = Math.round(overlayY / windowLineHeight);
             minY = numLinesBefore * windowLineHeight;
@@ -822,7 +822,9 @@ var usersWindow = (function () {
                 x: event.x - movingClickOffset.x,
                 y: event.y - movingClickOffset.y
             };
+            calculateWindowHeight();
             updateOverlayPositions();
+            updateUsersDisplay();
 
         } else {
 

From ec74cb4aec53d51ba25379e29f84419a2a6a1497 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 13 May 2016 13:22:03 +1200
Subject: [PATCH 14/29] Maintain offset of bottom with nearest display edge if
 Interface resized

---
 scripts/system/users.js | 47 +++++++++++++++++++++++++++--------------
 1 file changed, 31 insertions(+), 16 deletions(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index f3f74e303a..3b97b9d685 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -371,7 +371,7 @@ var usersWindow = (function () {
         isMinimized = false,
         isBorderVisible = false,
 
-        viewportHeight,
+        viewport,
         isMirrorDisplay = false,
         isFullscreenMirror = false,
 
@@ -858,22 +858,37 @@ var usersWindow = (function () {
     }
 
     function onScriptUpdate() {
-        var oldViewportHeight = viewportHeight,
+        var oldViewport = viewport,
             oldIsMirrorDisplay = isMirrorDisplay,
             oldIsFullscreenMirror = isFullscreenMirror,
             MIRROR_MENU_ITEM = "Mirror",
             FULLSCREEN_MIRROR_MENU_ITEM = "Fullscreen Mirror";
 
-        viewportHeight = Controller.getViewportDimensions().y;
+        viewport = Controller.getViewportDimensions();
         isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM);
         isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM);
 
-        if (viewportHeight !== oldViewportHeight || isMirrorDisplay !== oldIsMirrorDisplay
+        if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay
                 || isFullscreenMirror !== oldIsFullscreenMirror) {
             calculateWindowHeight();
             updateUsersDisplay();
-            updateOverlayPositions();
         }
+
+        if (viewport.y !== oldViewport.y) {
+            if (windowPosition.y > oldViewport.y / 2) {
+                // Maintain position w.r.t. bottom of window.
+                windowPosition.y = viewport.y - (oldViewport.y - windowPosition.y);
+            }
+        }
+
+        if (viewport.x !== oldViewport.x) {
+            if (windowPosition.x + (WINDOW_WIDTH / 2) > oldViewport.x / 2) {
+                // Maintain position w.r.t. right of window.
+                windowPosition.x = viewport.x - (oldViewport.x - windowPosition.x);
+            }
+        }
+
+        updateOverlayPositions();
     }
 
     function setUp() {
@@ -888,14 +903,14 @@ var usersWindow = (function () {
         windowLineHeight = windowTextHeight + windowLineSpacing;
         Overlays.deleteOverlay(textSizeOverlay);
 
-        viewportHeight = Controller.getViewportDimensions().y;
-        windowPosition = { x: 0, y: viewportHeight };
+        viewport = Controller.getViewportDimensions();
+        windowPosition = { x: 0, y: viewport.y };
 
         calculateWindowHeight();
 
         windowBorder = Overlays.addOverlay("rectangle", {
             x: 0,
-            y: viewportHeight,   // Start up off-screen
+            y: viewport.y,   // Start up off-screen
             width: WINDOW_BORDER_WIDTH,
             height: windowBorderHeight,
             radius: WINDOW_BORDER_RADIUS,
@@ -906,7 +921,7 @@ var usersWindow = (function () {
 
         windowPane = Overlays.addOverlay("text", {
             x: 0,
-            y: viewportHeight,
+            y: viewport.y,
             width: WINDOW_WIDTH,
             height: windowHeight,
             topMargin: WINDOW_MARGIN + windowLineHeight,
@@ -922,7 +937,7 @@ var usersWindow = (function () {
 
         windowHeading = Overlays.addOverlay("text", {
             x: 0,
-            y: viewportHeight,
+            y: viewport.y,
             width: WINDOW_WIDTH - 2 * WINDOW_MARGIN,
             height: windowTextHeight,
             topMargin: 0,
@@ -937,7 +952,7 @@ var usersWindow = (function () {
 
         minimizeButton = Overlays.addOverlay("image", {
             x: 0,
-            y: viewportHeight,
+            y: viewport.y,
             width: MIN_MAX_BUTTON_WIDTH,
             height: MIN_MAX_BUTTON_HEIGHT,
             imageURL: MIN_MAX_BUTTON_SVG,
@@ -954,7 +969,7 @@ var usersWindow = (function () {
 
         scrollbarBackgroundPosition = {
             x: 0,
-            y: viewportHeight
+            y: viewport.y
         };
         scrollbarBackground = Overlays.addOverlay("text", {
             x: 0,
@@ -969,7 +984,7 @@ var usersWindow = (function () {
 
         scrollbarBarPosition = {
             x: 0,
-            y: viewportHeight
+            y: viewport.y
         };
         scrollbarBar = Overlays.addOverlay("text", {
             x: 0,
@@ -984,7 +999,7 @@ var usersWindow = (function () {
 
         friendsButton = Overlays.addOverlay("image", {
             x: 0,
-            y: viewportHeight,
+            y: viewport.y,
             width: FRIENDS_BUTTON_WIDTH,
             height: FRIENDS_BUTTON_HEIGHT,
             imageURL: FRIENDS_BUTTON_SVG,
@@ -1004,7 +1019,7 @@ var usersWindow = (function () {
             values: DISPLAY_VALUES,
             displayValues: DISPLAY_DISPLAY_VALUES,
             x: 0,
-            y: viewportHeight,
+            y: viewport.y,
             width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
             promptWidth: DISPLAY_PROMPT_WIDTH,
             lineHeight: windowLineHeight,
@@ -1037,7 +1052,7 @@ var usersWindow = (function () {
             values: VISIBILITY_VALUES,
             displayValues: VISIBILITY_DISPLAY_VALUES,
             x: 0,
-            y: viewportHeight,
+            y: viewport.y,
             width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN,
             promptWidth: VISIBILITY_PROMPT_WIDTH,
             lineHeight: windowLineHeight,

From 1928f51b3e61f2c53ab6198de494b722b18d0485 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 13 May 2016 13:56:31 +1200
Subject: [PATCH 15/29] Restore window position at script start

---
 scripts/system/users.js | 31 +++++++++++++++++++++++++++----
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index 3b97b9d685..08c354be87 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -366,6 +366,8 @@ var usersWindow = (function () {
         MENU_ITEM_AFTER = "Chat...",
 
         SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized",
+        SETINGS_USERS_WINDOW_OFFSET = "UsersWindow.Offset",
+        // +ve x, y values are offset from left, top of screen; -ve from right, bottom.
 
         isVisible = true,
         isMinimized = false,
@@ -816,7 +818,6 @@ var usersWindow = (function () {
             }
         }
 
-
         if (isMovingWindow) {
             windowPosition = {
                 x: event.x - movingClickOffset.x,
@@ -848,13 +849,22 @@ var usersWindow = (function () {
     }
 
     function onMouseReleaseEvent() {
+        var offset = {};
+
         if (isMovingScrollbar) {
             Overlays.editOverlay(scrollbarBar, {
                 backgroundAlpha: SCROLLBAR_BAR_ALPHA
             });
             isMovingScrollbar = false;
         }
-        isMovingWindow = false;
+
+        if (isMovingWindow) {
+            // Save offset of bottom of window to nearest edge of the window.
+            offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) ? windowPosition.x : windowPosition.x - viewport.x;
+            offset.y = (windowPosition.y < viewport.y / 2) ? windowPosition.y : windowPosition.y - viewport.y;
+            Settings.setValue(SETINGS_USERS_WINDOW_OFFSET, JSON.stringify(offset));
+            isMovingWindow = false;
+        }
     }
 
     function onScriptUpdate() {
@@ -892,7 +902,9 @@ var usersWindow = (function () {
     }
 
     function setUp() {
-        var textSizeOverlay;
+        var textSizeOverlay,
+            offsetSetting,
+            offset = {};
 
         textSizeOverlay = Overlays.addOverlay("text", {
             font: WINDOW_FONT,
@@ -904,7 +916,18 @@ var usersWindow = (function () {
         Overlays.deleteOverlay(textSizeOverlay);
 
         viewport = Controller.getViewportDimensions();
-        windowPosition = { x: 0, y: viewport.y };
+
+        offsetSetting = Settings.getValue(SETINGS_USERS_WINDOW_OFFSET);
+        if (offsetSetting !== "") {
+            offset = JSON.parse(Settings.getValue(SETINGS_USERS_WINDOW_OFFSET));
+        }
+        if (offset.hasOwnProperty("x") && offset.hasOwnProperty("y")) {
+            windowPosition.x = offset.x < 0 ? viewport.x + offset.x : offset.x;
+            windowPosition.y = offset.y <= 0 ? viewport.y + offset.y : offset.y;
+
+        } else {
+            windowPosition = { x: 0, y: viewport.y };
+        }
 
         calculateWindowHeight();
 

From fa87c68ca7056f4409b33bed7ebd15d92d5424b0 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 13 May 2016 01:08:25 -0700
Subject: [PATCH 16/29] INtroducing the TransformBuffer

---
 libraries/gpu/src/gpu/Transform.h | 36 +++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 libraries/gpu/src/gpu/Transform.h

diff --git a/libraries/gpu/src/gpu/Transform.h b/libraries/gpu/src/gpu/Transform.h
new file mode 100644
index 0000000000..82974964a8
--- /dev/null
+++ b/libraries/gpu/src/gpu/Transform.h
@@ -0,0 +1,36 @@
+//
+//  Transform.h
+//  libraries/gpu/src/gpu
+//
+//  Created by Sam Gateau on 06/12/2016.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#ifndef hifi_gpu_Transform_h
+#define hifi_gpu_Transform_h
+
+#include <vector>
+
+#include <assert.h>
+
+#include "Resource.h"
+
+namespace gpu {
+
+class TransformBuffer {
+public:
+
+    TransformBuffer() {}
+    ~TransformBuffer() {}
+
+protected:
+    BufferPointer _buffer;
+};
+typedef std::shared_ptr<TransformBuffer> TransformBufferPointer;
+
+};
+
+
+#endif

From 70d0ebb91c94e59f47631f1e01ba3d89ddbf8c54 Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Fri, 13 May 2016 15:24:15 -0700
Subject: [PATCH 17/29] Consolidate stoppingAllScripts to
 ScriptEngines::_stopped

---
 libraries/script-engine/src/ScriptEngine.cpp  | 16 +++++++---------
 libraries/script-engine/src/ScriptEngine.h    |  3 ---
 libraries/script-engine/src/ScriptEngines.cpp |  7 +++----
 libraries/script-engine/src/ScriptEngines.h   |  3 +--
 4 files changed, 11 insertions(+), 18 deletions(-)

diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 6c7739c784..48635df324 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -52,8 +52,6 @@
 
 #include "MIDIEvent.h"
 
-std::atomic<bool> ScriptEngine::_stoppingAllScripts { false };
-
 static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
 
 Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
@@ -655,7 +653,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
 
 
 QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         return QScriptValue(); // bail early
     }
 
@@ -691,7 +689,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
 }
 
 void ScriptEngine::run() {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         return; // bail early - avoid setting state in init(), as evaluate() will bail too
     }
 
@@ -901,7 +899,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
 }
 
 QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();
         return NULL; // bail early
     }
@@ -910,7 +908,7 @@ QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS)
 }
 
 QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();
         return NULL; // bail early
     }
@@ -962,7 +960,7 @@ void ScriptEngine::print(const QString& message) {
 // If no callback is specified, the included files will be loaded synchronously and will block execution until
 // all of the files have finished loading.
 void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."
         << "includeFiles:" << includeFiles << "parent script:" << getFilename();
         return; // bail early
@@ -1063,7 +1061,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
 }
 
 void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "
             << "includeFile:" << includeFile << "parent script:" << getFilename();
         return; // bail early
@@ -1078,7 +1076,7 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
 // as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
 // the Application or other context will connect to in order to know to actually load the script
 void ScriptEngine::load(const QString& loadFile) {
-    if (_stoppingAllScripts) {
+    if (DependencyManager::get<ScriptEngines>()->_stopped) {
         qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "
             << "loadFile:" << loadFile << "parent script:" << getFilename();
         return; // bail early
diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h
index 175a3f1f1c..abd5ed51f4 100644
--- a/libraries/script-engine/src/ScriptEngine.h
+++ b/libraries/script-engine/src/ScriptEngine.h
@@ -222,9 +222,6 @@ protected:
     QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
     void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
     void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
-
-    friend class ScriptEngines;
-    static std::atomic<bool> _stoppingAllScripts;
 };
 
 #endif // hifi_ScriptEngine_h
diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index 61220bc00b..d519af1bce 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -118,7 +118,7 @@ void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
 }
 
 void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
-    if (!_stoppingAllScripts) {
+    if (!_stopped) {
         QMutexLocker locker(&_allScriptsMutex);
         _allKnownScriptEngines.insert(engine);
     }
@@ -128,15 +128,14 @@ void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
     // If we're not already in the middle of stopping all scripts, then we should remove ourselves
     // from the list of running scripts. We don't do this if we're in the process of stopping all scripts
     // because that method removes scripts from its list as it iterates them
-    if (!_stoppingAllScripts) {
+    if (!_stopped) {
         QMutexLocker locker(&_allScriptsMutex);
         _allKnownScriptEngines.remove(engine);
     }
 }
 
 void ScriptEngines::shutdownScripting() {
-    _stoppingAllScripts = true;
-    ScriptEngine::_stoppingAllScripts = true;
+    _stopped = true;
     QMutexLocker locker(&_allScriptsMutex);
     qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size();
 
diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h
index 0963b21600..5b4a21be66 100644
--- a/libraries/script-engine/src/ScriptEngines.h
+++ b/libraries/script-engine/src/ScriptEngines.h
@@ -87,17 +87,16 @@ protected:
     void onScriptEngineError(const QString& scriptFilename);
     void launchScriptEngine(ScriptEngine* engine);
 
-
     Setting::Handle<bool> _firstRun { "firstRun", true };
     QReadWriteLock _scriptEnginesHashLock;
     QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
     QSet<ScriptEngine*> _allKnownScriptEngines;
     QMutex _allScriptsMutex;
-    std::atomic<bool> _stoppingAllScripts { false };
     std::list<ScriptInitializer> _scriptInitializers;
     mutable Setting::Handle<QString> _scriptsLocationHandle;
     ScriptsModel _scriptsModel;
     ScriptsModelFilter _scriptsModelFilter;
+    std::atomic<bool> _stopped { false };
 };
 
 QUrl normalizeScriptURL(const QUrl& rawScriptURL);

From 13610b1220cc54e261596f02e36ff05ec5fbfc21 Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Fri, 13 May 2016 15:24:31 -0700
Subject: [PATCH 18/29] Delete late-added script engines

---
 libraries/script-engine/src/ScriptEngines.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index d519af1bce..fc2e7a3eaf 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -118,7 +118,9 @@ void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
 }
 
 void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
-    if (!_stopped) {
+    if (_stopped) {
+        engine->deleteLater();
+    } else {
         QMutexLocker locker(&_allScriptsMutex);
         _allKnownScriptEngines.insert(engine);
     }

From 36565598a786b40f0b9de5f76347efa418361ad1 Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Fri, 13 May 2016 15:52:53 -0700
Subject: [PATCH 19/29] Hide script stop behind accessor and fix friendship

---
 libraries/script-engine/src/ScriptEngine.cpp  | 14 +++++++-------
 libraries/script-engine/src/ScriptEngine.h    |  6 ++++--
 libraries/script-engine/src/ScriptEngines.cpp |  9 ++++++---
 libraries/script-engine/src/ScriptEngines.h   |  3 ++-
 4 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 90434c977c..df31ad59f1 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -754,7 +754,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
 
 
 QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         return QScriptValue(); // bail early
     }
 
@@ -790,7 +790,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
 }
 
 void ScriptEngine::run() {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         return; // bail early - avoid setting state in init(), as evaluate() will bail too
     }
 
@@ -1023,7 +1023,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
 }
 
 QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();
         return NULL; // bail early
     }
@@ -1032,7 +1032,7 @@ QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS)
 }
 
 QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();
         return NULL; // bail early
     }
@@ -1084,7 +1084,7 @@ void ScriptEngine::print(const QString& message) {
 // If no callback is specified, the included files will be loaded synchronously and will block execution until
 // all of the files have finished loading.
 void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."
         << "includeFiles:" << includeFiles << "parent script:" << getFilename();
         return; // bail early
@@ -1182,7 +1182,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
 }
 
 void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "
             << "includeFile:" << includeFile << "parent script:" << getFilename();
         return; // bail early
@@ -1197,7 +1197,7 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
 // as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
 // the Application or other context will connect to in order to know to actually load the script
 void ScriptEngine::load(const QString& loadFile) {
-    if (DependencyManager::get<ScriptEngines>()->_stopped) {
+    if (DependencyManager::get<ScriptEngines>()->isStopped()) {
         qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "
             << "loadFile:" << loadFile << "parent script:" << getFilename();
         return; // bail early
diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h
index 8a3acb0fcb..993b4e1e8a 100644
--- a/libraries/script-engine/src/ScriptEngine.h
+++ b/libraries/script-engine/src/ScriptEngine.h
@@ -83,6 +83,10 @@ public:
     /// run the script in the callers thread, exit when stop() is called.
     void run();
 
+    void waitTillDoneRunning();
+
+    QString getFilename() const;
+
     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
     //        properly ensure they are only called on the correct thread
@@ -199,8 +203,6 @@ protected:
     qint64 _lastUpdate;
 
     void init();
-    QString getFilename() const;
-    void waitTillDoneRunning();
     bool evaluatePending() const { return _evaluatesPending > 0; }
     void timerFired();
     void stopAllTimers();
diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index f2b5f1cd01..2dce6457f1 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -119,7 +119,7 @@ void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
 }
 
 void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
-    if (_stopped) {
+    if (_isStopped) {
         engine->deleteLater();
     } else {
         QMutexLocker locker(&_allScriptsMutex);
@@ -131,14 +131,14 @@ void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
     // If we're not already in the middle of stopping all scripts, then we should remove ourselves
     // from the list of running scripts. We don't do this if we're in the process of stopping all scripts
     // because that method removes scripts from its list as it iterates them
-    if (!_stopped) {
+    if (!_isStopped) {
         QMutexLocker locker(&_allScriptsMutex);
         _allKnownScriptEngines.remove(engine);
     }
 }
 
 void ScriptEngines::shutdownScripting() {
-    _stopped = true;
+    _isStopped = true;
     QMutexLocker locker(&_allScriptsMutex);
     qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size();
 
@@ -498,6 +498,9 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
     }
 }
 
+bool ScriptEngines::isStopped() const {
+    return _isStopped;
+}
 
 void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEngine* engine) {
     bool removed = false;
diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h
index 8a395052a0..9a0a52bb52 100644
--- a/libraries/script-engine/src/ScriptEngines.h
+++ b/libraries/script-engine/src/ScriptEngines.h
@@ -86,6 +86,7 @@ protected:
     void onScriptEngineLoaded(const QString& scriptFilename);
     void onScriptEngineError(const QString& scriptFilename);
     void launchScriptEngine(ScriptEngine* engine);
+    bool isStopped() const;
 
     QReadWriteLock _scriptEnginesHashLock;
     QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
@@ -95,7 +96,7 @@ protected:
     mutable Setting::Handle<QString> _scriptsLocationHandle;
     ScriptsModel _scriptsModel;
     ScriptsModelFilter _scriptsModelFilter;
-    std::atomic<bool> _stopped { false };
+    std::atomic<bool> _isStopped { false };
 };
 
 QUrl normalizeScriptURL(const QUrl& rawScriptURL);

From cdb9cb651904feee965cd23e52cc8b8fd52649c0 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 13 May 2016 16:33:14 -0700
Subject: [PATCH 20/29] Fixing the metallic simple material rendering black
 from obj

---
 libraries/fbx/src/OBJReader.cpp        | 1 -
 libraries/model/src/model/Material.cpp | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp
index aa8edc3f68..38121555ed 100644
--- a/libraries/fbx/src/OBJReader.cpp
+++ b/libraries/fbx/src/OBJReader.cpp
@@ -624,7 +624,6 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
     return geometryPtr;
 }
 
-
 void fbxDebugDump(const FBXGeometry& fbxgeo) {
     qCDebug(modelformat) << "---------------- fbxGeometry ----------------";
     qCDebug(modelformat) << "  hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints;
diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp
index ba61732237..53478be536 100755
--- a/libraries/model/src/model/Material.cpp
+++ b/libraries/model/src/model/Material.cpp
@@ -98,6 +98,7 @@ void Material::setFresnel(const Color& fresnel, bool isSRGB) {
 }
 
 void Material::setMetallic(float metallic) {
+    metallic = glm::clamp(metallic, 0.0f, 1.0f);
     _key.setMetallic(metallic > 0.0f);
     _schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
     _schemaBuffer.edit<Schema>()._metallic = metallic;

From 96dffad0d341987e685f9270f3faf0cf55291894 Mon Sep 17 00:00:00 2001
From: samcake <samuel.gateau@gmail.com>
Date: Fri, 13 May 2016 16:35:15 -0700
Subject: [PATCH 21/29] Fixing the metallic simple material rendering black
 from obj

---
 libraries/gpu/src/gpu/Transform.h | 36 -------------------------------
 1 file changed, 36 deletions(-)
 delete mode 100644 libraries/gpu/src/gpu/Transform.h

diff --git a/libraries/gpu/src/gpu/Transform.h b/libraries/gpu/src/gpu/Transform.h
deleted file mode 100644
index 82974964a8..0000000000
--- a/libraries/gpu/src/gpu/Transform.h
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-//  Transform.h
-//  libraries/gpu/src/gpu
-//
-//  Created by Sam Gateau on 06/12/2016.
-//  Copyright 2014 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-#ifndef hifi_gpu_Transform_h
-#define hifi_gpu_Transform_h
-
-#include <vector>
-
-#include <assert.h>
-
-#include "Resource.h"
-
-namespace gpu {
-
-class TransformBuffer {
-public:
-
-    TransformBuffer() {}
-    ~TransformBuffer() {}
-
-protected:
-    BufferPointer _buffer;
-};
-typedef std::shared_ptr<TransformBuffer> TransformBufferPointer;
-
-};
-
-
-#endif

From 6277bf86034bcdfb7434930b5c570f7ba41acfcc Mon Sep 17 00:00:00 2001
From: Seth Alves <seth.alves@gmail.com>
Date: Fri, 13 May 2016 16:37:43 -0700
Subject: [PATCH 22/29] if avatar is set to the default avatar file that came
 with the distribution, don't save that url to settings.  never send a file
 url to the avatar-mixer.

---
 interface/src/avatar/MyAvatar.cpp    | 6 +++++-
 libraries/avatars/src/AvatarData.cpp | 2 +-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index a3cf1f9f4f..ddc0407f14 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -675,7 +675,11 @@ void MyAvatar::saveData() {
     settings.setValue("leanScale", _leanScale);
     settings.setValue("scale", _targetScale);
 
-    settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences);
+    settings.setValue("fullAvatarURL",
+                      _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
+                      "" :
+                      _fullAvatarURLFromPreferences.toString());
+
     settings.setValue("fullAvatarModelName", _fullAvatarModelName);
     settings.setValue("animGraphURL", _animGraphUrl);
 
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index fd13f8c370..a2f4c98218 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -990,7 +990,7 @@ QByteArray AvatarData::identityByteArray() {
     QByteArray identityData;
     QDataStream identityStream(&identityData, QIODevice::Append);
     QUrl emptyURL("");
-    const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? emptyURL : _skeletonModelURL;
+    const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
 
     QUrl unusedModelURL; // legacy faceModel support
 

From 33e2cde266c0cf814ce7b1d994f6a9b096be0a14 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Sat, 14 May 2016 12:08:19 +1200
Subject: [PATCH 23/29] Change default position to be bottom left of HMD
 recommended rectangle

---
 scripts/system/users.js | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/scripts/system/users.js b/scripts/system/users.js
index 08c354be87..d935dd23ca 100644
--- a/scripts/system/users.js
+++ b/scripts/system/users.js
@@ -904,7 +904,8 @@ var usersWindow = (function () {
     function setUp() {
         var textSizeOverlay,
             offsetSetting,
-            offset = {};
+            offset = {},
+            hmdViewport;
 
         textSizeOverlay = Overlays.addOverlay("text", {
             font: WINDOW_FONT,
@@ -926,7 +927,11 @@ var usersWindow = (function () {
             windowPosition.y = offset.y <= 0 ? viewport.y + offset.y : offset.y;
 
         } else {
-            windowPosition = { x: 0, y: viewport.y };
+            hmdViewport = Controller.getRecommendedOverlayRect();
+            windowPosition = {
+                x: (viewport.x - hmdViewport.width) / 2,  // HMD viewport is narrower than screen.
+                y: hmdViewport.height  // HMD viewport starts at top of screen but only extends down so far.
+            };
         }
 
         calculateWindowHeight();

From 4e62d7ff6114e7c529cd6307d8229545f2d279bc Mon Sep 17 00:00:00 2001
From: Zach Pomerantz <zach@highfidelity.io>
Date: Fri, 13 May 2016 17:28:21 -0700
Subject: [PATCH 24/29] Define ScriptEngines::isStopped inline

---
 libraries/script-engine/src/ScriptEngines.cpp | 4 ----
 libraries/script-engine/src/ScriptEngines.h   | 2 +-
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index 2dce6457f1..805ddfdada 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -498,10 +498,6 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
     }
 }
 
-bool ScriptEngines::isStopped() const {
-    return _isStopped;
-}
-
 void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEngine* engine) {
     bool removed = false;
     {
diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h
index 9a0a52bb52..72bf7d529e 100644
--- a/libraries/script-engine/src/ScriptEngines.h
+++ b/libraries/script-engine/src/ScriptEngines.h
@@ -86,7 +86,7 @@ protected:
     void onScriptEngineLoaded(const QString& scriptFilename);
     void onScriptEngineError(const QString& scriptFilename);
     void launchScriptEngine(ScriptEngine* engine);
-    bool isStopped() const;
+    bool isStopped() const { return _isStopped; }
 
     QReadWriteLock _scriptEnginesHashLock;
     QHash<QUrl, ScriptEngine*> _scriptEnginesHash;

From eb84459f033f58f2d98befea8d576b99c446d96e Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Thu, 12 May 2016 16:32:29 -0700
Subject: [PATCH 25/29] Support partial CPU->GPU buffer transfers

---
 libraries/gpu/src/gpu/GLBackend.h         |  17 ++-
 libraries/gpu/src/gpu/GLBackendBuffer.cpp | 147 +++++++++++++++++-----
 libraries/gpu/src/gpu/Resource.cpp        | 144 +++++++++++----------
 libraries/gpu/src/gpu/Resource.h          |  56 +++++++--
 4 files changed, 254 insertions(+), 110 deletions(-)

diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h
index d9ead354ea..3a1a204d01 100644
--- a/libraries/gpu/src/gpu/GLBackend.h
+++ b/libraries/gpu/src/gpu/GLBackend.h
@@ -65,15 +65,22 @@ public:
 
     class GLBuffer : public GPUObject {
     public:
-        Stamp _stamp;
-        GLuint _buffer;
-        GLuint _size;
+        const GLuint _buffer;
+        const GLuint _size;
+        const Stamp _stamp;
 
-        GLBuffer();
+        GLBuffer(const Buffer& buffer);
         ~GLBuffer();
 
-        void setSize(GLuint size);
+        void transfer(bool forceAll = false);
+
+    private:
+        bool getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const;
+            
+        // The owning texture
+        const Buffer& _gpuBuffer;
     };
+
     static GLBuffer* syncGPUObject(const Buffer& buffer);
     static GLuint getBufferID(const Buffer& buffer);
 
diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp
index 080d743104..486f4cee8e 100755
--- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp
+++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp
@@ -12,14 +12,43 @@
 
 using namespace gpu;
 
-GLBackend::GLBuffer::GLBuffer() :
-    _stamp(0),
-    _buffer(0),
-    _size(0)
-{
-    Backend::incrementBufferGPUCount();
+static std::once_flag check_dsa;
+static bool DSA_SUPPORTED { false };
+
+GLuint allocateSingleBuffer() {
+    std::call_once(check_dsa, [&] {
+        DSA_SUPPORTED = (GLEW_VERSION_4_5 || GLEW_ARB_direct_state_access);
+    });
+    GLuint result;
+    if (DSA_SUPPORTED) {
+        glCreateBuffers(1, &result);
+    } else {
+        glGenBuffers(1, &result);
+    }
+    return result;
 }
 
+GLBackend::GLBuffer::GLBuffer(const Buffer& buffer) : 
+    _buffer(allocateSingleBuffer()), 
+    _size(buffer._sysmem.getSize()), 
+    _stamp(buffer._sysmem.getStamp()), 
+    _gpuBuffer(buffer) {
+    (void)CHECK_GL_ERROR();
+    Backend::setGPUObject(buffer, this);
+    if (DSA_SUPPORTED) {
+        glNamedBufferStorage(_buffer, _size, nullptr, GL_DYNAMIC_STORAGE_BIT);
+    } else {
+        glBindBuffer(GL_ARRAY_BUFFER, _buffer);
+        if (GLEW_VERSION_4_4 || GLEW_ARB_buffer_storage) {
+            glBufferStorage(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_STORAGE_BIT);
+        } else {
+            glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW);
+        }
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+    }
+    Backend::incrementBufferGPUCount();
+}
+    
 GLBackend::GLBuffer::~GLBuffer() {
     if (_buffer != 0) {
         glDeleteBuffers(1, &_buffer);
@@ -28,37 +57,99 @@ GLBackend::GLBuffer::~GLBuffer() {
     Backend::decrementBufferGPUCount();
 }
 
-void GLBackend::GLBuffer::setSize(GLuint size) {
-    Backend::updateBufferGPUMemoryUsage(_size, size);
-    _size = size;
+void GLBackend::GLBuffer::transfer(bool forceAll) {
+    const auto& pageFlags = _gpuBuffer._pages;
+    if (!forceAll) {
+        size_t transitions = 0;
+        if (pageFlags.size()) {
+            bool lastDirty = (0 != (pageFlags[0] & Buffer::DIRTY));
+            for (size_t i = 1; i < pageFlags.size(); ++i) {
+                bool newDirty = (0 != (pageFlags[0] & Buffer::DIRTY));
+                if (newDirty != lastDirty) {
+                    ++transitions;
+                    lastDirty = newDirty;
+                }
+            }
+        }
+
+        // If there are no transitions (implying the whole buffer is dirty) 
+        // or more than 20 transitions, then just transfer the whole buffer
+        if (transitions == 0 || transitions > 20) {
+            forceAll = true;
+        }
+    }
+
+    // Are we transferring the whole buffer?
+    if (forceAll) {
+        if (DSA_SUPPORTED) {
+            glNamedBufferSubData(_buffer, 0, _size, _gpuBuffer.getSysmem().readData());
+        } else {
+            // Now let's update the content of the bo with the sysmem version
+            // TODO: in the future, be smarter about when to actually upload the glBO version based on the data that did change
+            //if () {
+            glBindBuffer(GL_ARRAY_BUFFER, _buffer);
+            glBufferData(GL_ARRAY_BUFFER, _gpuBuffer.getSysmem().getSize(), _gpuBuffer.getSysmem().readData(), GL_DYNAMIC_DRAW);
+            glBindBuffer(GL_ARRAY_BUFFER, 0);
+        }
+    } else {
+        if (!DSA_SUPPORTED) {
+            glBindBuffer(GL_ARRAY_BUFFER, _buffer);
+        }
+        GLintptr offset;
+        GLsizeiptr size;
+        size_t currentPage { 0 };
+        auto data = _gpuBuffer.getSysmem().readData();
+        while (getNextTransferBlock(offset, size, currentPage)) {
+            if (DSA_SUPPORTED) {
+                glNamedBufferSubData(_buffer, offset, size, data + offset);
+            } else {
+                glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset);
+            }
+        }
+
+        if (!DSA_SUPPORTED) {
+            glBindBuffer(GL_ARRAY_BUFFER, 0);
+        }
+    }
+    _gpuBuffer._flags &= ~Buffer::DIRTY;
+    (void)CHECK_GL_ERROR();
+}
+
+bool GLBackend::GLBuffer::getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const {
+    size_t pageCount = _gpuBuffer._pages.size();
+    // Advance to the first dirty page
+    while (currentPage < pageCount && (0 == (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) {
+        ++currentPage;
+    }
+
+    // If we got to the end, we're done
+    if (currentPage >= pageCount) {
+        return false;
+    }
+
+    // Advance to the next clean page
+    outOffset = static_cast<GLintptr>(currentPage * _gpuBuffer._pageSize);
+    while (currentPage < pageCount && (0 != (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) {
+        ++currentPage;
+    }
+    outSize = static_cast<GLsizeiptr>((currentPage * _gpuBuffer._pageSize) - outOffset);
+    return true;
 }
 
 GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) {
     GLBuffer* object = Backend::getGPUObject<GLBackend::GLBuffer>(buffer);
 
-    if (object && (object->_stamp == buffer.getSysmem().getStamp())) {
-        return object;
+    bool forceTransferAll = false;
+    // Has the storage size changed?
+    if (!object || object->_stamp != buffer.getSysmem().getStamp()) {
+        object = new GLBuffer(buffer);
+        forceTransferAll = true;
     }
 
-    // need to have a gpu object?
-    if (!object) {
-        object = new GLBuffer();
-        glGenBuffers(1, &object->_buffer);
-        (void) CHECK_GL_ERROR();
-        Backend::setGPUObject(buffer, object);
+    if (forceTransferAll || (0 != (buffer._flags & Buffer::DIRTY))) {
+        object->transfer(forceTransferAll);
     }
 
-    // Now let's update the content of the bo with the sysmem version
-    // TODO: in the future, be smarter about when to actually upload the glBO version based on the data that did change
-    //if () {
-    glBindBuffer(GL_ARRAY_BUFFER, object->_buffer);
-    glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW);
-    glBindBuffer(GL_ARRAY_BUFFER, 0);
-    object->_stamp = buffer.getSysmem().getStamp();
-    object->setSize((GLuint)buffer.getSysmem().getSize());
-    //}
-    (void) CHECK_GL_ERROR();
-
     return object;
 }
 
diff --git a/libraries/gpu/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp
index deb17300c3..add9b1df3c 100644
--- a/libraries/gpu/src/gpu/Resource.cpp
+++ b/libraries/gpu/src/gpu/Resource.cpp
@@ -109,40 +109,18 @@ void Resource::Sysmem::deallocateMemory(Byte* dataAllocated, Size size) {
     }
 }
 
-Resource::Sysmem::Sysmem() :
-    _stamp(0),
-    _size(0),
-    _data(NULL)
-{
-}
+Resource::Sysmem::Sysmem() {}
 
-Resource::Sysmem::Sysmem(Size size, const Byte* bytes) :
-    _stamp(0),
-    _size(0),
-    _data(NULL)
-{
-    if (size > 0) {
-        _size = allocateMemory(&_data, size);
-        if (_size >= size) {
-            if (bytes) {
-                memcpy(_data, bytes, size);
-            }
-        }
+Resource::Sysmem::Sysmem(Size size, const Byte* bytes) {
+    if (size > 0 && bytes) {
+        setData(_size, bytes);
     }
 }
 
-Resource::Sysmem::Sysmem(const Sysmem& sysmem) :
-    _stamp(0),
-    _size(0),
-    _data(NULL)
-{
+Resource::Sysmem::Sysmem(const Sysmem& sysmem) {
     if (sysmem.getSize() > 0) {
-        _size = allocateMemory(&_data, sysmem.getSize());
-        if (_size >= sysmem.getSize()) {
-            if (sysmem.readData()) {
-                memcpy(_data, sysmem.readData(), sysmem.getSize());
-            }
-        }
+        allocate(sysmem._size);
+        setData(_size, sysmem._data);
     }
 }
 
@@ -208,7 +186,6 @@ Resource::Size Resource::Sysmem::setData( Size size, const Byte* bytes ) {
     if (allocate(size) == size) {
         if (size && bytes) {
             memcpy( _data, bytes, _size );
-            _stamp++;
         }
     }
     return _size;
@@ -217,7 +194,6 @@ Resource::Size Resource::Sysmem::setData( Size size, const Byte* bytes ) {
 Resource::Size Resource::Sysmem::setSubData( Size offset, Size size, const Byte* bytes) {
     if (size && ((offset + size) <= getSize()) && bytes) {
         memcpy( _data + offset, bytes, size );
-        _stamp++;
         return size;
     }
     return 0;
@@ -264,65 +240,105 @@ Buffer::Size Buffer::getBufferGPUMemoryUsage() {
     return Context::getBufferGPUMemoryUsage();
 }
 
-Buffer::Buffer() :
-    Resource(),
-    _sysmem(new Sysmem()) {
+Buffer::Buffer(Size pageSize) :
+    _pageSize(pageSize) {
     _bufferCPUCount++;
-
 }
 
-Buffer::Buffer(Size size, const Byte* bytes) :
-    Resource(),
-    _sysmem(new Sysmem(size, bytes)) {
-    _bufferCPUCount++;
-    Buffer::updateBufferCPUMemoryUsage(0, _sysmem->getSize());
+Buffer::Buffer(Size size, const Byte* bytes, Size pageSize) : Buffer(pageSize) {
+    setData(size, bytes);
 }
 
-Buffer::Buffer(const Buffer& buf) :
-    Resource(),
-    _sysmem(new Sysmem(buf.getSysmem())) {
-    _bufferCPUCount++;
-    Buffer::updateBufferCPUMemoryUsage(0, _sysmem->getSize());
+Buffer::Buffer(const Buffer& buf) : Buffer(buf._pageSize) {
+    setData(buf.getSize(), buf.getData());
 }
 
 Buffer& Buffer::operator=(const Buffer& buf) {
-    (*_sysmem) = buf.getSysmem();
+    const_cast<Size&>(_pageSize) = buf._pageSize;
+    setData(buf.getSize(), buf.getData());
     return (*this);
 }
 
 Buffer::~Buffer() {
     _bufferCPUCount--;
-
-    if (_sysmem) {
-        Buffer::updateBufferCPUMemoryUsage(_sysmem->getSize(), 0);
-        delete _sysmem;
-        _sysmem = NULL;
-    }
+    Buffer::updateBufferCPUMemoryUsage(_sysmem.getSize(), 0);
 }
 
 Buffer::Size Buffer::resize(Size size) {
+    _end = size;
     auto prevSize = editSysmem().getSize();
-    auto newSize = editSysmem().resize(size);
-    Buffer::updateBufferCPUMemoryUsage(prevSize, newSize);
-    return newSize;
+    if (prevSize < size) {
+        auto newPages = getRequiredPageCount();
+        auto newSize = newPages * _pageSize;
+        editSysmem().resize(size);
+        // All new pages start off as clean, because they haven't been populated by data
+        _pages.resize(newPages, 0);
+        Buffer::updateBufferCPUMemoryUsage(prevSize, newSize);
+    }
+    return _end;
 }
 
+void Buffer::dirtyPages(Size offset, Size bytes) {
+    if (!bytes) {
+        return;
+    }
+    _flags |= DIRTY;
+    // Find the starting page
+    Size startPage = (offset / _pageSize);
+    // Non-zero byte count, so at least one page is dirty
+    Size pageCount = 1;
+    // How much of the page is after the offset?
+    Size remainder = _pageSize - (offset % _pageSize);
+    //  If there are more bytes than page space remaining, we need to increase the page count
+    if (bytes > remainder) {
+        // Get rid of the amount that will fit in the current page
+        bytes -= remainder;
+
+        pageCount += (bytes / _pageSize);
+        if (bytes % _pageSize) {
+            ++pageCount;
+        }
+    }
+
+    // Mark the pages dirty
+    for (Size i = 0; i < pageCount; ++i) {
+        _pages[i + startPage] |= DIRTY;
+    }
+}
+
+
 Buffer::Size Buffer::setData(Size size, const Byte* data) {
-    auto prevSize = editSysmem().getSize();
-    auto newSize = editSysmem().setData(size, data);
-    Buffer::updateBufferCPUMemoryUsage(prevSize, newSize);
-    return newSize;
+    resize(size);
+    setSubData(0, size, data);
+    return _end;
 }
 
 Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) {
-    return editSysmem().setSubData( offset, size, data);
+    auto changedBytes = editSysmem().setSubData(offset, size, data);
+    if (changedBytes) {
+        dirtyPages(offset, changedBytes);
+    }
+    return changedBytes;
 }
 
 Buffer::Size Buffer::append(Size size, const Byte* data) {
-    auto prevSize = editSysmem().getSize();
-    auto newSize = editSysmem().append( size, data);
-    Buffer::updateBufferCPUMemoryUsage(prevSize, newSize);
-    return newSize;
+    auto offset = _end;
+    resize(_end + size);
+    setSubData(offset, size, data);
+    return _end;
+}
+
+Buffer::Size Buffer::getSize() const { 
+    Q_ASSERT(getSysmem().getSize() >= _end);
+    return _end;
+}
+
+Buffer::Size Buffer::getRequiredPageCount() const {
+    Size result = _end / _pageSize;
+    if (_end % _pageSize) {
+        ++result;
+    }
+    return result;
 }
 
 const Element BufferView::DEFAULT_ELEMENT = Element( gpu::SCALAR, gpu::UINT8, gpu::RAW );
diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h
index 570aff00fc..3f9a87b52c 100644
--- a/libraries/gpu/src/gpu/Resource.h
+++ b/libraries/gpu/src/gpu/Resource.h
@@ -88,10 +88,10 @@ protected:
         // Access the byte array.
         // The edit version allow to map data.
         const Byte* readData() const { return _data; } 
-        Byte* editData() { _stamp++; return _data; }
+        Byte* editData() { return _data; }
 
         template< typename T > const T* read() const { return reinterpret_cast< T* > ( _data ); } 
-        template< typename T > T* edit() { _stamp++; return reinterpret_cast< T* > ( _data ); } 
+        template< typename T > T* edit() { return reinterpret_cast< T* > ( _data ); } 
 
         // Access the current version of the sysmem, used to compare if copies are in sync
         Stamp getStamp() const { return _stamp; }
@@ -102,9 +102,9 @@ protected:
         bool isAvailable() const { return (_data != 0); }
 
     private:
-        Stamp _stamp;
-        Size  _size;
-        Byte* _data;
+        Stamp _stamp { 0 };
+        Size  _size { 0 };
+        Byte* _data { nullptr };
     };
 
 };
@@ -115,19 +115,26 @@ class Buffer : public Resource {
     static void updateBufferCPUMemoryUsage(Size prevObjectSize, Size newObjectSize);
 
 public:
+    enum Flag {
+        DIRTY = 0x01,
+    };
+
+    // Currently only one flag... 'dirty'
+    using PageFlags = std::vector<uint8_t>;
+    static const Size DEFAULT_PAGE_SIZE = 4096;
     static uint32_t getBufferCPUCount();
     static Size getBufferCPUMemoryUsage();
     static uint32_t getBufferGPUCount();
     static Size getBufferGPUMemoryUsage();
 
-    Buffer();
-    Buffer(Size size, const Byte* bytes);
+    Buffer(Size pageSize = DEFAULT_PAGE_SIZE);
+    Buffer(Size size, const Byte* bytes, Size pageSize = DEFAULT_PAGE_SIZE);
     Buffer(const Buffer& buf); // deep copy of the sysmem buffer
     Buffer& operator=(const Buffer& buf); // deep copy of the sysmem buffer
     ~Buffer();
 
     // The size in bytes of data stored in the buffer
-    Size getSize() const { return getSysmem().getSize(); }
+    Size getSize() const;
     const Byte* getData() const { return getSysmem().readData(); }
     Byte* editData() { return editSysmem().editData(); }
 
@@ -143,6 +150,20 @@ public:
     // \return the number of bytes copied
     Size setSubData(Size offset, Size size, const Byte* data);
 
+    template <typename T>
+    Size setSubData(Size index, const T& t) {
+        Size offset = index * sizeof(T);
+        Size size = sizeof(T);
+        return setSubData(offset, size, reinterpret_cast<const Byte*>(&t));
+    }
+
+    template <typename T>
+    Size setSubData(Size index, const std::vector<T>& t) {
+        Size offset = index * sizeof(T);
+        Size size = t.size() * sizeof(T);
+        return setSubData(offset, size, reinterpret_cast<const Byte*>(&t[0]));
+    }
+
     // Append new data at the end of the current buffer
     // do a resize( size + getSize) and copy the new data
     // \return the number of bytes copied
@@ -158,15 +179,24 @@ public:
         return append(sizeof(T) * t.size(), reinterpret_cast<const Byte*>(&t[0]));
     }
 
-    // Access the sysmem object.
-    const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); }
-    Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); }
-
     const GPUObjectPointer gpuObject {};
     
 protected:
+    // Access the sysmem object, limited to ourselves and GPUObject derived classes
+    const Sysmem& getSysmem() const { return _sysmem; }
+    Sysmem& editSysmem() { return _sysmem; }
 
-    Sysmem* _sysmem = NULL;
+    Size getRequiredPageCount() const;
+    void dirtyPages(Size offset, Size bytes);
+
+    Size _end { 0 };
+    mutable uint8_t _flags;
+    mutable PageFlags _pages;
+    const Size _pageSize;
+    Sysmem _sysmem;
+
+    // FIXME find a more generic way to do this.
+    friend class GLBackend;
 };
 
 typedef std::shared_ptr<Buffer> BufferPointer;

From 9509e32928dad7981e35fec3c2eda0ce50f99b21 Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Thu, 12 May 2016 21:37:13 -0700
Subject: [PATCH 26/29] PR feedback

---
 .../RenderableParticleEffectEntityItem.cpp    |   2 +-
 libraries/gpu/src/gpu/GLBackend.h             |   4 +-
 libraries/gpu/src/gpu/GLBackendBuffer.cpp     | 130 ++++++------------
 libraries/gpu/src/gpu/Resource.cpp            |   6 +-
 libraries/gpu/src/gpu/Resource.h              |  27 +++-
 libraries/render-utils/src/AnimDebugDraw.cpp  |  54 ++++----
 libraries/render/src/render/DrawStatus.cpp    |  71 ++++------
 libraries/render/src/render/DrawStatus.h      |  10 +-
 8 files changed, 129 insertions(+), 175 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index a199c6b10e..86c3f5ff35 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -267,7 +267,7 @@ void RenderableParticleEffectEntityItem::updateRenderItem() {
         if (numBytes == 0) {
             return;
         }
-        memcpy(particleBuffer->editData(), particlePrimitives->data(), numBytes);
+        particleBuffer->setData(numBytes, (const gpu::Byte*)particlePrimitives->data());
 
         // Update transform and bounds
         payload.setModelTransform(transform);
diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h
index 3a1a204d01..672a29538b 100644
--- a/libraries/gpu/src/gpu/GLBackend.h
+++ b/libraries/gpu/src/gpu/GLBackend.h
@@ -69,10 +69,10 @@ public:
         const GLuint _size;
         const Stamp _stamp;
 
-        GLBuffer(const Buffer& buffer);
+        GLBuffer(const Buffer& buffer, GLBuffer* original = nullptr);
         ~GLBuffer();
 
-        void transfer(bool forceAll = false);
+        void transfer();
 
     private:
         bool getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const;
diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp
index 486f4cee8e..60484ef1bf 100755
--- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp
+++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp
@@ -12,43 +12,42 @@
 
 using namespace gpu;
 
-static std::once_flag check_dsa;
-static bool DSA_SUPPORTED { false };
-
 GLuint allocateSingleBuffer() {
-    std::call_once(check_dsa, [&] {
-        DSA_SUPPORTED = (GLEW_VERSION_4_5 || GLEW_ARB_direct_state_access);
-    });
     GLuint result;
-    if (DSA_SUPPORTED) {
-        glCreateBuffers(1, &result);
-    } else {
-        glGenBuffers(1, &result);
-    }
+    glGenBuffers(1, &result);
+    (void)CHECK_GL_ERROR();
     return result;
 }
 
-GLBackend::GLBuffer::GLBuffer(const Buffer& buffer) : 
-    _buffer(allocateSingleBuffer()), 
-    _size(buffer._sysmem.getSize()), 
-    _stamp(buffer._sysmem.getStamp()), 
+GLBackend::GLBuffer::GLBuffer(const Buffer& buffer, GLBuffer* original) :
+    _buffer(allocateSingleBuffer()),
+    _size((GLuint)buffer._sysmem.getSize()),
+    _stamp(buffer._sysmem.getStamp()),
     _gpuBuffer(buffer) {
-    (void)CHECK_GL_ERROR();
-    Backend::setGPUObject(buffer, this);
-    if (DSA_SUPPORTED) {
-        glNamedBufferStorage(_buffer, _size, nullptr, GL_DYNAMIC_STORAGE_BIT);
+
+    glBindBuffer(GL_ARRAY_BUFFER, _buffer);
+    if (GLEW_VERSION_4_4 || GLEW_ARB_buffer_storage) {
+        glBufferStorage(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_STORAGE_BIT);
+        (void)CHECK_GL_ERROR();
     } else {
-        glBindBuffer(GL_ARRAY_BUFFER, _buffer);
-        if (GLEW_VERSION_4_4 || GLEW_ARB_buffer_storage) {
-            glBufferStorage(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_STORAGE_BIT);
-        } else {
-            glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW);
-        }
-        glBindBuffer(GL_ARRAY_BUFFER, 0);
+        glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW);
+        (void)CHECK_GL_ERROR();
     }
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+    if (original) {
+        glBindBuffer(GL_COPY_WRITE_BUFFER, _buffer);
+        glBindBuffer(GL_COPY_READ_BUFFER, original->_buffer);
+        glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, original->_size);
+        glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
+        glBindBuffer(GL_COPY_READ_BUFFER, 0);
+        (void)CHECK_GL_ERROR();
+    }
+
+    Backend::setGPUObject(buffer, this);
     Backend::incrementBufferGPUCount();
 }
-    
+
 GLBackend::GLBuffer::~GLBuffer() {
     if (_buffer != 0) {
         glDeleteBuffers(1, &_buffer);
@@ -57,62 +56,20 @@ GLBackend::GLBuffer::~GLBuffer() {
     Backend::decrementBufferGPUCount();
 }
 
-void GLBackend::GLBuffer::transfer(bool forceAll) {
-    const auto& pageFlags = _gpuBuffer._pages;
-    if (!forceAll) {
-        size_t transitions = 0;
-        if (pageFlags.size()) {
-            bool lastDirty = (0 != (pageFlags[0] & Buffer::DIRTY));
-            for (size_t i = 1; i < pageFlags.size(); ++i) {
-                bool newDirty = (0 != (pageFlags[0] & Buffer::DIRTY));
-                if (newDirty != lastDirty) {
-                    ++transitions;
-                    lastDirty = newDirty;
-                }
-            }
-        }
-
-        // If there are no transitions (implying the whole buffer is dirty) 
-        // or more than 20 transitions, then just transfer the whole buffer
-        if (transitions == 0 || transitions > 20) {
-            forceAll = true;
-        }
-    }
-
-    // Are we transferring the whole buffer?
-    if (forceAll) {
-        if (DSA_SUPPORTED) {
-            glNamedBufferSubData(_buffer, 0, _size, _gpuBuffer.getSysmem().readData());
-        } else {
-            // Now let's update the content of the bo with the sysmem version
-            // TODO: in the future, be smarter about when to actually upload the glBO version based on the data that did change
-            //if () {
-            glBindBuffer(GL_ARRAY_BUFFER, _buffer);
-            glBufferData(GL_ARRAY_BUFFER, _gpuBuffer.getSysmem().getSize(), _gpuBuffer.getSysmem().readData(), GL_DYNAMIC_DRAW);
-            glBindBuffer(GL_ARRAY_BUFFER, 0);
-        }
-    } else {
-        if (!DSA_SUPPORTED) {
-            glBindBuffer(GL_ARRAY_BUFFER, _buffer);
-        }
-        GLintptr offset;
-        GLsizeiptr size;
-        size_t currentPage { 0 };
-        auto data = _gpuBuffer.getSysmem().readData();
-        while (getNextTransferBlock(offset, size, currentPage)) {
-            if (DSA_SUPPORTED) {
-                glNamedBufferSubData(_buffer, offset, size, data + offset);
-            } else {
-                glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset);
-            }
-        }
-
-        if (!DSA_SUPPORTED) {
-            glBindBuffer(GL_ARRAY_BUFFER, 0);
-        }
-    }
-    _gpuBuffer._flags &= ~Buffer::DIRTY;
+void GLBackend::GLBuffer::transfer() {
+    glBindBuffer(GL_ARRAY_BUFFER, _buffer);
     (void)CHECK_GL_ERROR();
+    GLintptr offset;
+    GLsizeiptr size;
+    size_t currentPage { 0 };
+    auto data = _gpuBuffer.getSysmem().readData();
+    while (getNextTransferBlock(offset, size, currentPage)) {
+        glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset);
+        (void)CHECK_GL_ERROR();
+    }
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    (void)CHECK_GL_ERROR();
+    _gpuBuffer._flags &= ~Buffer::DIRTY;
 }
 
 bool GLBackend::GLBuffer::getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const {
@@ -130,6 +87,7 @@ bool GLBackend::GLBuffer::getNextTransferBlock(GLintptr& outOffset, GLsizeiptr&
     // Advance to the next clean page
     outOffset = static_cast<GLintptr>(currentPage * _gpuBuffer._pageSize);
     while (currentPage < pageCount && (0 != (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) {
+        _gpuBuffer._pages[currentPage] &= ~Buffer::DIRTY;
         ++currentPage;
     }
     outSize = static_cast<GLsizeiptr>((currentPage * _gpuBuffer._pageSize) - outOffset);
@@ -139,15 +97,13 @@ bool GLBackend::GLBuffer::getNextTransferBlock(GLintptr& outOffset, GLsizeiptr&
 GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) {
     GLBuffer* object = Backend::getGPUObject<GLBackend::GLBuffer>(buffer);
 
-    bool forceTransferAll = false;
     // Has the storage size changed?
     if (!object || object->_stamp != buffer.getSysmem().getStamp()) {
-        object = new GLBuffer(buffer);
-        forceTransferAll = true;
+        object = new GLBuffer(buffer, object);
     }
 
-    if (forceTransferAll || (0 != (buffer._flags & Buffer::DIRTY))) {
-        object->transfer(forceTransferAll);
+    if (0 != (buffer._flags & Buffer::DIRTY)) {
+        object->transfer();
     }
 
     return object;
diff --git a/libraries/gpu/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp
index add9b1df3c..7dbe662cbc 100644
--- a/libraries/gpu/src/gpu/Resource.cpp
+++ b/libraries/gpu/src/gpu/Resource.cpp
@@ -270,7 +270,7 @@ Buffer::Size Buffer::resize(Size size) {
     if (prevSize < size) {
         auto newPages = getRequiredPageCount();
         auto newSize = newPages * _pageSize;
-        editSysmem().resize(size);
+        editSysmem().resize(newSize);
         // All new pages start off as clean, because they haven't been populated by data
         _pages.resize(newPages, 0);
         Buffer::updateBufferCPUMemoryUsage(prevSize, newSize);
@@ -278,7 +278,7 @@ Buffer::Size Buffer::resize(Size size) {
     return _end;
 }
 
-void Buffer::dirtyPages(Size offset, Size bytes) {
+void Buffer::markDirty(Size offset, Size bytes) {
     if (!bytes) {
         return;
     }
@@ -316,7 +316,7 @@ Buffer::Size Buffer::setData(Size size, const Byte* data) {
 Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) {
     auto changedBytes = editSysmem().setSubData(offset, size, data);
     if (changedBytes) {
-        dirtyPages(offset, changedBytes);
+        markDirty(offset, changedBytes);
     }
     return changedBytes;
 }
diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h
index 3f9a87b52c..70292f215b 100644
--- a/libraries/gpu/src/gpu/Resource.h
+++ b/libraries/gpu/src/gpu/Resource.h
@@ -136,8 +136,7 @@ public:
     // The size in bytes of data stored in the buffer
     Size getSize() const;
     const Byte* getData() const { return getSysmem().readData(); }
-    Byte* editData() { return editSysmem().editData(); }
-
+    
     // Resize the buffer
     // Keep previous data [0 to min(pSize, mSize)]
     Size resize(Size pSize);
@@ -159,6 +158,9 @@ public:
 
     template <typename T>
     Size setSubData(Size index, const std::vector<T>& t) {
+        if (t.empty()) {
+            return 0;
+        }
         Size offset = index * sizeof(T);
         Size size = t.size() * sizeof(T);
         return setSubData(offset, size, reinterpret_cast<const Byte*>(&t[0]));
@@ -176,18 +178,28 @@ public:
 
     template <typename T>
     Size append(const std::vector<T>& t) {
+        if (t.empty()) {
+            return _end;
+        }
         return append(sizeof(T) * t.size(), reinterpret_cast<const Byte*>(&t[0]));
     }
 
     const GPUObjectPointer gpuObject {};
     
 protected:
+    void markDirty(Size offset, Size bytes);
+
+    template <typename T>
+    void markDirty(Size index, Size count = 1) {
+        markDirty(sizeof(T) * index, sizeof(T) * count);
+    }
+
     // Access the sysmem object, limited to ourselves and GPUObject derived classes
     const Sysmem& getSysmem() const { return _sysmem; }
     Sysmem& editSysmem() { return _sysmem; }
+    Byte* editData() { return editSysmem().editData(); }
 
     Size getRequiredPageCount() const;
-    void dirtyPages(Size offset, Size bytes);
 
     Size _end { 0 };
     mutable uint8_t _flags;
@@ -197,6 +209,7 @@ protected:
 
     // FIXME find a more generic way to do this.
     friend class GLBackend;
+    friend class BufferView;
 };
 
 typedef std::shared_ptr<Buffer> BufferPointer;
@@ -320,8 +333,14 @@ public:
         int _stride;
     };
 
+#if 0
+    // Direct memory access to the buffer contents is incompatible with the paging memory scheme
     template <typename T> Iterator<T> begin() { return Iterator<T>(&edit<T>(0), _stride); }
     template <typename T> Iterator<T> end() { return Iterator<T>(&edit<T>(getNum<T>()), _stride); }
+#else 
+    template <typename T> Iterator<const T> begin() const { return Iterator<const T>(&get<T>(), _stride); }
+    template <typename T> Iterator<const T> end() const { return Iterator<const T>(&get<T>(getNum<T>()), _stride); }
+#endif
     template <typename T> Iterator<const T> cbegin() const { return Iterator<const T>(&get<T>(), _stride); }
     template <typename T> Iterator<const T> cend() const { return Iterator<const T>(&get<T>(getNum<T>()), _stride); }
 
@@ -358,6 +377,7 @@ public:
             qDebug() << "Accessing buffer outside the BufferView range, element size = " << sizeof(T) << " when bufferView size = " << _size;
         }
  #endif
+        _buffer->markDirty(_offset, sizeof(T));
         T* t = (reinterpret_cast<T*> (_buffer->editData() + _offset));
         return *(t);
     }
@@ -391,6 +411,7 @@ public:
             qDebug() << "Accessing buffer outside the BufferView range, index = " << index << " number elements = " << getNum<T>();
         }
  #endif
+        _buffer->markDirty(elementOffset, sizeof(T));
         return *(reinterpret_cast<T*> (_buffer->editData() + elementOffset));
     }
 };
diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp
index 220b673591..11c43eaee4 100644
--- a/libraries/render-utils/src/AnimDebugDraw.cpp
+++ b/libraries/render-utils/src/AnimDebugDraw.cpp
@@ -118,29 +118,18 @@ AnimDebugDraw::AnimDebugDraw() :
 
     // HACK: add red, green and blue axis at (1,1,1)
     _animDebugDrawData->_vertexBuffer->resize(sizeof(Vertex) * 6);
-    Vertex* data = (Vertex*)_animDebugDrawData->_vertexBuffer->editData();
-
-    data[0].pos = glm::vec3(1.0, 1.0f, 1.0f);
-    data[0].rgba = toRGBA(255, 0, 0, 255);
-    data[1].pos = glm::vec3(2.0, 1.0f, 1.0f);
-    data[1].rgba = toRGBA(255, 0, 0, 255);
-
-    data[2].pos = glm::vec3(1.0, 1.0f, 1.0f);
-    data[2].rgba = toRGBA(0, 255, 0, 255);
-    data[3].pos = glm::vec3(1.0, 2.0f, 1.0f);
-    data[3].rgba = toRGBA(0, 255, 0, 255);
-
-    data[4].pos = glm::vec3(1.0, 1.0f, 1.0f);
-    data[4].rgba = toRGBA(0, 0, 255, 255);
-    data[5].pos = glm::vec3(1.0, 1.0f, 2.0f);
-    data[5].rgba = toRGBA(0, 0, 255, 255);
-
-    _animDebugDrawData->_indexBuffer->resize(sizeof(uint16_t) * 6);
-    uint16_t* indices = (uint16_t*)_animDebugDrawData->_indexBuffer->editData();
-    for (int i = 0; i < 6; i++) {
-        indices[i] = i;
-    }
-
+    
+    static std::vector<Vertex> vertices({ 
+        Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(255, 0, 0, 255) },
+        Vertex { glm::vec3(2.0, 1.0f, 1.0f), toRGBA(255, 0, 0, 255) },
+        Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(0, 255, 0, 255) },
+        Vertex { glm::vec3(1.0, 2.0f, 1.0f), toRGBA(0, 255, 0, 255) },
+        Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(0, 0, 255, 255) },
+        Vertex { glm::vec3(1.0, 1.0f, 2.0f), toRGBA(0, 0, 255, 255) },
+    });
+    static std::vector<uint16_t> indices({ 0, 1, 2, 3, 4, 5 });
+    _animDebugDrawData->_vertexBuffer->setSubData<Vertex>(0, vertices);
+    _animDebugDrawData->_indexBuffer->setSubData<uint16_t>(0, indices);
 }
 
 AnimDebugDraw::~AnimDebugDraw() {
@@ -356,9 +345,13 @@ void AnimDebugDraw::update() {
         numVerts += (int)DebugDraw::getInstance().getRays().size() * VERTICES_PER_RAY;
 
         // allocate verts!
-        data._vertexBuffer->resize(sizeof(Vertex) * numVerts);
-        Vertex* verts = (Vertex*)data._vertexBuffer->editData();
-        Vertex* v = verts;
+        std::vector<Vertex> vertices;
+        vertices.resize(numVerts);
+        //Vertex* verts = (Vertex*)data._vertexBuffer->editData();
+        Vertex* v = nullptr;
+        if (numVerts) {
+            v = &vertices[0];
+        }
 
         // draw absolute poses
         for (auto& iter : _absolutePoses) {
@@ -381,6 +374,8 @@ void AnimDebugDraw::update() {
                 }
             }
         }
+        data._vertexBuffer->resize(sizeof(Vertex) * numVerts);
+        data._vertexBuffer->setSubData<Vertex>(0, vertices);
 
         // draw markers from shared DebugDraw singleton
         for (auto& iter : markerMap) {
@@ -408,20 +403,19 @@ void AnimDebugDraw::update() {
         }
         DebugDraw::getInstance().clearRays();
 
-        assert(numVerts == (v - verts));
+        assert((!numVerts && !v) || (numVerts == (v - &vertices[0])));
 
         render::Item::Bound theBound;
         for (int i = 0; i < numVerts; i++) {
-            theBound += verts[i].pos;
+            theBound += vertices[i].pos;
         }
         data._bound = theBound;
 
         data._isVisible = (numVerts > 0);
 
         data._indexBuffer->resize(sizeof(uint16_t) * numVerts);
-        uint16_t* indices = (uint16_t*)data._indexBuffer->editData();
         for (int i = 0; i < numVerts; i++) {
-            indices[i] = i;
+            data._indexBuffer->setSubData<uint16_t>(i, (uint16_t)i);;
         }
     });
     scene->enqueuePendingChanges(pendingChanges);
diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp
index c2724fce42..bfbd123382 100644
--- a/libraries/render/src/render/DrawStatus.cpp
+++ b/libraries/render/src/render/DrawStatus.cpp
@@ -116,35 +116,25 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
     // FIrst thing, we collect the bound and the status for all the items we want to render
     int nbItems = 0;
     {
-        if (!_itemBounds) {
-            _itemBounds = std::make_shared<gpu::Buffer>();
-        }
-        if (!_itemStatus) {
-            _itemStatus = std::make_shared<gpu::Buffer>();;
-        }
-        if (!_itemCells) {
-            _itemCells = std::make_shared<gpu::Buffer>();;
-        }
+        _itemBounds.resize(inItems.size());
+        _itemStatus.resize(inItems.size());
+        _itemCells.resize(inItems.size());
 
-        _itemBounds->resize((inItems.size() * sizeof(AABox)));
-        _itemStatus->resize((inItems.size() * NUM_STATUS_VEC4_PER_ITEM * sizeof(glm::vec4)));
-        _itemCells->resize((inItems.size() * sizeof(Octree::Location)));
-
-        AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
-        glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
-        Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
-        for (auto& item : inItems) {
+//        AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
+//        glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
+//        Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
+        for (size_t i = 0; i < inItems.size(); ++i) {
+            const auto& item = inItems[i];
             if (!item.bound.isInvalid()) {
                 if (!item.bound.isNull()) {
-                    (*itemAABox) = item.bound;
+                    _itemBounds[i] = item.bound;
                 } else {
-                    (*itemAABox).setBox(item.bound.getCorner(), 0.1f);
+                    _itemBounds[i].setBox(item.bound.getCorner(), 0.1f);
                 }
                 
 
                 auto& itemScene = scene->getItem(item.id);
-
-                (*itemCell) = scene->getSpatialTree().getCellLocation(itemScene.getCell());
+                _itemCells[i] = scene->getSpatialTree().getCellLocation(itemScene.getCell());
 
                 auto itemStatusPointer = itemScene.getStatus();
                 if (itemStatusPointer) {
@@ -152,25 +142,19 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
                     auto&& currentStatusValues = itemStatusPointer->getCurrentValues();
                     int valueNum = 0;
                     for (int vec4Num = 0; vec4Num < NUM_STATUS_VEC4_PER_ITEM; vec4Num++) {
-                        (*itemStatus) = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
+                        auto& value = (vec4Num ? _itemStatus[i].first : _itemStatus[i].second);
+                        value = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
                         for (int component = 0; component < VEC4_LENGTH; component++) {
                             valueNum = vec4Num * VEC4_LENGTH + component;
                             if (valueNum < (int)currentStatusValues.size()) {
-                                (*itemStatus)[component] = currentStatusValues[valueNum].getPackedData();
+                                value[component] = currentStatusValues[valueNum].getPackedData();
                             }
                         }
-                        itemStatus++;
                     }
                 } else {
-                    (*itemStatus) = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
-                    itemStatus++;
-                    (*itemStatus) = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
-                    itemStatus++;
+                    _itemStatus[i].first = _itemStatus[i].second = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
                 }
-
                 nbItems++;
-                itemAABox++;
-                itemCell++;
             }
         }
     }
@@ -194,25 +178,20 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
         // bind the one gpu::Pipeline we need
         batch.setPipeline(getDrawItemBoundsPipeline());
 
-        AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
-        glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
-        Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
+        //AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
+        //glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
+        //Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
 
         const unsigned int VEC3_ADRESS_OFFSET = 3;
 
         if (_showDisplay) {
             for (int i = 0; i < nbItems; i++) {
-                batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*) (itemAABox + i));
-                batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET);
-               
-   
-                glm::ivec4 cellLocation(itemCell->pos.x, itemCell->pos.y, itemCell->pos.z, itemCell->depth);
+                batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)&(_itemBounds[i]));
+                batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*)&(_itemBounds[i])) + VEC3_ADRESS_OFFSET);
 
+                glm::ivec4 cellLocation(_itemCells[i].pos, _itemCells[i].depth);
                 batch._glUniform4iv(_drawItemCellLocLoc, 1, ((const int*)(&cellLocation)));
-     
-
                 batch.draw(gpu::LINES, 24, 0);
-                itemCell++;
             }
         }
 
@@ -222,10 +201,10 @@ void DrawStatus::run(const SceneContextPointer& sceneContext,
 
         if (_showNetwork) {
             for (int i = 0; i < nbItems; i++) {
-                batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*) (itemAABox + i));
-                batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*) (itemAABox + i)) + VEC3_ADRESS_OFFSET);
-                batch._glUniform4iv(_drawItemStatusValue0Loc, 1, (const int*)(itemStatus + NUM_STATUS_VEC4_PER_ITEM * i));
-                batch._glUniform4iv(_drawItemStatusValue1Loc, 1, (const int*)(itemStatus + NUM_STATUS_VEC4_PER_ITEM * i + 1));
+                batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*)&(_itemBounds[i]));
+                batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*)&(_itemBounds[i])) + VEC3_ADRESS_OFFSET);
+                batch._glUniform4iv(_drawItemStatusValue0Loc, 1, (const int*)&(_itemStatus[i].first));
+                batch._glUniform4iv(_drawItemStatusValue1Loc, 1, (const int*)&(_itemStatus[i].second));
                 batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0);
             }
         }
diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h
index 839a98b373..e60cb58779 100644
--- a/libraries/render/src/render/DrawStatus.h
+++ b/libraries/render/src/render/DrawStatus.h
@@ -68,9 +68,13 @@ namespace render {
         gpu::Stream::FormatPointer _drawItemFormat;
         gpu::PipelinePointer _drawItemBoundsPipeline;
         gpu::PipelinePointer _drawItemStatusPipeline;
-        gpu::BufferPointer _itemBounds;
-        gpu::BufferPointer _itemCells;
-        gpu::BufferPointer _itemStatus;
+
+        std::vector<AABox> _itemBounds;
+        std::vector<std::pair<glm::ivec4, glm::ivec4>> _itemStatus;
+        std::vector<Octree::Location> _itemCells;
+        //gpu::BufferPointer _itemBounds;
+        //gpu::BufferPointer _itemCells;
+        //gpu::BufferPointer _itemStatus;
         gpu::TexturePointer _statusIconMap;
     };
 }

From e8bd97c76101c87f82cdbe3a1322c36731f8db14 Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Fri, 13 May 2016 10:48:47 -0700
Subject: [PATCH 27/29] Fix GPU buffer memory statistics

---
 libraries/gpu/src/gpu/GLBackendBuffer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp
index 60484ef1bf..7098fd1feb 100755
--- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp
+++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp
@@ -24,7 +24,6 @@ GLBackend::GLBuffer::GLBuffer(const Buffer& buffer, GLBuffer* original) :
     _size((GLuint)buffer._sysmem.getSize()),
     _stamp(buffer._sysmem.getStamp()),
     _gpuBuffer(buffer) {
-
     glBindBuffer(GL_ARRAY_BUFFER, _buffer);
     if (GLEW_VERSION_4_4 || GLEW_ARB_buffer_storage) {
         glBufferStorage(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_STORAGE_BIT);
@@ -46,6 +45,7 @@ GLBackend::GLBuffer::GLBuffer(const Buffer& buffer, GLBuffer* original) :
 
     Backend::setGPUObject(buffer, this);
     Backend::incrementBufferGPUCount();
+    Backend::updateBufferGPUMemoryUsage(0, _size);
 }
 
 GLBackend::GLBuffer::~GLBuffer() {

From 1e136ed71faaeaf75f4bf40a5b36c3c3543e203b Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Sat, 14 May 2016 10:59:59 -0700
Subject: [PATCH 28/29] Fix procedural rendering on primitives

---
 libraries/render-utils/src/simple.slf | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf
index 5ad9dc174f..0f848ee231 100644
--- a/libraries/render-utils/src/simple.slf
+++ b/libraries/render-utils/src/simple.slf
@@ -18,7 +18,7 @@
 // the interpolated normal
 in vec3 _normal;
 in vec3 _modelNormal;
-in vec3 _color;
+in vec4 _color;
 in vec2 _texCoord0;
 in vec4 _position;
 

From 84f4945840c49c3e97f32613fbe28b9d10db6a3b Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Sun, 15 May 2016 13:05:50 -0700
Subject: [PATCH 29/29] Fix sampler mode lookup

---
 libraries/gpu/src/gpu/GLBackendTexture.cpp | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp
index 609451bd13..24b9544168 100755
--- a/libraries/gpu/src/gpu/GLBackendTexture.cpp
+++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp
@@ -535,13 +535,12 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL
         GLint minFilter;
         GLint magFilter;
     };
-    static const GLFilterMode filterModes[] = {
+    static const GLFilterMode filterModes[Sampler::NUM_FILTERS] = {
         { GL_NEAREST, GL_NEAREST },  //FILTER_MIN_MAG_POINT,
         { GL_NEAREST, GL_LINEAR },  //FILTER_MIN_POINT_MAG_LINEAR,
         { GL_LINEAR, GL_NEAREST },  //FILTER_MIN_LINEAR_MAG_POINT,
         { GL_LINEAR, GL_LINEAR },  //FILTER_MIN_MAG_LINEAR,
 
-        { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST },  //FILTER_MIN_MAG_MIP_POINT,
         { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST },  //FILTER_MIN_MAG_MIP_POINT,
         { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST },  //FILTER_MIN_MAG_POINT_MIP_LINEAR,
         { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR },  //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT,
@@ -557,7 +556,7 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL
     glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter);
     glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter);
 
-    static const GLenum comparisonFuncs[] = {
+    static const GLenum comparisonFuncs[NUM_COMPARISON_FUNCS] = {
         GL_NEVER,
         GL_LESS,
         GL_EQUAL,
@@ -574,7 +573,7 @@ void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GL
         glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
     }
 
-    static const GLenum wrapModes[] = {
+    static const GLenum wrapModes[Sampler::NUM_WRAP_MODES] = {
         GL_REPEAT,                         // WRAP_REPEAT,
         GL_MIRRORED_REPEAT,                // WRAP_MIRROR,
         GL_CLAMP_TO_EDGE,                  // WRAP_CLAMP,