diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 21d44a4145..4002cca0c1 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -106,8 +106,8 @@
 #include <MessagesClient.h>
 #include <hfm/ModelFormatRegistry.h>
 #include <model-networking/ModelCacheScriptingInterface.h>
+#include <material-networking/MaterialCacheScriptingInterface.h>
 #include <material-networking/TextureCacheScriptingInterface.h>
-#include <material-networking/MaterialCache.h>
 #include <ModelEntityItem.h>
 #include <NetworkAccessManager.h>
 #include <NetworkingConstants.h>
@@ -886,6 +886,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
     DependencyManager::set<TextureCache>();
     DependencyManager::set<MaterialCache>();
     DependencyManager::set<TextureCacheScriptingInterface>();
+    DependencyManager::set<MaterialCacheScriptingInterface>();
     DependencyManager::set<FramebufferCache>();
     DependencyManager::set<AnimationCache>();
     DependencyManager::set<AnimationCacheScriptingInterface>();
@@ -2906,6 +2907,7 @@ Application::~Application() {
     DependencyManager::destroy<AnimationCacheScriptingInterface>();
     DependencyManager::destroy<AnimationCache>();
     DependencyManager::destroy<FramebufferCache>();
+    DependencyManager::destroy<MaterialCacheScriptingInterface>();
     DependencyManager::destroy<MaterialCache>();
     DependencyManager::destroy<TextureCacheScriptingInterface>();
     DependencyManager::destroy<TextureCache>();
@@ -3431,6 +3433,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
     // Caches
     surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
     surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
+    surfaceContext->setContextProperty("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data());
     surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
     surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
 
@@ -7461,6 +7464,7 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine
     // Caches
     scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
     scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
+    scriptEngine->registerGlobalObject("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data());
     scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
     scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
 
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h
index 2697d30de4..d11f6dd6f4 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableEntityItem.h
@@ -108,15 +108,6 @@ protected:
     virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; }
     virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; }
     virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; }
-    
-    template <typename F, typename T>
-    T withReadLockResult(const std::function<T()>& f) {
-        T result;
-        withReadLock([&] {
-            result = f();
-        });
-        return result;
-    }
 
 signals:
     void requestRenderUpdate();
diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h
index 428c08e9f6..1295fc5722 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h
@@ -50,6 +50,8 @@ namespace scriptable {
      * @property {string} emissiveMap
      * @property {string} albedoMap
      * @property {string} opacityMap
+     * @property {string} opacityMapMode
+     * @property {number|string} opacityCutoff
      * @property {string} metallicMap
      * @property {string} specularMap
      * @property {string} roughnessMap
@@ -84,6 +86,8 @@ namespace scriptable {
         QString emissiveMap;
         QString albedoMap;
         QString opacityMap;
+        QString opacityMapMode;
+        float opacityCutoff;
         QString metallicMap;
         QString specularMap;
         QString roughnessMap;
@@ -94,7 +98,6 @@ namespace scriptable {
         QString lightMap;
         QString scatteringMap;
         std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texCoordTransforms;
-
         bool defaultFallthrough;
         std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script
 
diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
index 45f8461a1a..ca9634e365 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
@@ -420,6 +420,18 @@ namespace scriptable {
             obj.setProperty("opacityMap", material.opacityMap);
         }
 
+        if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT | graphics::MaterialKey::OPACITY_MASK_MAP_BIT)) {
+            obj.setProperty("opacityMapMode", FALLTHROUGH);
+        } else if (material.key.getOpacityMapMode() != graphics::Material::DEFAULT_OPACITY_MAP_MODE) {
+            obj.setProperty("opacityMapMode", material.opacityMapMode);
+        }
+
+        if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_CUTOFF_VAL_BIT)) {
+            obj.setProperty("opacityCutoff", FALLTHROUGH);
+        } else if (material.key.isOpacityCutoff()) {
+            obj.setProperty("opacityCutoff", material.opacityCutoff);
+        }
+
         if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT)) {
             obj.setProperty("occlusionMap", FALLTHROUGH);
         } else if (!material.occlusionMap.isEmpty()) {
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp
index e9cc7930ae..bc610108ec 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp
@@ -26,6 +26,7 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
     roughness = material.roughness;
     metallic = material.metallic;
     scattering = material.scattering;
+    opacityCutoff = material.opacityCutoff;
     unlit = material.unlit;
     emissive = material.emissive;
     albedo = material.albedo;
@@ -41,6 +42,8 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
     occlusionMap = material.occlusionMap;
     lightMap = material.lightMap;
     scatteringMap = material.scatteringMap;
+    opacityMapMode = material.opacityMapMode;
+
 
     defaultFallthrough = material.defaultFallthrough;
     propertyFallthroughs = material.propertyFallthroughs;
@@ -55,9 +58,12 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint
         name = material->getName().c_str();
         model = material->getModel().c_str();
         opacity = material->getOpacity();
+
+        opacityMapMode = QString(graphics::MaterialKey::getOpacityMapModeName(material->getOpacityMapMode()).c_str());
         roughness = material->getRoughness();
         metallic = material->getMetallic();
         scattering = material->getScattering();
+        opacityCutoff = material->getOpacityCutoff();
         unlit = material->isUnlit();
         emissive = material->getEmissive();
         albedo = material->getAlbedo();
diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp
index 6be8ec5f68..dffc52e29f 100755
--- a/libraries/graphics/src/graphics/Material.cpp
+++ b/libraries/graphics/src/graphics/Material.cpp
@@ -14,6 +14,8 @@
 
 #include <Transform.h>
 
+#include "GraphicsLogging.h"
+
 using namespace graphics;
 using namespace gpu;
 
@@ -22,7 +24,26 @@ const float Material::DEFAULT_OPACITY { 1.0f };
 const float Material::DEFAULT_ALBEDO { 0.5f };
 const float Material::DEFAULT_METALLIC { 0.0f };
 const float Material::DEFAULT_ROUGHNESS { 1.0f };
-const float Material::DEFAULT_SCATTERING { 0.0f };
+const float Material::DEFAULT_SCATTERING{ 0.0f };
+const MaterialKey::OpacityMapMode Material::DEFAULT_OPACITY_MAP_MODE{ MaterialKey::OPACITY_MAP_OPAQUE };
+const float Material::DEFAULT_OPACITY_CUTOFF { 0.5f };
+
+
+std::string MaterialKey::getOpacityMapModeName(OpacityMapMode mode) {
+    const std::string names[3] = { "OPACITY_MAP_OPAQUE", "OPACITY_MAP_MASK", "OPACITY_MAP_BLEND" };
+    return names[mode];
+}
+
+
+bool MaterialKey::getOpacityMapModeFromName(const std::string& modeName, MaterialKey::OpacityMapMode& mode) {
+    for (int i = OPACITY_MAP_OPAQUE; i <= OPACITY_MAP_BLEND; i++) {
+        mode = (MaterialKey::OpacityMapMode) i;
+        if (modeName == getOpacityMapModeName(mode)) {
+            return true;
+        }
+    }
+    return false;
+}
 
 Material::Material() {
     for (int i = 0; i < NUM_TOTAL_FLAGS; i++) {
@@ -40,6 +61,7 @@ Material::Material(const Material& material) :
     _roughness(material._roughness),
     _metallic(material._metallic),
     _scattering(material._scattering),
+    _opacityCutoff(material._opacityCutoff),
     _texcoordTransforms(material._texcoordTransforms),
     _lightmapParams(material._lightmapParams),
     _materialParams(material._materialParams),
@@ -50,7 +72,7 @@ Material::Material(const Material& material) :
 }
 
 Material& Material::operator=(const Material& material) {
-    QMutexLocker locker(&_textureMapsMutex);
+    std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex);
 
     _name = material._name;
     _model = material._model;
@@ -61,6 +83,7 @@ Material& Material::operator=(const Material& material) {
     _roughness = material._roughness;
     _metallic = material._metallic;
     _scattering = material._scattering;
+    _opacityCutoff = material._opacityCutoff;
     _texcoordTransforms = material._texcoordTransforms;
     _lightmapParams = material._lightmapParams;
     _materialParams = material._materialParams;
@@ -109,8 +132,22 @@ void Material::setScattering(float scattering) {
     _scattering = scattering;
 }
 
+void Material::setOpacityCutoff(float opacityCutoff) {
+    opacityCutoff = glm::clamp(opacityCutoff, 0.0f, 1.0f);
+    _key.setOpacityCutoff(opacityCutoff != DEFAULT_OPACITY_CUTOFF);
+    _opacityCutoff = opacityCutoff;
+}
+
+void Material::setOpacityMapMode(MaterialKey::OpacityMapMode opacityMapMode) {
+    _key.setOpacityMapMode(opacityMapMode);
+}
+
+MaterialKey::OpacityMapMode  Material::getOpacityMapMode() const {
+    return _key.getOpacityMapMode();
+}
+
 void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
-    QMutexLocker locker(&_textureMapsMutex);
+    std::lock_guard<std::recursive_mutex>  locker(_textureMapsMutex);
 
     if (textureMap) {
         _key.setMapChannel(channel, true);
@@ -139,7 +176,14 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur
 
 }
 
-void Material::resetOpacityMap() const {
+bool Material::resetOpacityMap() const {
+    // If OpacityMapMode explicit then nothing need to change here.
+    if (_key.isOpacityMapMode()) {
+        return false;
+    }
+
+    // Else, the legacy behavior is to interpret the albedo texture assigned to tune the opacity map mode value
+    auto previous = _key.getOpacityMapMode();
     // Clear the previous flags
     _key.setOpacityMaskMap(false);
     _key.setTranslucentMap(false);
@@ -163,10 +207,16 @@ void Material::resetOpacityMap() const {
             }
         }
     }
+    auto newious = _key.getOpacityMapMode();
+    if (previous != newious) {
+        //opacity change detected for this material
+        return true;
+    }
+    return false;
 }
 
 const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
-    QMutexLocker locker(&_textureMapsMutex);
+    std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex);
 
     auto result = _textureMaps.find(channel);
     if (result != _textureMaps.end()) {
diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h
index 25601c5743..25ff711c0c 100755
--- a/libraries/graphics/src/graphics/Material.h
+++ b/libraries/graphics/src/graphics/Material.h
@@ -11,8 +11,7 @@
 #ifndef hifi_model_Material_h
 #define hifi_model_Material_h
 
-#include <QMutex>
-
+#include <mutex>
 #include <bitset>
 #include <map>
 #include <unordered_map>
@@ -44,6 +43,8 @@ public:
         OPACITY_VAL_BIT,
         OPACITY_MASK_MAP_BIT,           // Opacity Map and Opacity MASK map are mutually exclusive
         OPACITY_TRANSLUCENT_MAP_BIT,
+        OPACITY_MAP_MODE_BIT,           // Opacity map mode bit is set if the value has set explicitely and not deduced from the textures assigned 
+        OPACITY_CUTOFF_VAL_BIT,
         SCATTERING_VAL_BIT,
 
         // THe map bits must be in the same sequence as the enum names for the map channels
@@ -73,6 +74,15 @@ public:
         NUM_MAP_CHANNELS,
     };
 
+    enum OpacityMapMode {
+        OPACITY_MAP_OPAQUE = 0,
+        OPACITY_MAP_MASK,
+        OPACITY_MAP_BLEND,
+    };
+    static std::string getOpacityMapModeName(OpacityMapMode mode);
+    // find the enum value from a string, return true if match found
+    static bool getOpacityMapModeFromName(const std::string& modeName, OpacityMapMode& mode);
+
     // The signature is the Flags
     Flags _flags;
 
@@ -94,6 +104,27 @@ public:
         Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); }
 
         Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); }
+        Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
+        Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); }
+        Builder& withOpacityMapMode(OpacityMapMode mode) {
+            switch (mode) {
+            case OPACITY_MAP_OPAQUE:
+                _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
+                _flags.reset(OPACITY_MASK_MAP_BIT);
+                break;
+            case OPACITY_MAP_MASK:
+                _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
+                _flags.set(OPACITY_MASK_MAP_BIT);
+                break;
+            case OPACITY_MAP_BLEND:
+                _flags.set(OPACITY_TRANSLUCENT_MAP_BIT);
+                _flags.reset(OPACITY_MASK_MAP_BIT);
+                break;
+            };
+            _flags.set(OPACITY_MAP_MODE_BIT); // Intentionally set the mode!
+            return (*this);
+        }
+        Builder& withOpacityCutoff() { _flags.set(OPACITY_CUTOFF_VAL_BIT); return (*this); }
 
         Builder& withScattering() { _flags.set(SCATTERING_VAL_BIT); return (*this); }
 
@@ -102,9 +133,6 @@ public:
         Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); }
         Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); }
 
-        Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
-        Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); }
-
         Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); }
         Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); }
         Builder& withLightMap() { _flags.set(LIGHT_MAP_BIT); return (*this); }
@@ -151,6 +179,9 @@ public:
     void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); }
     bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; }
 
+    void setOpacityCutoff(bool value) { _flags.set(OPACITY_CUTOFF_VAL_BIT, value); }
+    bool isOpacityCutoff() const { return _flags[OPACITY_CUTOFF_VAL_BIT]; }
+
     void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); }
     bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; }
 
@@ -171,6 +202,26 @@ public:
 
 
     // Translucency and Opacity Heuristics are combining several flags:
+    void setOpacityMapMode(OpacityMapMode mode) {
+        switch (mode) {
+        case OPACITY_MAP_OPAQUE:
+            _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
+            _flags.reset(OPACITY_MASK_MAP_BIT);
+            break;
+        case OPACITY_MAP_MASK:
+            _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT);
+            _flags.set(OPACITY_MASK_MAP_BIT);
+            break;
+        case OPACITY_MAP_BLEND:
+            _flags.set(OPACITY_TRANSLUCENT_MAP_BIT);
+            _flags.reset(OPACITY_MASK_MAP_BIT);
+            break;
+        };
+        _flags.set(OPACITY_MAP_MODE_BIT); // Intentionally set the mode!
+    }
+    bool isOpacityMapMode() const { return _flags[OPACITY_MAP_MODE_BIT]; }
+    OpacityMapMode getOpacityMapMode() const { return (isOpacityMaskMap() ? OPACITY_MAP_MASK : (isTranslucentMap() ? OPACITY_MAP_BLEND : OPACITY_MAP_OPAQUE)); }
+
     bool isTranslucent() const { return isTranslucentFactor() || isTranslucentMap(); }
     bool isOpaque() const { return !isTranslucent(); }
     bool isSurfaceOpaque() const { return isOpaque() && !isOpacityMaskMap(); }
@@ -229,6 +280,12 @@ public:
         Builder& withoutMaskMap()       { _value.reset(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
         Builder& withMaskMap()        { _value.set(MaterialKey::OPACITY_MASK_MAP_BIT);  _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
 
+        Builder& withoutOpacityMapMode() { _value.reset(MaterialKey::OPACITY_MAP_MODE_BIT); _mask.set(MaterialKey::OPACITY_MAP_MODE_BIT); return (*this); }
+        Builder& withOpacityMapMode() { _value.set(MaterialKey::OPACITY_MAP_MODE_BIT);  _mask.set(MaterialKey::OPACITY_MAP_MODE_BIT); return (*this); }
+
+        Builder& withoutOpacityCutoff() { _value.reset(MaterialKey::OPACITY_CUTOFF_VAL_BIT); _mask.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); return (*this); }
+        Builder& withOpacityCutoff() { _value.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT);  _mask.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); return (*this); }
+
         Builder& withoutNormalMap()       { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
         Builder& withNormalMap()        { _value.set(MaterialKey::NORMAL_MAP_BIT);  _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
 
@@ -283,6 +340,14 @@ public:
     void setOpacity(float opacity);
     float getOpacity() const { return _opacity; }
 
+    static const MaterialKey::OpacityMapMode DEFAULT_OPACITY_MAP_MODE;
+    void setOpacityMapMode(MaterialKey::OpacityMapMode opacityMapMode);
+    MaterialKey::OpacityMapMode getOpacityMapMode() const;
+
+    static const float DEFAULT_OPACITY_CUTOFF;
+    void setOpacityCutoff(float opacityCutoff);
+    float getOpacityCutoff() const { return _opacityCutoff; }
+
     void setUnlit(bool value);
     bool isUnlit() const { return _key.isUnlit(); }
 
@@ -310,7 +375,8 @@ public:
 
     // Albedo maps cannot have opacity detected until they are loaded
     // This method allows const changing of the key/schemaBuffer without touching the map
-    void resetOpacityMap() const;
+    // return true if the opacity changed, flase otherwise
+    bool resetOpacityMap() const;
 
     // conversion from legacy material properties to PBR equivalent
     static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }
@@ -357,6 +423,7 @@ private:
     float _roughness { DEFAULT_ROUGHNESS };
     float _metallic { DEFAULT_METALLIC };
     float _scattering { DEFAULT_SCATTERING };
+    float _opacityCutoff { DEFAULT_OPACITY_CUTOFF };
     std::array<glm::mat4, NUM_TEXCOORD_TRANSFORMS> _texcoordTransforms;
     glm::vec2 _lightmapParams { 0.0, 1.0 };
     glm::vec2 _materialParams { 0.0, 1.0 };
@@ -365,7 +432,7 @@ private:
     bool _defaultFallthrough { false };
     std::unordered_map<uint, bool> _propertyFallthroughs { NUM_TOTAL_FLAGS };
 
-    mutable QMutex _textureMapsMutex { QMutex::Recursive };
+    mutable std::recursive_mutex _textureMapsMutex;
 };
 typedef std::shared_ptr<Material> MaterialPointer;
 
@@ -425,18 +492,8 @@ public:
 
         float _metallic { Material::DEFAULT_METALLIC }; // Not Metallic
         float _scattering { Material::DEFAULT_SCATTERING }; // Scattering info
-#if defined(__clang__)
-        __attribute__((unused))
-#endif
-        glm::vec2 _spare { 0.0f }; // Padding
-
+        float _opacityCutoff { Material::DEFAULT_OPACITY_CUTOFF }; // Opacity cutoff applyed when using opacityMap as Mask 
         uint32_t _key { 0 }; // a copy of the materialKey
-#if defined(__clang__)
-        __attribute__((unused))
-#endif
-        glm::vec3 _spare2 { 0.0f };
-
-        // for alignment beauty, Material size == Mat4x4
 
         // Texture Coord Transform Array
         glm::mat4 _texcoordTransforms[Material::NUM_TEXCOORD_TRANSFORMS];
diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh
index dfd4a8eec4..328ff4a3af 100644
--- a/libraries/graphics/src/graphics/Material.slh
+++ b/libraries/graphics/src/graphics/Material.slh
@@ -49,8 +49,7 @@ struct TexMapArray {
 struct Material {
     vec4 _emissiveOpacity;
     vec4 _albedoRoughness;
-    vec4 _metallicScatteringSpare2;
-    vec4 _keySpare3;
+    vec4 _metallicScatteringOpacityCutoffKey;
 };
 
 LAYOUT_STD140(binding=GRAPHICS_BUFFER_MATERIAL) uniform materialBuffer {
@@ -72,10 +71,11 @@ vec3 getMaterialAlbedo(Material m) { return m._albedoRoughness.rgb; }
 float getMaterialRoughness(Material m) { return m._albedoRoughness.a; }
 float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); }
 
-float getMaterialMetallic(Material m) { return m._metallicScatteringSpare2.x; }
-float getMaterialScattering(Material m) { return m._metallicScatteringSpare2.y; }
+float getMaterialMetallic(Material m) { return m._metallicScatteringOpacityCutoffKey.x; }
+float getMaterialScattering(Material m) { return m._metallicScatteringOpacityCutoffKey.y; }
+float getMaterialOpacityCutoff(Material m) { return m._metallicScatteringOpacityCutoffKey.z; }
 
-BITFIELD getMaterialKey(Material m) { return floatBitsToInt(m._keySpare3.x); }
+BITFIELD getMaterialKey(Material m) { return floatBitsToInt(m._metallicScatteringOpacityCutoffKey.w); }
 
 const BITFIELD EMISSIVE_VAL_BIT              = 0x00000001;
 const BITFIELD UNLIT_VAL_BIT                 = 0x00000002;
@@ -85,16 +85,18 @@ const BITFIELD GLOSSY_VAL_BIT                = 0x00000010;
 const BITFIELD OPACITY_VAL_BIT               = 0x00000020;
 const BITFIELD OPACITY_MASK_MAP_BIT          = 0x00000040;
 const BITFIELD OPACITY_TRANSLUCENT_MAP_BIT   = 0x00000080;
-const BITFIELD SCATTERING_VAL_BIT            = 0x00000100;
+const BITFIELD OPACITY_MAP_MODE_BIT          = 0x00000100;
+const BITFIELD OPACITY_CUTOFF_VAL_BIT        = 0x00000200;
+const BITFIELD SCATTERING_VAL_BIT            = 0x00000400;
 
 
-const BITFIELD EMISSIVE_MAP_BIT              = 0x00000200;
-const BITFIELD ALBEDO_MAP_BIT                = 0x00000400;
-const BITFIELD METALLIC_MAP_BIT              = 0x00000800;
-const BITFIELD ROUGHNESS_MAP_BIT             = 0x00001000;
-const BITFIELD NORMAL_MAP_BIT                = 0x00002000;
-const BITFIELD OCCLUSION_MAP_BIT             = 0x00004000;
-const BITFIELD LIGHTMAP_MAP_BIT              = 0x00008000;
-const BITFIELD SCATTERING_MAP_BIT            = 0x00010000;
+const BITFIELD EMISSIVE_MAP_BIT              = 0x00000800;
+const BITFIELD ALBEDO_MAP_BIT                = 0x00001000;
+const BITFIELD METALLIC_MAP_BIT              = 0x00002000;
+const BITFIELD ROUGHNESS_MAP_BIT             = 0x00004000;
+const BITFIELD NORMAL_MAP_BIT                = 0x00008000;
+const BITFIELD OCCLUSION_MAP_BIT             = 0x00010000;
+const BITFIELD LIGHTMAP_MAP_BIT              = 0x00020000;
+const BITFIELD SCATTERING_MAP_BIT            = 0x00040000;
 
 <@endif@>
diff --git a/libraries/graphics/src/graphics/MaterialTextures.slh b/libraries/graphics/src/graphics/MaterialTextures.slh
index 278057b01a..2a291e5d57 100644
--- a/libraries/graphics/src/graphics/MaterialTextures.slh
+++ b/libraries/graphics/src/graphics/MaterialTextures.slh
@@ -214,14 +214,22 @@ vec3 fetchLightMap(vec2 uv) {
 }
 <@endfunc@>
 
-<@func evalMaterialOpacity(fetchedOpacity, materialOpacity, matKey, opacity)@>
+<@func evalMaterialOpacityMask(fetchedOpacity, materialOpacityCutoff, opacity)@>
 {
-    const float OPACITY_MASK_THRESHOLD = 0.5;
-    <$opacity$> = mix(1.0,
-                      mix(<$fetchedOpacity$>,
-                          step(OPACITY_MASK_THRESHOLD, <$fetchedOpacity$>),
-                          float((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0)),
-                      float((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0)) * <$materialOpacity$>;
+    // This path only valid for opaque or texel opaque material 
+    <$opacity$> = step(<$materialOpacityCutoff$>, <$fetchedOpacity$>);
+}
+<@endfunc@>
+
+
+<@func evalMaterialOpacity(fetchedOpacity, materialOpacityCutoff, materialOpacity, matKey, opacity)@>
+{
+    // This path only valid for transparent material
+    // Assert that float((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0)) == 1.0
+    <$opacity$> = mix(<$fetchedOpacity$>,
+                          step(<$materialOpacityCutoff$>, <$fetchedOpacity$>),
+                          float((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0))
+                       * <$materialOpacity$>;
 }
 <@endfunc@>
 
diff --git a/libraries/material-networking/src/material-networking/MaterialCache.cpp b/libraries/material-networking/src/material-networking/MaterialCache.cpp
index 087e1ca049..db4783d249 100644
--- a/libraries/material-networking/src/material-networking/MaterialCache.cpp
+++ b/libraries/material-networking/src/material-networking/MaterialCache.cpp
@@ -139,6 +139,14 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
  * @property {string} opacityMap - The URL of the opacity texture image. Set the value the same as the <code>albedoMap</code> 
  *     value for transparency. 
  *     <code>"hifi_pbr"</code> model only.
+ * @property {number|string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be:
+ *     <code>"OPACITY_MAP_OPAQUE"</code> for ignoring the opacity map information.
+ *     <code>"OPACITY_MAP_MASK"</code> for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque.
+ *     <code>"OPACITY_MAP_BLEND"</code> for using the opacity map for alpha blending the material surface with the background.
+ *     Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
+ * @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the Opacity map
+ *     when opacityMapMode is "OPACITY_MAP_MASK", range <code>0.0</code> &ndash; <code>1.0</code>.
+ *     Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
  * @property {string} roughnessMap - The URL of the roughness texture image. You can use this or <code>glossMap</code>, but not 
  *     both. 
  *     Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
@@ -258,6 +266,24 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
                 } else if (value.isDouble()) {
                     material->setMetallic(value.toDouble());
                 }
+           } else if (key == "opacityMapMode") {
+                auto value = materialJSON.value(key);
+                auto valueString = (value.isString() ? value.toString() : "");
+                if (valueString == FALLTHROUGH) {
+                    material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_MAP_MODE_BIT);
+                } else {
+                    graphics::MaterialKey::OpacityMapMode mode;
+                    if (graphics::MaterialKey::getOpacityMapModeFromName(valueString.toStdString(), mode)) {
+                        material->setOpacityMapMode(mode);
+                    }
+                }
+           } else if (key == "opacityCutoff") {
+                auto value = materialJSON.value(key);
+                if (value.isString() && value.toString() == FALLTHROUGH) {
+                    material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_CUTOFF_VAL_BIT);
+                } else if (value.isDouble()) {
+                    material->setOpacityCutoff(value.toDouble());
+                }
             } else if (key == "scattering") {
                 auto value = materialJSON.value(key);
                 if (value.isString() && value.toString() == FALLTHROUGH) {
@@ -748,13 +774,14 @@ bool NetworkMaterial::isMissingTexture() {
     return false;
 }
 
-void NetworkMaterial::checkResetOpacityMap() {
+bool NetworkMaterial::checkResetOpacityMap() {
     // If material textures are loaded, check the material translucency
     // FIXME: This should not be done here.  The opacity map should already be reset in Material::setTextureMap.
     // However, currently that code can be called before the albedo map is defined, so resetOpacityMap will fail.
     // Geometry::areTexturesLoaded() is called repeatedly until it returns true, so we do the check here for now
     const auto& albedoTexture = _textures[NetworkMaterial::MapChannel::ALBEDO_MAP];
     if (albedoTexture.texture) {
-        resetOpacityMap();
+        return resetOpacityMap();
     }
+    return false;
 }
diff --git a/libraries/material-networking/src/material-networking/MaterialCache.h b/libraries/material-networking/src/material-networking/MaterialCache.h
index 18aa5e5994..aa103adb1e 100644
--- a/libraries/material-networking/src/material-networking/MaterialCache.h
+++ b/libraries/material-networking/src/material-networking/MaterialCache.h
@@ -34,7 +34,7 @@ public:
     void setLightMap(const QUrl& url);
 
     bool isMissingTexture();
-    void checkResetOpacityMap();
+    bool checkResetOpacityMap();
 
     class Texture {
     public:
diff --git a/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.cpp b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.cpp
new file mode 100644
index 0000000000..193d9b96ee
--- /dev/null
+++ b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.cpp
@@ -0,0 +1,17 @@
+//
+//  MaterialCacheScriptingInterface.cpp
+//  libraries/mmodel-networking/src/model-networking
+//
+//  Created by Sam Gateau on 17 September 2019.
+//  Copyright 2019 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
+//
+
+#include "MaterialCacheScriptingInterface.h"
+
+MaterialCacheScriptingInterface::MaterialCacheScriptingInterface() :
+    ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<MaterialCache>())
+{ }
+
diff --git a/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.h b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.h
new file mode 100644
index 0000000000..c619966a2a
--- /dev/null
+++ b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.h
@@ -0,0 +1,51 @@
+//
+//  MaterialCacheScriptingInterface.h
+//  libraries/material-networking/src/material-networking
+//
+//  Created by Sam Gateau on 17 September 2019.
+//  Copyright 2019 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
+//
+#pragma once
+
+#ifndef hifi_MaterialCacheScriptingInterface_h
+#define hifi_MaterialCacheScriptingInterface_h
+
+#include <QObject>
+
+#include <ResourceCache.h>
+
+#include "MaterialCache.h"
+
+class MaterialCacheScriptingInterface : public ScriptableResourceCache, public Dependency {
+    Q_OBJECT
+
+    // Properties are copied over from ResourceCache (see ResourceCache.h for reason).
+
+    /**jsdoc
+     * The <code>TextureCache</code> API manages texture cache resources.
+     *
+     * @namespace TextureCache
+     *
+     * @hifi-interface
+     * @hifi-client-entity
+     * @hifi-avatar
+     *
+     * @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
+     * @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
+     * @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
+     * @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
+     *
+     * @borrows ResourceCache.getResourceList as getResourceList
+     * @borrows ResourceCache.updateTotalSize as updateTotalSize
+     * @borrows ResourceCache.prefetch as prefetch
+     * @borrows ResourceCache.dirty as dirty
+     */
+
+public:
+    MaterialCacheScriptingInterface();
+};
+
+#endif // hifi_MaterialCacheScriptingInterface_h
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index 1ed1c65358..1fcfcfcc70 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -472,7 +472,10 @@ bool Geometry::areTexturesLoaded() const {
                 return false;
             }
 
-            material->checkResetOpacityMap();
+            bool changed = material->checkResetOpacityMap();
+            if (changed) {
+                qCWarning(modelnetworking) << "Material list: opacity change detected for material " << material->getName().c_str();
+            }
         }
 
         for (auto& materialMapping : _materialMapping) {
@@ -483,7 +486,10 @@ bool Geometry::areTexturesLoaded() const {
                             return false;
                         }
 
-                        materialPair.second->checkResetOpacityMap();
+                       bool changed =  materialPair.second->checkResetOpacityMap();
+                       if (changed) {
+                           qCWarning(modelnetworking) << "Mapping list: opacity change detected for material " << materialPair.first.c_str();
+                       }
                     }
                 }
             }
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 88e6b14ec7..88a4f5bf32 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -148,6 +148,8 @@ void ResourceCacheSharedItems::clear() {
 
 ScriptableResourceCache::ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache) {
     _resourceCache = resourceCache;
+    connect(&(*_resourceCache), &ResourceCache::dirty,
+        this, &ScriptableResourceCache::dirty, Qt::DirectConnection);
 }
 
 QVariantList ScriptableResourceCache::getResourceList() {
@@ -323,7 +325,11 @@ QVariantList ResourceCache::getResourceList() {
         BLOCKING_INVOKE_METHOD(this, "getResourceList",
             Q_RETURN_ARG(QVariantList, list));
     } else {
-        auto resources = _resources.uniqueKeys();
+        QList<QUrl> resources;
+        {
+            QReadLocker locker(&_resourcesLock);
+            resources = _resources.uniqueKeys();
+        }
         list.reserve(resources.size());
         for (auto& resource : resources) {
             list << resource;
@@ -510,7 +516,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) {
 
     emit dirty();
 }
- 
+
 QList<QSharedPointer<Resource>> ResourceCache::getLoadingRequests() {
     return DependencyManager::get<ResourceCacheSharedItems>()->getLoadingRequests();
 }
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index e1f3098658..4213d92fc0 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -317,6 +317,13 @@ class ScriptableResourceCache : public QObject {
     Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
     Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)
 
+    /**jsdoc
+    * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource managers). <em>Read-only.</em>
+    * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). <em>Read-only.</em>
+    */
+    Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty)
+    Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty)
+
 public:
     ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache);
 
@@ -390,6 +397,9 @@ private:
     size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); }
     size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); }
     size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); }
+
+    size_t getNumGlobalQueriesPending() const { return ResourceCache::getPendingRequestCount(); }
+    size_t getNumGlobalQueriesLoading() const { return ResourceCache::getLoadingRequestCount(); }
 };
 
 /// Base class for resources.
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index d0ebe167f9..518e43a8ec 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -461,6 +461,13 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial
                         wasSet = true;
                     }
                     break;
+                case graphics::MaterialKey::OPACITY_CUTOFF_VAL_BIT:
+                    if (materialKey.isOpacityCutoff()) {
+                        schema._opacityCutoff = material->getOpacityCutoff();
+                        schemaKey.setOpacityCutoff(true);
+                        wasSet = true;
+                    }
+                    break;
                 case graphics::MaterialKey::SCATTERING_VAL_BIT:
                     if (materialKey.isScattering()) {
                         schema._scattering = material->getScattering();
@@ -752,7 +759,7 @@ bool RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
 
     // For shadows, we only need opacity mask information
     auto key = multiMaterial.getMaterialKey();
-    if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE || key.isOpacityMaskMap()) {
+    if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE || (key.isOpacityMaskMap() || key.isTranslucentMap())) {
         auto& schemaBuffer = multiMaterial.getSchemaBuffer();
         batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
         if (enableTextures) {
diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf
index 3e4711dac8..bacc6b0ab1 100644
--- a/libraries/render-utils/src/model.slf
+++ b/libraries/render-utils/src/model.slf
@@ -103,14 +103,14 @@ void main(void) {
         <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$>
 
     <@if HIFI_USE_TRANSLUCENT@>
+        float cutoff = getMaterialOpacityCutoff(mat);
         float opacity = getMaterialOpacity(mat) * _color.a;
-    <@else@>
-        float opacity = 1.0;
-    <@endif@>
-        <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
-    <@if HIFI_USE_TRANSLUCENT@>
+        <$evalMaterialOpacity(albedoTex.a, cutoff, opacity, matKey, opacity)$>;
         <$discardInvisible(opacity)$>;
     <@else@>
+        float cutoff = getMaterialOpacityCutoff(mat);
+        float opacity = 1.0;
+        <$evalMaterialOpacityMask(albedoTex.a, cutoff, opacity)$>;
         <$discardTransparent(opacity)$>;
     <@endif@>
 
@@ -155,15 +155,15 @@ void main(void) {
     <@endif@>
 
     <@if HIFI_USE_TRANSLUCENT@>
+        float cutoff = getMaterialOpacityCutoff(mat);
         float opacity = getMaterialOpacity(mat) * _color.a;
+        <$evalMaterialOpacity(albedoTex.a, cutoff, opacity, matKey, opacity)$>;
+        <$discardInvisible(opacity)$>;
     <@else@>
+        float cutoff = getMaterialOpacityCutoff(mat);
         float opacity = 1.0;
-    <@endif@>
-        <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
-    <@if HIFI_USE_TRANSLUCENT@>
-            <$discardInvisible(opacity)$>;
-    <@else@>
-            <$discardTransparent(opacity)$>;
+        <$evalMaterialOpacityMask(albedoTex.a, cutoff, opacity)$>;
+        <$discardTransparent(opacity)$>;
     <@endif@>
 
         vec3 albedo = getMaterialAlbedo(mat);
@@ -217,13 +217,13 @@ void main(void) {
                 _fragColor0 = color;
             <@else@>
                 _fragColor0 = vec4(evalLightmappedColor(
-		            cam._viewInverse,
-		            1.0,
-		            DEFAULT_OCCLUSION,
-		            fragNormalWS,
-		            albedo,
-		            lightmap),
-		            opacity);
+                    cam._viewInverse,
+                    1.0,
+                    DEFAULT_OCCLUSION,
+                    fragNormalWS,
+                    albedo,
+                    lightmap),
+                    opacity);
             <@endif@>
         <@else@>
             <@if not HIFI_USE_LIGHTMAP@>
@@ -241,13 +241,13 @@ void main(void) {
                     opacity);
             <@else@>
                 _fragColor0 = vec4(evalLightmappedColor(
-		            cam._viewInverse,
-		            1.0,
-		            DEFAULT_OCCLUSION,
-		            fragNormalWS,
-		            albedo,
-		            lightmap),
-		            opacity);
+                cam._viewInverse,
+                1.0,
+                DEFAULT_OCCLUSION,
+                fragNormalWS,
+                albedo,
+                lightmap),
+                opacity);
             <@endif@>
         <@endif@>
     <@else@>
@@ -315,13 +315,13 @@ void main(void) {
                     opacity);
             <@else@>
                 _fragColor0 = vec4(evalLightmappedColor(
-		            cam._viewInverse,
-		            1.0,
-		            DEFAULT_OCCLUSION,
-		            fragNormalWS,
-		            albedo,
-		            lightmap),
-		            opacity);
+                    cam._viewInverse,
+                    1.0,
+                    DEFAULT_OCCLUSION,
+                    fragNormalWS,
+                    albedo,
+                    lightmap),
+                    opacity);
             <@endif@>
         <@endif@>
     <@endif@>
diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp
index ae1467ac0f..6fbd3e6f9c 100644
--- a/libraries/render/src/render/EngineStats.cpp
+++ b/libraries/render/src/render/EngineStats.cpp
@@ -63,4 +63,6 @@ void EngineStats::run(const RenderContextPointer& renderContext) {
 
     config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines;
     config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges;
+
+    // These new stat values are notified with the "newStats" signal triggered by the timer
 }
diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h
index 3ccbd40715..9f8be748f7 100644
--- a/libraries/render/src/render/EngineStats.h
+++ b/libraries/render/src/render/EngineStats.h
@@ -24,42 +24,42 @@ namespace render {
     class EngineStatsConfig : public Job::Config{
         Q_OBJECT
 
-        Q_PROPERTY(quint32 bufferCPUCount MEMBER bufferCPUCount NOTIFY dirty)
-        Q_PROPERTY(quint32 bufferGPUCount MEMBER bufferGPUCount NOTIFY dirty)
-        Q_PROPERTY(qint64 bufferCPUMemSize MEMBER bufferCPUMemSize NOTIFY dirty)
-        Q_PROPERTY(qint64 bufferGPUMemSize MEMBER bufferGPUMemSize NOTIFY dirty)
+        Q_PROPERTY(quint32 bufferCPUCount MEMBER bufferCPUCount NOTIFY newStats)
+        Q_PROPERTY(quint32 bufferGPUCount MEMBER bufferGPUCount NOTIFY newStats)
+        Q_PROPERTY(qint64 bufferCPUMemSize MEMBER bufferCPUMemSize NOTIFY newStats)
+        Q_PROPERTY(qint64 bufferGPUMemSize MEMBER bufferGPUMemSize NOTIFY newStats)
 
-        Q_PROPERTY(quint32 textureCPUCount MEMBER textureCPUCount NOTIFY dirty)
-        Q_PROPERTY(quint32 textureGPUCount MEMBER textureGPUCount NOTIFY dirty)
-        Q_PROPERTY(quint32 textureResidentGPUCount MEMBER textureResidentGPUCount NOTIFY dirty)
-        Q_PROPERTY(quint32 textureFramebufferGPUCount MEMBER textureFramebufferGPUCount NOTIFY dirty)
-        Q_PROPERTY(quint32 textureResourceGPUCount MEMBER textureResourceGPUCount NOTIFY dirty)
-        Q_PROPERTY(quint32 textureExternalGPUCount MEMBER textureExternalGPUCount NOTIFY dirty)
+        Q_PROPERTY(quint32 textureCPUCount MEMBER textureCPUCount NOTIFY newStats)
+        Q_PROPERTY(quint32 textureGPUCount MEMBER textureGPUCount NOTIFY newStats)
+        Q_PROPERTY(quint32 textureResidentGPUCount MEMBER textureResidentGPUCount NOTIFY newStats)
+        Q_PROPERTY(quint32 textureFramebufferGPUCount MEMBER textureFramebufferGPUCount NOTIFY newStats)
+        Q_PROPERTY(quint32 textureResourceGPUCount MEMBER textureResourceGPUCount NOTIFY newStats)
+        Q_PROPERTY(quint32 textureExternalGPUCount MEMBER textureExternalGPUCount NOTIFY newStats)
 
-        Q_PROPERTY(qint64 textureCPUMemSize MEMBER textureCPUMemSize NOTIFY dirty)
-        Q_PROPERTY(qint64 textureGPUMemSize MEMBER textureGPUMemSize NOTIFY dirty)
-        Q_PROPERTY(qint64 textureResidentGPUMemSize MEMBER textureResidentGPUMemSize NOTIFY dirty)
-        Q_PROPERTY(qint64 textureFramebufferGPUMemSize MEMBER textureFramebufferGPUMemSize NOTIFY dirty)
-        Q_PROPERTY(qint64 textureResourceGPUMemSize MEMBER textureResourceGPUMemSize NOTIFY dirty)
-        Q_PROPERTY(qint64 textureExternalGPUMemSize MEMBER textureExternalGPUMemSize NOTIFY dirty)
+        Q_PROPERTY(qint64 textureCPUMemSize MEMBER textureCPUMemSize NOTIFY newStats)
+        Q_PROPERTY(qint64 textureGPUMemSize MEMBER textureGPUMemSize NOTIFY newStats)
+        Q_PROPERTY(qint64 textureResidentGPUMemSize MEMBER textureResidentGPUMemSize NOTIFY newStats)
+        Q_PROPERTY(qint64 textureFramebufferGPUMemSize MEMBER textureFramebufferGPUMemSize NOTIFY newStats)
+        Q_PROPERTY(qint64 textureResourceGPUMemSize MEMBER textureResourceGPUMemSize NOTIFY newStats)
+        Q_PROPERTY(qint64 textureExternalGPUMemSize MEMBER textureExternalGPUMemSize NOTIFY newStats)
 
-        Q_PROPERTY(quint32 texturePendingGPUTransferCount MEMBER texturePendingGPUTransferCount NOTIFY dirty)
-        Q_PROPERTY(qint64 texturePendingGPUTransferSize MEMBER texturePendingGPUTransferSize NOTIFY dirty)
-        Q_PROPERTY(qint64 textureResourcePopulatedGPUMemSize MEMBER textureResourcePopulatedGPUMemSize NOTIFY dirty)
+        Q_PROPERTY(quint32 texturePendingGPUTransferCount MEMBER texturePendingGPUTransferCount NOTIFY newStats)
+        Q_PROPERTY(qint64 texturePendingGPUTransferSize MEMBER texturePendingGPUTransferSize NOTIFY newStats)
+        Q_PROPERTY(qint64 textureResourcePopulatedGPUMemSize MEMBER textureResourcePopulatedGPUMemSize NOTIFY newStats)
 
-        Q_PROPERTY(quint32 frameAPIDrawcallCount MEMBER frameAPIDrawcallCount NOTIFY dirty)
-        Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY dirty)
-        Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY dirty)
+        Q_PROPERTY(quint32 frameAPIDrawcallCount MEMBER frameAPIDrawcallCount NOTIFY newStats)
+        Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY newStats)
+        Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY newStats)
 
-        Q_PROPERTY(quint32 frameTriangleCount MEMBER frameTriangleCount NOTIFY dirty)
-        Q_PROPERTY(quint32 frameTriangleRate MEMBER frameTriangleRate NOTIFY dirty)
+        Q_PROPERTY(quint32 frameTriangleCount MEMBER frameTriangleCount NOTIFY newStats)
+        Q_PROPERTY(quint32 frameTriangleRate MEMBER frameTriangleRate NOTIFY newStats)
 
-        Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY dirty)
-        Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY dirty)
-        Q_PROPERTY(quint64 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY dirty)
+        Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY newStats)
+        Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY newStats)
+        Q_PROPERTY(quint64 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY newStats)
 
-        Q_PROPERTY(quint32 frameSetPipelineCount MEMBER frameSetPipelineCount NOTIFY dirty)
-        Q_PROPERTY(quint32 frameSetInputFormatCount MEMBER frameSetInputFormatCount NOTIFY dirty)
+        Q_PROPERTY(quint32 frameSetPipelineCount MEMBER frameSetPipelineCount NOTIFY newStats)
+        Q_PROPERTY(quint32 frameSetInputFormatCount MEMBER frameSetInputFormatCount NOTIFY newStats)
 
 
     public:
@@ -101,13 +101,6 @@ namespace render {
         quint32 frameSetPipelineCount{ 0 };
 
         quint32 frameSetInputFormatCount{ 0 };
-
-
-
-        void emitDirty() { emit dirty(); }
-
-    signals:
-        void dirty();
     };
 
     class EngineStats {
diff --git a/scripts/developer/utilities/cache/cash.js b/scripts/developer/utilities/cache/cash.js
new file mode 100644
index 0000000000..14ecf873e3
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash.js
@@ -0,0 +1,59 @@
+"use strict";
+var Page = Script.require('../lib/skit/Page.js');
+
+function openView() {
+    //window.closed.connect(function() { Script.stop(); });
+
+
+    var pages = new Pages(Script.resolvePath("."));  
+    function fromQml(message) {
+        console.log(JSON.stringify(message))
+        if (message.method == "inspectResource") {
+            pages.open("openResourceInspector")
+            pages.sendTo("openResourceInspector", message)
+            return;
+        } 
+        if (pages.open(message.method)) {
+            return;
+        }    
+    }
+
+    function openCashWindow(window) {
+        var onMousePressEvent = function (e) {
+        };
+        Controller.mousePressEvent.connect(onMousePressEvent);
+    
+        var onMouseReleaseEvent = function () {
+        };
+        Controller.mouseReleaseEvent.connect(onMouseReleaseEvent);
+    
+        var onMouseMoveEvent = function (e) {
+        };
+        Controller.mouseMoveEvent.connect(onMouseMoveEvent);
+    }
+
+    function closeCashWindow() {
+        Controller.mousePressEvent.disconnect(onMousePressEvent);
+        Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent);
+        Controller.mouseMoveEvent.disconnect(onMouseMoveEvent);
+        pages.clear();
+    }
+ 
+
+    pages.addPage('Cash', 'Cash', "cash.qml", 300, 500, fromQml, openCashWindow, closeCashWindow);
+    pages.addPage('openModelCacheInspector', 'Model Cache Inspector', "cash/ModelCacheInspector.qml", 300, 500, fromQml);
+    pages.addPage('openMaterialCacheInspector', 'Material Cache Inspector', "cash/MaterialCacheInspector.qml", 300, 500, fromQml);
+    pages.addPage('openTextureCacheInspector', 'Texture Cache Inspector', "cash/TextureCacheInspector.qml", 300, 500, fromQml);
+    pages.addPage('openAnimationCacheInspector', 'Animation Cache Inspector', "cash/AnimationCacheInspector.qml", 300, 500);
+    pages.addPage('openSoundCacheInspector', 'Sound Cache Inspector', "cash/SoundCacheInspector.qml", 300, 500);
+    pages.addPage('openResourceInspector', 'Resource Inspector', "cash/ResourceInspector.qml", 300, 500);
+
+
+    pages.open('Cash');
+
+    
+    return pages;
+}
+
+
+openView();
diff --git a/scripts/developer/utilities/cache/cash.qml b/scripts/developer/utilities/cache/cash.qml
new file mode 100644
index 0000000000..159ce95c5f
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash.qml
@@ -0,0 +1,167 @@
+//
+//  cash.qml
+//
+//  Created by Sam Gateau on 17/9/2019
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+
+import controlsUit 1.0 as HifiControls
+
+import "../lib/prop" as Prop
+import "cash"
+import "../lib/plotperf"
+
+Rectangle {
+    anchors.fill: parent 
+    id: root;   
+    
+    Prop.Global { id: global;}
+    color: global.color
+
+    ScrollView {
+        id: scrollView
+        anchors.fill: parent 
+        contentWidth: parent.width
+        clip: true
+         
+        Column {
+            id: column
+            width: parent.width
+
+            Prop.PropFolderPanel {
+                label: "Resource Queries Inspector"
+                isUnfold: true
+                panelFrameData: Component {
+                    Column {
+                        PlotPerf {
+                        title: "Global Queries"
+                        height: 80
+                        valueScale: 1
+                        valueUnit: ""
+                        plots: [
+                        {
+                            object: ModelCache,
+                            prop: "numGlobalQueriesPending",
+                            label: "Pending",
+                            color: "#1AC567"
+                        },
+                        {
+                            object: ModelCache,
+                            prop: "numGlobalQueriesLoading",
+                            label: "Loading",
+                            color: "#FEC567"
+                        },
+                        {
+                            object: ModelCache,
+                            prop: "numLoading",
+                            label: "Model Loading",
+                            color: "#C5FE67"
+                        }
+                        ]
+                        }
+                    }
+                }
+            }
+
+            Prop.PropFolderPanel {
+                label: "Cache Inspectors"
+                isUnfold: true
+                panelFrameData: Component {
+                    Column {
+                        Prop.PropButton {
+                            text: "Model"
+                            onClicked: {
+                                sendToScript({method: "openModelCacheInspector"}); 
+                            }
+                            width:column.width
+                        }
+                        Prop.PropButton {
+                            text: "Material"
+                            onClicked: {
+                                sendToScript({method: "openMaterialCacheInspector"}); 
+                            }
+                            width:column.width
+                        }
+                        Prop.PropButton {
+                            text: "Texture"
+                            onClicked: {
+                                sendToScript({method: "openTextureCacheInspector"}); 
+                            }
+                            width:column.width
+                        }
+                        Prop.PropButton {
+                            text: "Animation"
+                            onClicked: {
+                                sendToScript({method: "openAnimationCacheInspector"}); 
+                            }
+                            width:column.width
+                        }
+                        Prop.PropButton {
+                            text: "Sound"
+                            onClicked: {
+                                sendToScript({method: "openSoundCacheInspector"}); 
+                            }
+                            width:column.width
+                        }
+                    }
+                }
+            }
+            Prop.PropFolderPanel {
+                label: "Stats"
+                isUnfold: true
+                panelFrameData: Component { Column {
+                    PlotPerf {
+                        title: "Resources"
+                        height: 200
+                        valueScale: 1
+                        valueUnit: ""
+                        plots: [
+                        {
+                            object: TextureCache,
+                            prop: "numTotal",
+                            label: "Textures",
+                            color: "#1AC567"
+                        },
+                        {
+                            object: TextureCache,
+                            prop: "numCached",
+                            label: "Textures Cached",
+                            color: "#FEC567"
+                        },
+                        {
+                            object: ModelCache,
+                            prop: "numTotal",
+                            label: "Models",
+                            color: "#FED959"
+                        },
+                        {
+                            object: ModelCache,
+                            prop: "numCached",
+                            label: "Models Cached",
+                            color: "#FEFE59"
+                        },
+                        {
+                            object: MaterialCache,
+                            prop: "numTotal",
+                            label: "Materials",
+                            color: "#00B4EF"
+                        },
+                        {
+                            object: MaterialCache,
+                            prop: "numCached",
+                            label: "Materials Cached",
+                            color: "#FFB4EF"
+                        }
+                        ]
+                    }}
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/scripts/developer/utilities/cache/cash/AnimationCacheInspector.qml b/scripts/developer/utilities/cache/cash/AnimationCacheInspector.qml
new file mode 100644
index 0000000000..4ded44c2b1
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/AnimationCacheInspector.qml
@@ -0,0 +1,21 @@
+//
+//  AnimationCacheInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-17
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.7
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+import "../../lib/prop" as Prop
+
+ResourceCacheInspector {
+    id: root;
+    anchors.fill: parent.fill
+    cache: AnimationCache
+    cacheResourceName: "Animation"
+}
diff --git a/scripts/developer/utilities/cache/cash/MaterialCacheInspector.qml b/scripts/developer/utilities/cache/cash/MaterialCacheInspector.qml
new file mode 100644
index 0000000000..160c47c946
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/MaterialCacheInspector.qml
@@ -0,0 +1,21 @@
+//
+//  MaterialCacheInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-17
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.7
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+import "../../lib/prop" as Prop
+
+ResourceCacheInspector {
+    id: root;
+    anchors.fill: parent.fill
+    cache: MaterialCache
+    cacheResourceName: "Material"
+}
diff --git a/scripts/developer/utilities/cache/cash/ModelCacheInspector.qml b/scripts/developer/utilities/cache/cash/ModelCacheInspector.qml
new file mode 100644
index 0000000000..017942dfc9
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/ModelCacheInspector.qml
@@ -0,0 +1,21 @@
+//
+//  ModelCacheInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-17
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.7
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+import "../../lib/prop" as Prop
+
+ResourceCacheInspector {
+    id: root;
+    anchors.fill: parent.fill
+    cache: ModelCache
+    cacheResourceName: "Model"
+}
diff --git a/scripts/developer/utilities/cache/cash/ResourceCacheInspector.qml b/scripts/developer/utilities/cache/cash/ResourceCacheInspector.qml
new file mode 100644
index 0000000000..2fa0af8cbc
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/ResourceCacheInspector.qml
@@ -0,0 +1,367 @@
+//
+//  ResourceCacheInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-17
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+import QtQml.Models 2.12
+
+import "../../lib/skit/qml" as Skit
+import "../../lib/prop" as Prop
+
+Item {
+    id: root;
+    Prop.Global { id: global }
+
+    anchors.fill: parent.fill
+    property var cache: {}
+    property string cacheResourceName: "" 
+
+    function fromScript(message) {
+        switch (message.method) {
+        case "resetItemList":
+            resetItemListFromCache()
+            break;
+        }
+    }
+
+    function requestResourceDetails(resourceURL) {
+        sendToScript({method: "inspectResource", params: {url: resourceURL, semantic: cacheResourceName}}); 
+    }
+
+    Component.onCompleted: {
+        resetItemListFromCache();
+    }
+
+    function fetchItemsList() {
+        var theList;
+        if (cache !== undefined) {
+            theList = cache.getResourceList();
+        } else {
+            theList = ["ResourceCacheInspector.cache is undefined"];
+        }
+        var theListString = new Array(theList.length)
+        for (var i in theList) {
+            theListString[i] = (theList[i].toString())
+        }
+        return theListString;
+    }
+
+    function resetItemListFromCache() {  
+        resetItemList(fetchItemsList())
+    }
+
+    property var needFreshList : false
+
+    function updateItemListFromCache() { 
+        needFreshList = true
+    }
+
+
+    Timer {
+        interval: 2000; running: true; repeat: true
+        onTriggered: pullFreshValues()
+    }
+
+    function pullFreshValues() {
+        if (needFreshList) {
+            updateItemList(fetchItemsList())
+            needFreshList = false
+        }
+    }
+    
+    property alias resourceItemsModel: visualModel.model  
+    property var currentItemsList: new Array();
+  
+    function packItemEntry(item, identifier) {
+        var entry = { "identifier": identifier, "name": "", "scheme": "", "host": "", "pathDir": "", "url": item}
+        if (item.length > 0) {
+            // Detect scheme:
+            var schemePos = item.search(":") 
+            entry.scheme = item.substring(0, schemePos)
+            if (schemePos < 0) schemePos = 0
+            else schemePos += 1
+
+            // path pos is probably after schemePos
+            var pathPos = schemePos
+            
+            // try to detect //userinfo@host:port
+            var token = item.substr(schemePos, 2);
+            if (token.search("//") == 0) {
+                pathPos += 2
+            }
+            item = item.substring(pathPos, item.length)
+            // item is now everything after scheme:[//]
+            var splitted = item.split('/')
+
+            // odd ball, the rest of the url has no other'/' ?
+            // in theory this means it s just the host info ?
+            // we are assuming that path ALWAYS starts with a slash
+            entry.host = splitted[0]
+            
+            if (splitted.length > 1) {           
+                entry.name = splitted[splitted.length - 1] 
+
+                // if splitted is longer than 2 then there should be a path dir
+                if (splitted.length > 2) {  
+                    for (var i = 1; i < splitted.length - 1; i++) {
+                        entry.pathDir += '/' +   splitted[i]   
+                    }
+                }
+            }
+        }
+        return entry
+    }
+
+
+    function resetItemList(itemList) {
+        currentItemsList = []
+        resourceItemsModel.clear()
+        for (var i in itemList) {
+            var item = itemList[i]
+            currentItemsList.push(item)
+            resourceItemsModel.append(packItemEntry(item, currentItemsList.length -1))
+        }
+        // At the end of it, force an update
+        visualModel.forceUpdate()   
+    }
+
+    function updateItemList(newItemList) {
+        resetItemList(newItemList) 
+    }
+
+    property var itemFields: ['identifier', 'name', 'scheme', 'host', 'pathDir', 'url']
+ 
+
+    Column {
+        id: header
+
+        anchors.top: parent.top
+        anchors.left: parent.left
+        anchors.right: parent.right
+        
+        Item {
+            anchors.left: parent.left
+            anchors.right: parent.right
+            height: totalCount.height
+            id: headerTop
+
+            Prop.PropButton {
+                id: refreshButton
+                anchors.left: parent.left
+                anchors.verticalCenter: parent.verticalCenter
+                text: "Refresh"
+                color: needFreshList ? global.colorOrangeAccent : global.fontColor 
+                onPressed: { pullFreshValues() }
+            }
+
+            GridLayout {
+                anchors.left: refreshButton.right
+                anchors.right: parent.right
+                id: headerCountLane
+                columns: 3
+
+                Item {
+                    Layout.fillWidth: true
+                    Prop.PropScalar {
+                        id: itemCount
+                        label: "Count"
+                        object: root.cache
+                        property: "numTotal" 
+                        integral: true
+                        readOnly: true
+                    }
+                }
+                Item {
+                    Layout.fillWidth: true
+                    Prop.PropScalar {
+                        id: totalCount
+                        label: "Count"
+                        object: root.cache
+                        property: "numTotal" 
+                        integral: true
+                        readOnly: true
+                        onSourceValueVarChanged: { updateItemListFromCache() }
+                    }
+                }
+                Item {
+                    Layout.fillWidth: true
+                    Prop.PropScalar {
+                        id: cachedCount
+                        label: "Cached"
+                        object: root.cache
+                        property: "numCached" 
+                        integral: true
+                        readOnly: true
+                        onSourceValueVarChanged: { updateItemListFromCache() }
+                    }
+                }
+            }
+        }
+        Item {
+            anchors.left: parent.left
+            anchors.right: parent.right
+            height: orderSelector.height
+            
+            Prop.PropText {
+                anchors.left: parent.left
+                anchors.verticalCenter: parent.verticalCenter
+                width: 50
+                id: orderSelectorLabel
+                text: "Sort by"
+                horizontalAlignment: Text.AlignHCenter
+            }  
+            Prop.PropComboBox {
+                anchors.left: orderSelectorLabel.right
+                width: 80
+                id: orderSelector
+                model: itemFields
+                currentIndex: 1
+                
+                property var isSchemeVisible: (currentIndex == 2 || filterFieldSelector.currentIndex == 2)
+                property var isHostVisible: (currentIndex == 3 || filterFieldSelector.currentIndex == 3)
+                property var isPathDirVisible: (currentIndex == 4 || filterFieldSelector.currentIndex == 4)
+                property var isURLVisible: (currentIndex == 5 || filterFieldSelector.currentIndex == 5)
+            }
+
+            Prop.PropTextField {
+                anchors.left: orderSelector.right
+                anchors.right: filterFieldSelector.left
+                id: nameFilter
+                placeholderText: qsTr("Filter by " + itemFields[filterFieldSelector.currentIndex] + "...")
+                Layout.fillWidth: true
+            }
+            Prop.PropComboBox {
+                anchors.right: parent.right
+                id: filterFieldSelector
+                model: itemFields
+                currentIndex: 1
+                width: 80
+                opacity: (nameFilter.text.length > 0) ? 1.0 : 0.5
+            }
+        }
+    }
+
+    Component {
+        id: resouceItemDelegate
+        MouseArea {
+            id: dragArea
+            property bool held: false
+            anchors { left: parent.left; right: parent.right }
+            height: item.height
+            onPressed: {held = true}
+            onReleased: {held = false}
+            onDoubleClicked: { requestResourceDetails(model.url) }
+            Rectangle {
+                id: item
+                width: parent.width
+                height: global.slimHeight
+                color: dragArea.held ? global.colorBackHighlight : (index % 2 ? global.colorBackShadow : global.colorBack)              
+                Row {
+                    id: itemRow
+                    anchors.verticalCenter : parent.verticalCenter
+                    Prop.PropText {
+                        id: itemIdentifier
+                        text: model.identifier
+                        width: 30
+                    }
+                    Prop.PropSplitter {
+                        visible: orderSelector.isSchemeVisible
+                        size:8
+                    }
+                    Prop.PropLabel {
+                        visible: orderSelector.isSchemeVisible
+                        text: model.scheme
+                        width: 30
+                    }
+                    Prop.PropSplitter {
+                        visible: orderSelector.isHostVisible
+                        size:8
+                    }
+                    Prop.PropLabel {
+                        visible: orderSelector.isHostVisible
+                        text: model.host
+                        width: 150
+                    }
+                    Prop.PropSplitter {
+                        visible: orderSelector.isPathDirVisible
+                        size:8
+                    }
+                    Prop.PropLabel {
+                        visible: orderSelector.isPathDirVisible
+                        text: model.pathDir
+                    }
+                    Prop.PropSplitter {
+                        size:8
+                    }
+                    Prop.PropLabel {
+                        visible: !orderSelector.isURLVisible
+                        text: model.name
+                    }
+                    Prop.PropLabel {
+                        visible: orderSelector.isURLVisible
+                        text: model.url
+                    }
+                }
+            }
+            
+        }
+    }
+
+    Skit.SortFilterModel {
+        id: visualModel
+        model: ListModel {}
+
+        property int sortOrder: orderSelector.currentIndex
+
+        property var lessThanArray: [
+            function(left, right) { return left.index < right.index },
+            function(left, right) { return left.name < right.name },
+            function(left, right) { return left.scheme < right.scheme },
+            function(left, right) { return left.host < right.host },
+            function(left, right) { return left.pathDir < right.pathDir },
+            function(left, right) { return left.url < right.url }
+        ];
+        lessThan: lessThanArray[sortOrder]
+
+        property int filterField: filterFieldSelector.currentIndex
+        onFilterFieldChanged: { refreshFilter() }
+        property var textFilter: nameFilter.text
+        onTextFilterChanged: { refreshFilter() }
+        
+        function filterToken(itemWord, token) { return  (itemWord.search(token) > -1) }
+        property var acceptItemArray: [
+            function(item) { return true },
+            function(item) { return filterToken(item.identifier.toString(), textFilter) },
+            function(item) { return filterToken(item.name, textFilter) },
+            function(item) { return filterToken(item.scheme, textFilter) },
+            function(item) { return filterToken(item.host, textFilter) },
+            function(item) { return filterToken(item.pathDir, textFilter) },
+            function(item) { return filterToken(item.url, textFilter) }
+        ]
+
+        function refreshFilter() {
+            //console.log("refreshFilter! token = " + textFilter + " field = " + filterField)
+            acceptItem = acceptItemArray[(textFilter.length != 0) * + (1 + filterField)]
+        }
+
+        delegate: resouceItemDelegate
+    }
+
+    ListView {
+        anchors.top: header.bottom 
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.right: parent.right
+        clip: true
+    
+        id: listView
+        model: visualModel
+    }
+}
diff --git a/scripts/developer/utilities/cache/cash/ResourceInspector.qml b/scripts/developer/utilities/cache/cash/ResourceInspector.qml
new file mode 100644
index 0000000000..099eb735d0
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/ResourceInspector.qml
@@ -0,0 +1,58 @@
+//
+//  ResourceInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-24
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+import QtQml.Models 2.12
+
+import "../../lib/prop" as Prop
+
+Item {
+    id: root;
+    Prop.Global { id: global }
+
+    anchors.fill: parent.fill
+    property var cache: {}
+    property string cacheResourceName: "" 
+
+    function fromScript(message) {
+        switch (message.method) {
+        case "inspectResource":
+            inspectResource(message.params.url, message.params.semantic)
+            break;
+        }
+    }
+
+    function inspectResource(url, semantic) {
+        console.log("inspectResource :" + url + " as " + semantic)
+        info.text = "url: " + url + "\nsemantic: " + semantic + "\n";
+
+        if (semantic == "Texture") {
+            var res = TextureCache.prefetch(url, 0)
+            info.text += JSON.stringify(res);
+        } else if (semantic == "Model") {
+            var res = ModelCache.prefetch(url)
+            info.text += JSON.stringify(res);
+        }
+    }
+
+    TextEdit {
+            id: info
+            anchors.fill: parent
+            text: "Click an object to get material JSON"
+            width: root.width
+            font.pointSize: 10
+            color: "#FFFFFF"
+            readOnly: true
+            selectByMouse: true
+            wrapMode: Text.WordWrap
+    }
+}
+
diff --git a/scripts/developer/utilities/cache/cash/SoundCacheInspector.qml b/scripts/developer/utilities/cache/cash/SoundCacheInspector.qml
new file mode 100644
index 0000000000..26b043469e
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/SoundCacheInspector.qml
@@ -0,0 +1,21 @@
+//
+//  SoundCacheInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-17
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.7
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+import "../../lib/prop" as Prop
+
+ResourceCacheInspector {
+    id: root;
+    anchors.fill: parent.fill
+    cache: SoundCache
+    cacheResourceName: "Sound"
+}
diff --git a/scripts/developer/utilities/cache/cash/TextureCacheInspector.qml b/scripts/developer/utilities/cache/cash/TextureCacheInspector.qml
new file mode 100644
index 0000000000..9bfd663a4e
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/TextureCacheInspector.qml
@@ -0,0 +1,21 @@
+//
+//  TextureCacheInspector.qml
+//
+//  Created by Sam Gateau on 2019-09-17
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.7
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+import "../../lib/prop" as Prop
+
+ResourceCacheInspector {
+    id: root;
+    anchors.fill: parent.fill
+    cache: TextureCache
+    cacheResourceName: "Texture"
+}
diff --git a/scripts/developer/utilities/cache/cash/qmldir b/scripts/developer/utilities/cache/cash/qmldir
new file mode 100644
index 0000000000..8793a6b8f5
--- /dev/null
+++ b/scripts/developer/utilities/cache/cash/qmldir
@@ -0,0 +1,6 @@
+ResourceCacheInspector  1.0 ResourceCacheInspector.qml
+TextureCacheInspector  1.0 TextureCacheInspector.qml
+MaterialCacheInspector  1.0 MaterialCacheInspector.qml
+ModelCacheInspector  1.0 ModelCacheInspector.qml
+AnimationCacheInspector  1.0 AnimationCacheInspector.qml
+SoundCacheInspector  1.0 SoundCacheInspector.qml
\ No newline at end of file
diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml
index e2576fe783..a935163bd9 100644
--- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml
+++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml
@@ -91,12 +91,12 @@ Rectangle {
         }
     }
 
-    Original.ScrollView {
+    ListView {
         anchors.fill: parent 
-        ListView {
-            id: theView
-            model: jobsModel
-            delegate: objRecursiveDelegate
-        }
+
+        id: theView
+        model: jobsModel
+        delegate: objRecursiveDelegate
     }
+    
 }
\ No newline at end of file
diff --git a/scripts/developer/utilities/lib/prop/PropEnum.qml b/scripts/developer/utilities/lib/prop/PropEnum.qml
index 2268b21e34..97c385281d 100644
--- a/scripts/developer/utilities/lib/prop/PropEnum.qml
+++ b/scripts/developer/utilities/lib/prop/PropEnum.qml
@@ -16,6 +16,7 @@ PropItem {
     id: root
 
     property alias enums : valueCombo.model
+    property alias currentIndex : valueCombo.currentIndex
 
     PropComboBox {
         id: valueCombo
diff --git a/scripts/developer/utilities/lib/prop/PropItem.qml b/scripts/developer/utilities/lib/prop/PropItem.qml
index 6d2f4c11ad..3779e7f4a9 100644
--- a/scripts/developer/utilities/lib/prop/PropItem.qml
+++ b/scripts/developer/utilities/lib/prop/PropItem.qml
@@ -25,19 +25,16 @@ Item {
     // 
     function defaultGet() { var v = root.object[root.property]; return v; }
     function defaultSet(value) { root.object[root.property] = value; }  
-   // function defaultSetReadOnly(value) { log ( "read only " + property + ", NOT setting to " + value); }  
- //   function defaultSetReadOnly(value) {}  
-  //  property var valueVarSetter: (root.readOnly ? defaultSetReadOnly : defaultSet)
-    property var valueVarSetter: defaultSet
+    function defaultSetReadOnly(value) {}   
+
+    property var valueVarSetter: (readOnly ? defaultSetReadOnly : defaultSet)
     property var valueVarGetter: defaultGet
 
     // PropItem is stretching horizontally accross its parent
     // Fixed height
+    height: global.lineHeight
     anchors.left: parent.left
     anchors.right: parent.right    
-    height: global.lineHeight
-    anchors.leftMargin: global.horizontalMargin
-    anchors.rightMargin: global.horizontalMargin
 
     // LabelControl And SplitterControl are on the left side of the PropItem
     property bool showLabel: true  
diff --git a/scripts/developer/utilities/lib/prop/PropScalar.qml b/scripts/developer/utilities/lib/prop/PropScalar.qml
index ce89342997..3776f5c4bc 100644
--- a/scripts/developer/utilities/lib/prop/PropScalar.qml
+++ b/scripts/developer/utilities/lib/prop/PropScalar.qml
@@ -32,9 +32,7 @@ PropItem {
     property var sourceValueVar: root.valueVarGetter()
 
     function applyValueVarFromWidgets(value) {
-        if (!root.readOnly) { 
-           root.valueVarSetter(value)
-        }
+        root.valueVarSetter(value)
     }
 
     PropLabel {
@@ -58,6 +56,7 @@ PropItem {
 
         MouseArea{
             id: mousearea
+            enabled: !root.readOnly
             anchors.fill: parent
             onDoubleClicked: { sliderControl.visible = !sliderControl.visible }
         }
diff --git a/scripts/developer/utilities/lib/prop/qmldir b/scripts/developer/utilities/lib/prop/qmldir
index e09785846d..99e721fb33 100644
--- a/scripts/developer/utilities/lib/prop/qmldir
+++ b/scripts/developer/utilities/lib/prop/qmldir
@@ -1,8 +1,10 @@
 Module Prop
 Global 1.0 style/Global.qml
 PropText 1.0 style/PiText.qml
+PropTextField 1.0 style/PiTextField.qml
 PropLabel 1.0 style/PiLabel.qml
 PropSplitter 1.0 style/PiSplitter.qml
+PropButton 1.0 style/PiButton.qml
 PropComboBox 1.0 style/PiComboBox.qml
 PropCanvasIcon 1.0 style/PiCanvasIcon.qml
 PropCheckBox 1.0 style/PiCheckBox.qml
diff --git a/scripts/developer/utilities/lib/prop/style/PiButton.qml b/scripts/developer/utilities/lib/prop/style/PiButton.qml
new file mode 100644
index 0000000000..5469431d81
--- /dev/null
+++ b/scripts/developer/utilities/lib/prop/style/PiButton.qml
@@ -0,0 +1,35 @@
+//
+//  Prop/style/PiButton.qml
+//
+//  Created by Sam Gateau on 17/09/2019
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+
+Button {
+    Global { id: global }
+    id: control
+    text: ""
+    spacing: 0
+    property alias color: theContentItem.color
+
+    contentItem: PiText {
+        id: theContentItem
+        text: control.text
+        horizontalAlignment: Text.AlignHCenter
+        color: global.fontColor
+    }
+
+    background: Rectangle {
+        color: control.down ? global.colorBackHighlight : global.colorBackShadow 
+        opacity: enabled ? 1 : 0.3
+        border.color: control.down ? global.colorBorderHighight : (control.hovered ? global.colorBorderHighight : global.colorBorderLight)
+        border.width: global.valueBorderWidth
+        radius: global.valueBorderRadius
+    }
+}
\ No newline at end of file
diff --git a/scripts/developer/utilities/lib/prop/style/PiComboBox.qml b/scripts/developer/utilities/lib/prop/style/PiComboBox.qml
index 92164fefc5..9430228eba 100644
--- a/scripts/developer/utilities/lib/prop/style/PiComboBox.qml
+++ b/scripts/developer/utilities/lib/prop/style/PiComboBox.qml
@@ -16,7 +16,9 @@ ComboBox {
     id: valueCombo
 
     height: global.slimHeight
-
+    width: 120
+    implicitHeight: global.slimHeight
+       
 
     // look
     flat: true
@@ -51,8 +53,6 @@ ComboBox {
     }
 
     background: Rectangle {
-        implicitWidth: 120
-        implicitHeight: 40
         color: global.colorBack
         border.color: valueCombo.popup.visible ? global.colorBorderLighter : global.colorBorderLight
         border.width: global.valueBorderWidth
diff --git a/scripts/developer/utilities/lib/prop/style/PiTextField.qml b/scripts/developer/utilities/lib/prop/style/PiTextField.qml
new file mode 100644
index 0000000000..8dfb9d88ee
--- /dev/null
+++ b/scripts/developer/utilities/lib/prop/style/PiTextField.qml
@@ -0,0 +1,31 @@
+//
+//  Prop/style/PiTextField.qml
+//
+//  Created by Sam Gateau on 9/24/2019
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+TextField {
+    id: control
+    Global { id: global }
+    implicitHeight: global.slimHeight
+    implicitWidth: 200
+
+    placeholderText: qsTr("Enter description")
+
+    color: global.fontColor
+    font.pixelSize: global.fontSize
+    font.family: global.fontFamily
+    font.weight: global.fontWeight
+
+    background: Rectangle {
+        color: (control.text.length > 0) ? global.colorBackHighlight : global.colorBackShadow
+        border.color: (control.text.length > 0) ? global.colorBorderHighight : "transparent"
+    }
+}
diff --git a/scripts/developer/utilities/render/luci/Page.js b/scripts/developer/utilities/lib/skit/Page.js
similarity index 69%
rename from scripts/developer/utilities/render/luci/Page.js
rename to scripts/developer/utilities/lib/skit/Page.js
index 06c9704abf..189c26044a 100644
--- a/scripts/developer/utilities/render/luci/Page.js
+++ b/scripts/developer/utilities/lib/skit/Page.js
@@ -10,11 +10,12 @@
 "use strict";
 
 (function() {
-function Page(title, qmlurl, width, height, onViewCreated, onViewClosed) {
+function Page(title, qmlurl, width, height, onFromQml, onViewCreated, onViewClosed) {
     this.title = title;
-    this.qml = qmlurl;
+    this.qmlurl = qmlurl;
     this.width = width;
     this.height = height;
+    this.onFromQml = onFromQml;
     this.onViewCreated = onViewCreated;
     this.onViewClosed = onViewClosed;
 
@@ -30,6 +31,9 @@ Page.prototype.killView = function () {
         //this.window.closed.disconnect(function () {
         //    this.killView();
         //});
+        if (this.onFromQml) {
+            this.window.fromQml.disconnect(this.onFromQml)
+        }
         this.window.close();
         this.window = false;
     }
@@ -39,12 +43,15 @@ Page.prototype.createView = function () {
     var that = this;
     if (!this.window) {
         print("Page: New window for page:" + this.title);
-        this.window = Desktop.createWindow(Script.resolvePath(this.qml), {
+        this.window = Desktop.createWindow(this.qmlurl, {
             title: this.title,
             presentationMode: Desktop.PresentationMode.NATIVE,
             size: {x: this.width, y: this.height}
         });
         this.onViewCreated(this.window);
+        if (this.onFromQml) {
+            this.window.fromQml.connect(this.onFromQml)
+        }
         this.window.closed.connect(function () {
             that.killView();
             that.onViewClosed();
@@ -53,11 +60,13 @@ Page.prototype.createView = function () {
 };
 
 
-Pages = function () {
+Pages = function (relativePath) {
+    print(relativePath)
+    this._relativePath = relativePath
     this._pages = {};
 };
 
-Pages.prototype.addPage = function (command, title, qmlurl, width, height, onViewCreated, onViewClosed) {
+Pages.prototype.addPage = function (command, title, qmlurl, width, height, onFromQml, onViewCreated, onViewClosed) {
     if (onViewCreated === undefined) {
         // Workaround for bad linter
         onViewCreated = function(window) {};
@@ -66,7 +75,7 @@ Pages.prototype.addPage = function (command, title, qmlurl, width, height, onVie
         // Workaround for bad linter
         onViewClosed = function() {};
     }
-    this._pages[command] = new Page(title, qmlurl, width, height, onViewCreated, onViewClosed);
+    this._pages[command] = new Page(title, Script.resolvePath(this._relativePath + qmlurl), width, height, onFromQml, onViewCreated, onViewClosed);
 };
 
 Pages.prototype.open = function (command) {
@@ -87,4 +96,12 @@ Pages.prototype.clear = function () {
     this._pages = {};
 };
 
+Pages.prototype.sendTo = function (command, message) {
+    if (!this._pages[command]) {
+        print("Pages: unknown command = " + command);
+        return;
+    }
+    this._pages[command].window.sendToQml(message);
+};
+
 }()); 
diff --git a/scripts/developer/utilities/lib/skit/qml/SortFilterModel.qml b/scripts/developer/utilities/lib/skit/qml/SortFilterModel.qml
new file mode 100644
index 0000000000..08ad8d1dbd
--- /dev/null
+++ b/scripts/developer/utilities/lib/skit/qml/SortFilterModel.qml
@@ -0,0 +1,93 @@
+import QtQuick 2.9
+import QtQml.Models 2.3
+
+DelegateModel {
+    id: delegateModel
+
+    property var lessThan: function(left, right) { return true; }
+    property var acceptItem: function(item) { return true; }
+
+    function insertPosition(lessThanFunctor, item) {
+        var lower = 0
+        var upper = visibleItems.count
+        while (lower < upper) {
+            var middle = Math.floor(lower + (upper - lower) / 2)
+            var result = lessThanFunctor(item.model, visibleItems.get(middle).model);
+            if (result) {
+                upper = middle
+            } else {
+                lower = middle + 1
+            }
+        }
+        return lower
+    }
+
+    function sortAndFilter(lessThanFunctor, acceptItemFunctor) {
+        while (unsortedItems.count > 0) {
+            var item = unsortedItems.get(0)
+            
+            if (acceptItemFunctor(item.model)) {
+                var index = insertPosition(lessThanFunctor, item)
+
+                item.groups = ["items","visible"]
+                visibleItems.move(item.visibleIndex, index)
+            } else {
+                item.groups = ["items"]
+            }
+        }
+    }
+
+    // Private bool to track when items changed and view is dirty
+    property bool itemsDirty: true
+    function update() {
+        console.log("SortFilterMode: update and sort and filter items !!" + items.count);
+        if (items.count > 0) {
+            items.setGroups(0, items.count, ["items","unsorted"]);
+        }
+
+        sortAndFilter(lessThan, acceptItem)
+        itemsDirty = false;
+        itemsUpdated()
+    }
+
+    signal itemsUpdated()
+
+    function updateOnItemsChanged() {
+        itemsDirty = true;
+        if (isAutoUpdateOnChanged) {
+            update()
+        }
+    }
+
+    property bool isAutoUpdateOnChanged: false
+    function setAutoUpdateOnChanged(enabled) {
+        isAutoUpdateOnChanged = enabled
+        if (enabled) {
+            update()
+        }
+    }
+
+    function forceUpdate() {
+        update();
+    }
+    items.onChanged: updateOnItemsChanged()
+    onLessThanChanged: update()
+    onAcceptItemChanged: update()
+
+    groups: [
+        DelegateModelGroup {
+            id: visibleItems
+
+            name: "visible"
+            includeByDefault: false
+        },
+        DelegateModelGroup {
+            id: unsortedItems
+
+            name: "unsorted"
+            includeByDefault: false
+        }
+    ]
+
+    filterOnGroup: "visible"
+}
\ No newline at end of file
diff --git a/scripts/developer/utilities/lib/skit/qml/qmldir b/scripts/developer/utilities/lib/skit/qml/qmldir
new file mode 100644
index 0000000000..14d11d998a
--- /dev/null
+++ b/scripts/developer/utilities/lib/skit/qml/qmldir
@@ -0,0 +1 @@
+SortFilterModel  1.0 SortFilterModel.qml
\ No newline at end of file
diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js
index e2e5523ccd..3b832bdfb4 100644
--- a/scripts/developer/utilities/render/luci.js
+++ b/scripts/developer/utilities/render/luci.js
@@ -1,14 +1,13 @@
 
 
 var MaterialInspector = Script.require('./materialInspector.js');
-var Page = Script.require('./luci/Page.js');
+
+var Page = Script.require('../lib/skit/Page.js');
 
 
 function openView() {
-    //window.closed.connect(function() { Script.stop(); });
+    var pages = new Pages(Script.resolvePath("."));
 
-
-    var pages = new Pages();
     function fromQml(message) {
         if (pages.open(message.method)) {
             return;
@@ -17,12 +16,6 @@ function openView() {
 
     var luciWindow  
     function openLuciWindow(window) {
-        if (luciWindow !== undefined) {
-            activeWindow.fromQml.disconnect(fromQml);
-        }
-        if (window !== undefined) {
-            window.fromQml.connect(fromQml);
-        }
         luciWindow = window;
 
 
@@ -57,9 +50,6 @@ function openView() {
     }
 
     function closeLuciWindow() {
-        if (luciWindow !== undefined) {
-            activeWindow.fromQml.disconnect(fromQml);
-        }
         luciWindow = {};
 
         Controller.mousePressEvent.disconnect(onMousePressEvent);
@@ -68,10 +58,10 @@ function openView() {
         pages.clear();
     }
 
-    pages.addPage('Luci', 'Luci', '../luci.qml', 300, 420, openLuciWindow, closeLuciWindow);
-    pages.addPage('openEngineInspectorView', 'Render Engine Inspector', '../engineInspector.qml', 300, 400);
-    pages.addPage('openEngineLODView', 'Render LOD', '../lod.qml', 300, 400);
-    pages.addPage('openMaterialInspectorView', 'Material Inspector', '../materialInspector.qml', 300, 400, MaterialInspector.setWindow, MaterialInspector.setWindow);
+    pages.addPage('Luci', 'Luci', 'luci.qml', 300, 420, fromQml, openLuciWindow, closeLuciWindow);
+    pages.addPage('openEngineInspectorView', 'Render Engine Inspector', 'engineInspector.qml', 300, 400);
+    pages.addPage('openEngineLODView', 'Render LOD', 'lod.qml', 300, 400);
+    pages.addPage('openMaterialInspectorView', 'Material Inspector', 'materialInspector.qml', 300, 400, null, MaterialInspector.setWindow, MaterialInspector.setWindow);
 
     pages.open('Luci');
 
diff --git a/scripts/developer/utilities/render/materialInspector.js b/scripts/developer/utilities/render/materialInspector.js
index 98d9f769fb..8a7d5ad7dd 100644
--- a/scripts/developer/utilities/render/materialInspector.js
+++ b/scripts/developer/utilities/render/materialInspector.js
@@ -128,7 +128,7 @@ function fromQml(message) {
 
 var SELECT_LIST = "luci_materialInspector_SelectionList";
 Selection.enableListHighlight(SELECT_LIST, {
-    outlineUnoccludedColor: { red: 255, green: 255, blue: 255 }
+    outlineUnoccludedColor: { red: 125, green: 255, blue: 225 }
 });
 function setSelectedObject(id, type) {
     Selection.clearSelectedItemsList(SELECT_LIST);
diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp
index ff0d7630e5..31e154d38c 100644
--- a/tools/gpu-frame-player/src/RenderThread.cpp
+++ b/tools/gpu-frame-player/src/RenderThread.cpp
@@ -51,6 +51,12 @@ void RenderThread::initialize(QWindow* window) {
     _backend = _gpuContext->getBackend();
     _context.doneCurrent();
     _context.moveToThread(_thread);
+    
+    if (!_presentPipeline) {
+        gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
+        gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+        _presentPipeline = gpu::Pipeline::create(program, state);
+    }
 #else
     auto size = window->size();
     _extent = vk::Extent2D{ (uint32_t)size.width(), (uint32_t)size.height() };
@@ -169,15 +175,28 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) {
     }
 
 #ifdef USE_GL
+    static gpu::BatchPointer batch = nullptr;
+    if (!batch) {
+        batch = std::make_shared<gpu::Batch>();
+        batch->setPipeline(_presentPipeline);
+        batch->setFramebuffer(nullptr);
+        batch->setResourceTexture(0, frame->framebuffer->getRenderBuffer(0));
+        batch->setViewportTransform(ivec4(uvec2(0), ivec2(windowSize.width(), windowSize.height())));
+        batch->draw(gpu::TRIANGLE_STRIP, 4);
+    }
+    glDisable(GL_FRAMEBUFFER_SRGB);
+    _gpuContext->executeBatch(*batch);
+    
+    // Keep this raw gl code here for reference
     //glDisable(GL_FRAMEBUFFER_SRGB);
     //glClear(GL_COLOR_BUFFER_BIT);
-    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+  /*  glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
     glBlitFramebuffer(
         0, 0, fboSize.x, fboSize.y, 
         0, 0, windowSize.width(), windowSize.height(),
         GL_COLOR_BUFFER_BIT, GL_NEAREST);
-
+*/
     (void)CHECK_GL_ERROR();
     _context.swapBuffers();
     _context.doneCurrent();
diff --git a/tools/gpu-frame-player/src/RenderThread.h b/tools/gpu-frame-player/src/RenderThread.h
index 09eef56623..b090e1737f 100644
--- a/tools/gpu-frame-player/src/RenderThread.h
+++ b/tools/gpu-frame-player/src/RenderThread.h
@@ -57,7 +57,7 @@ public:
     uint32_t _externalTexture{ 0 };
     void move(const glm::vec3& v);
     glm::mat4 _correction;
-
+    gpu::PipelinePointer _presentPipeline;
 
     void resize(const QSize& newSize);
     void setup() override;