From 105d17e85ef36a70208b1a354b5d3ff6d0f7609f Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 31 Mar 2017 15:07:34 -0700
Subject: [PATCH 01/85] Add byte range support to HTTPResourceRequest

---
 interface/src/Application.cpp                            | 9 +++++++++
 interface/src/ui/overlays/Image3DOverlay.cpp             | 1 +
 libraries/ktx/src/ktx/KTX.h                              | 2 ++
 .../src/model-networking/TextureCache.cpp                | 8 +++++++-
 .../model-networking/src/model-networking/TextureCache.h | 1 +
 libraries/networking/src/HTTPResourceRequest.cpp         | 6 ++++++
 libraries/networking/src/ResourceCache.cpp               | 2 ++
 libraries/networking/src/ResourceCache.h                 | 1 +
 libraries/networking/src/ResourceManager.h               | 1 +
 libraries/networking/src/ResourceRequest.h               | 9 +++++++++
 libraries/script-engine/src/ScriptEngine.cpp             | 6 ++++--
 11 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index f51c3d2b46..886487603e 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -188,6 +188,7 @@
 #include <src/scripting/LimitlessVoiceRecognitionScriptingInterface.h>
 #include <EntityScriptClient.h>
 #include <ModelScriptingInterface.h>
+#include <QtNetwork/QNetworkProxy>
 
 // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
 // FIXME seems to be broken.
@@ -604,6 +605,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     {
         const QString TEST_SCRIPT = "--testScript";
         const QString TRACE_FILE = "--traceFile";
+        const QString HTTP_PROXY = "--httpProxy";
         const QStringList args = arguments();
         for (int i = 0; i < args.size() - 1; ++i) {
             if (args.at(i) == TEST_SCRIPT) {
@@ -615,10 +617,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
                 QString traceFilePath = args.at(i + 1);
                 setProperty(hifi::properties::TRACING, traceFilePath);
                 DependencyManager::get<tracing::Tracer>()->startTracing();
+            } else if (args.at(i) == HTTP_PROXY) {
             }
         }
     }
 
+    QNetworkProxy proxy;
+    proxy.setType(QNetworkProxy::HttpProxy);
+    proxy.setHostName("127.0.0.1");
+    proxy.setPort(8888);
+    QNetworkProxy::setApplicationProxy(proxy);
+
     // make sure the debug draw singleton is initialized on the main thread.
     DebugDraw::getInstance().removeMarker("");
 
diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp
index 45d63d9cf1..f06efcef1c 100644
--- a/interface/src/ui/overlays/Image3DOverlay.cpp
+++ b/interface/src/ui/overlays/Image3DOverlay.cpp
@@ -53,6 +53,7 @@ void Image3DOverlay::update(float deltatime) {
 }
 
 void Image3DOverlay::render(RenderArgs* args) {
+    qDebug() << _url;
     if (!_isLoaded) {
         _isLoaded = true;
         _texture = DependencyManager::get<TextureCache>()->getTexture(_url);
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 043de573ed..7eab67c0db 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -379,6 +379,8 @@ namespace ktx {
         void setCubeArray(uint32_t width, uint32_t height, uint32_t numSlices) { setDimensions(width, height, 0, (numSlices > 0 ? numSlices : 1), NUM_CUBEMAPFACES); }
 
     };
+    static const size_t KTX_HEADER_SIZE = 64;
+    static_assert(sizeof(Header) == KTX_HEADER_SIZE, "KTX Header size is static");
 
     // Key Values
     struct KeyValue {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index f6e256bb06..45000b30a8 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -266,7 +266,8 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSh
 NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
     Resource(url),
     _type(type),
-    _maxNumPixels(maxNumPixels)
+    _maxNumPixels(maxNumPixels),
+    _sourceIsKTX(url.path().endsWith(".ktx"))
 {
     _textureSource = std::make_shared<gpu::TextureSource>();
 
@@ -274,6 +275,11 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
         _loaded = true;
     }
 
+    if (_sourceIsKTX) {
+        _requestByteRange.fromInclusive = 0;
+        _requestByteRange.toExclusive = 1000;
+    }
+
     // if we have content, load it after we have our self pointer
     if (!content.isEmpty()) {
         _startedLoading = true;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index d0600c3dce..d0916d61eb 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -73,6 +73,7 @@ private:
 
     image::TextureUsage::Type _type;
     KTXFilePointer _file;
+    bool _sourceIsKTX { false };
     int _originalWidth { 0 };
     int _originalHeight { 0 };
     int _width { 0 };
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 85da5de5b8..28d0485383 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -59,6 +59,12 @@ void HTTPResourceRequest::doSend() {
         networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
     }
 
+    if (_byteRange.isSet()) {
+        auto byteRange = QString("bytes={}-{}").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
+        networkRequest.setRawHeader("Range", byteRange.toLatin1());
+    }
+    networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+
     _reply = NetworkAccessManager::getInstance().get(networkRequest);
     
     connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished);
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 4031ff8bf7..7fdbc7885b 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -676,6 +676,8 @@ void Resource::makeRequest() {
         return;
     }
     
+    _request->setByteRange(_requestByteRange);
+
     qCDebug(resourceLog).noquote() << "Starting request for:" << _url.toDisplayString();
     emit loading();
 
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index 53ccd2c386..32364bf71c 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -442,6 +442,7 @@ protected:
 
     QUrl _url;
     QUrl _activeUrl;
+    ByteRange _requestByteRange;
     bool _startedLoading = false;
     bool _failedToLoad = false;
     bool _loaded = false;
diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h
index 162892abaf..d193c39cae 100644
--- a/libraries/networking/src/ResourceManager.h
+++ b/libraries/networking/src/ResourceManager.h
@@ -26,6 +26,7 @@ const QString URL_SCHEME_ATP = "atp";
 
 class ResourceManager {
 public:
+
     static void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
     static QString normalizeURL(const QString& urlString);
     static QUrl normalizeURL(const QUrl& url);
diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h
index 7588fca046..03b46e715d 100644
--- a/libraries/networking/src/ResourceRequest.h
+++ b/libraries/networking/src/ResourceRequest.h
@@ -17,6 +17,13 @@
 
 #include <cstdint>
 
+struct ByteRange {
+    int64_t fromInclusive { 0 };
+    int64_t toExclusive { 0 };
+
+    bool isSet() { return fromInclusive < -1 || fromInclusive < toExclusive; }
+};
+
 class ResourceRequest : public QObject {
     Q_OBJECT
 public:
@@ -48,6 +55,7 @@ public:
     bool loadedFromCache() const { return _loadedFromCache; }
 
     void setCacheEnabled(bool value) { _cacheEnabled = value; }
+    void setByteRange(ByteRange byteRange) { _byteRange = byteRange; }
 
 public slots:
     void send();
@@ -65,6 +73,7 @@ protected:
     QByteArray _data;
     bool _cacheEnabled { true };
     bool _loadedFromCache { false };
+    ByteRange _byteRange;
 };
 
 #endif
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 067c7c1412..575d23c4c4 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -2317,6 +2317,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
 
     if (_entityScripts.contains(entityID)) {
         const EntityScriptDetails &oldDetails = _entityScripts[entityID];
+        auto scriptText = oldDetails.scriptText;
+
         if (isEntityScriptRunning(entityID)) {
             callEntityScriptMethod(entityID, "unload");
         }
@@ -2334,14 +2336,14 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
             newDetails.status = EntityScriptStatus::UNLOADED;
             newDetails.lastModified = QDateTime::currentMSecsSinceEpoch();
             // keep scriptText populated for the current need to "debouce" duplicate calls to unloadEntityScript
-            newDetails.scriptText = oldDetails.scriptText;
+            newDetails.scriptText = scriptText;
             setEntityScriptDetails(entityID, newDetails);
         }
 
         stopAllTimersForEntityScript(entityID);
         {
             // FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests
-            processDeferredEntityLoads(oldDetails.scriptText, entityID);
+            processDeferredEntityLoads(scriptText, entityID);
         }
     }
 }

From 00cbfa0f70c0776a0e38739e7e9d692ecfa5e9ec Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 7 Apr 2017 16:48:22 -0700
Subject: [PATCH 02/85] Add start of progressive ktx-loading

---
 interface/src/ui/overlays/Image3DOverlay.cpp  |  1 -
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp     |  8 ++-
 libraries/gpu/src/gpu/Texture.h               | 10 ++-
 libraries/gpu/src/gpu/Texture_ktx.cpp         | 11 +++
 libraries/ktx/src/ktx/KTX.cpp                 | 23 +++++-
 libraries/ktx/src/ktx/KTX.h                   | 11 ++-
 libraries/ktx/src/ktx/Writer.cpp              | 62 ++++++++++++++++
 .../src/model-networking/TextureCache.cpp     | 72 +++++++++++++++++++
 .../src/model-networking/TextureCache.h       |  8 +++
 .../networking/src/HTTPResourceRequest.cpp    | 53 +++++++++++++-
 .../networking/src/NetworkAccessManager.cpp   | 10 ++-
 libraries/networking/src/ResourceRequest.h    |  4 ++
 libraries/shared/src/shared/Storage.cpp       |  1 +
 libraries/shared/src/shared/Storage.h         |  1 +
 14 files changed, 264 insertions(+), 11 deletions(-)

diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp
index f06efcef1c..45d63d9cf1 100644
--- a/interface/src/ui/overlays/Image3DOverlay.cpp
+++ b/interface/src/ui/overlays/Image3DOverlay.cpp
@@ -53,7 +53,6 @@ void Image3DOverlay::update(float deltatime) {
 }
 
 void Image3DOverlay::render(RenderArgs* args) {
-    qDebug() << _url;
     if (!_isLoaded) {
         _isLoaded = true;
         _texture = DependencyManager::get<TextureCache>()->getTexture(_url);
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index a6e6bf4fa3..b7d2ee0b0f 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -217,8 +217,12 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
         _transferSize = mipSize;
         _bufferingLambda = [=] {
             auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face);
-            _buffer.resize(_transferSize);
-            memcpy(&_buffer[0], mipData->readData(), _transferSize);
+            if (!mipData) {
+                qWarning() << "Mip not available: " << sourceMip;
+            } else {
+                _buffer.resize(_transferSize);
+                memcpy(&_buffer[0], mipData->readData(), _transferSize);
+            }
             _bufferingCompleted = true;
         };
 
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 2f63bd6719..756748497d 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -28,6 +28,8 @@ namespace ktx {
     struct KTXDescriptor;
     using KTXDescriptorPointer = std::unique_ptr<KTXDescriptor>;
     struct Header;
+    struct KeyValue;
+    using KeyValues = std::list<KeyValue>;
 }
 
 namespace gpu {
@@ -503,9 +505,15 @@ public:
 
     ExternalUpdates getUpdates() const;
 
-    // Textures can be serialized directly to ktx data file, here is how
+    // Serialize ktx header and keyvalues directly to a file, and return a Texture representing that file
+    static Texture* serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues);
+
+    // Serialize a texture into a KTX file
     static ktx::KTXUniquePointer serialize(const Texture& texture);
+
     static TexturePointer unserialize(const std::string& ktxFile, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
+    static TexturePointer unserialize(const std::string& ktxFile, const ktx::KTXDescriptor& descriptor, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
+
     static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
     static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);
 
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 50e9cb6d07..db6808a866 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -207,6 +207,10 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType
     }
 
     ktx::KTXDescriptor descriptor { ktxPointer->toDescriptor() };
+    return unserialize(ktxfile, ktxPointer->toDescriptor(), usageType, usage, sampler);
+}
+
+TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDescriptor& descriptor, TextureUsageType usageType, Usage usage, const Sampler::Desc& sampler) {
     const auto& header = descriptor.header;
 
     Format mipFormat = Format::COLOR_BGRA_32;
@@ -256,6 +260,13 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType
     return tex;
 }
 
+Texture* Texture::serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues) {
+    // Create a memory-backed KTX object
+    auto ktxBuffer = ktx::KTX::createBare(header, keyValues);
+
+    return unserialize(ktxfile, ktxBuffer->toDescriptor());
+}
+
 bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) {
     if (texelFormat == Format::COLOR_RGBA_32 && mipFormat == Format::COLOR_BGRA_32) {
         header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::BGRA, ktx::GLInternalFormat_Uncompressed::RGBA8, ktx::GLBaseInternalFormat::RGBA);
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 6fca39788b..0580d9a8c3 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -45,7 +45,7 @@ uint32_t Header::evalPixelDepth(uint32_t level) const {
 }
 
 size_t Header::evalPixelSize() const {
-    return glTypeSize; // Really we should generate the size from the FOrmat etc
+    return 4;//glTypeSize; // Really we should generate the size from the FOrmat etc
 }
 
 size_t Header::evalRowSize(uint32_t level) const {
@@ -70,6 +70,25 @@ size_t Header::evalImageSize(uint32_t level) const {
     }
 }
 
+ImageDescriptors Header::generateImageDescriptors() const {
+    ImageDescriptors descriptors;
+
+    for (auto level = 0; level < numberOfMipmapLevels; ++level) {
+        ImageHeader header {
+            numberOfFaces == NUM_CUBEMAPFACES,
+            static_cast<uint32_t>(evalImageSize(level)),
+            0
+        };
+        ImageHeader::FaceOffsets offsets;
+        for (auto i = 0; i < numberOfFaces; ++i) {
+            offsets.push_back(0);
+        }
+        descriptors.push_back(ImageDescriptor(header, offsets));
+    }
+
+    return descriptors;
+}
+
 
 KeyValue::KeyValue(const std::string& key, uint32_t valueByteSize, const Byte* value) :
     _byteSize((uint32_t) key.size() + 1 + valueByteSize), // keyString size + '\0' ending char + the value size
@@ -209,4 +228,4 @@ KTXDescriptor KTX::toDescriptor() const {
 
 KTX::KTX(const StoragePointer& storage, const Header& header, const KeyValues& keyValues, const Images& images)
     : _header(header), _storage(storage), _keyValues(keyValues), _images(images) {
-}
\ No newline at end of file
+}
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 7eab67c0db..fb8927eca0 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -292,6 +292,9 @@ namespace ktx {
     using Storage = storage::Storage;
     using StoragePointer = std::shared_ptr<Storage>;
 
+    struct ImageDescriptor;
+    using ImageDescriptors = std::vector<ImageDescriptor>;
+
     // Header
     struct Header {
         static const size_t IDENTIFIER_LENGTH = 12;
@@ -378,6 +381,7 @@ namespace ktx {
         void setCube(uint32_t width, uint32_t height) { setDimensions(width, height, 0, 0, NUM_CUBEMAPFACES); }
         void setCubeArray(uint32_t width, uint32_t height, uint32_t numSlices) { setDimensions(width, height, 0, (numSlices > 0 ? numSlices : 1), NUM_CUBEMAPFACES); }
 
+        ImageDescriptors generateImageDescriptors() const;
     };
     static const size_t KTX_HEADER_SIZE = 64;
     static_assert(sizeof(Header) == KTX_HEADER_SIZE, "KTX Header size is static");
@@ -421,14 +425,14 @@ namespace ktx {
 
     struct Image;
 
+    // Image without the image data itself
     struct ImageDescriptor : public ImageHeader {
         const FaceOffsets _faceOffsets;
         ImageDescriptor(const ImageHeader& header, const FaceOffsets& offsets) : ImageHeader(header), _faceOffsets(offsets) {}
         Image toImage(const ktx::StoragePointer& storage) const;
     };
 
-    using ImageDescriptors = std::vector<ImageDescriptor>;
-
+    // Image with the image data itself
     struct Image : public ImageHeader {
         FaceBytes _faceBytes;
         Image(const ImageHeader& header, const FaceBytes& faces) : ImageHeader(header), _faceBytes(faces) {}
@@ -473,6 +477,7 @@ namespace ktx {
         // This path allocate the Storage where to store header, keyvalues and copy mips
         // Then COPY all the data
         static std::unique_ptr<KTX> create(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
+        static std::unique_ptr<KTX> createBare(const Header& header, const KeyValues& keyValues = KeyValues());
 
         // Instead of creating a full Copy of the src data in a KTX object, the write serialization can be performed with the
         // following two functions
@@ -486,7 +491,9 @@ namespace ktx {
         //
         // This is exactly what is done in the create function
         static size_t evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
+        static size_t evalStorageSize(const Header& header, const ImageDescriptors& images, const KeyValues& keyValues = KeyValues());
         static size_t write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& images, const KeyValues& keyValues = KeyValues());
+        static size_t writeWithoutImages(Byte* destBytes, size_t destByteSize, const Header& header, const ImageDescriptors& descriptors, const KeyValues& keyValues = KeyValues());
         static size_t writeKeyValues(Byte* destBytes, size_t destByteSize, const KeyValues& keyValues);
         static Images writeImages(Byte* destBytes, size_t destByteSize, const Images& images);
 
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 25b363d31b..396aa09f9a 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -40,6 +40,20 @@ namespace ktx {
         return create(storagePointer);
     }
 
+    std::unique_ptr<KTX> KTX::createBare(const Header& header, const KeyValues& keyValues) {
+        auto descriptors = header.generateImageDescriptors();
+
+        StoragePointer storagePointer;
+        {
+            auto storageSize = ktx::KTX::evalStorageSize(header, descriptors, keyValues);
+            auto memoryStorage = new storage::MemoryStorage(storageSize);
+            qDebug() << "Memory storage size is: " << storageSize;
+            ktx::KTX::writeWithoutImages(memoryStorage->data(), memoryStorage->size(), header, descriptors, keyValues);
+            storagePointer.reset(memoryStorage);
+        }
+        return create(storagePointer);
+    }
+
     size_t KTX::evalStorageSize(const Header& header, const Images& images, const KeyValues& keyValues) {
         size_t storageSize = sizeof(Header);
 
@@ -59,6 +73,25 @@ namespace ktx {
         return storageSize;
     }
 
+    size_t KTX::evalStorageSize(const Header& header, const ImageDescriptors& imageDescriptors, const KeyValues& keyValues) {
+        size_t storageSize = sizeof(Header);
+
+        if (!keyValues.empty()) {
+            size_t keyValuesSize = KeyValue::serializedKeyValuesByteSize(keyValues);
+            storageSize += keyValuesSize;
+        }
+
+        auto numMips = header.getNumberOfLevels();
+        for (uint32_t l = 0; l < numMips; l++) {
+            if (imageDescriptors.size() > l) {
+                storageSize += sizeof(uint32_t);
+                storageSize += imageDescriptors[l]._imageSize;
+                storageSize += Header::evalPadding(imageDescriptors[l]._imageSize);
+            }
+        }
+        return storageSize;
+    }
+
     size_t KTX::write(Byte* destBytes, size_t destByteSize, const Header& header, const Images& srcImages, const KeyValues& keyValues) {
         // Check again that we have enough destination capacity
         if (!destBytes || (destByteSize < evalStorageSize(header, srcImages, keyValues))) {
@@ -87,6 +120,35 @@ namespace ktx {
         return destByteSize;
     }
 
+    size_t KTX::writeWithoutImages(Byte* destBytes, size_t destByteSize, const Header& header, const ImageDescriptors& descriptors, const KeyValues& keyValues) {
+        // Check again that we have enough destination capacity
+        if (!destBytes || (destByteSize < evalStorageSize(header, descriptors, keyValues))) {
+            return 0;
+        }
+
+        auto currentDestPtr = destBytes;
+        // Header
+        auto destHeader = reinterpret_cast<Header*>(currentDestPtr);
+        memcpy(currentDestPtr, &header, sizeof(Header));
+        currentDestPtr += sizeof(Header);
+
+        // KeyValues
+        if (!keyValues.empty()) {
+            destHeader->bytesOfKeyValueData = (uint32_t) writeKeyValues(currentDestPtr, destByteSize - sizeof(Header), keyValues);
+        } else {
+            // Make sure the header contains the right bytesOfKeyValueData size
+            destHeader->bytesOfKeyValueData = 0;
+        }
+        currentDestPtr += destHeader->bytesOfKeyValueData;
+
+        for (int i = 0; i < descriptors.size(); ++i) {
+            *currentDestPtr = descriptors[i]._imageSize;
+            currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t);
+        }
+
+        return destByteSize;
+    }
+
     uint32_t KeyValue::writeSerializedKeyAndValue(Byte* destBytes, uint32_t destByteSize, const KeyValue& keyval) {
         uint32_t keyvalSize = keyval.serializedByteSize();
         if (keyvalSize > destByteSize) {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 45000b30a8..6b6fc09975 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -335,6 +335,77 @@ void NetworkTexture::downloadFinished(const QByteArray& data) {
 }
 
 void NetworkTexture::loadContent(const QByteArray& content) {
+    if (_sourceIsKTX) {
+        if (_ktxLoadState == LOADING_HEADER) {
+            // TODO Handle case where we already have the source hash texture on disk
+            // TODO Handle case where data isn't as large as the ktx header
+            _ktxLoadState = LOADING_LOWEST_SIX;
+            auto header = reinterpret_cast<const ktx::Header*>(content.data());
+            qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
+            qDebug() << "Type:" << header->glType;
+            qDebug() << "TypeSize:" << header->glTypeSize;
+            qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
+            qDebug() << "numberOfFaces:" << header->numberOfFaces;
+            qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
+            auto kvSize = header->bytesOfKeyValueData;
+            if (kvSize > content.size() - ktx::KTX_HEADER_SIZE) {
+                qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
+                return;
+            }
+
+            auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(content.data()) + ktx::KTX_HEADER_SIZE);
+
+            // Create bare ktx in memory
+            std::string filename = "test";
+            auto memKtx = ktx::KTX::createBare(*header, keyValues);
+
+
+            auto textureCache = DependencyManager::get<TextureCache>();
+
+            // Move ktx to file
+            const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
+            size_t length = memKtx->_storage->size();
+            KTXFilePointer file;
+            auto& ktxCache = textureCache->_ktxCache;
+            if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
+                qCWarning(modelnetworking) << _url << "file cache failed";
+            } else {
+                _file = file;
+            }
+
+            //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
+            gpu::TexturePointer texture;
+            texture.reset(gpu::Texture::unserialize(_file->getFilepath(), memKtx->toDescriptor()));
+            texture->setKtxBacking(file->getFilepath());
+
+            // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
+            // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
+            // be the winner
+            if (textureCache) {
+                texture = textureCache->cacheTextureByHash(filename, texture);
+            }
+
+            setImage(texture, header->getPixelWidth(), header->getPixelHeight());
+
+
+            auto desc = memKtx->toDescriptor();
+            int numMips = desc.images.size();
+            auto numMipsToGet = glm::min(numMips, 6);
+            auto sizeOfTopMips = 0;
+            for (int i = 0; i < numMipsToGet; ++i) {
+                auto& img = desc.images[i];
+                sizeOfTopMips += img._imageSize;
+            }
+            _requestByteRange.fromInclusive = length - sizeOfTopMips;
+            _requestByteRange.toExclusive = length;
+            attemptRequest();
+
+        } else {
+            qDebug() << "Got highest 6 mips";
+        }
+        return;
+    }
+
     QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels));
 }
 
@@ -457,6 +528,7 @@ void ImageReader::read() {
     if (texture && textureCache) {
         auto memKtx = gpu::Texture::serialize(*texture);
 
+        // Move the texture into a memory mapped file
         if (memKtx) {
             const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
             size_t length = memKtx->_storage->size();
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index d0916d61eb..40eb29de35 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -72,6 +72,14 @@ private:
     friend class ImageReader;
 
     image::TextureUsage::Type _type;
+
+    enum KTXLoadState {
+        LOADING_HEADER,
+        LOADING_LOWEST_SIX,
+        DONE_LOADING 
+    };
+
+    KTXLoadState _ktxLoadState { LOADING_HEADER };
     KTXFilePointer _file;
     bool _sourceIsKTX { false };
     int _originalWidth { 0 };
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 28d0485383..5932193830 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -60,7 +60,7 @@ void HTTPResourceRequest::doSend() {
     }
 
     if (_byteRange.isSet()) {
-        auto byteRange = QString("bytes={}-{}").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
+        auto byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
         networkRequest.setRawHeader("Range", byteRange.toLatin1());
     }
     networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
@@ -78,12 +78,61 @@ void HTTPResourceRequest::onRequestFinished() {
     Q_ASSERT(_reply);
 
     cleanupTimer();
-    
+
+    // Content-Range headers have the form: 
+    //
+    //   Content-Range: <unit> <range-start>-<range-end>/<size>
+    //   Content-Range: <unit> <range-start>-<range-end>/*
+    //   Content-Range: <unit> */<size>
+    //
+    auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
+        auto unitRangeParts = contentRangeHeader.split(' ');
+        if (unitRangeParts.size() != 2) {
+            return { false, 0 };
+        }
+
+        auto rangeSizeParts = unitRangeParts[1].split('/');
+        if (rangeSizeParts.size() != 2) {
+            return { false, 0 };
+        }
+
+        auto sizeStr = rangeSizeParts[1];
+        if (sizeStr == "*") {
+            return { true, 0 };
+        } else {
+            bool ok;
+            auto size = sizeStr.toLong(&ok);
+            return { ok, size };
+        }
+    };
+
     switch(_reply->error()) {
         case QNetworkReply::NoError:
             _data = _reply->readAll();
             _loadedFromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
             _result = Success;
+
+            if (_byteRange.isSet()) {
+                auto statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+                if (statusCode == 206) {
+                    _rangeRequestSuccessful = true;
+                    auto contentRangeHeader = _reply->rawHeader("Content-Range");
+                    bool success;
+                    uint64_t size;
+                    std::tie(success, size) = parseContentRangeHeader(contentRangeHeader);
+                    if (success) {
+                        qWarning(networking) << "Total http resource size is: " << size;
+                        _totalSizeOfResource = size;
+                    } else {
+                        qWarning(networking) << "Error parsing content-range header: " << contentRangeHeader;
+                        _totalSizeOfResource = 0;
+                    }
+                } else {
+                    _rangeRequestSuccessful = false;
+                    _totalSizeOfResource = _data.size();
+                }
+            }
+
             break;
 
         case QNetworkReply::TimeoutError:
diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp
index 73096825e0..6895118be5 100644
--- a/libraries/networking/src/NetworkAccessManager.cpp
+++ b/libraries/networking/src/NetworkAccessManager.cpp
@@ -13,12 +13,20 @@
 
 #include "AtpReply.h"
 #include "NetworkAccessManager.h"
+#include <QtNetwork/QNetworkProxy>
 
 QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
 
 QNetworkAccessManager& NetworkAccessManager::getInstance() {
     if (!networkAccessManagers.hasLocalData()) {
-        networkAccessManagers.setLocalData(new QNetworkAccessManager());
+        auto nm = new QNetworkAccessManager();
+        networkAccessManagers.setLocalData(nm);
+
+        QNetworkProxy proxy;
+        proxy.setType(QNetworkProxy::HttpProxy);
+        proxy.setHostName("127.0.0.1");
+        proxy.setPort(8888);
+        nm->setProxy(proxy);
     }
     
     return *networkAccessManagers.localData();
diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h
index 03b46e715d..25db34ab0a 100644
--- a/libraries/networking/src/ResourceRequest.h
+++ b/libraries/networking/src/ResourceRequest.h
@@ -53,6 +53,8 @@ public:
     QString getResultString() const;
     QUrl getUrl() const { return _url; }
     bool loadedFromCache() const { return _loadedFromCache; }
+    bool getRangeRequestSuccessful() const { return _rangeRequestSuccessful; }
+    bool getTotalSizeOfResource() const { return _totalSizeOfResource; }
 
     void setCacheEnabled(bool value) { _cacheEnabled = value; }
     void setByteRange(ByteRange byteRange) { _byteRange = byteRange; }
@@ -74,6 +76,8 @@ protected:
     bool _cacheEnabled { true };
     bool _loadedFromCache { false };
     ByteRange _byteRange;
+    bool _rangeRequestSuccessful { false };
+    uint64_t _totalSizeOfResource { 0 };
 };
 
 #endif
diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp
index 3c46347a49..8999caf1e8 100644
--- a/libraries/shared/src/shared/Storage.cpp
+++ b/libraries/shared/src/shared/Storage.cpp
@@ -67,6 +67,7 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
     return std::make_shared<FileStorage>(filename);
 }
 
+// Represents a memory mapped file
 FileStorage::FileStorage(const QString& filename) : _file(filename) {
     if (_file.open(QFile::ReadOnly)) {
         _mapped = _file.map(0, _file.size());
diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h
index 306984040f..46f45cfdc5 100644
--- a/libraries/shared/src/shared/Storage.h
+++ b/libraries/shared/src/shared/Storage.h
@@ -20,6 +20,7 @@ namespace storage {
     class Storage;
     using StoragePointer = std::shared_ptr<const Storage>;
 
+    // Abstract class to represent memory that stored _somewhere_ (in system memory or in a file, for example)
     class Storage : public std::enable_shared_from_this<Storage> {
     public:
         virtual ~Storage() {}

From 1fec531c68418b6320924cb85c522c4457a9f25f Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 10 Apr 2017 07:12:49 -0700
Subject: [PATCH 03/85] Add basic mip writing to gpu::Texture

---
 interface/src/Application.cpp                            | 2 +-
 libraries/gpu/src/gpu/Texture.h                          | 2 ++
 libraries/ktx/src/ktx/Writer.cpp                         | 9 +++++++--
 .../src/model-networking/TextureCache.cpp                | 3 ++-
 libraries/networking/src/ResourceCache.cpp               | 1 +
 5 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 886487603e..8703c09497 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -626,7 +626,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     proxy.setType(QNetworkProxy::HttpProxy);
     proxy.setHostName("127.0.0.1");
     proxy.setPort(8888);
-    QNetworkProxy::setApplicationProxy(proxy);
+    //QNetworkProxy::setApplicationProxy(proxy);
 
     // make sure the debug draw singleton is initialized on the main thread.
     DebugDraw::getInstance().removeMarker("");
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 756748497d..b01ec8f0bc 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -270,6 +270,7 @@ public:
         virtual void reset() = 0;
         virtual PixelsPointer getMipFace(uint16 level, uint8 face = 0) const = 0;
         virtual Size getMipFaceSize(uint16 level, uint8 face = 0) const = 0;
+        virtual void assignMipData(uint16 level, const char* data, const size_t length) = 0;
         virtual void assignMipData(uint16 level, const storage::StoragePointer& storage) = 0;
         virtual void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) = 0;
         virtual bool isMipAvailable(uint16 level, uint8 face = 0) const = 0;
@@ -296,6 +297,7 @@ public:
         void reset() override;
         PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
         Size getMipFaceSize(uint16 level, uint8 face = 0) const override;
+        void assignMipData(uint16 level, const char* data, const size_t length) override;
         void assignMipData(uint16 level, const storage::StoragePointer& storage) override;
         void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override;
         bool isMipAvailable(uint16 level, uint8 face = 0) const override;
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 396aa09f9a..e2803a2258 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -141,8 +141,13 @@ namespace ktx {
         }
         currentDestPtr += destHeader->bytesOfKeyValueData;
 
-        for (int i = 0; i < descriptors.size(); ++i) {
-            *currentDestPtr = descriptors[i]._imageSize;
+        for (size_t i = 0; i < descriptors.size(); ++i) {
+            auto ptr = reinterpret_cast<uint32_t*>(currentDestPtr);
+            *ptr = descriptors[i]._imageSize;
+            ptr++;
+            for (size_t k = 0; k < descriptors[i]._imageSize/4; k++) {
+                *(ptr + k) = 0xFFFF0000;
+            }
             currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t);
         }
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 6b6fc09975..593a7c163e 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -393,7 +393,8 @@ void NetworkTexture::loadContent(const QByteArray& content) {
             auto numMipsToGet = glm::min(numMips, 6);
             auto sizeOfTopMips = 0;
             for (int i = 0; i < numMipsToGet; ++i) {
-                auto& img = desc.images[i];
+                auto mipLevel = numMips - 1 - i;
+                auto& img = desc.images[mipLevel];
                 sizeOfTopMips += img._imageSize;
             }
             _requestByteRange.fromInclusive = length - sizeOfTopMips;
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 7fdbc7885b..8373a9cf99 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -665,6 +665,7 @@ void Resource::makeRequest() {
     }
 
     PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
+    qDebug() << "Making request to " << _url << " for byte range " << _requestByteRange.fromInclusive << "-" << _requestByteRange.toExclusive;
 
     _request = ResourceManager::createResourceRequest(this, _activeUrl);
 

From ccd9c4697b2bf67f1c6912effcd5f0a40f9144aa Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 11 Apr 2017 22:44:43 -0700
Subject: [PATCH 04/85] Add extended ktx header/high-mip request handling to
 NetworkTexture

---
 interface/src/Application.cpp                 |   2 +-
 libraries/gpu/src/gpu/Texture.h               |  13 +-
 libraries/gpu/src/gpu/Texture_ktx.cpp         |  20 +++
 .../src/model-networking/TextureCache.cpp     | 160 ++++++++++++++++++
 .../src/model-networking/TextureCache.h       |  21 +++
 .../networking/src/HTTPResourceRequest.cpp    |   7 +-
 libraries/networking/src/ResourceCache.cpp    |  60 ++++---
 libraries/networking/src/ResourceCache.h      |  19 ++-
 libraries/networking/src/ResourceRequest.h    |   2 +-
 9 files changed, 257 insertions(+), 47 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 8703c09497..886487603e 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -626,7 +626,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     proxy.setType(QNetworkProxy::HttpProxy);
     proxy.setHostName("127.0.0.1");
     proxy.setPort(8888);
-    //QNetworkProxy::setApplicationProxy(proxy);
+    QNetworkProxy::setApplicationProxy(proxy);
 
     // make sure the debug draw singleton is initialized on the main thread.
     DebugDraw::getInstance().removeMarker("");
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index b01ec8f0bc..e82566b852 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -270,7 +270,6 @@ public:
         virtual void reset() = 0;
         virtual PixelsPointer getMipFace(uint16 level, uint8 face = 0) const = 0;
         virtual Size getMipFaceSize(uint16 level, uint8 face = 0) const = 0;
-        virtual void assignMipData(uint16 level, const char* data, const size_t length) = 0;
         virtual void assignMipData(uint16 level, const storage::StoragePointer& storage) = 0;
         virtual void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) = 0;
         virtual bool isMipAvailable(uint16 level, uint8 face = 0) const = 0;
@@ -297,7 +296,6 @@ public:
         void reset() override;
         PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
         Size getMipFaceSize(uint16 level, uint8 face = 0) const override;
-        void assignMipData(uint16 level, const char* data, const size_t length) override;
         void assignMipData(uint16 level, const storage::StoragePointer& storage) override;
         void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override;
         bool isMipAvailable(uint16 level, uint8 face = 0) const override;
@@ -313,15 +311,12 @@ public:
         PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
         Size getMipFaceSize(uint16 level, uint8 face = 0) const override;
         // By convention, all mip levels and faces MUST be populated when using KTX backing
-        bool isMipAvailable(uint16 level, uint8 face = 0) const override { return true; }
+        bool isMipAvailable(uint16 level, uint8 face = 0) const override;
 
-        void assignMipData(uint16 level, const storage::StoragePointer& storage) override {
-            throw std::runtime_error("Invalid call");
-        }
+        void assignMipData(uint16 level, const storage::StoragePointer& storage) override;
+
+        void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override;
 
-        void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override {
-            throw std::runtime_error("Invalid call");
-        }
         void reset() override { }
 
     protected:
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index db6808a866..487d32f91d 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -72,6 +72,25 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
     return _ktxDescriptor->getMipFaceTexelsSize(level, face);
 }
 
+
+bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
+    auto numLevels = _ktxDescriptor->header.numberOfMipmapLevels;
+    auto minLevel = 7 > numLevels ? 0 : numLevels - 7;
+    auto avail = level >= minLevel;
+    qDebug() << "isMipAvailable: " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels;
+    //return true;
+    return level > _ktxDescriptor->header.numberOfMipmapLevels - 7;
+}
+
+void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
+    throw std::runtime_error("Invalid call");
+}
+
+void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) {
+    throw std::runtime_error("Invalid call");
+}
+
+
 void Texture::setKtxBacking(const std::string& filename) {
     // Check the KTX file for validity before using it as backing storage
     {
@@ -86,6 +105,7 @@ void Texture::setKtxBacking(const std::string& filename) {
     setStorage(newBacking);
 }
 
+
 ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
     ktx::Header header;
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 593a7c163e..f4fe192a4a 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -330,6 +330,166 @@ private:
     int _maxNumPixels;
 };
 
+const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
+void NetworkTexture::makeRequest() {
+    if (!_sourceIsKTX) {
+        Resource::makeRequest();
+        return;
+    } 
+
+    // We special-handle ktx requests to run 2 concurrent requests right off the bat
+    PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
+
+    if (!_ktxHeaderLoaded) {
+        qDebug() << ">>> Making request to " << _url << " for header";
+        _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
+
+        if (!_ktxHeaderRequest) {
+            //qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
+            ResourceCache::requestCompleted(_self);
+            finishedLoading(false);
+            PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
+            return;
+        }
+
+        ByteRange range;
+        range.fromInclusive = 0;
+        range.toExclusive = 1000;
+        _ktxHeaderRequest->setByteRange(range);
+
+        //qCDebug(resourceLog).noquote() << "Starting request for:" << _url.toDisplayString();
+        emit loading();
+
+        connect(_ktxHeaderRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxHeaderRequestProgress);
+        //connect(this, &Resource::onProgress, this, &NetworkTexture::ktxHeaderRequestFinished);
+
+        connect(_ktxHeaderRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxHeaderRequestFinished);
+
+        _bytesReceived = _bytesTotal = _bytes = 0;
+
+        _ktxHeaderRequest->send();
+    }
+
+    startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
+}
+
+// Load mips in the range [low, high] (inclusive)
+void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
+    if (_ktxMipRequest) {
+        return;
+    }
+
+    bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
+    
+    if (!isHighMipRequest && !_ktxHeaderLoaded) {
+        return;
+    }
+
+    _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
+    qDebug() << ">>> Making request to " << _url << " for " << low << " to " << high;
+
+    if (isHighMipRequest) {
+        // This is a special case where we load the high 7 mips
+        ByteRange range;
+        range.fromInclusive = -15000;
+        _ktxMipRequest->setByteRange(range);
+    } else {
+        // TODO: Discover range for other mips
+    }
+
+    connect(_ktxMipRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxMipRequestProgress);
+    connect(_ktxMipRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxMipRequestFinished);
+
+    _ktxMipRequest->send();
+}
+
+
+void NetworkTexture::ktxHeaderRequestFinished() {
+    assert(!_ktxHeaderLoaded);
+
+    if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) {
+        _ktxHeaderLoaded = true;
+        _ktxHeaderData = _ktxHeaderRequest->getData();
+        maybeCreateKTX();
+    } else {
+        handleFailedRequest(_ktxHeaderRequest->getResult());
+    }
+    _ktxHeaderRequest->deleteLater();
+    _ktxHeaderRequest = nullptr;
+}
+
+void NetworkTexture::ktxMipRequestFinished() {
+    bool isHighMipRequest = _ktxMipLevelRangeInFlight.first == NULL_MIP_LEVEL
+                            && _ktxMipLevelRangeInFlight.second == NULL_MIP_LEVEL;
+
+    if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
+        _ktxHighMipData = _ktxMipRequest->getData();
+        maybeCreateKTX();
+    } else {
+        handleFailedRequest(_ktxHeaderRequest->getResult());
+    }
+    _ktxMipRequest->deleteLater();
+    _ktxMipRequest = nullptr;
+}
+
+// This is called when the header or top mips have been loaded
+void NetworkTexture::maybeCreateKTX() {
+    qDebug() << "Maybe create ktx...";
+    if (_ktxHeaderData.size() > 0 && _ktxHighMipData.size() > 0) {
+        // create ktx...
+        auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
+
+        qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
+        qDebug() << "Type:" << header->glType;
+        qDebug() << "TypeSize:" << header->glTypeSize;
+        qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
+        qDebug() << "numberOfFaces:" << header->numberOfFaces;
+        qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
+        auto kvSize = header->bytesOfKeyValueData;
+        if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
+            qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
+            return;
+        }
+
+        auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(_ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
+
+        // Create bare ktx in memory
+        std::string filename = "test";
+        auto memKtx = ktx::KTX::createBare(*header, keyValues);
+
+        auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
+        memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
+
+        auto textureCache = DependencyManager::get<TextureCache>();
+
+        // Move ktx to file
+        const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
+        size_t length = memKtx->_storage->size();
+        KTXFilePointer file;
+        auto& ktxCache = textureCache->_ktxCache;
+        if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
+            qCWarning(modelnetworking) << _url << "file cache failed";
+        } else {
+            _file = file;
+        }
+
+        //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
+        gpu::TexturePointer texture;
+        texture.reset(gpu::Texture::unserialize(_file->getFilepath(), memKtx->toDescriptor()));
+        texture->setKtxBacking(file->getFilepath());
+
+        // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
+        // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
+        // be the winner
+        if (textureCache) {
+            texture = textureCache->cacheTextureByHash(filename, texture);
+        }
+
+        setImage(texture, header->getPixelWidth(), header->getPixelHeight());
+
+    }
+}
+
 void NetworkTexture::downloadFinished(const QByteArray& data) {
     loadContent(data);
 }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 40eb29de35..3998ba3408 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -59,7 +59,16 @@ public:
 signals:
     void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
 
+public slots:
+    void ktxHeaderRequestProgress(uint64_t bytesReceived, uint64_t bytesTotal) { }
+    void ktxHeaderRequestFinished();
+
+    void ktxMipRequestProgress(uint64_t bytesReceived, uint64_t bytesTotal) { }
+    void ktxMipRequestFinished();
+
 protected:
+    void makeRequest() override;
+
     virtual bool isCacheable() const override { return _loaded; }
 
     virtual void downloadFinished(const QByteArray& data) override;
@@ -67,6 +76,9 @@ protected:
     Q_INVOKABLE void loadContent(const QByteArray& content);
     Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
 
+    void startMipRangeRequest(uint16_t low, uint16_t high);
+    void maybeCreateKTX();
+
 private:
     friend class KTXReader;
     friend class ImageReader;
@@ -79,9 +91,18 @@ private:
         DONE_LOADING 
     };
 
+
     KTXLoadState _ktxLoadState { LOADING_HEADER };
     KTXFilePointer _file;
+    static const uint16_t NULL_MIP_LEVEL;
     bool _sourceIsKTX { false };
+    bool _ktxHeaderLoaded { false };
+    std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
+    ResourceRequest* _ktxHeaderRequest { nullptr };
+    ResourceRequest* _ktxMipRequest { nullptr };
+    QByteArray _ktxHeaderData;
+    QByteArray _ktxHighMipData;
+
     int _originalWidth { 0 };
     int _originalHeight { 0 };
     int _width { 0 };
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 5932193830..1bb292a3ef 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -60,7 +60,12 @@ void HTTPResourceRequest::doSend() {
     }
 
     if (_byteRange.isSet()) {
-        auto byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
+        QString byteRange;
+        if (_byteRange.fromInclusive < 0) {
+            auto byteRange = QString("bytes=%1").arg(_byteRange.fromInclusive);
+        } else {
+            auto byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
+        }
         networkRequest.setRawHeader("Range", byteRange.toLatin1());
     }
     networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 8373a9cf99..62ea85bc86 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -725,34 +725,7 @@ void Resource::handleReplyFinished() {
         emit loaded(data);
         downloadFinished(data);
     } else {
-        switch (result) {
-            case ResourceRequest::Result::Timeout: {
-                qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
-                // Fall through to other cases
-            }
-            case ResourceRequest::Result::ServerUnavailable: {
-                // retry with increasing delays
-                const int BASE_DELAY_MS = 1000;
-                if (_attempts++ < MAX_ATTEMPTS) {
-                    auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts);
-
-                    qCDebug(networking).noquote() << "Server unavailable for" << _url << "- may retry in" << waitTime << "ms"
-                        << "if resource is still needed";
-
-                    QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
-                    break;
-                }
-                // fall through to final failure
-            }
-            default: {
-                qCDebug(networking) << "Error loading " << _url;
-                auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
-                                                                  : QNetworkReply::UnknownNetworkError;
-                emit failed(error);
-                finishedLoading(false);
-                break;
-            }
-        }
+        handleFailedRequest(result);
     }
     
     _request->disconnect(this);
@@ -760,6 +733,37 @@ void Resource::handleReplyFinished() {
     _request = nullptr;
 }
 
+void Resource::handleFailedRequest(ResourceRequest::Result result) {
+    switch (result) {
+        case ResourceRequest::Result::Timeout: {
+            qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
+            // Fall through to other cases
+        }
+        case ResourceRequest::Result::ServerUnavailable: {
+            // retry with increasing delays
+            const int BASE_DELAY_MS = 1000;
+            if (_attempts++ < MAX_ATTEMPTS) {
+                auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts);
+
+                qCDebug(networking).noquote() << "Server unavailable for" << _url << "- may retry in" << waitTime << "ms"
+                    << "if resource is still needed";
+
+                QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
+                break;
+            }
+            // fall through to final failure
+        }
+        default: {
+            qCDebug(networking) << "Error loading " << _url;
+            auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
+                                                              : QNetworkReply::UnknownNetworkError;
+            emit failed(error);
+            finishedLoading(false);
+            break;
+        }
+    }
+}
+
 uint qHash(const QPointer<QObject>& value, uint seed) {
     return qHash(value.data(), seed);
 }
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index 32364bf71c..e699c3d198 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -424,6 +424,8 @@ protected slots:
 protected:
     virtual void init();
 
+    virtual void makeRequest();
+
     /// Checks whether the resource is cacheable.
     virtual bool isCacheable() const { return true; }
 
@@ -440,6 +442,8 @@ protected:
 
     Q_INVOKABLE void allReferencesCleared();
 
+    void handleFailedRequest(ResourceRequest::Result result);
+
     QUrl _url;
     QUrl _activeUrl;
     ByteRange _requestByteRange;
@@ -449,8 +453,15 @@ protected:
     QHash<QPointer<QObject>, float> _loadPriorities;
     QWeakPointer<Resource> _self;
     QPointer<ResourceCache> _cache;
+
+    qint64 _bytesReceived{ 0 };
+    qint64 _bytesTotal{ 0 };
+    qint64 _bytes{ 0 };
+
+    int _requestID;
+    ResourceRequest* _request{ nullptr };
     
-private slots:
+public slots:
     void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
     void handleReplyFinished();
 
@@ -460,20 +471,14 @@ private:
     
     void setLRUKey(int lruKey) { _lruKey = lruKey; }
     
-    void makeRequest();
     void retry();
     void reinsert();
 
     bool isInScript() const { return _isInScript; }
     void setInScript(bool isInScript) { _isInScript = isInScript; }
     
-    int _requestID;
-    ResourceRequest* _request{ nullptr };
     int _lruKey{ 0 };
     QTimer* _replyTimer{ nullptr };
-    qint64 _bytesReceived{ 0 };
-    qint64 _bytesTotal{ 0 };
-    qint64 _bytes{ 0 };
     int _attempts{ 0 };
     bool _isInScript{ false };
 };
diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h
index 25db34ab0a..01ca62cf05 100644
--- a/libraries/networking/src/ResourceRequest.h
+++ b/libraries/networking/src/ResourceRequest.h
@@ -21,7 +21,7 @@ struct ByteRange {
     int64_t fromInclusive { 0 };
     int64_t toExclusive { 0 };
 
-    bool isSet() { return fromInclusive < -1 || fromInclusive < toExclusive; }
+    bool isSet() { return fromInclusive < 0 || fromInclusive < toExclusive; }
 };
 
 class ResourceRequest : public QObject {

From b20fcbfcdb7d0b19cff97295dc27d46f6542efec Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 12 Apr 2017 09:40:08 -0700
Subject: [PATCH 05/85] Add a way to write data to a storage object

---
 .../gpu-gl/src/gpu/gl/GLBackendPipeline.cpp     |  4 +++-
 libraries/gpu/src/gpu/Texture.cpp               |  1 +
 libraries/gpu/src/gpu/Texture_ktx.cpp           |  4 +++-
 libraries/ktx/src/ktx/KTX.cpp                   |  1 +
 libraries/ktx/src/ktx/KTX.h                     |  4 +++-
 libraries/ktx/src/ktx/Writer.cpp                |  7 +++++++
 .../src/model-networking/TextureCache.cpp       | 17 +++++++++++++++--
 .../networking/src/HTTPResourceRequest.cpp      |  6 ++++++
 libraries/networking/src/ResourceCache.cpp      |  1 +
 libraries/shared/src/shared/Storage.cpp         |  4 +++-
 libraries/shared/src/shared/Storage.h           |  4 ++++
 11 files changed, 47 insertions(+), 6 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp
index 1d1f92b297..008b658205 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp
@@ -259,7 +259,9 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) {
         glActiveTexture(GL_TEXTURE0 + slot);
         glBindTexture(target, to);
 
-        (void) CHECK_GL_ERROR();
+        if (CHECK_GL_ERROR()) {
+            qDebug() << "slot: " << slot << ", target: " << target << ", to: " << to;
+        }
 
         _resource._textures[slot] = resourceTexture;
 
diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp
index ebde9d4d27..6a1e5ca699 100755
--- a/libraries/gpu/src/gpu/Texture.cpp
+++ b/libraries/gpu/src/gpu/Texture.cpp
@@ -411,6 +411,7 @@ const Element& Texture::getStoredMipFormat() const {
 }
 
 void Texture::assignStoredMip(uint16 level, Size size, const Byte* bytes) {
+    // TODO Skip the extra allocation here
     storage::StoragePointer storage = std::make_shared<storage::MemoryStorage>(size, bytes);
     assignStoredMip(level, storage);
 }
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 487d32f91d..1e6fe1115e 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -45,6 +45,7 @@ std::string GPUKTXPayload::KEY { "hifi.gpu" };
 
 KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
     {
+        // We are doing a lot of work here just to get descriptor data
         ktx::StoragePointer storage { new storage::FileStorage(_filename.c_str()) };
         auto ktxPointer = ktx::KTX::create(storage);
         _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
@@ -74,8 +75,9 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
 
 
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
+    return true;
     auto numLevels = _ktxDescriptor->header.numberOfMipmapLevels;
-    auto minLevel = 7 > numLevels ? 0 : numLevels - 7;
+    auto minLevel = 7 > numLevels ? 0 : numLevels - 10;
     auto avail = level >= minLevel;
     qDebug() << "isMipAvailable: " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels;
     //return true;
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 0580d9a8c3..1f22514226 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -211,6 +211,7 @@ Image ImageDescriptor::toImage(const ktx::StoragePointer& storage) const {
     FaceBytes faces;
     faces.resize(_faceOffsets.size());
     for (size_t face = 0; face < _numFaces; ++face) {
+        // TODO Should we be storing pointers to unowned data?
         faces[face] = storage->data() + _faceOffsets[face];
     }
     // Note, implicit cast of *this to const ImageHeader&
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index fb8927eca0..988c921ce9 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -384,7 +384,7 @@ namespace ktx {
         ImageDescriptors generateImageDescriptors() const;
     };
     static const size_t KTX_HEADER_SIZE = 64;
-    static_assert(sizeof(Header) == KTX_HEADER_SIZE, "KTX Header size is static");
+    static_assert(sizeof(Header) == KTX_HEADER_SIZE, "KTX Header size is static and should not change from the spec");
 
     // Key Values
     struct KeyValue {
@@ -497,6 +497,8 @@ namespace ktx {
         static size_t writeKeyValues(Byte* destBytes, size_t destByteSize, const KeyValues& keyValues);
         static Images writeImages(Byte* destBytes, size_t destByteSize, const Images& images);
 
+        void writeMipData(uint16_t level, const Byte* sourceBytes, size_t source_size);
+
         // Parse a block of memory and create a KTX object from it
         static std::unique_ptr<KTX> create(const StoragePointer& src);
 
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index e2803a2258..0d6cd737b6 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -235,4 +235,11 @@ namespace ktx {
         return destImages;
     }
 
+    void KTX::writeMipData(uint16_t level, const Byte* sourceBytes, size_t sourceSize) {
+        Q_ASSERT(level > 0);
+        Q_ASSERT(level < _images.size());
+        Q_ASSERT(sourceSize == _images[level]._imageSize);
+
+        //memcpy(reinterpret_cast<void*>(_images[level]._faceBytes[0]), sourceBytes, sourceSize);
+    }
 }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index f4fe192a4a..706c62c8a1 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -545,7 +545,6 @@ void NetworkTexture::loadContent(const QByteArray& content) {
                 texture = textureCache->cacheTextureByHash(filename, texture);
             }
 
-            setImage(texture, header->getPixelWidth(), header->getPixelHeight());
 
 
             auto desc = memKtx->toDescriptor();
@@ -559,10 +558,24 @@ void NetworkTexture::loadContent(const QByteArray& content) {
             }
             _requestByteRange.fromInclusive = length - sizeOfTopMips;
             _requestByteRange.toExclusive = length;
-            attemptRequest();
+            QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection);
+
+
+            //texture->setMinMip(desc.images.size() - 1);
+            setImage(texture, header->getPixelWidth(), header->getPixelHeight());
 
         } else {
             qDebug() << "Got highest 6 mips";
+
+            ktx::StoragePointer storage { new storage::FileStorage(QString::fromStdString(_file->getFilepath())) };
+            auto data = storage->mutableData();
+            auto size = storage->getSize();
+            //*data = 'H';
+            memcpy(data + _requestByteRange.fromInclusive, content.data(), content.size());
+            //getGPUTexture()->setMinMip(getGPUTexture()->getMinMip() - 6);
+            //auto ktxPointer = ktx::KTX::create(storage);
+
+            //ktxPointer->writeMipData(level, data, size);
         }
         return;
     }
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 1bb292a3ef..499708b12d 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -22,6 +22,7 @@
 #include "NetworkLogging.h"
 
 HTTPResourceRequest::~HTTPResourceRequest() {
+    qDebug() << "Cleaning up:" << _url << " " << _byteRange.fromInclusive << "-" << _byteRange.toExclusive;
     if (_reply) {
         _reply->disconnect(this);
         _reply->deleteLater();
@@ -76,9 +77,12 @@ void HTTPResourceRequest::doSend() {
     connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress);
 
     setupTimer();
+    qDebug() << "Sent: " << _url;
 }
 
 void HTTPResourceRequest::onRequestFinished() {
+    qDebug() << "On request finished: " << _url;
+
     Q_ASSERT(_state == InProgress);
     Q_ASSERT(_reply);
 
@@ -181,6 +185,7 @@ void HTTPResourceRequest::onRequestFinished() {
 }
 
 void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
+    qDebug() << "Progress: " << _url;
     Q_ASSERT(_state == InProgress);
     
     // We've received data, so reset the timer
@@ -190,6 +195,7 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
 }
 
 void HTTPResourceRequest::onTimeout() {
+    qDebug() << "Timeout: " << _url << ":" << _reply->isFinished();
     Q_ASSERT(_state == InProgress);
     _reply->disconnect(this);
     _reply->abort();
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 62ea85bc86..038ee7fb53 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -698,6 +698,7 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota
 }
 
 void Resource::handleReplyFinished() {
+    qDebug() << "Got response for " << _activeUrl;
     Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished");
 
     PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp
index 8999caf1e8..0f2b696a66 100644
--- a/libraries/shared/src/shared/Storage.cpp
+++ b/libraries/shared/src/shared/Storage.cpp
@@ -69,7 +69,8 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
 
 // Represents a memory mapped file
 FileStorage::FileStorage(const QString& filename) : _file(filename) {
-    if (_file.open(QFile::ReadOnly)) {
+    if (_file.open(QFile::ReadWrite)) {
+        qDebug() << ">>> Opening mmapped file: " << filename;
         _mapped = _file.map(0, _file.size());
         if (_mapped) {
             _valid = true;
@@ -82,6 +83,7 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) {
 }
 
 FileStorage::~FileStorage() {
+    qDebug() << ">>> Closing mmapped file: " << _file.fileName();
     if (_mapped) {
         if (!_file.unmap(_mapped)) {
             throw std::runtime_error("Unable to unmap file");
diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h
index 46f45cfdc5..3983387c15 100644
--- a/libraries/shared/src/shared/Storage.h
+++ b/libraries/shared/src/shared/Storage.h
@@ -25,6 +25,7 @@ namespace storage {
     public:
         virtual ~Storage() {}
         virtual const uint8_t* data() const = 0;
+        virtual uint8_t* mutableData() = 0;
         virtual size_t size() const = 0;
         virtual operator bool() const { return true; }
 
@@ -42,6 +43,7 @@ namespace storage {
         MemoryStorage(size_t size, const uint8_t* data = nullptr);
         const uint8_t* data() const override { return _data.data(); }
         uint8_t* data() { return _data.data(); }
+        uint8_t* mutableData() override { return 0; }
         size_t size() const override { return _data.size(); }
         operator bool() const override { return true; }
     private:
@@ -58,6 +60,7 @@ namespace storage {
         FileStorage& operator=(const FileStorage& other) = delete;
 
         const uint8_t* data() const override { return _mapped; }
+        uint8_t* mutableData() override { return _mapped; }
         size_t size() const override { return _file.size(); }
         operator bool() const override { return _valid; }
     private:
@@ -70,6 +73,7 @@ namespace storage {
     public:
         ViewStorage(const storage::StoragePointer& owner, size_t size, const uint8_t* data);
         const uint8_t* data() const override { return _data; }
+        uint8_t* mutableData() override { return 0; }
         size_t size() const override { return _size; }
         operator bool() const override { return *_owner; }
     private:

From aca7ad27c661a7e11c59bd50cbddee9274ef7c1e Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 12 Apr 2017 17:01:13 -0700
Subject: [PATCH 06/85] Fix ktx dependencies

---
 .../gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp      | 1 +
 libraries/gpu/src/gpu/Texture.h                             | 1 +
 libraries/gpu/src/gpu/Texture_ktx.cpp                       | 4 ++--
 .../model-networking/src/model-networking/TextureCache.cpp  | 6 ++++--
 .../model-networking/src/model-networking/TextureCache.h    | 3 +++
 libraries/procedural/CMakeLists.txt                         | 2 +-
 libraries/script-engine/CMakeLists.txt                      | 2 +-
 plugins/openvr/CMakeLists.txt                               | 2 +-
 8 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index a453d4207d..f87240f9d0 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -171,6 +171,7 @@ void GL45ResourceTexture::populateTransferQueue() {
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
         for (uint8_t face = 0; face < maxFace; ++face) {
+            qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 continue;
             }
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index e82566b852..832be610ba 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -321,6 +321,7 @@ public:
 
     protected:
         std::string _filename;
+        //storage::FileStorage _cacheFile;
         ktx::KTXDescriptorPointer _ktxDescriptor;
         friend class Texture;
     };
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 1e6fe1115e..306d72e58a 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -60,6 +60,7 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
 }
 
 PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
+    qDebug() << "getMipFace: " << QString::fromStdString(_filename) << ": " << level << " " << face;
     storage::StoragePointer result;
     auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
     auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
@@ -75,11 +76,10 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
 
 
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
-    return true;
     auto numLevels = _ktxDescriptor->header.numberOfMipmapLevels;
     auto minLevel = 7 > numLevels ? 0 : numLevels - 10;
     auto avail = level >= minLevel;
-    qDebug() << "isMipAvailable: " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels;
+    qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels;
     //return true;
     return level > _ktxDescriptor->header.numberOfMipmapLevels - 7;
 }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 706c62c8a1..404c44d454 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -30,7 +30,6 @@
 
 #include <gpu/Batch.h>
 
-#include <ktx/KTX.h>
 
 #include <image/Image.h>
 
@@ -473,9 +472,11 @@ void NetworkTexture::maybeCreateKTX() {
             _file = file;
         }
 
+        _ktxDescriptor.reset(new ktx::KTXDescriptor(memKtx->toDescriptor()));
+
         //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
         gpu::TexturePointer texture;
-        texture.reset(gpu::Texture::unserialize(_file->getFilepath(), memKtx->toDescriptor()));
+        texture.reset(gpu::Texture::unserialize(_file->getFilepath(), *_ktxDescriptor));
         texture->setKtxBacking(file->getFilepath());
 
         // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
@@ -496,6 +497,7 @@ void NetworkTexture::downloadFinished(const QByteArray& data) {
 
 void NetworkTexture::loadContent(const QByteArray& content) {
     if (_sourceIsKTX) {
+        assert(false);
         if (_ktxLoadState == LOADING_HEADER) {
             // TODO Handle case where we already have the source hash texture on disk
             // TODO Handle case where data isn't as large as the ktx header
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 3998ba3408..a86c20c145 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -23,6 +23,7 @@
 #include <ResourceCache.h>
 #include <model/TextureMap.h>
 #include <image/Image.h>
+#include <ktx/KTX.h>
 
 #include "KTXCache.h"
 
@@ -102,6 +103,8 @@ private:
     ResourceRequest* _ktxMipRequest { nullptr };
     QByteArray _ktxHeaderData;
     QByteArray _ktxHighMipData;
+    ktx::KTXDescriptorPointer _ktxDescriptor;
+
 
     int _originalWidth { 0 };
     int _originalHeight { 0 };
diff --git a/libraries/procedural/CMakeLists.txt b/libraries/procedural/CMakeLists.txt
index 8c66442c59..3ebd0f3d14 100644
--- a/libraries/procedural/CMakeLists.txt
+++ b/libraries/procedural/CMakeLists.txt
@@ -1,5 +1,5 @@
 set(TARGET_NAME procedural)
 AUTOSCRIBE_SHADER_LIB(gpu model)
 setup_hifi_library()
-link_hifi_libraries(shared gpu gpu-gl networking model model-networking image)
+link_hifi_libraries(shared gpu gpu-gl networking model model-networking ktx image)
 
diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt
index 7b176a6973..39338fd767 100644
--- a/libraries/script-engine/CMakeLists.txt
+++ b/libraries/script-engine/CMakeLists.txt
@@ -16,6 +16,6 @@ if (NOT ANDROID)
 
 endif ()
 
-link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics image)
+link_hifi_libraries(shared networking octree gpu ui procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
 # ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit
 include_hifi_library_headers(gl)
diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt
index 2300a38e56..bc62117e70 100644
--- a/plugins/openvr/CMakeLists.txt
+++ b/plugins/openvr/CMakeLists.txt
@@ -13,7 +13,7 @@ if (WIN32)
     setup_hifi_plugin(OpenGL Script Qml Widgets)
     link_hifi_libraries(shared gl networking controllers ui 
         plugins display-plugins ui-plugins input-plugins script-engine
-        render-utils model gpu gpu-gl render model-networking fbx image)
+        render-utils model gpu gpu-gl render model-networking fbx ktx image)
 
     include_hifi_library_headers(octree)
 

From cf3dc12542452310b48bc48b43917cce1032f8a7 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 13 Apr 2017 15:54:25 -0700
Subject: [PATCH 07/85] Add object labels to GL objects and add ktx min mip kv

---
 .../src/gpu/gl45/GL45BackendTexture.cpp       |  2 ++
 .../gpu/gl45/GL45BackendVariableTexture.cpp   | 35 +++++++++++++++----
 libraries/gpu/src/gpu/Texture.h               |  1 +
 libraries/gpu/src/gpu/Texture_ktx.cpp         | 20 +++++++----
 libraries/ktx/src/ktx/KTX.h                   |  3 ++
 libraries/ktx/src/ktx/Writer.cpp              | 16 +++++++--
 .../src/model-networking/TextureCache.cpp     |  4 +--
 .../src/model-networking/TextureCache.h       |  5 +++
 .../networking/src/HTTPResourceRequest.cpp    |  5 +--
 9 files changed, 72 insertions(+), 19 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index c6f1ef41ae..a539b76b6c 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -109,6 +109,8 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
 GLuint GL45Texture::allocate(const Texture& texture) {
     GLuint result;
     glCreateTextures(getGLTextureType(texture), 1, &result);
+    auto source = texture.source();
+    glObjectLabel(GL_TEXTURE, result, source.length(), source.data());
     return result;
 }
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index f87240f9d0..320d694473 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -41,16 +41,25 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {
 using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
 
 GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {
+    if (texture.source().find_first_of("box.ktx") != std::string::npos) {
+        qDebug() << "In box.ktx ctor";
+    }
     auto mipLevels = texture.getNumMips();
     _allocatedMip = mipLevels;
+
+    _maxAllocatedMip = _populatedMip = mipLevels;
+
     uvec3 mipDimensions;
     for (uint16_t mip = 0; mip < mipLevels; ++mip) {
-        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
+        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
+            && texture.isStoredMipFaceAvailable(mip)) {
             _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
+    //_maxAllocatedMip = _populatedMip = mipLevels;
 
+    //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
     allocateStorage(allocatedMip);
     copyMipsFromTexture();
@@ -87,6 +96,10 @@ void GL45ResourceTexture::copyMipsFromTexture() {
 
 void GL45ResourceTexture::syncSampler() const {
     Parent::syncSampler();
+    qDebug() << "glTextureParameteri " << QString::fromStdString(_source) << _populatedMip << _populatedMip - _allocatedMip;
+    if (_source == "test" && _populatedMip == 0) {
+        qDebug() << "here";
+    }
     glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
 }
 
@@ -97,6 +110,7 @@ void GL45ResourceTexture::promote() {
     auto oldSize = _size;
     // create new texture
     const_cast<GLuint&>(_id) = allocate(_gpuObject);
+    //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     uint16_t oldAllocatedMip = _allocatedMip;
     // allocate storage for new level
     allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
@@ -130,6 +144,7 @@ void GL45ResourceTexture::demote() {
     auto oldId = _id;
     auto oldSize = _size;
     const_cast<GLuint&>(_id) = allocate(_gpuObject);
+    //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     allocateStorage(_allocatedMip + 1);
     _populatedMip = std::max(_populatedMip, _allocatedMip);
     uint16_t mips = _gpuObject.getNumMips();
@@ -166,20 +181,24 @@ void GL45ResourceTexture::populateTransferQueue() {
 
     const uint8_t maxFace = GLTexture::getFaceCount(_target);
     uint16_t sourceMip = _populatedMip;
+    qDebug() << "populateTransferQueue info : " << _populatedMip << " " << _maxAllocatedMip << " " << _allocatedMip;
     do {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
+        bool transferQueued = false;
+        qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
         for (uint8_t face = 0; face < maxFace; ++face) {
-            qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 continue;
             }
 
             // If the mip is less than the max transfer size, then just do it in one transfer
             if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
+                qDebug() << "mip is less than max transfer size";
                 // Can the mip be transferred in one go
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
+                transferQueued = true;
                 continue;
             }
 
@@ -191,18 +210,22 @@ void GL45ResourceTexture::populateTransferQueue() {
             Q_ASSERT(0 == (mipSize % lines));
             uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine);
             uint32_t lineOffset = 0;
+            qDebug() << "queing up single line transfers " << linesPerTransfer << " " << lineOffset;
             while (lineOffset < lines) {
                 uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
                 lineOffset += linesToCopy;
+                transferQueued = true;
             }
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
-        _pendingTransfers.emplace(new TransferJob(*this, [=] {
-            _populatedMip = sourceMip;
-            syncSampler();
-        }));
+        if (transferQueued) {
+            _pendingTransfers.emplace(new TransferJob(*this, [=] {
+                _populatedMip = sourceMip;
+                syncSampler();
+            }));
+        }
     } while (sourceMip != _allocatedMip);
 }
 
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 832be610ba..83f1f154d3 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -321,6 +321,7 @@ public:
 
     protected:
         std::string _filename;
+        uint8_t _minMipLevelAvailable;
         //storage::FileStorage _cacheFile;
         ktx::KTXDescriptorPointer _ktxDescriptor;
         friend class Texture;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 306d72e58a..65dc48f634 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -30,7 +30,7 @@ struct GPUKTXPayload {
     }
 
     static bool findInKeyValues(const ktx::KeyValues& keyValues, GPUKTXPayload& payload) {
-        auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX); 
+        auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX);
         if (found != keyValues.end()) {
             if ((*found)._value.size() == sizeof(GPUKTXPayload)) {
                 memcpy(&payload, (*found)._value.data(), sizeof(GPUKTXPayload));
@@ -41,14 +41,23 @@ struct GPUKTXPayload {
     }
 };
 
-std::string GPUKTXPayload::KEY { "hifi.gpu" };
+std::string GPUKTXPayload::KEY{ "hifi.gpu" };
 
 KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
     {
         // We are doing a lot of work here just to get descriptor data
-        ktx::StoragePointer storage { new storage::FileStorage(_filename.c_str()) };
+        ktx::StoragePointer storage{ new storage::FileStorage(_filename.c_str()) };
         auto ktxPointer = ktx::KTX::create(storage);
         _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
+        auto& keyValues = _ktxDescriptor->keyValues;
+        auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
+            return val._key.compare(ktx::HIFI_MIN_POPULATED_MIP_KEY) == 0;
+        });
+        if (found != keyValues.end()) {
+            _minMipLevelAvailable = found->_value[0];
+        } else {
+            _minMipLevelAvailable = 4;// _ktxDescriptor->header.numberOfMipmapLevels;
+        }
     }
 
     // now that we know the ktx, let's get the header info to configure this Texture::Storage:
@@ -76,12 +85,11 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
 
 
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
-    auto numLevels = _ktxDescriptor->header.numberOfMipmapLevels;
-    auto minLevel = 7 > numLevels ? 0 : numLevels - 10;
+    auto minLevel = _minMipLevelAvailable;
     auto avail = level >= minLevel;
     qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels;
     //return true;
-    return level > _ktxDescriptor->header.numberOfMipmapLevels - 7;
+    return avail;
 }
 
 void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 988c921ce9..8b5a62ebb3 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -70,6 +70,9 @@ end
 
 
 namespace ktx {
+    const std::string HIFI_MIN_POPULATED_MIP_KEY = "hifiMinMip";
+
+
     const uint32_t PACKING_SIZE { sizeof(uint32_t) };
     using Byte = uint8_t;
 
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 0d6cd737b6..20d4d598a9 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -43,12 +43,19 @@ namespace ktx {
     std::unique_ptr<KTX> KTX::createBare(const Header& header, const KeyValues& keyValues) {
         auto descriptors = header.generateImageDescriptors();
 
+        auto newHeader = header;
+
+        Byte minMip = header.numberOfMipmapLevels - 6;
+        auto newKeyValues = keyValues;
+        //newKeyValues.emplace_back(KeyValue(HIFI_MIN_POPULATED_MIP_KEY, sizeof(Byte), &minMip));
+        //newHeader.bytesOfKeyValueData = KeyValue::serializedKeyValuesByteSize(newKeyValues);
+
         StoragePointer storagePointer;
         {
-            auto storageSize = ktx::KTX::evalStorageSize(header, descriptors, keyValues);
+            auto storageSize = ktx::KTX::evalStorageSize(header, descriptors, newKeyValues);
             auto memoryStorage = new storage::MemoryStorage(storageSize);
             qDebug() << "Memory storage size is: " << storageSize;
-            ktx::KTX::writeWithoutImages(memoryStorage->data(), memoryStorage->size(), header, descriptors, keyValues);
+            ktx::KTX::writeWithoutImages(memoryStorage->data(), memoryStorage->size(), header, descriptors, newKeyValues);
             storagePointer.reset(memoryStorage);
         }
         return create(storagePointer);
@@ -132,6 +139,7 @@ namespace ktx {
         memcpy(currentDestPtr, &header, sizeof(Header));
         currentDestPtr += sizeof(Header);
 
+
         // KeyValues
         if (!keyValues.empty()) {
             destHeader->bytesOfKeyValueData = (uint32_t) writeKeyValues(currentDestPtr, destByteSize - sizeof(Header), keyValues);
@@ -145,9 +153,11 @@ namespace ktx {
             auto ptr = reinterpret_cast<uint32_t*>(currentDestPtr);
             *ptr = descriptors[i]._imageSize;
             ptr++;
+#ifdef DEBUG
             for (size_t k = 0; k < descriptors[i]._imageSize/4; k++) {
-                *(ptr + k) = 0xFFFF0000;
+                *(ptr + k) = 0xFFFF00FF;
             }
+#endif
             currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t);
         }
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 404c44d454..26691db083 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -425,7 +425,7 @@ void NetworkTexture::ktxMipRequestFinished() {
         _ktxHighMipData = _ktxMipRequest->getData();
         maybeCreateKTX();
     } else {
-        handleFailedRequest(_ktxHeaderRequest->getResult());
+        handleFailedRequest(_ktxMipRequest->getResult());
     }
     _ktxMipRequest->deleteLater();
     _ktxMipRequest = nullptr;
@@ -433,7 +433,6 @@ void NetworkTexture::ktxMipRequestFinished() {
 
 // This is called when the header or top mips have been loaded
 void NetworkTexture::maybeCreateKTX() {
-    qDebug() << "Maybe create ktx...";
     if (_ktxHeaderData.size() > 0 && _ktxHighMipData.size() > 0) {
         // create ktx...
         auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
@@ -478,6 +477,7 @@ void NetworkTexture::maybeCreateKTX() {
         gpu::TexturePointer texture;
         texture.reset(gpu::Texture::unserialize(_file->getFilepath(), *_ktxDescriptor));
         texture->setKtxBacking(file->getFilepath());
+        texture->setSource(filename);
 
         // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
         // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index a86c20c145..a029b5b147 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -103,6 +103,11 @@ private:
     ResourceRequest* _ktxMipRequest { nullptr };
     QByteArray _ktxHeaderData;
     QByteArray _ktxHighMipData;
+
+    // This is a copy of the original KTX descriptor from the source url.
+    // We need this because the KTX that will be cached will likely include extra data
+    // in its key/value data, and so will not match up with the original, causing
+    // mip offsets to change.
     ktx::KTXDescriptorPointer _ktxDescriptor;
 
 
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 499708b12d..f07ab4450b 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -63,10 +63,11 @@ void HTTPResourceRequest::doSend() {
     if (_byteRange.isSet()) {
         QString byteRange;
         if (_byteRange.fromInclusive < 0) {
-            auto byteRange = QString("bytes=%1").arg(_byteRange.fromInclusive);
+            byteRange = QString("bytes=%1").arg(_byteRange.fromInclusive);
         } else {
-            auto byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
+            byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
         }
+        qDebug() << "Setting http range to " << byteRange;
         networkRequest.setRawHeader("Range", byteRange.toLatin1());
     }
     networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);

From e9bb895bff8c122fa5f73dee722e92dfa835ff68 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 13 Apr 2017 17:03:59 -0700
Subject: [PATCH 08/85] Implement KTXStorage::assignMipData and add writing of
 mips to TextureCache

---
 libraries/gpu/src/gpu/Texture.cpp             |  1 +
 libraries/gpu/src/gpu/Texture_ktx.cpp         | 30 +++++++++++++++++--
 libraries/ktx/src/ktx/Writer.cpp              |  6 ++--
 .../src/model-networking/TextureCache.cpp     | 18 +++++++++++
 4 files changed, 50 insertions(+), 5 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp
index 6a1e5ca699..205cf3a65a 100755
--- a/libraries/gpu/src/gpu/Texture.cpp
+++ b/libraries/gpu/src/gpu/Texture.cpp
@@ -118,6 +118,7 @@ Texture::Size Texture::getAllowedGPUMemoryUsage() {
     return _allowedCPUMemoryUsage;
 }
 
+
 void Texture::setAllowedGPUMemoryUsage(Size size) {
     qCDebug(gpulogging) << "New MAX texture memory " << BYTES_TO_MB(size) << " MB";
     _allowedCPUMemoryUsage = size;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 65dc48f634..ede1f21c1f 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -56,7 +56,8 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
         if (found != keyValues.end()) {
             _minMipLevelAvailable = found->_value[0];
         } else {
-            _minMipLevelAvailable = 4;// _ktxDescriptor->header.numberOfMipmapLevels;
+            // Assume all mip levels are available
+            _minMipLevelAvailable = 0;
         }
     }
 
@@ -93,7 +94,32 @@ bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
 }
 
 void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
-    throw std::runtime_error("Invalid call");
+    if (level != _minMipLevelAvailable - 1) {
+        qWarning() << "Invalid level to be stored";
+        return;
+    }
+
+    if (level >= _ktxDescriptor->images.size()) {
+        throw std::runtime_error("Invalid level");
+    }
+
+    if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
+        throw std::runtime_error("Invalid image size for level");
+    }
+
+
+    ktx::StoragePointer file { new storage::FileStorage(_filename.c_str()) };
+    auto data = file->mutableData();
+    data += file->size();
+
+    // TODO Cache this data inside Image or ImageDescriptor?
+    for (auto i = _ktxDescriptor->header.numberOfMipmapLevels - 1; i >= level; --i) {
+        data -= _ktxDescriptor->images[i]._imageSize;
+        data -= 4;
+    }
+    data += 4;
+    memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
+    _minMipLevelAvailable = level;
 }
 
 void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) {
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 20d4d598a9..d149d559f9 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -45,10 +45,10 @@ namespace ktx {
 
         auto newHeader = header;
 
-        Byte minMip = header.numberOfMipmapLevels - 6;
+        Byte minMip = header.numberOfMipmapLevels;
         auto newKeyValues = keyValues;
-        //newKeyValues.emplace_back(KeyValue(HIFI_MIN_POPULATED_MIP_KEY, sizeof(Byte), &minMip));
-        //newHeader.bytesOfKeyValueData = KeyValue::serializedKeyValuesByteSize(newKeyValues);
+        newKeyValues.emplace_back(KeyValue(HIFI_MIN_POPULATED_MIP_KEY, sizeof(Byte), &minMip));
+        newHeader.bytesOfKeyValueData = KeyValue::serializedKeyValuesByteSize(newKeyValues);
 
         StoragePointer storagePointer;
         {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 26691db083..93a25330b1 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -387,6 +387,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
     qDebug() << ">>> Making request to " << _url << " for " << low << " to " << high;
 
+    _ktxMipLevelRangeInFlight = { low, high };
     if (isHighMipRequest) {
         // This is a special case where we load the high 7 mips
         ByteRange range;
@@ -479,6 +480,23 @@ void NetworkTexture::maybeCreateKTX() {
         texture->setKtxBacking(file->getFilepath());
         texture->setSource(filename);
 
+        auto& images = _ktxDescriptor->images;
+        size_t imageSizeRemaining = _ktxHighMipData.size();
+        uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data());
+        ktxData += _ktxHighMipData.size();
+        // TODO Move image offset calculation to ktx ImageDescriptor
+        for (uint16_t i = images.size() - 1; i >= 0; --i) {
+            auto& image = images[i];
+            if (image._imageSize > imageSizeRemaining) {
+                break;
+            }
+            qDebug() << "Transferring " << i;
+            ktxData -= image._imageSize;
+            texture->assignStoredMip(i, image._imageSize, ktxData);
+            ktxData -= 4;
+            imageSizeRemaining - image._imageSize - 4;
+        }
+
         // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
         // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
         // be the winner

From 39c3fee838acde023a40a5dc80ab66816d57f42a Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 13 Apr 2017 23:05:23 -0700
Subject: [PATCH 09/85] Add caching of imageOffset to ktx ImageDescriptor

---
 libraries/gpu/src/gpu/Texture_ktx.cpp         |   9 +-
 libraries/ktx/src/ktx/KTX.cpp                 |   8 +-
 libraries/ktx/src/ktx/KTX.h                   |  15 +-
 libraries/ktx/src/ktx/Reader.cpp              |   5 +-
 libraries/ktx/src/ktx/Writer.cpp              |   5 +-
 .../src/model-networking/TextureCache.cpp     | 135 ++++++------------
 .../src/model-networking/TextureCache.h       |   3 +-
 .../networking/src/HTTPResourceRequest.cpp    |   3 +-
 8 files changed, 79 insertions(+), 104 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index ede1f21c1f..d9a0348e54 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -108,7 +108,8 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     }
 
 
-    ktx::StoragePointer file { new storage::FileStorage(_filename.c_str()) };
+    auto fileStorage = new storage::FileStorage(_filename.c_str());
+    ktx::StoragePointer file { fileStorage };
     auto data = file->mutableData();
     data += file->size();
 
@@ -197,19 +198,21 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
     header.numberOfMipmapLevels = texture.getNumMips();
 
     ktx::Images images;
+    uint32_t imageOffset = 0;
     for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) {
         auto mip = texture.accessStoredMipFace(level);
         if (mip) {
             if (numFaces == 1) {
-                images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, mip->readData()));
+                images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, mip->readData()));
             } else {
                 ktx::Image::FaceBytes cubeFaces(Texture::CUBE_FACE_COUNT);
                 cubeFaces[0] = mip->readData();
                 for (uint32_t face = 1; face < Texture::CUBE_FACE_COUNT; face++) {
                     cubeFaces[face] = texture.accessStoredMipFace(level, face)->readData();
                 }
-                images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, cubeFaces));
+                images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces));
             }
+            imageOffset += mip->getSize() + 4;
         }
     }
 
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 1f22514226..73751bdef2 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -73,12 +73,18 @@ size_t Header::evalImageSize(uint32_t level) const {
 ImageDescriptors Header::generateImageDescriptors() const {
     ImageDescriptors descriptors;
 
+    uint32_t imageOffset = 0;
     for (auto level = 0; level < numberOfMipmapLevels; ++level) {
+        auto imageSize = static_cast<uint32_t>(evalImageSize(level));
         ImageHeader header {
             numberOfFaces == NUM_CUBEMAPFACES,
-            static_cast<uint32_t>(evalImageSize(level)),
+            imageOffset,
+            imageSize,
             0
         };
+
+        imageOffset += imageSize + 4;
+
         ImageHeader::FaceOffsets offsets;
         for (auto i = 0; i < numberOfFaces; ++i) {
             offsets.push_back(0);
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 8b5a62ebb3..7056f22ba8 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -414,12 +414,17 @@ namespace ktx {
     struct ImageHeader {
         using FaceOffsets = std::vector<size_t>;
         using FaceBytes = std::vector<const Byte*>;
+        // This is the byte offset from the _start_ of the image region. For example, level 0
+        // will have a byte offset of 0.
+        const uint32_t _imageOffset;
+
         const uint32_t _numFaces;
         const uint32_t _imageSize;
         const uint32_t _faceSize;
         const uint32_t _padding;
-        ImageHeader(bool cube, uint32_t imageSize, uint32_t padding) : 
+        ImageHeader(bool cube, uint32_t imageOffset, uint32_t imageSize, uint32_t padding) :
             _numFaces(cube ? NUM_CUBEMAPFACES : 1),
+            _imageOffset(imageOffset),
             _imageSize(imageSize * _numFaces),
             _faceSize(imageSize),
             _padding(padding) {
@@ -439,11 +444,11 @@ namespace ktx {
     struct Image : public ImageHeader {
         FaceBytes _faceBytes;
         Image(const ImageHeader& header, const FaceBytes& faces) : ImageHeader(header), _faceBytes(faces) {}
-        Image(uint32_t imageSize, uint32_t padding, const Byte* bytes) :
-            ImageHeader(false, imageSize, padding),
+        Image(uint32_t imageOffset, uint32_t imageSize, uint32_t padding, const Byte* bytes) :
+            ImageHeader(false, imageOffset, imageSize, padding),
             _faceBytes(1, bytes) {}
-        Image(uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) :
-            ImageHeader(true, pageSize, padding)
+        Image(uint32_t imageOffset, uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) :
+            ImageHeader(true, imageOffset, pageSize, padding)
             {
                 if (cubeFaceBytes.size() == NUM_CUBEMAPFACES) {
                     _faceBytes = cubeFaceBytes;
diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp
index bf72faeba5..b22f262e85 100644
--- a/libraries/ktx/src/ktx/Reader.cpp
+++ b/libraries/ktx/src/ktx/Reader.cpp
@@ -144,6 +144,7 @@ namespace ktx {
         while ((currentPtr - srcBytes) + sizeof(uint32_t) <= (srcSize)) {
 
             // Grab the imageSize coming up
+            uint32_t imageOffset = currentPtr - srcBytes;
             size_t imageSize = *reinterpret_cast<const uint32_t*>(currentPtr);
             currentPtr += sizeof(uint32_t);
 
@@ -158,10 +159,10 @@ namespace ktx {
                         faces[face] = currentPtr;
                         currentPtr += faceSize;
                     }
-                    images.emplace_back(Image((uint32_t) faceSize, padding, faces));
+                    images.emplace_back(Image(imageOffset, (uint32_t) faceSize, padding, faces));
                     currentPtr += padding;
                 } else {
-                    images.emplace_back(Image((uint32_t) imageSize, padding, currentPtr));
+                    images.emplace_back(Image(imageOffset, (uint32_t) imageSize, padding, currentPtr));
                     currentPtr += imageSize + padding;
                 }
             } else {
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index d149d559f9..4596bf00c0 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -211,6 +211,7 @@ namespace ktx {
 
         for (uint32_t l = 0; l < srcImages.size(); l++) {
             if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) {
+                uint32_t imageOffset = currentPtr - destBytes;
                 size_t imageSize = srcImages[l]._imageSize;
                 *(reinterpret_cast<uint32_t*> (currentPtr)) = (uint32_t) imageSize;
                 currentPtr += sizeof(uint32_t);
@@ -223,7 +224,7 @@ namespace ktx {
                     // Single face vs cubes
                     if (srcImages[l]._numFaces == 1) {
                         memcpy(currentPtr, srcImages[l]._faceBytes[0], imageSize);
-                        destImages.emplace_back(Image((uint32_t) imageSize, padding, currentPtr));
+                        destImages.emplace_back(Image(imageOffset, (uint32_t) imageSize, padding, currentPtr));
                         currentPtr += imageSize;
                     } else {
                         Image::FaceBytes faceBytes(NUM_CUBEMAPFACES);
@@ -233,7 +234,7 @@ namespace ktx {
                              faceBytes[face] = currentPtr;
                              currentPtr += faceSize;
                         }
-                        destImages.emplace_back(Image(faceSize, padding, faceBytes));
+                        destImages.emplace_back(Image(imageOffset, faceSize, padding, faceBytes));
                     }
 
                     currentPtr += padding;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 93a25330b1..592413e2bc 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -30,7 +30,6 @@
 
 #include <gpu/Batch.h>
 
-
 #include <image/Image.h>
 
 #include <NumericalConstants.h>
@@ -395,6 +394,12 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
         _ktxMipRequest->setByteRange(range);
     } else {
         // TODO: Discover range for other mips
+        ByteRange range;
+        range.fromInclusive = ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData
+                              + _ktxDescriptor->images[low]._imageOffset + 4;
+        range.toExclusive = ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData
+                              + _ktxDescriptor->images[high + 1]._imageOffset;
+        _ktxMipRequest->setByteRange(range);
     }
 
     connect(_ktxMipRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxMipRequestProgress);
@@ -423,8 +428,17 @@ void NetworkTexture::ktxMipRequestFinished() {
                             && _ktxMipLevelRangeInFlight.second == NULL_MIP_LEVEL;
 
     if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
-        _ktxHighMipData = _ktxMipRequest->getData();
-        maybeCreateKTX();
+        if (_initialKtxLoaded) {
+            assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
+            
+            _textureSource->getGPUTexture()->assignStoredMip(_ktxMipLevelRangeInFlight.first,
+                _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
+            //texture->assignStoredMip(level, image._imageSize, ktxData);
+        } else {
+            _ktxHighMipData = _ktxMipRequest->getData();
+            maybeCreateKTX();
+        }
+
     } else {
         handleFailedRequest(_ktxMipRequest->getResult());
     }
@@ -457,7 +471,7 @@ void NetworkTexture::maybeCreateKTX() {
         auto memKtx = ktx::KTX::createBare(*header, keyValues);
 
         auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
-        memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
+        ///memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
 
         auto textureCache = DependencyManager::get<TextureCache>();
 
@@ -485,18 +499,21 @@ void NetworkTexture::maybeCreateKTX() {
         uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data());
         ktxData += _ktxHighMipData.size();
         // TODO Move image offset calculation to ktx ImageDescriptor
-        for (uint16_t i = images.size() - 1; i >= 0; --i) {
-            auto& image = images[i];
+        uint16_t level;
+        for (level = images.size() - 1; level >= 0; --level) {
+            auto& image = images[level];
             if (image._imageSize > imageSizeRemaining) {
                 break;
             }
-            qDebug() << "Transferring " << i;
+            qDebug() << "Transferring " << level;
             ktxData -= image._imageSize;
-            texture->assignStoredMip(i, image._imageSize, ktxData);
+            texture->assignStoredMip(level, image._imageSize, ktxData);
             ktxData -= 4;
             imageSizeRemaining - image._imageSize - 4;
         }
 
+        _initialKtxLoaded = true;
+
         // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
         // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
         // be the winner
@@ -506,6 +523,27 @@ void NetworkTexture::maybeCreateKTX() {
 
         setImage(texture, header->getPixelWidth(), header->getPixelHeight());
 
+
+        // Force load the next two levels
+        {
+            QTimer* timer = new QTimer();
+            connect(timer, &QTimer::timeout, this, [=]() {
+                startMipRangeRequest(level, level);
+            });
+            timer->setSingleShot(true);
+            timer->setInterval(4000);
+            timer->start();
+        }
+
+        {
+            QTimer* timer = new QTimer();
+            connect(timer, &QTimer::timeout, this, [=]() {
+                startMipRangeRequest(level - 1, level - 1);
+            });
+            timer->setSingleShot(true);
+            timer->setInterval(6000);
+            timer->start();
+        }
     }
 }
 
@@ -516,87 +554,6 @@ void NetworkTexture::downloadFinished(const QByteArray& data) {
 void NetworkTexture::loadContent(const QByteArray& content) {
     if (_sourceIsKTX) {
         assert(false);
-        if (_ktxLoadState == LOADING_HEADER) {
-            // TODO Handle case where we already have the source hash texture on disk
-            // TODO Handle case where data isn't as large as the ktx header
-            _ktxLoadState = LOADING_LOWEST_SIX;
-            auto header = reinterpret_cast<const ktx::Header*>(content.data());
-            qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
-            qDebug() << "Type:" << header->glType;
-            qDebug() << "TypeSize:" << header->glTypeSize;
-            qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
-            qDebug() << "numberOfFaces:" << header->numberOfFaces;
-            qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
-            auto kvSize = header->bytesOfKeyValueData;
-            if (kvSize > content.size() - ktx::KTX_HEADER_SIZE) {
-                qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
-                return;
-            }
-
-            auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(content.data()) + ktx::KTX_HEADER_SIZE);
-
-            // Create bare ktx in memory
-            std::string filename = "test";
-            auto memKtx = ktx::KTX::createBare(*header, keyValues);
-
-
-            auto textureCache = DependencyManager::get<TextureCache>();
-
-            // Move ktx to file
-            const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
-            size_t length = memKtx->_storage->size();
-            KTXFilePointer file;
-            auto& ktxCache = textureCache->_ktxCache;
-            if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
-                qCWarning(modelnetworking) << _url << "file cache failed";
-            } else {
-                _file = file;
-            }
-
-            //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
-            gpu::TexturePointer texture;
-            texture.reset(gpu::Texture::unserialize(_file->getFilepath(), memKtx->toDescriptor()));
-            texture->setKtxBacking(file->getFilepath());
-
-            // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
-            // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
-            // be the winner
-            if (textureCache) {
-                texture = textureCache->cacheTextureByHash(filename, texture);
-            }
-
-
-
-            auto desc = memKtx->toDescriptor();
-            int numMips = desc.images.size();
-            auto numMipsToGet = glm::min(numMips, 6);
-            auto sizeOfTopMips = 0;
-            for (int i = 0; i < numMipsToGet; ++i) {
-                auto mipLevel = numMips - 1 - i;
-                auto& img = desc.images[mipLevel];
-                sizeOfTopMips += img._imageSize;
-            }
-            _requestByteRange.fromInclusive = length - sizeOfTopMips;
-            _requestByteRange.toExclusive = length;
-            QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection);
-
-
-            //texture->setMinMip(desc.images.size() - 1);
-            setImage(texture, header->getPixelWidth(), header->getPixelHeight());
-
-        } else {
-            qDebug() << "Got highest 6 mips";
-
-            ktx::StoragePointer storage { new storage::FileStorage(QString::fromStdString(_file->getFilepath())) };
-            auto data = storage->mutableData();
-            auto size = storage->getSize();
-            //*data = 'H';
-            memcpy(data + _requestByteRange.fromInclusive, content.data(), content.size());
-            //getGPUTexture()->setMinMip(getGPUTexture()->getMinMip() - 6);
-            //auto ktxPointer = ktx::KTX::create(storage);
-
-            //ktxPointer->writeMipData(level, data, size);
-        }
         return;
     }
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index a029b5b147..c032a9c29d 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -93,7 +93,8 @@ private:
     };
 
 
-    KTXLoadState _ktxLoadState { LOADING_HEADER };
+    bool _initialKtxLoaded { false };
+    //KTXLoadState _ktxLoadState;
     KTXFilePointer _file;
     static const uint16_t NULL_MIP_LEVEL;
     bool _sourceIsKTX { false };
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index f07ab4450b..8958eeaf3b 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -65,7 +65,8 @@ void HTTPResourceRequest::doSend() {
         if (_byteRange.fromInclusive < 0) {
             byteRange = QString("bytes=%1").arg(_byteRange.fromInclusive);
         } else {
-            byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive);
+            // HTTP byte ranges are inclusive on the `to` end: [from, to]
+            byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive - 1);
         }
         qDebug() << "Setting http range to " << byteRange;
         networkRequest.setRawHeader("Range", byteRange.toLatin1());

From ab7099b3eb28b95773ce650ab075c61a8e3cecc2 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Sun, 16 Apr 2017 23:16:23 -0700
Subject: [PATCH 10/85] Add loading of lower mips to NetworkTexture

---
 .../gpu/gl45/GL45BackendVariableTexture.cpp   |  1 +
 libraries/gpu/src/gpu/Texture.cpp             | 26 +++++++++++++
 libraries/gpu/src/gpu/Texture.h               | 26 ++++++++++---
 libraries/gpu/src/gpu/Texture_ktx.cpp         | 36 ++++++++++++++----
 libraries/ktx/src/ktx/KTX.h                   |  4 +-
 .../src/model-networking/TextureCache.cpp     | 37 ++++++++++++++++++-
 .../src/model-networking/TextureCache.h       | 11 ++++--
 7 files changed, 121 insertions(+), 20 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 320d694473..c37ee46bfd 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -190,6 +190,7 @@ void GL45ResourceTexture::populateTransferQueue() {
         qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
+                const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
                 continue;
             }
 
diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp
index 205cf3a65a..1dbe9db2b4 100755
--- a/libraries/gpu/src/gpu/Texture.cpp
+++ b/libraries/gpu/src/gpu/Texture.cpp
@@ -476,6 +476,32 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
     }
 }
 
+void Texture::requestInterestInMip(uint16 level) {
+    if (!_storage->isMipAvailable(level, 0)) {
+        std::lock_guard<std::mutex> lock(_mipInterestListenersMutex);
+        for (auto& callback : _mipInterestListeners) {
+            callback->handleMipInterestCallback(level);
+        }
+    }
+}
+
+bool Texture::isStoredMipFaceAvailable(uint16 level, uint8 face) const {
+    return _storage->isMipAvailable(level, face);
+}
+
+void Texture::registerMipInterestListener(MipInterestListener* listener) {
+    std::lock_guard<std::mutex> lock(_mipInterestListenersMutex);
+    _mipInterestListeners.push_back(listener);
+}
+
+void Texture::unregisterMipInterestListener(MipInterestListener* listener) {
+    std::lock_guard<std::mutex> lock(_mipInterestListenersMutex);
+    auto it = find(_mipInterestListeners.begin(), _mipInterestListeners.end(), listener);
+    if (it != _mipInterestListeners.end()) {
+        _mipInterestListeners.erase(it);
+    }
+}
+
 void Texture::setAutoGenerateMips(bool enable) {
     bool changed = false;
     if (!_autoGenerateMips) {
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 83f1f154d3..8c9d62e3b8 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -310,19 +310,22 @@ public:
         KtxStorage(const std::string& filename);
         PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
         Size getMipFaceSize(uint16 level, uint8 face = 0) const override;
-        // By convention, all mip levels and faces MUST be populated when using KTX backing
         bool isMipAvailable(uint16 level, uint8 face = 0) const override;
-
         void assignMipData(uint16 level, const storage::StoragePointer& storage) override;
-
         void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override;
 
         void reset() override { }
 
     protected:
+        std::shared_ptr<storage::FileStorage> maybeOpenFile();
+
+        std::mutex _cacheFileCreateMutex;
+        std::mutex _cacheFileWriteMutex;
+        std::weak_ptr<storage::FileStorage> _cacheFile;
+
         std::string _filename;
-        uint8_t _minMipLevelAvailable;
-        //storage::FileStorage _cacheFile;
+        std::atomic<uint8_t> _minMipLevelAvailable;
+
         ktx::KTXDescriptorPointer _ktxDescriptor;
         friend class Texture;
     };
@@ -470,7 +473,7 @@ public:
 
     // Access the stored mips and faces
     const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); }
-    bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const { return _storage->isMipAvailable(level, face); }
+    bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const;// { return _storage->isMipAvailable(level, face); }
     Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); }
     Size getStoredMipSize(uint16 level) const;
     Size getStoredSize() const;
@@ -478,6 +481,17 @@ public:
     void setStorage(std::unique_ptr<Storage>& newStorage);
     void setKtxBacking(const std::string& filename);
 
+    class MipInterestListener {
+        public:
+            virtual void handleMipInterestCallback(uint16 level) = 0;
+    };
+    void registerMipInterestListener(MipInterestListener* listener);
+    void unregisterMipInterestListener(MipInterestListener* listener);
+    std::vector<MipInterestListener*> _mipInterestListeners;
+    std::mutex _mipInterestListenersMutex;
+
+    void requestInterestInMip(uint16 level);
+
     // Usage is a a set of flags providing Semantic about the usage of the Texture.
     void setUsage(const Usage& usage) { _usage = usage; }
     Usage getUsage() const { return _usage; }
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index d9a0348e54..0ea9f2ce4d 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -69,6 +69,19 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
     }
 }
 
+std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() {
+    std::shared_ptr<storage::FileStorage> file = _cacheFile.lock();
+    if (file) {
+        return file;
+    }
+
+    std::lock_guard<std::mutex> lock { _cacheFileCreateMutex };
+    file = std::make_shared<storage::FileStorage>(_filename.c_str());
+    _cacheFile = file;
+
+    return file;
+}
+
 PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
     qDebug() << "getMipFace: " << QString::fromStdString(_filename) << ": " << level << " " << face;
     storage::StoragePointer result;
@@ -86,9 +99,8 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
 
 
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
-    auto minLevel = _minMipLevelAvailable;
-    auto avail = level >= minLevel;
-    qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels;
+    auto avail = level >= _minMipLevelAvailable;
+    qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << _minMipLevelAvailable << " " << _ktxDescriptor->header.numberOfMipmapLevels;
     //return true;
     return avail;
 }
@@ -108,8 +120,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     }
 
 
-    auto fileStorage = new storage::FileStorage(_filename.c_str());
-    ktx::StoragePointer file { fileStorage };
+    //auto fileStorage = new storage::FileStorage(_filename.c_str());
+    //ktx::StoragePointer file { fileStorage };
+    auto file = maybeOpenFile();
     auto data = file->mutableData();
     data += file->size();
 
@@ -119,8 +132,17 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
         data -= 4;
     }
     data += 4;
-    memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
-    _minMipLevelAvailable = level;
+    {
+        std::lock_guard<std::mutex> lock { _cacheFileWriteMutex };
+
+        if (level != _minMipLevelAvailable - 1) {
+            qWarning() << "Invalid level to be stored";
+            return;
+        }
+
+        memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
+        _minMipLevelAvailable = level;
+    }
 }
 
 void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) {
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 7056f22ba8..e8ed7da8e3 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -70,7 +70,7 @@ end
 
 
 namespace ktx {
-    const std::string HIFI_MIN_POPULATED_MIP_KEY = "hifiMinMip";
+    const std::string HIFI_MIN_POPULATED_MIP_KEY = "hifi.minMip";
 
 
     const uint32_t PACKING_SIZE { sizeof(uint32_t) };
@@ -414,10 +414,10 @@ namespace ktx {
     struct ImageHeader {
         using FaceOffsets = std::vector<size_t>;
         using FaceBytes = std::vector<const Byte*>;
+
         // This is the byte offset from the _start_ of the image region. For example, level 0
         // will have a byte offset of 0.
         const uint32_t _imageOffset;
-
         const uint32_t _numFaces;
         const uint32_t _imageSize;
         const uint32_t _faceSize;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 592413e2bc..e45c353aac 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -244,6 +244,10 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
     return result;
 }
 
+NetworkTexture::~NetworkTexture() {
+    _textureSource->getGPUTexture()->unregisterMipInterestListener(this);
+}
+
 /// Returns a texture version of an image file
 gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) {
     QImage image = QImage(path);
@@ -371,6 +375,21 @@ void NetworkTexture::makeRequest() {
     startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
 }
 
+void NetworkTexture::handleMipInterestCallback(uint16_t level) {
+    QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(uint16_t, level));
+}
+
+void NetworkTexture::handleMipInterestLevel(uint16_t level) {
+    _lowestRequestedMipLevel = std::min(level, _lowestRequestedMipLevel);
+    if (!_ktxMipRequest) {
+        startRequestForNextMipLevel();
+    }
+}
+
+void NetworkTexture::startRequestForNextMipLevel() {
+    startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1));
+}
+
 // Load mips in the range [low, high] (inclusive)
 void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     if (_ktxMipRequest) {
@@ -430,6 +449,8 @@ void NetworkTexture::ktxMipRequestFinished() {
     if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
         if (_initialKtxLoaded) {
             assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
+
+            _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
             
             _textureSource->getGPUTexture()->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                 _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
@@ -493,6 +514,7 @@ void NetworkTexture::maybeCreateKTX() {
         texture.reset(gpu::Texture::unserialize(_file->getFilepath(), *_ktxDescriptor));
         texture->setKtxBacking(file->getFilepath());
         texture->setSource(filename);
+        texture->registerMipInterestListener(this);
 
         auto& images = _ktxDescriptor->images;
         size_t imageSizeRemaining = _ktxHighMipData.size();
@@ -521,6 +543,15 @@ void NetworkTexture::maybeCreateKTX() {
             texture = textureCache->cacheTextureByHash(filename, texture);
         }
 
+
+        _lowestKnownPopulatedMip = _ktxDescriptor->header.numberOfMipmapLevels;
+        for (uint16_t l = 0; l < 200; l++) {
+            if (texture->isStoredMipFaceAvailable(l)) {
+                _lowestKnownPopulatedMip = l;
+                break;
+            }
+        }
+
         setImage(texture, header->getPixelWidth(), header->getPixelHeight());
 
 
@@ -528,7 +559,8 @@ void NetworkTexture::maybeCreateKTX() {
         {
             QTimer* timer = new QTimer();
             connect(timer, &QTimer::timeout, this, [=]() {
-                startMipRangeRequest(level, level);
+                //startMipRangeRequest(level, level);
+                startRequestForNextMipLevel();
             });
             timer->setSingleShot(true);
             timer->setInterval(4000);
@@ -538,7 +570,8 @@ void NetworkTexture::maybeCreateKTX() {
         {
             QTimer* timer = new QTimer();
             connect(timer, &QTimer::timeout, this, [=]() {
-                startMipRangeRequest(level - 1, level - 1);
+                //startMipRangeRequest(level - 1, level - 1);
+                startRequestForNextMipLevel();
             });
             timer->setSingleShot(true);
             timer->setInterval(6000);
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index c032a9c29d..b11ae687a2 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -41,7 +41,7 @@ public:
 };
 
 /// A texture loaded from the network.
-class NetworkTexture : public Resource, public Texture {
+class NetworkTexture : public Resource, public Texture, public gpu::Texture::MipInterestListener {
     Q_OBJECT
 
 public:
@@ -57,6 +57,9 @@ public:
 
     gpu::TexturePointer getFallbackTexture() const;
 
+    void handleMipInterestCallback(uint16_t level) override;
+    Q_INVOKABLE void handleMipInterestLevel(uint16_t level);
+
 signals:
     void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
 
@@ -77,6 +80,8 @@ protected:
     Q_INVOKABLE void loadContent(const QByteArray& content);
     Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
 
+    void startRequestForNextMipLevel();
+
     void startMipRangeRequest(uint16_t low, uint16_t high);
     void maybeCreateKTX();
 
@@ -92,9 +97,7 @@ private:
         DONE_LOADING 
     };
 
-
     bool _initialKtxLoaded { false };
-    //KTXLoadState _ktxLoadState;
     KTXFilePointer _file;
     static const uint16_t NULL_MIP_LEVEL;
     bool _sourceIsKTX { false };
@@ -102,6 +105,8 @@ private:
     std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
     ResourceRequest* _ktxHeaderRequest { nullptr };
     ResourceRequest* _ktxMipRequest { nullptr };
+    uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
+    uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
     QByteArray _ktxHeaderData;
     QByteArray _ktxHighMipData;
 

From b21dc12cc6e0f112134c498c9c846eacba09c10f Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 17 Apr 2017 10:37:21 -0700
Subject: [PATCH 11/85] Cleanup NetworkTexture ktx handling

---
 libraries/gpu/src/gpu/Texture_ktx.cpp         | 21 ++++++++++++---
 .../src/model-networking/TextureCache.cpp     | 27 +++++++++++--------
 .../src/model-networking/TextureCache.h       |  4 +--
 3 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 0ea9f2ce4d..1eb6d6b10f 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -75,9 +75,17 @@ std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() {
         return file;
     }
 
-    std::lock_guard<std::mutex> lock { _cacheFileCreateMutex };
-    file = std::make_shared<storage::FileStorage>(_filename.c_str());
-    _cacheFile = file;
+    {
+        std::lock_guard<std::mutex> lock{ _cacheFileCreateMutex };
+
+        file = _cacheFile.lock();
+        if (file) {
+            return file;
+        }
+
+        file = std::make_shared<storage::FileStorage>(_filename.c_str());
+        _cacheFile = file;
+    }
 
     return file;
 }
@@ -127,11 +135,16 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     data += file->size();
 
     // TODO Cache this data inside Image or ImageDescriptor?
-    for (auto i = _ktxDescriptor->header.numberOfMipmapLevels - 1; i >= level; --i) {
+    for (int i = _ktxDescriptor->header.numberOfMipmapLevels - 1; i >= level; --i) {
         data -= _ktxDescriptor->images[i]._imageSize;
         data -= 4;
     }
     data += 4;
+
+    data = file->mutableData();
+    data += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
+    data += 4;
+
     {
         std::lock_guard<std::mutex> lock { _cacheFileWriteMutex };
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index e45c353aac..abc7ecfc41 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -376,11 +376,11 @@ void NetworkTexture::makeRequest() {
 }
 
 void NetworkTexture::handleMipInterestCallback(uint16_t level) {
-    QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(uint16_t, level));
+    QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(int, level));
 }
 
-void NetworkTexture::handleMipInterestLevel(uint16_t level) {
-    _lowestRequestedMipLevel = std::min(level, _lowestRequestedMipLevel);
+void NetworkTexture::handleMipInterestLevel(int level) {
+    _lowestRequestedMipLevel = std::min(static_cast<uint16_t>(level), _lowestRequestedMipLevel);
     if (!_ktxMipRequest) {
         startRequestForNextMipLevel();
     }
@@ -414,10 +414,10 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     } else {
         // TODO: Discover range for other mips
         ByteRange range;
-        range.fromInclusive = ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData
-                              + _ktxDescriptor->images[low]._imageOffset + 4;
-        range.toExclusive = ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData
-                              + _ktxDescriptor->images[high + 1]._imageOffset;
+        range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
+                              + _originalKtxDescriptor->images[low]._imageOffset + 4;
+        range.toExclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
+                              + _originalKtxDescriptor->images[high + 1]._imageOffset;
         _ktxMipRequest->setByteRange(range);
     }
 
@@ -487,6 +487,9 @@ void NetworkTexture::maybeCreateKTX() {
 
         auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(_ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
 
+        auto imageDescriptors = header->generateImageDescriptors();
+        _originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
+
         // Create bare ktx in memory
         std::string filename = "test";
         auto memKtx = ktx::KTX::createBare(*header, keyValues);
@@ -507,16 +510,17 @@ void NetworkTexture::maybeCreateKTX() {
             _file = file;
         }
 
-        _ktxDescriptor.reset(new ktx::KTXDescriptor(memKtx->toDescriptor()));
+        //_ktxDescriptor.reset(new ktx::KTXDescriptor(memKtx->toDescriptor()));
+        auto newKtxDescriptor = memKtx->toDescriptor();
 
         //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
         gpu::TexturePointer texture;
-        texture.reset(gpu::Texture::unserialize(_file->getFilepath(), *_ktxDescriptor));
+        texture.reset(gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor));
         texture->setKtxBacking(file->getFilepath());
         texture->setSource(filename);
         texture->registerMipInterestListener(this);
 
-        auto& images = _ktxDescriptor->images;
+        auto& images = _originalKtxDescriptor->images;
         size_t imageSizeRemaining = _ktxHighMipData.size();
         uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data());
         ktxData += _ktxHighMipData.size();
@@ -544,7 +548,7 @@ void NetworkTexture::maybeCreateKTX() {
         }
 
 
-        _lowestKnownPopulatedMip = _ktxDescriptor->header.numberOfMipmapLevels;
+        _lowestKnownPopulatedMip = _originalKtxDescriptor->header.numberOfMipmapLevels;
         for (uint16_t l = 0; l < 200; l++) {
             if (texture->isStoredMipFaceAvailable(l)) {
                 _lowestKnownPopulatedMip = l;
@@ -554,6 +558,7 @@ void NetworkTexture::maybeCreateKTX() {
 
         setImage(texture, header->getPixelWidth(), header->getPixelHeight());
 
+        return;
 
         // Force load the next two levels
         {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index b11ae687a2..50c8ecf352 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -58,7 +58,7 @@ public:
     gpu::TexturePointer getFallbackTexture() const;
 
     void handleMipInterestCallback(uint16_t level) override;
-    Q_INVOKABLE void handleMipInterestLevel(uint16_t level);
+    Q_INVOKABLE void handleMipInterestLevel(int level);
 
 signals:
     void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
@@ -114,7 +114,7 @@ private:
     // We need this because the KTX that will be cached will likely include extra data
     // in its key/value data, and so will not match up with the original, causing
     // mip offsets to change.
-    ktx::KTXDescriptorPointer _ktxDescriptor;
+    ktx::KTXDescriptorPointer _originalKtxDescriptor;
 
 
     int _originalWidth { 0 };

From 20f4d14e07eac0c4ba07e1c3256ebb87244d20cc Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 17 Apr 2017 17:45:02 -0700
Subject: [PATCH 12/85] Add compressed KTX size evaluation

---
 .../src/RenderableWebEntityItem.cpp           |   4 +-
 .../gpu/gl45/GL45BackendVariableTexture.cpp   |   4 +-
 libraries/gpu/src/gpu/Texture.h               |   2 +
 libraries/gpu/src/gpu/Texture_ktx.cpp         |  14 +-
 libraries/ktx/src/ktx/KTX.cpp                 |  67 ++++-
 libraries/ktx/src/ktx/KTX.h                   |   8 +-
 libraries/ktx/src/ktx/Writer.cpp              |   2 +-
 .../src/model-networking/TextureCache.cpp     | 248 ++++++++++--------
 .../src/model-networking/TextureCache.h       |  10 +-
 libraries/shared/src/shared/Storage.cpp       |   4 +-
 10 files changed, 220 insertions(+), 143 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index 0d286c46eb..20e4f4bc18 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -31,7 +31,7 @@
 const float METERS_TO_INCHES = 39.3701f;
 static uint32_t _currentWebCount { 0 };
 // Don't allow more than 100 concurrent web views
-static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20;
+static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 0;
 // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
 static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
 
@@ -71,7 +71,7 @@ RenderableWebEntityItem::~RenderableWebEntityItem() {
 
 bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer> renderer) {
     if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
-        qWarning() << "Too many concurrent web views to create new view";
+        //qWarning() << "Too many concurrent web views to create new view";
         return false;
     }
     QString javaScriptToInject;
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index c37ee46bfd..09361689d8 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -181,13 +181,13 @@ void GL45ResourceTexture::populateTransferQueue() {
 
     const uint8_t maxFace = GLTexture::getFaceCount(_target);
     uint16_t sourceMip = _populatedMip;
-    qDebug() << "populateTransferQueue info : " << _populatedMip << " " << _maxAllocatedMip << " " << _allocatedMip;
+    //qDebug() << "populateTransferQueue info : " << _populatedMip << " " << _maxAllocatedMip << " " << _allocatedMip;
     do {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
         bool transferQueued = false;
-        qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
+        //qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 8c9d62e3b8..e71cd63fbd 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -34,6 +34,8 @@ namespace ktx {
 
 namespace gpu {
 
+extern const std::string SOURCE_HASH_KEY;
+
 // THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
 // with the cube texture
 class Texture;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 1eb6d6b10f..eb6bb169ce 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -11,6 +11,7 @@
 
 
 #include "Texture.h"
+#include <qdebug.h>
 
 #include <ktx/KTX.h>
 using namespace gpu;
@@ -41,6 +42,7 @@ struct GPUKTXPayload {
     }
 };
 
+const std::string gpu::SOURCE_HASH_KEY { "hifi.sourceHash" };
 std::string GPUKTXPayload::KEY{ "hifi.gpu" };
 
 KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
@@ -91,7 +93,7 @@ std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() {
 }
 
 PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
-    qDebug() << "getMipFace: " << QString::fromStdString(_filename) << ": " << level << " " << face;
+    //qDebug() << "getMipFace: " << QString::fromStdString(_filename) << ": " << level << " " << face;
     storage::StoragePointer result;
     auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
     auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
@@ -108,14 +110,14 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
 
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
     auto avail = level >= _minMipLevelAvailable;
-    qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << _minMipLevelAvailable << " " << _ktxDescriptor->header.numberOfMipmapLevels;
+    //qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << _minMipLevelAvailable << " " << _ktxDescriptor->header.numberOfMipmapLevels;
     //return true;
     return avail;
 }
 
 void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
     if (level != _minMipLevelAvailable - 1) {
-        qWarning() << "Invalid level to be stored";
+        qWarning() << "Invalid level to be stored, expected: " << (_minMipLevelAvailable - 1) << ", got: " << level;
         return;
     }
 
@@ -124,7 +126,10 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     }
 
     if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
-        throw std::runtime_error("Invalid image size for level");
+        qDebug() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
+            << ", filename: " << QString::fromStdString(_filename);
+        //throw std::runtime_error("Invalid image size for level");
+        return;
     }
 
 
@@ -258,7 +263,6 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
     ktx::KeyValues keyValues;
     keyValues.emplace_back(ktx::KeyValue(GPUKTXPayload::KEY, sizeof(GPUKTXPayload), (ktx::Byte*) &keyval));
 
-    static const std::string SOURCE_HASH_KEY = "hifi.sourceHash";
     auto hash = texture.sourceHash();
     if (!hash.empty()) {
         keyValues.emplace_back(ktx::KeyValue(SOURCE_HASH_KEY, static_cast<uint32>(hash.size()), (ktx::Byte*) hash.c_str()));
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 73751bdef2..45820b9351 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -34,30 +34,75 @@ uint32_t Header::evalMaxDimension() const {
     return std::max(getPixelWidth(), std::max(getPixelHeight(), getPixelDepth()));
 }
 
-uint32_t Header::evalPixelWidth(uint32_t level) const {
-    return std::max(getPixelWidth() >> level, 1U);
+uint32_t Header::evalPixelOrBlockWidth(uint32_t level) const {
+    auto pixelWidth = std::max(getPixelWidth() >> level, 1U);
+    if (getGLType() == GLType::COMPRESSED_TYPE) {
+        return (pixelWidth + 3) / 4;
+    } else {
+        return pixelWidth;
+    }
 }
-uint32_t Header::evalPixelHeight(uint32_t level) const {
-    return std::max(getPixelHeight() >> level, 1U);
+uint32_t Header::evalPixelOrBlockHeight(uint32_t level) const {
+    auto pixelWidth = std::max(getPixelHeight() >> level, 1U);
+    if (getGLType() == GLType::COMPRESSED_TYPE) {
+        auto format = getGLInternaFormat_Compressed();
+        switch (format) {
+            case GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT: // BC1
+            case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: // BC1A
+            case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3
+            case GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1: // BC4
+            case GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2: // BC5
+                return (pixelWidth + 3) / 4;
+            default:
+                throw std::runtime_error("Unknown format");
+        }
+    } else {
+        return pixelWidth;
+    }
 }
-uint32_t Header::evalPixelDepth(uint32_t level) const {
+uint32_t Header::evalPixelOrBlockDepth(uint32_t level) const {
     return std::max(getPixelDepth() >> level, 1U);
 }
 
-size_t Header::evalPixelSize() const {
-    return 4;//glTypeSize; // Really we should generate the size from the FOrmat etc
+size_t Header::evalPixelOrBlockSize() const {
+    if (getGLType() == GLType::COMPRESSED_TYPE) {
+        auto format = getGLInternaFormat_Compressed();
+        if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
+            return 8;
+        } else if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
+            return 8;
+        } else if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
+            return 16;
+        } else if (format == GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1) {
+            return 8;
+        } else if (format == GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) {
+            return 16;
+        }
+    } else {
+        auto baseFormat = getGLBaseInternalFormat();
+        if (baseFormat == GLBaseInternalFormat::RED) {
+            return 1;
+        } else if (baseFormat == GLBaseInternalFormat::RG) {
+            return 2;
+        } else if (baseFormat == GLBaseInternalFormat::RGB) {
+            return 3;
+        } else if (baseFormat == GLBaseInternalFormat::RGBA) {
+            return 4;
+        }
+    }
+    throw std::runtime_error("Unknown format");
 }
 
 size_t Header::evalRowSize(uint32_t level) const {
-    auto pixWidth = evalPixelWidth(level);
-    auto pixSize = evalPixelSize();
+    auto pixWidth = evalPixelOrBlockWidth(level);
+    auto pixSize = evalPixelOrBlockSize();
     auto netSize = pixWidth * pixSize;
     auto padding = evalPadding(netSize);
     return netSize + padding;
 }
 size_t Header::evalFaceSize(uint32_t level) const {
-    auto pixHeight = evalPixelHeight(level);
-    auto pixDepth = evalPixelDepth(level);
+    auto pixHeight = evalPixelOrBlockHeight(level);
+    auto pixDepth = evalPixelOrBlockDepth(level);
     auto rowSize = evalRowSize(level);
     return pixDepth * pixHeight * rowSize;
 }
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index e8ed7da8e3..7f6f2da939 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -336,11 +336,11 @@ namespace ktx {
         uint32_t getNumberOfLevels() const { return (numberOfMipmapLevels ? numberOfMipmapLevels : 1); }
 
         uint32_t evalMaxDimension() const;
-        uint32_t evalPixelWidth(uint32_t level) const;
-        uint32_t evalPixelHeight(uint32_t level) const;
-        uint32_t evalPixelDepth(uint32_t level) const;
+        uint32_t evalPixelOrBlockWidth(uint32_t level) const;
+        uint32_t evalPixelOrBlockHeight(uint32_t level) const;
+        uint32_t evalPixelOrBlockDepth(uint32_t level) const;
 
-        size_t evalPixelSize() const;
+        size_t evalPixelOrBlockSize() const;
         size_t evalRowSize(uint32_t level) const;
         size_t evalFaceSize(uint32_t level) const;
         size_t evalImageSize(uint32_t level) const;
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 4596bf00c0..0d3d4e1d60 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -155,7 +155,7 @@ namespace ktx {
             ptr++;
 #ifdef DEBUG
             for (size_t k = 0; k < descriptors[i]._imageSize/4; k++) {
-                *(ptr + k) = 0xFFFF00FF;
+                *(ptr + k) = 0xFFFFFFFF;
             }
 #endif
             currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t);
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index abc7ecfc41..3e06142d8e 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -245,7 +245,10 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
 }
 
 NetworkTexture::~NetworkTexture() {
-    _textureSource->getGPUTexture()->unregisterMipInterestListener(this);
+    auto texture = _textureSource->getGPUTexture();
+    if (texture) {
+        texture->unregisterMipInterestListener(this);
+    }
 }
 
 /// Returns a texture version of an image file
@@ -412,7 +415,6 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
         range.fromInclusive = -15000;
         _ktxMipRequest->setByteRange(range);
     } else {
-        // TODO: Discover range for other mips
         ByteRange range;
         range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
                               + _originalKtxDescriptor->images[low]._imageOffset + 4;
@@ -431,6 +433,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
 void NetworkTexture::ktxHeaderRequestFinished() {
     assert(!_ktxHeaderLoaded);
 
+    _headerRequestFinished = true;
     if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) {
         _ktxHeaderLoaded = true;
         _ktxHeaderData = _ktxHeaderRequest->getData();
@@ -447,7 +450,7 @@ void NetworkTexture::ktxMipRequestFinished() {
                             && _ktxMipLevelRangeInFlight.second == NULL_MIP_LEVEL;
 
     if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
-        if (_initialKtxLoaded) {
+        if (_highMipRequestFinished) {
             assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
 
             _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
@@ -456,6 +459,7 @@ void NetworkTexture::ktxMipRequestFinished() {
                 _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
             //texture->assignStoredMip(level, image._imageSize, ktxData);
         } else {
+            _highMipRequestFinished = true;
             _ktxHighMipData = _ktxMipRequest->getData();
             maybeCreateKTX();
         }
@@ -469,118 +473,144 @@ void NetworkTexture::ktxMipRequestFinished() {
 
 // This is called when the header or top mips have been loaded
 void NetworkTexture::maybeCreateKTX() {
-    if (_ktxHeaderData.size() > 0 && _ktxHighMipData.size() > 0) {
-        // create ktx...
-        auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
+    if (_headerRequestFinished && _highMipRequestFinished) {
+        ResourceCache::requestCompleted(_self);
+
+        if (_ktxHeaderData.size() > 0 && _ktxHighMipData.size() > 0) {
+            // create ktx...
+            auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
+
+            qDebug() << "Creating KTX";
+            qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
+            qDebug() << "Type:" << header->glType;
+            qDebug() << "TypeSize:" << header->glTypeSize;
+            qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
+            qDebug() << "numberOfFaces:" << header->numberOfFaces;
+            qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
+            auto kvSize = header->bytesOfKeyValueData;
+            if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
+                qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
+                return;
+            }
+
+            auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(_ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
+
+            auto imageDescriptors = header->generateImageDescriptors();
+            _originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
+
+            // Create bare ktx in memory
+            auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
+                return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
+            });
+            std::string filename;
+            if (found == keyValues.end()) {
+                qWarning("Source hash key not found, bailing");
+                filename = "test";
+                //return;
+
+            }
+            else {
+                if (found->_value.size() < 16) {
+                    filename = _activeUrl.fileName().toStdString();
+                }
+                else {
+                    filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
+                }
+            }
+
+            auto memKtx = ktx::KTX::createBare(*header, keyValues);
+
+            auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
+            ///memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
+
+            auto textureCache = DependencyManager::get<TextureCache>();
+
+            // Move ktx to file
+            const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
+            size_t length = memKtx->_storage->size();
+            KTXFilePointer file;
+            auto& ktxCache = textureCache->_ktxCache;
+            if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
+                qCWarning(modelnetworking) << _url << "file cache failed";
+                return;
+            }
+            else {
+                _file = file;
+            }
+
+            //_ktxDescriptor.reset(new ktx::KTXDescriptor(memKtx->toDescriptor()));
+            auto newKtxDescriptor = memKtx->toDescriptor();
+
+            //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
+            gpu::TexturePointer texture;
+            texture.reset(gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor));
+            texture->setKtxBacking(file->getFilepath());
+            texture->setSource(filename);
+            texture->registerMipInterestListener(this);
+
+            auto& images = _originalKtxDescriptor->images;
+            size_t imageSizeRemaining = _ktxHighMipData.size();
+            uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data());
+            ktxData += _ktxHighMipData.size();
+            // TODO Move image offset calculation to ktx ImageDescriptor
+            int level;
+            for (level = images.size() - 1; level >= 0; --level) {
+                auto& image = images[level];
+                if (image._imageSize > imageSizeRemaining) {
+                    break;
+                }
+                qDebug() << "Transferring " << level;
+                ktxData -= image._imageSize;
+                texture->assignStoredMip(level, image._imageSize, ktxData);
+                ktxData -= 4;
+                imageSizeRemaining - image._imageSize - 4;
+            }
+
+            // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
+            // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
+            // be the winner
+            if (textureCache) {
+                texture = textureCache->cacheTextureByHash(filename, texture);
+            }
+
+
+            _lowestKnownPopulatedMip = _originalKtxDescriptor->header.numberOfMipmapLevels;
+            for (uint16_t l = 0; l < 200; l++) {
+                if (texture->isStoredMipFaceAvailable(l)) {
+                    _lowestKnownPopulatedMip = l;
+                    break;
+                }
+            }
+            ResourceCache::requestCompleted(_self);
+
+            setImage(texture, header->getPixelWidth(), header->getPixelHeight());
 
-        qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
-        qDebug() << "Type:" << header->glType;
-        qDebug() << "TypeSize:" << header->glTypeSize;
-        qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
-        qDebug() << "numberOfFaces:" << header->numberOfFaces;
-        qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
-        auto kvSize = header->bytesOfKeyValueData;
-        if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
-            qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
             return;
-        }
 
-        auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(_ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
-
-        auto imageDescriptors = header->generateImageDescriptors();
-        _originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
-
-        // Create bare ktx in memory
-        std::string filename = "test";
-        auto memKtx = ktx::KTX::createBare(*header, keyValues);
-
-        auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
-        ///memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
-
-        auto textureCache = DependencyManager::get<TextureCache>();
-
-        // Move ktx to file
-        const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
-        size_t length = memKtx->_storage->size();
-        KTXFilePointer file;
-        auto& ktxCache = textureCache->_ktxCache;
-        if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
-            qCWarning(modelnetworking) << _url << "file cache failed";
-        } else {
-            _file = file;
-        }
-
-        //_ktxDescriptor.reset(new ktx::KTXDescriptor(memKtx->toDescriptor()));
-        auto newKtxDescriptor = memKtx->toDescriptor();
-
-        //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
-        gpu::TexturePointer texture;
-        texture.reset(gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor));
-        texture->setKtxBacking(file->getFilepath());
-        texture->setSource(filename);
-        texture->registerMipInterestListener(this);
-
-        auto& images = _originalKtxDescriptor->images;
-        size_t imageSizeRemaining = _ktxHighMipData.size();
-        uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data());
-        ktxData += _ktxHighMipData.size();
-        // TODO Move image offset calculation to ktx ImageDescriptor
-        uint16_t level;
-        for (level = images.size() - 1; level >= 0; --level) {
-            auto& image = images[level];
-            if (image._imageSize > imageSizeRemaining) {
-                break;
+            /*
+            // Force load the next two levels
+            {
+                QTimer* timer = new QTimer();
+                connect(timer, &QTimer::timeout, this, [=]() {
+                    //startMipRangeRequest(level, level);
+                    startRequestForNextMipLevel();
+                });
+                timer->setSingleShot(true);
+                timer->setInterval(4000);
+                timer->start();
             }
-            qDebug() << "Transferring " << level;
-            ktxData -= image._imageSize;
-            texture->assignStoredMip(level, image._imageSize, ktxData);
-            ktxData -= 4;
-            imageSizeRemaining - image._imageSize - 4;
-        }
 
-        _initialKtxLoaded = true;
-
-        // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
-        // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
-        // be the winner
-        if (textureCache) {
-            texture = textureCache->cacheTextureByHash(filename, texture);
-        }
-
-
-        _lowestKnownPopulatedMip = _originalKtxDescriptor->header.numberOfMipmapLevels;
-        for (uint16_t l = 0; l < 200; l++) {
-            if (texture->isStoredMipFaceAvailable(l)) {
-                _lowestKnownPopulatedMip = l;
-                break;
+            {
+                QTimer* timer = new QTimer();
+                connect(timer, &QTimer::timeout, this, [=]() {
+                    //startMipRangeRequest(level - 1, level - 1);
+                    startRequestForNextMipLevel();
+                });
+                timer->setSingleShot(true);
+                timer->setInterval(6000);
+                timer->start();
             }
-        }
-
-        setImage(texture, header->getPixelWidth(), header->getPixelHeight());
-
-        return;
-
-        // Force load the next two levels
-        {
-            QTimer* timer = new QTimer();
-            connect(timer, &QTimer::timeout, this, [=]() {
-                //startMipRangeRequest(level, level);
-                startRequestForNextMipLevel();
-            });
-            timer->setSingleShot(true);
-            timer->setInterval(4000);
-            timer->start();
-        }
-
-        {
-            QTimer* timer = new QTimer();
-            connect(timer, &QTimer::timeout, this, [=]() {
-                //startMipRangeRequest(level - 1, level - 1);
-                startRequestForNextMipLevel();
-            });
-            timer->setSingleShot(true);
-            timer->setInterval(6000);
-            timer->start();
+            */
         }
     }
 }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 50c8ecf352..30eb9d2d2e 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -46,6 +46,7 @@ class NetworkTexture : public Resource, public Texture, public gpu::Texture::Mip
 
 public:
     NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels);
+    NetworkTexture::~NetworkTexture() override;
 
     QString getType() const override { return "NetworkTexture"; }
 
@@ -91,13 +92,6 @@ private:
 
     image::TextureUsage::Type _type;
 
-    enum KTXLoadState {
-        LOADING_HEADER,
-        LOADING_LOWEST_SIX,
-        DONE_LOADING 
-    };
-
-    bool _initialKtxLoaded { false };
     KTXFilePointer _file;
     static const uint16_t NULL_MIP_LEVEL;
     bool _sourceIsKTX { false };
@@ -105,6 +99,8 @@ private:
     std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
     ResourceRequest* _ktxHeaderRequest { nullptr };
     ResourceRequest* _ktxMipRequest { nullptr };
+    bool _headerRequestFinished{ false };
+    bool _highMipRequestFinished{ false };
     uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
     uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
     QByteArray _ktxHeaderData;
diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp
index 0f2b696a66..6eb311fa60 100644
--- a/libraries/shared/src/shared/Storage.cpp
+++ b/libraries/shared/src/shared/Storage.cpp
@@ -70,7 +70,7 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
 // Represents a memory mapped file
 FileStorage::FileStorage(const QString& filename) : _file(filename) {
     if (_file.open(QFile::ReadWrite)) {
-        qDebug() << ">>> Opening mmapped file: " << filename;
+        //qDebug() << ">>> Opening mmapped file: " << filename;
         _mapped = _file.map(0, _file.size());
         if (_mapped) {
             _valid = true;
@@ -83,7 +83,7 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) {
 }
 
 FileStorage::~FileStorage() {
-    qDebug() << ">>> Closing mmapped file: " << _file.fileName();
+    //qDebug() << ">>> Closing mmapped file: " << _file.fileName();
     if (_mapped) {
         if (!_file.unmap(_mapped)) {
             throw std::runtime_error("Unable to unmap file");

From 11751611e1085e2c3a48b1b8da21b00291a6ad85 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 17 Apr 2017 22:10:21 -0700
Subject: [PATCH 13/85] Update NetworkTexture to track current KTX download
 state

---
 libraries/gpu/src/gpu/Texture_ktx.cpp         |   2 +-
 libraries/ktx/src/ktx/KTX.cpp                 |   6 +-
 .../src/model-networking/TextureCache.cpp     | 102 +++++++++---------
 .../src/model-networking/TextureCache.h       |  21 +++-
 libraries/networking/src/ResourceCache.cpp    |  15 ++-
 libraries/networking/src/ResourceCache.h      |   5 +
 6 files changed, 89 insertions(+), 62 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index eb6bb169ce..533797a657 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -252,7 +252,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
                 }
                 images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces));
             }
-            imageOffset += mip->getSize() + 4;
+            imageOffset += static_cast<uint32_t>(mip->getSize()) + 4;
         }
     }
 
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 45820b9351..d9b5ea4a62 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -12,6 +12,7 @@
 #include "KTX.h"
 
 #include <algorithm> //min max and more
+#include <QDebug>
 
 using namespace ktx;
 
@@ -90,6 +91,7 @@ size_t Header::evalPixelOrBlockSize() const {
             return 4;
         }
     }
+    qWarning() << "Unknown ktx format: " << glFormat << " " << glBaseInternalFormat << " " << glInternalFormat;
     throw std::runtime_error("Unknown format");
 }
 
@@ -119,7 +121,7 @@ ImageDescriptors Header::generateImageDescriptors() const {
     ImageDescriptors descriptors;
 
     uint32_t imageOffset = 0;
-    for (auto level = 0; level < numberOfMipmapLevels; ++level) {
+    for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) {
         auto imageSize = static_cast<uint32_t>(evalImageSize(level));
         ImageHeader header {
             numberOfFaces == NUM_CUBEMAPFACES,
@@ -131,7 +133,7 @@ ImageDescriptors Header::generateImageDescriptors() const {
         imageOffset += imageSize + 4;
 
         ImageHeader::FaceOffsets offsets;
-        for (auto i = 0; i < numberOfFaces; ++i) {
+        for (uint32_t i = 0; i < numberOfFaces; ++i) {
             offsets.push_back(0);
         }
         descriptors.push_back(ImageDescriptor(header, offsets));
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 3e06142d8e..3d81dd51e9 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -38,6 +38,7 @@
 #include <Finally.h>
 #include <Profile.h>
 
+#include "NetworkLogging.h"
 #include "ModelNetworkingLogging.h"
 #include <Trace.h>
 #include <StatTracker.h>
@@ -345,14 +346,12 @@ void NetworkTexture::makeRequest() {
     // We special-handle ktx requests to run 2 concurrent requests right off the bat
     PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
 
-    if (!_ktxHeaderLoaded) {
+    if (!_ktxHeaderLoaded && !_highMipRequestFinished) {
         qDebug() << ">>> Making request to " << _url << " for header";
         _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
 
         if (!_ktxHeaderRequest) {
             //qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
-            ResourceCache::requestCompleted(_self);
-            finishedLoading(false);
             PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
             return;
         }
@@ -373,9 +372,14 @@ void NetworkTexture::makeRequest() {
         _bytesReceived = _bytesTotal = _bytes = 0;
 
         _ktxHeaderRequest->send();
+
+        startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
+    } else {
+        if (_lowestKnownPopulatedMip > 0) {
+            startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1);
+        }
     }
 
-    startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
 }
 
 void NetworkTexture::handleMipInterestCallback(uint16_t level) {
@@ -384,13 +388,24 @@ void NetworkTexture::handleMipInterestCallback(uint16_t level) {
 
 void NetworkTexture::handleMipInterestLevel(int level) {
     _lowestRequestedMipLevel = std::min(static_cast<uint16_t>(level), _lowestRequestedMipLevel);
-    if (!_ktxMipRequest) {
-        startRequestForNextMipLevel();
+    if (!_ktxMipRequest && _lowestKnownPopulatedMip > 0) {
+        //startRequestForNextMipLevel();
+        clearLoadPriority(this);
+        setLoadPriority(this, _lowestKnownPopulatedMip - 1);
+        ResourceCache::attemptRequest(_self);
     }
 }
 
 void NetworkTexture::startRequestForNextMipLevel() {
-    startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1));
+    if (_lowestKnownPopulatedMip == 0) {
+        qWarning(networking) << "Requesting next mip level but all have been fulfilled";
+        return;
+    }
+    if (_pending) {
+        return;
+    }
+
+    //startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1));
 }
 
 // Load mips in the range [low, high] (inclusive)
@@ -406,7 +421,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     }
 
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
-    qDebug() << ">>> Making request to " << _url << " for " << low << " to " << high;
+    qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
 
     _ktxMipLevelRangeInFlight = { low, high };
     if (isHighMipRequest) {
@@ -437,7 +452,7 @@ void NetworkTexture::ktxHeaderRequestFinished() {
     if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) {
         _ktxHeaderLoaded = true;
         _ktxHeaderData = _ktxHeaderRequest->getData();
-        maybeCreateKTX();
+        maybeHandleFinishedInitialLoad();
     } else {
         handleFailedRequest(_ktxHeaderRequest->getResult());
     }
@@ -455,15 +470,18 @@ void NetworkTexture::ktxMipRequestFinished() {
 
             _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
             
-            _textureSource->getGPUTexture()->assignStoredMip(_ktxMipLevelRangeInFlight.first,
-                _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
-            //texture->assignStoredMip(level, image._imageSize, ktxData);
+            auto texture = _textureSource->getGPUTexture();
+            if (!texture) {
+                texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
+                    _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
+            } else {
+                qWarning(networking) << "Trying to update mips but texture is null";
+            }
         } else {
             _highMipRequestFinished = true;
             _ktxHighMipData = _ktxMipRequest->getData();
-            maybeCreateKTX();
+            maybeHandleFinishedInitialLoad();
         }
-
     } else {
         handleFailedRequest(_ktxMipRequest->getResult());
     }
@@ -472,11 +490,13 @@ void NetworkTexture::ktxMipRequestFinished() {
 }
 
 // This is called when the header or top mips have been loaded
-void NetworkTexture::maybeCreateKTX() {
+void NetworkTexture::maybeHandleFinishedInitialLoad() {
     if (_headerRequestFinished && _highMipRequestFinished) {
         ResourceCache::requestCompleted(_self);
 
-        if (_ktxHeaderData.size() > 0 && _ktxHighMipData.size() > 0) {
+        if (_ktxHeaderData.size() == 0 || _ktxHighMipData.size() == 0) {
+            finishedLoading(false);
+        } else {
             // create ktx...
             auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
 
@@ -490,6 +510,7 @@ void NetworkTexture::maybeCreateKTX() {
             auto kvSize = header->bytesOfKeyValueData;
             if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
                 qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
+                finishedLoading(false);
                 return;
             }
 
@@ -505,23 +526,23 @@ void NetworkTexture::maybeCreateKTX() {
             std::string filename;
             if (found == keyValues.end()) {
                 qWarning("Source hash key not found, bailing");
-                filename = "test";
-                //return;
-
+                finishedLoading(false);
+                return;
             }
             else {
-                if (found->_value.size() < 16) {
-                    filename = _activeUrl.fileName().toStdString();
-                }
-                else {
+                if (found->_value.size() < 32) {
+                    qWarning("Invalid source hash key found, bailing");
+                    finishedLoading(false);
+                    return;
+                } else {
                     filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
                 }
             }
 
             auto memKtx = ktx::KTX::createBare(*header, keyValues);
 
-            auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
-            ///memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
+            //auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
+            //memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
 
             auto textureCache = DependencyManager::get<TextureCache>();
 
@@ -531,7 +552,8 @@ void NetworkTexture::maybeCreateKTX() {
             KTXFilePointer file;
             auto& ktxCache = textureCache->_ktxCache;
             if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
-                qCWarning(modelnetworking) << _url << "file cache failed";
+                qCWarning(modelnetworking) << _url << " failed to write cache file";
+                finishedLoading(false);
                 return;
             }
             else {
@@ -581,36 +603,8 @@ void NetworkTexture::maybeCreateKTX() {
                     break;
                 }
             }
-            ResourceCache::requestCompleted(_self);
 
             setImage(texture, header->getPixelWidth(), header->getPixelHeight());
-
-            return;
-
-            /*
-            // Force load the next two levels
-            {
-                QTimer* timer = new QTimer();
-                connect(timer, &QTimer::timeout, this, [=]() {
-                    //startMipRangeRequest(level, level);
-                    startRequestForNextMipLevel();
-                });
-                timer->setSingleShot(true);
-                timer->setInterval(4000);
-                timer->start();
-            }
-
-            {
-                QTimer* timer = new QTimer();
-                connect(timer, &QTimer::timeout, this, [=]() {
-                    //startMipRangeRequest(level - 1, level - 1);
-                    startRequestForNextMipLevel();
-                });
-                timer->setSingleShot(true);
-                timer->setInterval(6000);
-                timer->start();
-            }
-            */
         }
     }
 }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 30eb9d2d2e..3048f16eb8 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -84,7 +84,7 @@ protected:
     void startRequestForNextMipLevel();
 
     void startMipRangeRequest(uint16_t low, uint16_t high);
-    void maybeCreateKTX();
+    void maybeHandleFinishedInitialLoad();
 
 private:
     friend class KTXReader;
@@ -92,15 +92,28 @@ private:
 
     image::TextureUsage::Type _type;
 
-    KTXFilePointer _file;
     static const uint16_t NULL_MIP_LEVEL;
+    struct KTXResourceState {
+        NOT_LOADED = 0,
+        LOADING_INITIAL_DATA,    // Loading KTX Header + Low Resolution Mips
+        WAITING_FOR_MIP_REQUEST, // Waiting for the gpu layer to report that it needs higher resolution mips
+        PENDING_MIP_REQUEST,     // We have added ourselves to the ResourceCache queue
+        REQUESTING_MIP           // We have a mip in flight
+    };
+
+    KTXResourceState _ktxResourceState{ NOT_LOADED };
+
+    KTXFilePointer _file;
+
     bool _sourceIsKTX { false };
     bool _ktxHeaderLoaded { false };
+    bool _highMipRequestFinished { false };
+
     std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
+
     ResourceRequest* _ktxHeaderRequest { nullptr };
     ResourceRequest* _ktxMipRequest { nullptr };
-    bool _headerRequestFinished{ false };
-    bool _highMipRequestFinished{ false };
+    bool _headerRequestFinished { false };
     uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
     uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
     QByteArray _ktxHeaderData;
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 038ee7fb53..6716b7473f 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -474,8 +474,15 @@ int ResourceCache::getLoadingRequestCount() {
 
 bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
     Q_ASSERT(!resource.isNull());
-    auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
 
+    if (resource->_pending) {
+        qWarning(networking) << "Attempted to request " << resource->getURL() << " but it was already pending";
+        return false;
+    }
+
+    resource->_pending = true;
+
+    auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
     if (_requestsActive >= _requestLimit) {
         // wait until a slot becomes available
         sharedItems->appendPendingRequest(resource);
@@ -490,6 +497,12 @@ bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
 
 void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
     auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
+
+    auto sharedResource = resource.lock();
+    if (sharedResource) {
+        sharedResource->_pending = true;
+    }
+
     sharedItems->removeRequest(resource);
     --_requestsActive;
 
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index e699c3d198..13162aac9d 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -424,6 +424,9 @@ protected slots:
 protected:
     virtual void init();
 
+    /// Called by ResourceCache to begin loading this Resource.
+    /// This method can be overriden to provide custom request functionality. If this is done,
+    /// downloadFinished and ResourceCache::requestCompleted must be called.
     virtual void makeRequest();
 
     /// Checks whether the resource is cacheable.
@@ -460,6 +463,8 @@ protected:
 
     int _requestID;
     ResourceRequest* _request{ nullptr };
+
+    bool _pending{ false };
     
 public slots:
     void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);

From 70b816827eb56ded04abbc36f9c3ec117e2c8634 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 18 Apr 2017 00:14:04 -0700
Subject: [PATCH 14/85] Improve handling of KTX downloads in NetworkTexture

---
 .../src/model-networking/TextureCache.cpp     | 135 ++++++++++--------
 .../src/model-networking/TextureCache.h       |  22 +--
 libraries/networking/src/ResourceCache.cpp    |  17 +--
 libraries/networking/src/ResourceCache.h      |   5 +-
 4 files changed, 95 insertions(+), 84 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 3d81dd51e9..25d7018a1a 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -281,10 +281,10 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
         _loaded = true;
     }
 
-    if (_sourceIsKTX) {
-        _requestByteRange.fromInclusive = 0;
-        _requestByteRange.toExclusive = 1000;
-    }
+    //if (_sourceIsKTX) {
+        //_requestByteRange.fromInclusive = 0;
+        //_requestByteRange.toExclusive = 1000;
+    //}
 
     // if we have content, load it after we have our self pointer
     if (!content.isEmpty()) {
@@ -346,7 +346,9 @@ void NetworkTexture::makeRequest() {
     // We special-handle ktx requests to run 2 concurrent requests right off the bat
     PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
 
-    if (!_ktxHeaderLoaded && !_highMipRequestFinished) {
+    if (_ktxResourceState == PENDING_INITIAL_LOAD) {
+        _ktxResourceState = LOADING_INITIAL_DATA;
+
         qDebug() << ">>> Making request to " << _url << " for header";
         _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
 
@@ -374,10 +376,13 @@ void NetworkTexture::makeRequest() {
         _ktxHeaderRequest->send();
 
         startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
-    } else {
+    } else if (_ktxResourceState == PENDING_MIP_REQUEST) {
+        _ktxResourceState = REQUESTING_MIP;
         if (_lowestKnownPopulatedMip > 0) {
             startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1);
         }
+    } else {
+        qWarning(networking) << "NetworkTexture::makeRequest() called while not in a valid state: " << _ktxResourceState;
     }
 
 }
@@ -387,13 +392,7 @@ void NetworkTexture::handleMipInterestCallback(uint16_t level) {
 }
 
 void NetworkTexture::handleMipInterestLevel(int level) {
-    _lowestRequestedMipLevel = std::min(static_cast<uint16_t>(level), _lowestRequestedMipLevel);
-    if (!_ktxMipRequest && _lowestKnownPopulatedMip > 0) {
-        //startRequestForNextMipLevel();
-        clearLoadPriority(this);
-        setLoadPriority(this, _lowestKnownPopulatedMip - 1);
-        ResourceCache::attemptRequest(_self);
-    }
+    startRequestForNextMipLevel();
 }
 
 void NetworkTexture::startRequestForNextMipLevel() {
@@ -401,11 +400,15 @@ void NetworkTexture::startRequestForNextMipLevel() {
         qWarning(networking) << "Requesting next mip level but all have been fulfilled";
         return;
     }
-    if (_pending) {
-        return;
-    }
 
-    //startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1));
+    if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
+        _ktxResourceState = PENDING_MIP_REQUEST;
+
+        setLoadPriority(this, _lowestKnownPopulatedMip);
+
+        init();
+        ResourceCache::attemptRequest(_self);
+    }
 }
 
 // Load mips in the range [low, high] (inclusive)
@@ -415,10 +418,6 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     }
 
     bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
-    
-    if (!isHighMipRequest && !_ktxHeaderLoaded) {
-        return;
-    }
 
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
     qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
@@ -446,59 +445,75 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
 
 
 void NetworkTexture::ktxHeaderRequestFinished() {
-    assert(!_ktxHeaderLoaded);
+    Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
 
-    _headerRequestFinished = true;
-    if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) {
-        _ktxHeaderLoaded = true;
-        _ktxHeaderData = _ktxHeaderRequest->getData();
-        maybeHandleFinishedInitialLoad();
-    } else {
-        handleFailedRequest(_ktxHeaderRequest->getResult());
-    }
-    _ktxHeaderRequest->deleteLater();
-    _ktxHeaderRequest = nullptr;
+    _ktxHeaderRequestFinished = true;
+    maybeHandleFinishedInitialLoad();
 }
 
 void NetworkTexture::ktxMipRequestFinished() {
-    bool isHighMipRequest = _ktxMipLevelRangeInFlight.first == NULL_MIP_LEVEL
-                            && _ktxMipLevelRangeInFlight.second == NULL_MIP_LEVEL;
+    Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA || _ktxResourceState == REQUESTING_MIP);
 
-    if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
-        if (_highMipRequestFinished) {
-            assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
+    if (_ktxResourceState == LOADING_INITIAL_DATA) {
+        _ktxHighMipRequestFinished = true;
+        maybeHandleFinishedInitialLoad();
+    } else if (_ktxResourceState == REQUESTING_MIP) {
+        ResourceCache::requestCompleted(_self);
+
+        if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
+            Q_ASSERT(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
 
-            _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
-            
             auto texture = _textureSource->getGPUTexture();
-            if (!texture) {
+            if (texture) {
+                _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
                 texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                     _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
             } else {
                 qWarning(networking) << "Trying to update mips but texture is null";
             }
+            _ktxResourceState = WAITING_FOR_MIP_REQUEST;
         } else {
-            _highMipRequestFinished = true;
-            _ktxHighMipData = _ktxMipRequest->getData();
-            maybeHandleFinishedInitialLoad();
+            _ktxResourceState = PENDING_MIP_REQUEST;
+            handleFailedRequest(_ktxMipRequest->getResult());
         }
+
+        _ktxMipRequest->deleteLater();
+        _ktxMipRequest = nullptr;
     } else {
-        handleFailedRequest(_ktxMipRequest->getResult());
+        qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
     }
-    _ktxMipRequest->deleteLater();
-    _ktxMipRequest = nullptr;
 }
 
 // This is called when the header or top mips have been loaded
 void NetworkTexture::maybeHandleFinishedInitialLoad() {
-    if (_headerRequestFinished && _highMipRequestFinished) {
+    Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
+
+    if (_ktxHeaderRequestFinished && _ktxHighMipRequestFinished) {
+
         ResourceCache::requestCompleted(_self);
 
-        if (_ktxHeaderData.size() == 0 || _ktxHighMipData.size() == 0) {
-            finishedLoading(false);
+        if (_ktxHeaderRequest->getResult() != ResourceRequest::Success || _ktxMipRequest->getResult() != ResourceRequest::Success) {
+            if (handleFailedRequest(_ktxMipRequest->getResult())) {
+                _ktxResourceState = PENDING_INITIAL_LOAD;
+            } else {
+                _ktxResourceState = FAILED_TO_LOAD;
+            }
+
+            _ktxHeaderRequest->deleteLater();
+            _ktxHeaderRequest = nullptr;
+            _ktxMipRequest->deleteLater();
+            _ktxMipRequest = nullptr;
         } else {
             // create ktx...
-            auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
+            auto ktxHeaderData = _ktxHeaderRequest->getData();
+            auto ktxHighMipData = _ktxMipRequest->getData();
+
+            _ktxHeaderRequest->deleteLater();
+            _ktxHeaderRequest = nullptr;
+            _ktxMipRequest->deleteLater();
+            _ktxMipRequest = nullptr;
+
+            auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
 
             qDebug() << "Creating KTX";
             qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
@@ -507,14 +522,16 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
             qDebug() << "numberOfFaces:" << header->numberOfFaces;
             qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
+
             auto kvSize = header->bytesOfKeyValueData;
-            if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
+            if (kvSize > ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
                 qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
+                _ktxResourceState = FAILED_TO_LOAD;
                 finishedLoading(false);
                 return;
             }
 
-            auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(_ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
+            auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
 
             auto imageDescriptors = header->generateImageDescriptors();
             _originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
@@ -532,6 +549,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             else {
                 if (found->_value.size() < 32) {
                     qWarning("Invalid source hash key found, bailing");
+                    _ktxResourceState = FAILED_TO_LOAD;
                     finishedLoading(false);
                     return;
                 } else {
@@ -553,10 +571,10 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             auto& ktxCache = textureCache->_ktxCache;
             if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
                 qCWarning(modelnetworking) << _url << " failed to write cache file";
+                _ktxResourceState = FAILED_TO_LOAD;
                 finishedLoading(false);
                 return;
-            }
-            else {
+            } else {
                 _file = file;
             }
 
@@ -571,9 +589,9 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             texture->registerMipInterestListener(this);
 
             auto& images = _originalKtxDescriptor->images;
-            size_t imageSizeRemaining = _ktxHighMipData.size();
-            uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data());
-            ktxData += _ktxHighMipData.size();
+            size_t imageSizeRemaining = ktxHighMipData.size();
+            uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
+            ktxData += ktxHighMipData.size();
             // TODO Move image offset calculation to ktx ImageDescriptor
             int level;
             for (level = images.size() - 1; level >= 0; --level) {
@@ -585,7 +603,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 ktxData -= image._imageSize;
                 texture->assignStoredMip(level, image._imageSize, ktxData);
                 ktxData -= 4;
-                imageSizeRemaining - image._imageSize - 4;
+                imageSizeRemaining -= (image._imageSize + 4);
             }
 
             // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
@@ -604,6 +622,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 }
             }
 
+            _ktxResourceState = WAITING_FOR_MIP_REQUEST;
             setImage(texture, header->getPixelWidth(), header->getPixelHeight());
         }
     }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 3048f16eb8..1ea266a115 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -93,31 +93,31 @@ private:
     image::TextureUsage::Type _type;
 
     static const uint16_t NULL_MIP_LEVEL;
-    struct KTXResourceState {
-        NOT_LOADED = 0,
+    enum KTXResourceState {
+        PENDING_INITIAL_LOAD = 0,
         LOADING_INITIAL_DATA,    // Loading KTX Header + Low Resolution Mips
         WAITING_FOR_MIP_REQUEST, // Waiting for the gpu layer to report that it needs higher resolution mips
         PENDING_MIP_REQUEST,     // We have added ourselves to the ResourceCache queue
-        REQUESTING_MIP           // We have a mip in flight
+        REQUESTING_MIP,          // We have a mip in flight
+        FAILED_TO_LOAD
     };
 
-    KTXResourceState _ktxResourceState{ NOT_LOADED };
+    bool _sourceIsKTX { false };
+    KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
 
+    // TODO Can this be removed?
     KTXFilePointer _file;
 
-    bool _sourceIsKTX { false };
-    bool _ktxHeaderLoaded { false };
-    bool _highMipRequestFinished { false };
-
+    // The current mips that are currently being requested w/ _ktxMipRequest
     std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
 
     ResourceRequest* _ktxHeaderRequest { nullptr };
     ResourceRequest* _ktxMipRequest { nullptr };
-    bool _headerRequestFinished { false };
+    bool _ktxHeaderRequestFinished{ false };
+    bool _ktxHighMipRequestFinished{ false };
+
     uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
     uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
-    QByteArray _ktxHeaderData;
-    QByteArray _ktxHighMipData;
 
     // This is a copy of the original KTX descriptor from the source url.
     // We need this because the KTX that will be cached will likely include extra data
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 6716b7473f..95bfd0e24d 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -475,12 +475,6 @@ int ResourceCache::getLoadingRequestCount() {
 bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
     Q_ASSERT(!resource.isNull());
 
-    if (resource->_pending) {
-        qWarning(networking) << "Attempted to request " << resource->getURL() << " but it was already pending";
-        return false;
-    }
-
-    resource->_pending = true;
 
     auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
     if (_requestsActive >= _requestLimit) {
@@ -498,11 +492,6 @@ bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
 void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
     auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
 
-    auto sharedResource = resource.lock();
-    if (sharedResource) {
-        sharedResource->_pending = true;
-    }
-
     sharedItems->removeRequest(resource);
     --_requestsActive;
 
@@ -747,7 +736,8 @@ void Resource::handleReplyFinished() {
     _request = nullptr;
 }
 
-void Resource::handleFailedRequest(ResourceRequest::Result result) {
+bool Resource::handleFailedRequest(ResourceRequest::Result result) {
+    bool willRetry = false;
     switch (result) {
         case ResourceRequest::Result::Timeout: {
             qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
@@ -763,6 +753,7 @@ void Resource::handleFailedRequest(ResourceRequest::Result result) {
                     << "if resource is still needed";
 
                 QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
+                willRetry = true;
                 break;
             }
             // fall through to final failure
@@ -772,10 +763,12 @@ void Resource::handleFailedRequest(ResourceRequest::Result result) {
             auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
                                                               : QNetworkReply::UnknownNetworkError;
             emit failed(error);
+            willRetry = false;
             finishedLoading(false);
             break;
         }
     }
+    return willRetry;
 }
 
 uint qHash(const QPointer<QObject>& value, uint seed) {
diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h
index 13162aac9d..3a28c6c313 100644
--- a/libraries/networking/src/ResourceCache.h
+++ b/libraries/networking/src/ResourceCache.h
@@ -445,7 +445,8 @@ protected:
 
     Q_INVOKABLE void allReferencesCleared();
 
-    void handleFailedRequest(ResourceRequest::Result result);
+    /// Return true if the resource will be retried
+    bool handleFailedRequest(ResourceRequest::Result result);
 
     QUrl _url;
     QUrl _activeUrl;
@@ -464,8 +465,6 @@ protected:
     int _requestID;
     ResourceRequest* _request{ nullptr };
 
-    bool _pending{ false };
-    
 public slots:
     void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
     void handleReplyFinished();

From 5594e81fe40779c5c20c1c3646d9e19464904dab Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 18 Apr 2017 17:03:54 -0700
Subject: [PATCH 15/85] Improve gl backend handling of unavailable mips

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp          |  1 +
 libraries/gpu-gl/src/gpu/gl/GLTexture.h            |  1 +
 libraries/gpu-gl/src/gpu/gl41/GL41Backend.h        |  1 +
 .../gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp     | 14 ++++++++++----
 libraries/gpu-gl/src/gpu/gl45/GL45Backend.h        |  1 +
 .../gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp     | 10 ++++++++++
 .../src/gpu/gl45/GL45BackendVariableTexture.cpp    | 10 +++++++++-
 libraries/gpu/src/gpu/Texture.h                    |  4 ++++
 libraries/gpu/src/gpu/Texture_ktx.cpp              |  7 ++++++-
 libraries/ktx/src/ktx/KTX.cpp                      |  6 ++++--
 .../src/model-networking/TextureCache.cpp          | 14 +++++++++++---
 11 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index b7d2ee0b0f..3853d0a9cc 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -520,6 +520,7 @@ void GLVariableAllocationSupport::processWorkQueues() {
             _memoryPressureStateStale = true;
         } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) {
             if (!vartexture->canPromote()) {
+                vartexture->populateTransferQueue();
                 continue;
             }
             vartexture->promote();
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
index 8b4b545b7d..c8ec4c5fe6 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
@@ -116,6 +116,7 @@ protected:
     bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
     bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
     void executeNextTransfer(const TexturePointer& currentTexture);
+    virtual bool canPopulate() const = 0;
     virtual void populateTransferQueue() = 0;
     virtual void promote() = 0;
     virtual void demote() = 0;
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
index 19979a1778..c0b9ea0e45 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
@@ -100,6 +100,7 @@ public:
         GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
         ~GL41VariableAllocationTexture();
 
+        bool canPopulate() const override { return _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
         void allocateStorage(uint16 allocatedMip);
         void syncSampler() const override;
         void promote() override;
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index bff5bf3f2c..04c2201c75 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -392,8 +392,10 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
+        bool didQueueTransfer = false;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
+                const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
                 continue;
             }
 
@@ -401,6 +403,7 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
             if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
                 // Can the mip be transferred in one go
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
+                didQueueTransfer = true;
                 continue;
             }
 
@@ -416,14 +419,17 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
                 uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
                 lineOffset += linesToCopy;
+                didQueueTransfer = true;
             }
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
-        _pendingTransfers.emplace(new TransferJob(*this, [=] {
-            _populatedMip = sourceMip;
-            syncSampler();
-        }));
+        if (didQueueTransfer) {
+            _pendingTransfers.emplace(new TransferJob(*this, [=] {
+                _populatedMip = sourceMip;
+                syncSampler();
+            }));
+        }
     } while (sourceMip != _allocatedMip);
 }
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
index dbedd81c76..15e98c3af7 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
@@ -100,6 +100,7 @@ public:
         GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
         ~GL45VariableAllocationTexture();
         Size size() const override { return _size; }
+        bool canPopulate() const override { return _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
         Size _size { 0 };
     };
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index a539b76b6c..54b2411a14 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -80,6 +80,16 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
             default:
                 Q_UNREACHABLE();
         }
+    } else {
+
+        if (texture.getUsageType() == TextureUsageType::RESOURCE) {
+            auto varTex = static_cast<GL45VariableAllocationTexture*> (object);
+
+            if (varTex->canPromoteAndPopulate()) {
+                GL45VariableAllocationTexture::_memoryPressureStateStale = true;
+            }
+
+        }
     }
 
     return object;
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 09361689d8..ac2b5f607f 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -62,6 +62,7 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
     //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
     allocateStorage(allocatedMip);
+    _memoryPressureStateStale = true;
     copyMipsFromTexture();
     syncSampler();
 
@@ -105,7 +106,12 @@ void GL45ResourceTexture::syncSampler() const {
 
 void GL45ResourceTexture::promote() {
     PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
-    Q_ASSERT(_allocatedMip > 0);
+    //Q_ASSERT(_allocatedMip > 0);
+    uint16_t sourceMip = _populatedMip;
+    if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, 0)) {
+        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
+        return;
+    }
     GLuint oldId = _id;
     auto oldSize = _size;
     // create new texture
@@ -191,6 +197,7 @@ void GL45ResourceTexture::populateTransferQueue() {
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
+                _minRequestedMip = sourceMip;
                 continue;
             }
 
@@ -218,6 +225,7 @@ void GL45ResourceTexture::populateTransferQueue() {
                 lineOffset += linesToCopy;
                 transferQueued = true;
             }
+            _minRequestedMip = std::min(_minRequestedMip, sourceMip);
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index e71cd63fbd..6e13598caa 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -275,6 +275,7 @@ public:
         virtual void assignMipData(uint16 level, const storage::StoragePointer& storage) = 0;
         virtual void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) = 0;
         virtual bool isMipAvailable(uint16 level, uint8 face = 0) const = 0;
+        virtual uint16 minAvailableMipLevel() const { return 0; }
         Texture::Type getType() const { return _type; }
 
         Stamp getStamp() const { return _stamp; }
@@ -315,6 +316,7 @@ public:
         bool isMipAvailable(uint16 level, uint8 face = 0) const override;
         void assignMipData(uint16 level, const storage::StoragePointer& storage) override;
         void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override;
+        uint16 minAvailableMipLevel() const override;
 
         void reset() override { }
 
@@ -332,6 +334,8 @@ public:
         friend class Texture;
     };
 
+    uint16 minAvailableMipLevel() const { return _storage->minAvailableMipLevel(); };
+
     static const uint16 MAX_NUM_MIPS = 0;
     static const uint16 SINGLE_MIP = 1;
     static TexturePointer create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 533797a657..e2b9b8d9ae 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -115,9 +115,14 @@ bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
     return avail;
 }
 
+uint16 KtxStorage::minAvailableMipLevel() const {
+    return _minMipLevelAvailable;
+}
+
 void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
     if (level != _minMipLevelAvailable - 1) {
         qWarning() << "Invalid level to be stored, expected: " << (_minMipLevelAvailable - 1) << ", got: " << level;
+        //throw std::runtime_error("Invalid image size for level");
         return;
     }
 
@@ -128,7 +133,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
         qDebug() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
             << ", filename: " << QString::fromStdString(_filename);
-        //throw std::runtime_error("Invalid image size for level");
+        throw std::runtime_error("Invalid image size for level");
         return;
     }
 
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index d9b5ea4a62..c9dd18d665 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -92,7 +92,8 @@ size_t Header::evalPixelOrBlockSize() const {
         }
     }
     qWarning() << "Unknown ktx format: " << glFormat << " " << glBaseInternalFormat << " " << glInternalFormat;
-    throw std::runtime_error("Unknown format");
+    return 1;
+    //throw std::runtime_error("Unknown format");
 }
 
 size_t Header::evalRowSize(uint32_t level) const {
@@ -130,9 +131,10 @@ ImageDescriptors Header::generateImageDescriptors() const {
             0
         };
 
-        imageOffset += imageSize + 4;
+        imageOffset += (imageSize * numberOfFaces) + 4;
 
         ImageHeader::FaceOffsets offsets;
+        // TODO Add correct face offsets
         for (uint32_t i = 0; i < numberOfFaces; ++i) {
             offsets.push_back(0);
         }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 25d7018a1a..11121570c1 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -388,6 +388,7 @@ void NetworkTexture::makeRequest() {
 }
 
 void NetworkTexture::handleMipInterestCallback(uint16_t level) {
+    //qDebug(networking) << "++++ Got request for mip level: " << _url << " " << level;
     QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(int, level));
 }
 
@@ -466,6 +467,7 @@ void NetworkTexture::ktxMipRequestFinished() {
             auto texture = _textureSource->getGPUTexture();
             if (texture) {
                 _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
+                qDebug() << "Writing mip for " << _url;
                 texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                     _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
             } else {
@@ -542,9 +544,10 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             });
             std::string filename;
             if (found == keyValues.end()) {
-                qWarning("Source hash key not found, bailing");
-                finishedLoading(false);
-                return;
+                //qWarning("Source hash key not found, bailing");
+                //finishedLoading(false);
+                //return;
+                filename = _url.fileName().toStdString();
             }
             else {
                 if (found->_value.size() < 32) {
@@ -558,6 +561,11 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             }
 
             auto memKtx = ktx::KTX::createBare(*header, keyValues);
+            if (!memKtx) {
+                qWarning() << " Ktx could not be created, bailing";
+                finishedLoading(false);
+                return;
+            }
 
             //auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
             //memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());

From 70eaac8d6c5b509d3e395f2595e41011ee129f71 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 18 Apr 2017 21:16:17 -0700
Subject: [PATCH 16/85] Add persisting of ktx min mips available to ktx cache
 file

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp            |  6 +++---
 libraries/gpu/src/gpu/Texture_ktx.cpp                |  4 ++++
 libraries/ktx/src/ktx/KTX.cpp                        | 12 ++++++++++++
 libraries/ktx/src/ktx/KTX.h                          |  1 +
 .../src/model-networking/TextureCache.cpp            |  3 ++-
 5 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index 3853d0a9cc..c82fa4c744 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -458,12 +458,12 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
     float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
 
     auto newState = MemoryPressureState::Idle;
-    if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
+    if (hasTransfers) {
+        newState = MemoryPressureState::Transfer;
+    } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
         newState = MemoryPressureState::Oversubscribed;
     } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) {
         newState = MemoryPressureState::Undersubscribed;
-    } else if (hasTransfers) {
-        newState = MemoryPressureState::Transfer;
     }
 
     if (newState != _memoryPressureState) {
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index e2b9b8d9ae..c0fe3f5c9c 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -155,6 +155,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     data += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
     data += 4;
 
+    auto offset = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
     {
         std::lock_guard<std::mutex> lock { _cacheFileWriteMutex };
 
@@ -165,6 +166,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
 
         memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
         _minMipLevelAvailable = level;
+        if (offset > 0) {
+            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + offset, (uint8_t)_minMipLevelAvailable, 1);
+        }
     }
 }
 
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index c9dd18d665..68b83b1682 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -118,6 +118,18 @@ size_t Header::evalImageSize(uint32_t level) const {
     }
 }
 
+
+size_t KTXDescriptor::getValueOffsetForKey(const std::string& key) const {
+    size_t offset { 0 };
+    for (auto& kv : keyValues) {
+        if (kv._key == key) {
+            return offset + kv._key.size() + 1;
+        }
+        offset += kv.serializedByteSize();
+    }
+    return 0;
+}
+
 ImageDescriptors Header::generateImageDescriptors() const {
     ImageDescriptors descriptors;
 
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 7f6f2da939..65ecc430b9 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -471,6 +471,7 @@ namespace ktx {
         const ImageDescriptors images;
         size_t getMipFaceTexelsSize(uint16_t mip = 0, uint8_t face = 0) const;
         size_t getMipFaceTexelsOffset(uint16_t mip = 0, uint8_t face = 0) const;
+        size_t getValueOffsetForKey(const std::string& key) const;
     };
 
     class KTX {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 11121570c1..8b6530b5b9 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -377,8 +377,8 @@ void NetworkTexture::makeRequest() {
 
         startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
     } else if (_ktxResourceState == PENDING_MIP_REQUEST) {
-        _ktxResourceState = REQUESTING_MIP;
         if (_lowestKnownPopulatedMip > 0) {
+            _ktxResourceState = REQUESTING_MIP;
             startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1);
         }
     } else {
@@ -393,6 +393,7 @@ void NetworkTexture::handleMipInterestCallback(uint16_t level) {
 }
 
 void NetworkTexture::handleMipInterestLevel(int level) {
+    _lowestRequestedMipLevel = std::min((uint16_t)level, _lowestRequestedMipLevel);
     startRequestForNextMipLevel();
 }
 

From ce12a216c07bc654381eb42e4816f2f8e3812360 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 19 Apr 2017 01:20:03 -0700
Subject: [PATCH 17/85] I think it might be working.

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp     |   3 +
 libraries/gpu-gl/src/gpu/gl/GLTexture.h       |   2 +-
 libraries/gpu/src/gpu/Texture_ktx.cpp         |   5 +-
 .../src/model-networking/ModelCache.cpp       |   1 +
 .../src/model-networking/TextureCache.cpp     | 147 ++++++++++--------
 .../networking/src/HTTPResourceRequest.cpp    |   3 +-
 6 files changed, 90 insertions(+), 71 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index c82fa4c744..652dc3c46f 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -459,10 +459,13 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
 
     auto newState = MemoryPressureState::Idle;
     if (hasTransfers) {
+        qDebug() << "Transferring";
         newState = MemoryPressureState::Transfer;
     } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
+        qDebug() << "Demoting";
         newState = MemoryPressureState::Oversubscribed;
     } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) {
+        qDebug() << "Promoting";
         newState = MemoryPressureState::Undersubscribed;
     }
 
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
index c8ec4c5fe6..7f3431b7eb 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
@@ -112,7 +112,7 @@ protected:
     static void manageMemory();
 
     //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
-    bool canPromote() const { return _allocatedMip > 0; }
+    bool canPromote() const { return _allocatedMip > 0 || _populatedMip > 0; }
     bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
     bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
     void executeNextTransfer(const TexturePointer& currentTexture);
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index c0fe3f5c9c..a7af329505 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -51,6 +51,9 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
         ktx::StoragePointer storage{ new storage::FileStorage(_filename.c_str()) };
         auto ktxPointer = ktx::KTX::create(storage);
         _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
+        if (_ktxDescriptor->images.size() < _ktxDescriptor->header.numberOfMipmapLevels) {
+            qDebug() << "bad images found";
+        }
         auto& keyValues = _ktxDescriptor->keyValues;
         auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
             return val._key.compare(ktx::HIFI_MIN_POPULATED_MIP_KEY) == 0;
@@ -167,7 +170,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
         memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
         _minMipLevelAvailable = level;
         if (offset > 0) {
-            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + offset, (uint8_t)_minMipLevelAvailable, 1);
+            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + offset, (void*)&_minMipLevelAvailable, 1);
         }
     }
 }
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index 623832aaa8..defe868abc 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -364,6 +364,7 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
 
 bool Geometry::areTexturesLoaded() const {
     if (!_areTexturesLoaded) {
+        qDebug() << "Textures not loaded for " << _fbxGeometry->originalURL;
         for (auto& material : _materials) {
             // Check if material textures are loaded
             bool materialMissingTexture = std::any_of(material->_textures.cbegin(), material->_textures.cend(),
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 8b6530b5b9..4c40075daa 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -422,7 +422,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
 
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
-    qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
+    //qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
 
     _ktxMipLevelRangeInFlight = { low, high };
     if (isHighMipRequest) {
@@ -460,6 +460,7 @@ void NetworkTexture::ktxMipRequestFinished() {
         _ktxHighMipRequestFinished = true;
         maybeHandleFinishedInitialLoad();
     } else if (_ktxResourceState == REQUESTING_MIP) {
+        Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
         ResourceCache::requestCompleted(_self);
 
         if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
@@ -468,20 +469,26 @@ void NetworkTexture::ktxMipRequestFinished() {
             auto texture = _textureSource->getGPUTexture();
             if (texture) {
                 _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
-                qDebug() << "Writing mip for " << _url;
+                //qDebug() << "Writing mip for " << _url;
                 texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                     _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
             } else {
                 qWarning(networking) << "Trying to update mips but texture is null";
             }
+            finishedLoading(true);
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;
         } else {
             _ktxResourceState = PENDING_MIP_REQUEST;
+            finishedLoading(false);
             handleFailedRequest(_ktxMipRequest->getResult());
         }
 
         _ktxMipRequest->deleteLater();
         _ktxMipRequest = nullptr;
+
+        if (_ktxResourceState == WAITING_FOR_MIP_REQUEST && _lowestRequestedMipLevel < _lowestKnownPopulatedMip) {
+            startRequestForNextMipLevel();
+        }
     } else {
         qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
     }
@@ -544,85 +551,88 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
             });
             std::string filename;
-            if (found == keyValues.end()) {
-                //qWarning("Source hash key not found, bailing");
-                //finishedLoading(false);
-                //return;
-                filename = _url.fileName().toStdString();
-            }
-            else {
-                if (found->_value.size() < 32) {
-                    qWarning("Invalid source hash key found, bailing");
-                    _ktxResourceState = FAILED_TO_LOAD;
-                    finishedLoading(false);
-                    return;
-                } else {
-                    filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
-                }
-            }
-
-            auto memKtx = ktx::KTX::createBare(*header, keyValues);
-            if (!memKtx) {
-                qWarning() << " Ktx could not be created, bailing";
-                finishedLoading(false);
-                return;
-            }
-
-            //auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
-            //memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
-
-            auto textureCache = DependencyManager::get<TextureCache>();
-
-            // Move ktx to file
-            const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
-            size_t length = memKtx->_storage->size();
-            KTXFilePointer file;
-            auto& ktxCache = textureCache->_ktxCache;
-            if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
-                qCWarning(modelnetworking) << _url << " failed to write cache file";
+            std::string hash;
+            if (found == keyValues.end() || found->_value.size() != 32) {
+                qWarning("Invalid source hash key found, bailing");
                 _ktxResourceState = FAILED_TO_LOAD;
                 finishedLoading(false);
                 return;
             } else {
-                _file = file;
+                hash = filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
+                //hash = filename = _url.path().replace("/", "_").toStdString();
             }
 
-            //_ktxDescriptor.reset(new ktx::KTXDescriptor(memKtx->toDescriptor()));
-            auto newKtxDescriptor = memKtx->toDescriptor();
+            auto textureCache = DependencyManager::get<TextureCache>();
 
-            //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
-            gpu::TexturePointer texture;
-            texture.reset(gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor));
-            texture->setKtxBacking(file->getFilepath());
-            texture->setSource(filename);
-            texture->registerMipInterestListener(this);
+            gpu::TexturePointer texture = textureCache->getTextureByHash(hash);
 
-            auto& images = _originalKtxDescriptor->images;
-            size_t imageSizeRemaining = ktxHighMipData.size();
-            uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
-            ktxData += ktxHighMipData.size();
-            // TODO Move image offset calculation to ktx ImageDescriptor
-            int level;
-            for (level = images.size() - 1; level >= 0; --level) {
-                auto& image = images[level];
-                if (image._imageSize > imageSizeRemaining) {
-                    break;
+            if (!texture) {
+                KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
+                if (ktxFile) {
+                    texture.reset(gpu::Texture::unserialize(ktxFile->getFilepath()));
+                    if (texture) {
+                        texture = textureCache->cacheTextureByHash(hash, texture);
+                    }
                 }
-                qDebug() << "Transferring " << level;
-                ktxData -= image._imageSize;
-                texture->assignStoredMip(level, image._imageSize, ktxData);
-                ktxData -= 4;
-                imageSizeRemaining -= (image._imageSize + 4);
             }
 
-            // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
-            // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
-            // be the winner
-            if (textureCache) {
+            if (!texture) {
+
+                auto memKtx = ktx::KTX::createBare(*header, keyValues);
+                if (!memKtx) {
+                    qWarning() << " Ktx could not be created, bailing";
+                    finishedLoading(false);
+                    return;
+                }
+
+                // Move ktx to file
+                const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
+                size_t length = memKtx->_storage->size();
+                KTXFilePointer file;
+                auto& ktxCache = textureCache->_ktxCache;
+                if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
+                    qCWarning(modelnetworking) << _url << " failed to write cache file";
+                    _ktxResourceState = FAILED_TO_LOAD;
+                    finishedLoading(false);
+                    return;
+                }
+                else {
+                    _file = file;
+                }
+
+                auto newKtxDescriptor = memKtx->toDescriptor();
+
+                //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
+                texture.reset(gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor));
+                texture->setKtxBacking(file->getFilepath());
+                texture->setSource(filename);
+
+                auto& images = _originalKtxDescriptor->images;
+                size_t imageSizeRemaining = ktxHighMipData.size();
+                uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
+                ktxData += ktxHighMipData.size();
+                // TODO Move image offset calculation to ktx ImageDescriptor
+                int level;
+                for (level = images.size() - 1; level >= 0; --level) {
+                    auto& image = images[level];
+                    if (image._imageSize > imageSizeRemaining) {
+                        break;
+                    }
+                    //qDebug() << "Transferring " << level;
+                    ktxData -= image._imageSize;
+                    texture->assignStoredMip(level, image._imageSize, ktxData);
+                    ktxData -= 4;
+                    imageSizeRemaining -= (image._imageSize + 4);
+                }
+
+                // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 
+                // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
+                // be the winner
                 texture = textureCache->cacheTextureByHash(filename, texture);
-            }
 
 
+            }
+
             _lowestKnownPopulatedMip = _originalKtxDescriptor->header.numberOfMipmapLevels;
             for (uint16_t l = 0; l < 200; l++) {
                 if (texture->isStoredMipFaceAvailable(l)) {
@@ -631,8 +641,11 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 }
             }
 
+
+            texture->registerMipInterestListener(this);
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;
             setImage(texture, header->getPixelWidth(), header->getPixelHeight());
+            qDebug() << "Loaded KTX: " << QString::fromStdString(hash) << " : " << _url;
         }
     }
 }
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 8958eeaf3b..0023ecb09c 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -132,7 +132,7 @@ void HTTPResourceRequest::onRequestFinished() {
                     uint64_t size;
                     std::tie(success, size) = parseContentRangeHeader(contentRangeHeader);
                     if (success) {
-                        qWarning(networking) << "Total http resource size is: " << size;
+                        //qWarning(networking) << "Total http resource size is: " << size;
                         _totalSizeOfResource = size;
                     } else {
                         qWarning(networking) << "Error parsing content-range header: " << contentRangeHeader;
@@ -187,7 +187,6 @@ void HTTPResourceRequest::onRequestFinished() {
 }
 
 void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
-    qDebug() << "Progress: " << _url;
     Q_ASSERT(_state == InProgress);
     
     // We've received data, so reset the timer

From cc7169b754e4656a87f5a134a06c244d53240117 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 19 Apr 2017 11:08:37 -0700
Subject: [PATCH 18/85] Add debug logging for ktx downloads

---
 libraries/gpu/src/gpu/Texture_ktx.cpp                         | 4 ++--
 .../model-networking/src/model-networking/ModelCache.cpp      | 2 +-
 .../model-networking/src/model-networking/TextureCache.cpp    | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index a7af329505..5d85a9f01e 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -135,8 +135,8 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
 
     if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
         qDebug() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
-            << ", filename: " << QString::fromStdString(_filename);
-        throw std::runtime_error("Invalid image size for level");
+            << ", level: " << level << ", filename: " << QString::fromStdString(_filename);
+        //throw std::runtime_error("Invalid image size for level");
         return;
     }
 
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index defe868abc..fc662e32a3 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -364,7 +364,7 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
 
 bool Geometry::areTexturesLoaded() const {
     if (!_areTexturesLoaded) {
-        qDebug() << "Textures not loaded for " << _fbxGeometry->originalURL;
+        //qDebug() << "Textures not loaded for " << _fbxGeometry->originalURL;
         for (auto& material : _materials) {
             // Check if material textures are loaded
             bool materialMissingTexture = std::any_of(material->_textures.cbegin(), material->_textures.cend(),
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 4c40075daa..1a80280546 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -399,7 +399,7 @@ void NetworkTexture::handleMipInterestLevel(int level) {
 
 void NetworkTexture::startRequestForNextMipLevel() {
     if (_lowestKnownPopulatedMip == 0) {
-        qWarning(networking) << "Requesting next mip level but all have been fulfilled";
+        qWarning(networking) << "Requesting next mip level but all have been fulfilled: " << _url;
         return;
     }
 
@@ -422,7 +422,6 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
 
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
-    //qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
 
     _ktxMipLevelRangeInFlight = { low, high };
     if (isHighMipRequest) {
@@ -431,6 +430,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
         range.fromInclusive = -15000;
         _ktxMipRequest->setByteRange(range);
     } else {
+        qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
         ByteRange range;
         range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
                               + _originalKtxDescriptor->images[low]._imageOffset + 4;

From 841d301decaf3cc093bc9684c0f4451ef92287e3 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 19 Apr 2017 11:25:20 -0700
Subject: [PATCH 19/85] Fix build errors due to TexturePointer change

---
 libraries/gpu/src/gpu/Texture.h                               | 2 +-
 libraries/gpu/src/gpu/Texture_ktx.cpp                         | 2 +-
 .../model-networking/src/model-networking/TextureCache.cpp    | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 6e13598caa..adf6a464f7 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -525,7 +525,7 @@ public:
     ExternalUpdates getUpdates() const;
 
     // Serialize ktx header and keyvalues directly to a file, and return a Texture representing that file
-    static Texture* serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues);
+    static TexturePointer serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues);
 
     // Serialize a texture into a KTX file
     static ktx::KTXUniquePointer serialize(const Texture& texture);
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 5d85a9f01e..9a1c633be5 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -370,7 +370,7 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDe
     return tex;
 }
 
-Texture* Texture::serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues) {
+TexturePointer Texture::serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues) {
     // Create a memory-backed KTX object
     auto ktxBuffer = ktx::KTX::createBare(header, keyValues);
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 1a80280546..cf94192442 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -569,7 +569,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             if (!texture) {
                 KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
                 if (ktxFile) {
-                    texture.reset(gpu::Texture::unserialize(ktxFile->getFilepath()));
+                    texture = gpu::Texture::unserialize(ktxFile->getFilepath());
                     if (texture) {
                         texture = textureCache->cacheTextureByHash(hash, texture);
                     }
@@ -603,7 +603,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 auto newKtxDescriptor = memKtx->toDescriptor();
 
                 //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
-                texture.reset(gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor));
+                texture = gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor);
                 texture->setKtxBacking(file->getFilepath());
                 texture->setSource(filename);
 

From 472c888529c96d752cc75e91c5ba405349aa2580 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 19 Apr 2017 13:16:53 -0700
Subject: [PATCH 20/85] Fix handling of failed ktx downloads

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.h       |  2 +-
 libraries/ktx/src/ktx/KTX.cpp                 |  8 +++-
 libraries/ktx/src/ktx/KTX.h                   |  2 +
 .../src/model-networking/TextureCache.cpp     | 44 ++++++++++++++-----
 .../render-utils/src/MeshPartPayload.cpp      |  3 +-
 tests/gpu-test/CMakeLists.txt                 |  2 +-
 6 files changed, 46 insertions(+), 15 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
index 7f3431b7eb..bc8467b808 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
@@ -114,7 +114,7 @@ protected:
     //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
     bool canPromote() const { return _allocatedMip > 0 || _populatedMip > 0; }
     bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
-    bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
+    bool hasPendingTransfers() const { return _pendingTransfers.size() > 0; }
     void executeNextTransfer(const TexturePointer& currentTexture);
     virtual bool canPopulate() const = 0;
     virtual void populateTransferQueue() = 0;
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 68b83b1682..e00937a67a 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -92,13 +92,16 @@ size_t Header::evalPixelOrBlockSize() const {
         }
     }
     qWarning() << "Unknown ktx format: " << glFormat << " " << glBaseInternalFormat << " " << glInternalFormat;
-    return 1;
+    return 0;
     //throw std::runtime_error("Unknown format");
 }
 
 size_t Header::evalRowSize(uint32_t level) const {
     auto pixWidth = evalPixelOrBlockWidth(level);
     auto pixSize = evalPixelOrBlockSize();
+    if (pixSize == 0) {
+        return 0;
+    }
     auto netSize = pixWidth * pixSize;
     auto padding = evalPadding(netSize);
     return netSize + padding;
@@ -136,6 +139,9 @@ ImageDescriptors Header::generateImageDescriptors() const {
     uint32_t imageOffset = 0;
     for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) {
         auto imageSize = static_cast<uint32_t>(evalImageSize(level));
+        if (imageSize == 0) {
+            return ImageDescriptors();
+        }
         ImageHeader header {
             numberOfFaces == NUM_CUBEMAPFACES,
             imageOffset,
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 65ecc430b9..713733b1dd 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -298,6 +298,8 @@ namespace ktx {
     struct ImageDescriptor;
     using ImageDescriptors = std::vector<ImageDescriptor>;
 
+    bool checkIdentifier(const Byte* identifier);
+
     // Header
     struct Header {
         static const size_t IDENTIFIER_LENGTH = 12;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index cf94192442..17f1d62b7b 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -399,14 +399,15 @@ void NetworkTexture::handleMipInterestLevel(int level) {
 
 void NetworkTexture::startRequestForNextMipLevel() {
     if (_lowestKnownPopulatedMip == 0) {
-        qWarning(networking) << "Requesting next mip level but all have been fulfilled: " << _url;
+        qWarning(networking) << "Requesting next mip level but all have been fulfilled: " << _lowestKnownPopulatedMip
+            << " " << _textureSource->getGPUTexture()->minAvailableMipLevel() << " " << _url;
         return;
     }
 
     if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
         _ktxResourceState = PENDING_MIP_REQUEST;
 
-        setLoadPriority(this, _lowestKnownPopulatedMip);
+        setLoadPriority(this, -_originalKtxDescriptor->header.numberOfMipmapLevels + _lowestKnownPopulatedMip);
 
         init();
         ResourceCache::attemptRequest(_self);
@@ -449,8 +450,8 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
 void NetworkTexture::ktxHeaderRequestFinished() {
     Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
 
-    _ktxHeaderRequestFinished = true;
-    maybeHandleFinishedInitialLoad();
+_ktxHeaderRequestFinished = true;
+maybeHandleFinishedInitialLoad();
 }
 
 void NetworkTexture::ktxMipRequestFinished() {
@@ -459,7 +460,8 @@ void NetworkTexture::ktxMipRequestFinished() {
     if (_ktxResourceState == LOADING_INITIAL_DATA) {
         _ktxHighMipRequestFinished = true;
         maybeHandleFinishedInitialLoad();
-    } else if (_ktxResourceState == REQUESTING_MIP) {
+    }
+    else if (_ktxResourceState == REQUESTING_MIP) {
         Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
         ResourceCache::requestCompleted(_self);
 
@@ -472,15 +474,22 @@ void NetworkTexture::ktxMipRequestFinished() {
                 //qDebug() << "Writing mip for " << _url;
                 texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                     _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
-            } else {
+            }
+            else {
                 qWarning(networking) << "Trying to update mips but texture is null";
             }
             finishedLoading(true);
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;
-        } else {
-            _ktxResourceState = PENDING_MIP_REQUEST;
+        }
+        else {
             finishedLoading(false);
-            handleFailedRequest(_ktxMipRequest->getResult());
+            if (handleFailedRequest(_ktxMipRequest->getResult())) {
+                _ktxResourceState = PENDING_MIP_REQUEST;
+            }
+            else {
+                qWarning() << "Failed to load mip: " << _url;
+                _ktxResourceState = FAILED_TO_LOAD;
+            }
         }
 
         _ktxMipRequest->deleteLater();
@@ -489,7 +498,8 @@ void NetworkTexture::ktxMipRequestFinished() {
         if (_ktxResourceState == WAITING_FOR_MIP_REQUEST && _lowestRequestedMipLevel < _lowestKnownPopulatedMip) {
             startRequestForNextMipLevel();
         }
-    } else {
+    }
+    else {
         qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
     }
 }
@@ -505,7 +515,8 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
         if (_ktxHeaderRequest->getResult() != ResourceRequest::Success || _ktxMipRequest->getResult() != ResourceRequest::Success) {
             if (handleFailedRequest(_ktxMipRequest->getResult())) {
                 _ktxResourceState = PENDING_INITIAL_LOAD;
-            } else {
+            }
+            else {
                 _ktxResourceState = FAILED_TO_LOAD;
             }
 
@@ -533,6 +544,12 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             qDebug() << "numberOfFaces:" << header->numberOfFaces;
             qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
 
+            if (!ktx::checkIdentifier(header->identifier)) {
+                qWarning() << "Cannot load " << _url << ", invalid header identifier";
+                _ktxResourceState = FAILED_TO_LOAD;
+                finishedLoading(false);
+            }
+
             auto kvSize = header->bytesOfKeyValueData;
             if (kvSize > ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
                 qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
@@ -544,6 +561,11 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
 
             auto imageDescriptors = header->generateImageDescriptors();
+            if (imageDescriptors.size() == 0) {
+                qWarning(networking) << "Failed to process ktx file " << _url;
+                _ktxResourceState = FAILED_TO_LOAD;
+                finishedLoading(false);
+            }
             _originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
 
             // Create bare ktx in memory
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 51ce0fffa7..e6ae6d21a1 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -544,7 +544,8 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
     }
 
     if (_fadeState == FADE_WAITING_TO_START) {
-        if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
+        //if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
+        if (_model->isLoaded()) {
             if (EntityItem::getEntitiesShouldFadeFunction()()) {
                 _fadeStartTime = usecTimestampNow();
                 _fadeState = FADE_IN_PROGRESS;
diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt
index 1712a5a3e1..c37e36b53b 100644
--- a/tests/gpu-test/CMakeLists.txt
+++ b/tests/gpu-test/CMakeLists.txt
@@ -3,7 +3,7 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils)
 # This is not a testcase -- just set it up as a regular hifi project
 setup_hifi_project(Quick Gui OpenGL Script Widgets)
 set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
-link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils octree image)
+link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils octree image ktx)
 package_libraries_for_deployment()
 
 target_nsight()

From d5f1e6fb373e7c004353fd0d771da8996f3aeb08 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 19 Apr 2017 17:01:36 -0700
Subject: [PATCH 21/85] Adjust handling of requested mips in gl backend

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp     |  2 +-
 .../gpu/gl45/GL45BackendVariableTexture.cpp   |  8 ++---
 libraries/gpu/src/gpu/Texture_ktx.cpp         |  3 +-
 libraries/ktx/src/ktx/Reader.cpp              |  2 +-
 .../src/model-networking/TextureCache.cpp     | 32 +++++++++++++------
 5 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index 652dc3c46f..1179403bff 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -450,7 +450,7 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
         // Track how much we're actually using
         totalVariableMemoryAllocation += gltexture->size();
         canDemote |= vartexture->canDemote();
-        canPromote |= vartexture->canPromote();
+        canPromote |= vartexture->canPromote() || (texture->minAvailableMipLevel() < vartexture->_allocatedMip);
         hasTransfers |= vartexture->hasPendingTransfers();
     }
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index ac2b5f607f..f99209a7c2 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -98,18 +98,14 @@ void GL45ResourceTexture::copyMipsFromTexture() {
 void GL45ResourceTexture::syncSampler() const {
     Parent::syncSampler();
     qDebug() << "glTextureParameteri " << QString::fromStdString(_source) << _populatedMip << _populatedMip - _allocatedMip;
-    if (_source == "test" && _populatedMip == 0) {
-        qDebug() << "here";
-    }
     glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
 }
 
 void GL45ResourceTexture::promote() {
     PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
     //Q_ASSERT(_allocatedMip > 0);
-    uint16_t sourceMip = _populatedMip;
-    if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, 0)) {
-        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
+    if (!_gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0)) {
+        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(_populatedMip - 1);
         return;
     }
     GLuint oldId = _id;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 9a1c633be5..620fd56277 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -123,8 +123,9 @@ uint16 KtxStorage::minAvailableMipLevel() const {
 }
 
 void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
+    qDebug() << "Populating " << level << " " << _filename.c_str();
     if (level != _minMipLevelAvailable - 1) {
-        qWarning() << "Invalid level to be stored, expected: " << (_minMipLevelAvailable - 1) << ", got: " << level;
+        qWarning() << "Invalid level to be stored, expected: " << (_minMipLevelAvailable - 1) << ", got: " << level << " " << _filename.c_str();
         //throw std::runtime_error("Invalid image size for level");
         return;
     }
diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp
index b22f262e85..27aee8771c 100644
--- a/libraries/ktx/src/ktx/Reader.cpp
+++ b/libraries/ktx/src/ktx/Reader.cpp
@@ -50,7 +50,7 @@ namespace ktx {
 
     bool checkIdentifier(const Byte* identifier) {
         if (!(0 == memcmp(identifier, Header::IDENTIFIER.data(), Header::IDENTIFIER_LENGTH))) {
-            throw ReaderException("identifier field invalid");
+            //throw ReaderException("identifier field invalid");
             return false;
         }
         return true;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 17f1d62b7b..84e9c82946 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -353,7 +353,8 @@ void NetworkTexture::makeRequest() {
         _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
 
         if (!_ktxHeaderRequest) {
-            //qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
+            qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
+
             PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
             return;
         }
@@ -424,6 +425,13 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
 
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
 
+    if (!_ktxMipRequest) {
+        qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
+
+        PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
+        return;
+    }
+
     _ktxMipLevelRangeInFlight = { low, high };
     if (isHighMipRequest) {
         // This is a special case where we load the high 7 mips
@@ -450,8 +458,8 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
 void NetworkTexture::ktxHeaderRequestFinished() {
     Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
 
-_ktxHeaderRequestFinished = true;
-maybeHandleFinishedInitialLoad();
+    _ktxHeaderRequestFinished = true;
+    maybeHandleFinishedInitialLoad();
 }
 
 void NetworkTexture::ktxMipRequestFinished() {
@@ -470,10 +478,11 @@ void NetworkTexture::ktxMipRequestFinished() {
 
             auto texture = _textureSource->getGPUTexture();
             if (texture) {
-                _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
+                //_lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
                 //qDebug() << "Writing mip for " << _url;
                 texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                     _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
+                _lowestKnownPopulatedMip = _textureSource->getGPUTexture()->minAvailableMipLevel();
             }
             else {
                 qWarning(networking) << "Trying to update mips but texture is null";
@@ -529,11 +538,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             auto ktxHeaderData = _ktxHeaderRequest->getData();
             auto ktxHighMipData = _ktxMipRequest->getData();
 
-            _ktxHeaderRequest->deleteLater();
-            _ktxHeaderRequest = nullptr;
-            _ktxMipRequest->deleteLater();
-            _ktxMipRequest = nullptr;
-
             auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
 
             qDebug() << "Creating KTX";
@@ -548,10 +552,11 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 qWarning() << "Cannot load " << _url << ", invalid header identifier";
                 _ktxResourceState = FAILED_TO_LOAD;
                 finishedLoading(false);
+                return;
             }
 
             auto kvSize = header->bytesOfKeyValueData;
-            if (kvSize > ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
+            if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) {
                 qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
                 _ktxResourceState = FAILED_TO_LOAD;
                 finishedLoading(false);
@@ -663,11 +668,18 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 }
             }
 
+            _lowestKnownPopulatedMip = texture->minAvailableMipLevel();
+
 
             texture->registerMipInterestListener(this);
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;
             setImage(texture, header->getPixelWidth(), header->getPixelHeight());
             qDebug() << "Loaded KTX: " << QString::fromStdString(hash) << " : " << _url;
+
+            _ktxHeaderRequest->deleteLater();
+            _ktxHeaderRequest = nullptr;
+            _ktxMipRequest->deleteLater();
+            _ktxMipRequest = nullptr;
         }
     }
 }

From 18fb695614c007af22f73414af13b0d12d194230 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 20 Apr 2017 15:26:05 -0700
Subject: [PATCH 22/85] Disable pipelining in HTTPResourceRequest

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

diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 0023ecb09c..7045894633 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -71,7 +71,7 @@ void HTTPResourceRequest::doSend() {
         qDebug() << "Setting http range to " << byteRange;
         networkRequest.setRawHeader("Range", byteRange.toLatin1());
     }
-    networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
+    networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, false);
 
     _reply = NetworkAccessManager::getInstance().get(networkRequest);
     

From 4e583416312e4fa6be22158ad63347d4958b1f90 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 20 Apr 2017 16:28:57 -0700
Subject: [PATCH 23/85] Remove http proxy

---
 interface/src/Application.cpp                     | 9 ---------
 libraries/networking/src/NetworkAccessManager.cpp | 9 +--------
 2 files changed, 1 insertion(+), 17 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 886487603e..f51c3d2b46 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -188,7 +188,6 @@
 #include <src/scripting/LimitlessVoiceRecognitionScriptingInterface.h>
 #include <EntityScriptClient.h>
 #include <ModelScriptingInterface.h>
-#include <QtNetwork/QNetworkProxy>
 
 // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
 // FIXME seems to be broken.
@@ -605,7 +604,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     {
         const QString TEST_SCRIPT = "--testScript";
         const QString TRACE_FILE = "--traceFile";
-        const QString HTTP_PROXY = "--httpProxy";
         const QStringList args = arguments();
         for (int i = 0; i < args.size() - 1; ++i) {
             if (args.at(i) == TEST_SCRIPT) {
@@ -617,17 +615,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
                 QString traceFilePath = args.at(i + 1);
                 setProperty(hifi::properties::TRACING, traceFilePath);
                 DependencyManager::get<tracing::Tracer>()->startTracing();
-            } else if (args.at(i) == HTTP_PROXY) {
             }
         }
     }
 
-    QNetworkProxy proxy;
-    proxy.setType(QNetworkProxy::HttpProxy);
-    proxy.setHostName("127.0.0.1");
-    proxy.setPort(8888);
-    QNetworkProxy::setApplicationProxy(proxy);
-
     // make sure the debug draw singleton is initialized on the main thread.
     DebugDraw::getInstance().removeMarker("");
 
diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp
index 6895118be5..fd356c3e94 100644
--- a/libraries/networking/src/NetworkAccessManager.cpp
+++ b/libraries/networking/src/NetworkAccessManager.cpp
@@ -19,14 +19,7 @@ QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
 
 QNetworkAccessManager& NetworkAccessManager::getInstance() {
     if (!networkAccessManagers.hasLocalData()) {
-        auto nm = new QNetworkAccessManager();
-        networkAccessManagers.setLocalData(nm);
-
-        QNetworkProxy proxy;
-        proxy.setType(QNetworkProxy::HttpProxy);
-        proxy.setHostName("127.0.0.1");
-        proxy.setPort(8888);
-        nm->setProxy(proxy);
+        networkAccessManagers.setLocalData(new QNetworkAccessManager());
     }
     
     return *networkAccessManagers.localData();

From 7cab70debac07e44a443ed01ff2ae558e0c0ba9d Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 20 Apr 2017 23:17:42 -0700
Subject: [PATCH 24/85] Remove gl error logging

---
 libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp
index 008b658205..1d1f92b297 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp
@@ -259,9 +259,7 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) {
         glActiveTexture(GL_TEXTURE0 + slot);
         glBindTexture(target, to);
 
-        if (CHECK_GL_ERROR()) {
-            qDebug() << "slot: " << slot << ", target: " << target << ", to: " << to;
-        }
+        (void) CHECK_GL_ERROR();
 
         _resource._textures[slot] = resourceTexture;
 

From b2ff0a711e4e9319b937363c7c06c81237312efc Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 20 Apr 2017 23:30:42 -0700
Subject: [PATCH 25/85] Replace canPromoteAndPopulate with multiple function
 calls

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

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index 54b2411a14..761cd305bb 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -85,7 +85,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
         if (texture.getUsageType() == TextureUsageType::RESOURCE) {
             auto varTex = static_cast<GL45VariableAllocationTexture*> (object);
 
-            if (varTex->canPromoteAndPopulate()) {
+            if (varTex->canPromote() && varTex->canPopulate()) {
                 GL45VariableAllocationTexture::_memoryPressureStateStale = true;
             }
 

From 790290f40ca7faa829aeb7d9a60cc8b759317b78 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 20 Apr 2017 23:31:10 -0700
Subject: [PATCH 26/85] Fix NetworkTexture build errors on osx/linux

---
 .../model-networking/src/model-networking/TextureCache.cpp  | 6 +++---
 .../model-networking/src/model-networking/TextureCache.h    | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 84e9c82946..0bfca1f1fb 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -411,7 +411,7 @@ void NetworkTexture::startRequestForNextMipLevel() {
         setLoadPriority(this, -_originalKtxDescriptor->header.numberOfMipmapLevels + _lowestKnownPopulatedMip);
 
         init();
-        ResourceCache::attemptRequest(_self);
+        TextureCache::attemptRequest(_self);
     }
 }
 
@@ -471,7 +471,7 @@ void NetworkTexture::ktxMipRequestFinished() {
     }
     else if (_ktxResourceState == REQUESTING_MIP) {
         Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
-        ResourceCache::requestCompleted(_self);
+        TextureCache::requestCompleted(_self);
 
         if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
             Q_ASSERT(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
@@ -519,7 +519,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
 
     if (_ktxHeaderRequestFinished && _ktxHighMipRequestFinished) {
 
-        ResourceCache::requestCompleted(_self);
+        TextureCache::requestCompleted(_self);
 
         if (_ktxHeaderRequest->getResult() != ResourceRequest::Success || _ktxMipRequest->getResult() != ResourceRequest::Success) {
             if (handleFailedRequest(_ktxMipRequest->getResult())) {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 1ea266a115..5fb23c0d2d 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -46,7 +46,7 @@ class NetworkTexture : public Resource, public Texture, public gpu::Texture::Mip
 
 public:
     NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels);
-    NetworkTexture::~NetworkTexture() override;
+    ~NetworkTexture() override;
 
     QString getType() const override { return "NetworkTexture"; }
 

From 5901ab63812eb136c7f58d98a0625064aca86073 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Thu, 20 Apr 2017 23:38:22 -0700
Subject: [PATCH 27/85] Fix initalization order warnings in KTX.h and
 TextureCache.cpp

---
 libraries/ktx/src/ktx/KTX.h                                   | 2 +-
 .../model-networking/src/model-networking/TextureCache.cpp    | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 713733b1dd..e84cba085d 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -419,8 +419,8 @@ namespace ktx {
 
         // This is the byte offset from the _start_ of the image region. For example, level 0
         // will have a byte offset of 0.
-        const uint32_t _imageOffset;
         const uint32_t _numFaces;
+        const uint32_t _imageOffset;
         const uint32_t _imageSize;
         const uint32_t _faceSize;
         const uint32_t _padding;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 0bfca1f1fb..05cfc7c06e 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -272,8 +272,8 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSh
 NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
     Resource(url),
     _type(type),
-    _maxNumPixels(maxNumPixels),
-    _sourceIsKTX(url.path().endsWith(".ktx"))
+    _sourceIsKTX(url.path().endsWith(".ktx")),
+    _maxNumPixels(maxNumPixels)
 {
     _textureSource = std::make_shared<gpu::TextureSource>();
 

From bc6d476a4d2c2cb7aab644f815f332974cf18d9b Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 21 Apr 2017 00:21:25 -0700
Subject: [PATCH 28/85] Cleanup Texture_ktx.cpp

---
 libraries/gpu/src/gpu/Texture_ktx.cpp | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 620fd56277..250b9ea8f3 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -52,7 +52,7 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
         auto ktxPointer = ktx::KTX::create(storage);
         _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
         if (_ktxDescriptor->images.size() < _ktxDescriptor->header.numberOfMipmapLevels) {
-            qDebug() << "bad images found";
+            qWarning() << "Bad images found in ktx";
         }
         auto& keyValues = _ktxDescriptor->keyValues;
         auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
@@ -96,7 +96,6 @@ std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() {
 }
 
 PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
-    //qDebug() << "getMipFace: " << QString::fromStdString(_filename) << ": " << level << " " << face;
     storage::StoragePointer result;
     auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
     auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
@@ -112,10 +111,7 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
 
 
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
-    auto avail = level >= _minMipLevelAvailable;
-    //qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << _minMipLevelAvailable << " " << _ktxDescriptor->header.numberOfMipmapLevels;
-    //return true;
-    return avail;
+    return level >= _minMipLevelAvailable;
 }
 
 uint16 KtxStorage::minAvailableMipLevel() const {
@@ -123,10 +119,8 @@ uint16 KtxStorage::minAvailableMipLevel() const {
 }
 
 void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& storage) {
-    qDebug() << "Populating " << level << " " << _filename.c_str();
     if (level != _minMipLevelAvailable - 1) {
         qWarning() << "Invalid level to be stored, expected: " << (_minMipLevelAvailable - 1) << ", got: " << level << " " << _filename.c_str();
-        //throw std::runtime_error("Invalid image size for level");
         return;
     }
 
@@ -135,9 +129,8 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     }
 
     if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
-        qDebug() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
+        qWarning() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
             << ", level: " << level << ", filename: " << QString::fromStdString(_filename);
-        //throw std::runtime_error("Invalid image size for level");
         return;
     }
 

From 8d03d50d35843656264842cdb64240eb5b946fc0 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 21 Apr 2017 00:33:05 -0700
Subject: [PATCH 29/85] Cleanup progressive ktx debugging

---
 .../gpu/gl45/GL45BackendVariableTexture.cpp   |  9 +------
 libraries/gpu/src/gpu/Texture.cpp             |  1 +
 libraries/gpu/src/gpu/Texture.h               |  3 ---
 libraries/gpu/src/gpu/Texture_ktx.cpp         | 22 +--------------
 libraries/ktx/src/ktx/KTX.cpp                 |  3 +--
 libraries/ktx/src/ktx/Reader.cpp              |  2 +-
 .../src/model-networking/ModelCache.cpp       |  1 -
 .../src/model-networking/TextureCache.cpp     | 27 ++-----------------
 .../networking/src/HTTPResourceRequest.cpp    |  1 -
 libraries/networking/src/ResourceCache.cpp    |  2 --
 .../render-utils/src/MeshPartPayload.cpp      |  1 -
 libraries/shared/src/shared/Storage.cpp       |  3 ---
 12 files changed, 7 insertions(+), 68 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index f99209a7c2..77e42e7fdb 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -41,9 +41,6 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {
 using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
 
 GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {
-    if (texture.source().find_first_of("box.ktx") != std::string::npos) {
-        qDebug() << "In box.ktx ctor";
-    }
     auto mipLevels = texture.getNumMips();
     _allocatedMip = mipLevels;
 
@@ -53,13 +50,11 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
     for (uint16_t mip = 0; mip < mipLevels; ++mip) {
         if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
             && texture.isStoredMipFaceAvailable(mip)) {
-            _maxAllocatedMip = _populatedMip = mip;
+            _lowestRequestedMip = _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
-    //_maxAllocatedMip = _populatedMip = mipLevels;
 
-    //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
     allocateStorage(allocatedMip);
     _memoryPressureStateStale = true;
@@ -112,7 +107,6 @@ void GL45ResourceTexture::promote() {
     auto oldSize = _size;
     // create new texture
     const_cast<GLuint&>(_id) = allocate(_gpuObject);
-    //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     uint16_t oldAllocatedMip = _allocatedMip;
     // allocate storage for new level
     allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
@@ -146,7 +140,6 @@ void GL45ResourceTexture::demote() {
     auto oldId = _id;
     auto oldSize = _size;
     const_cast<GLuint&>(_id) = allocate(_gpuObject);
-    //glObjectLabel(GL_TEXTURE, _id, _source.length(), _source.data());
     allocateStorage(_allocatedMip + 1);
     _populatedMip = std::max(_populatedMip, _allocatedMip);
     uint16_t mips = _gpuObject.getNumMips();
diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp
index 1dbe9db2b4..bed01a805d 100755
--- a/libraries/gpu/src/gpu/Texture.cpp
+++ b/libraries/gpu/src/gpu/Texture.cpp
@@ -397,6 +397,7 @@ Size Texture::evalTotalSize(uint16 startingMip) const {
     Size size = 0;
     uint16 minMipLevel = std::max(getMinMip(), startingMip);
     uint16 maxMipLevel = getMaxMip();
+    qDebug() << " min: " << minMipLevel << " " << maxMipLevel;
     for (uint16 level = minMipLevel; level <= maxMipLevel; level++) {
         size += evalMipSize(level);
     }
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index adf6a464f7..146615d631 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -524,9 +524,6 @@ public:
 
     ExternalUpdates getUpdates() const;
 
-    // Serialize ktx header and keyvalues directly to a file, and return a Texture representing that file
-    static TexturePointer serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues);
-
     // Serialize a texture into a KTX file
     static ktx::KTXUniquePointer serialize(const Texture& texture);
 
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 250b9ea8f3..8e30645837 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -134,21 +134,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
         return;
     }
 
-
-    //auto fileStorage = new storage::FileStorage(_filename.c_str());
-    //ktx::StoragePointer file { fileStorage };
     auto file = maybeOpenFile();
+
     auto data = file->mutableData();
-    data += file->size();
-
-    // TODO Cache this data inside Image or ImageDescriptor?
-    for (int i = _ktxDescriptor->header.numberOfMipmapLevels - 1; i >= level; --i) {
-        data -= _ktxDescriptor->images[i]._imageSize;
-        data -= 4;
-    }
-    data += 4;
-
-    data = file->mutableData();
     data += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
     data += 4;
 
@@ -173,7 +161,6 @@ void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::Stor
     throw std::runtime_error("Invalid call");
 }
 
-
 void Texture::setKtxBacking(const std::string& filename) {
     // Check the KTX file for validity before using it as backing storage
     {
@@ -364,13 +351,6 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDe
     return tex;
 }
 
-TexturePointer Texture::serializeHeader(const std::string& ktxfile, const ktx::Header& header, const ktx::KeyValues& keyValues) {
-    // Create a memory-backed KTX object
-    auto ktxBuffer = ktx::KTX::createBare(header, keyValues);
-
-    return unserialize(ktxfile, ktxBuffer->toDescriptor());
-}
-
 bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) {
     if (texelFormat == Format::COLOR_RGBA_32 && mipFormat == Format::COLOR_BGRA_32) {
         header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::BGRA, ktx::GLInternalFormat_Uncompressed::RGBA8, ktx::GLBaseInternalFormat::RGBA);
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index e00937a67a..0dbc2e720f 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -91,9 +91,9 @@ size_t Header::evalPixelOrBlockSize() const {
             return 4;
         }
     }
+
     qWarning() << "Unknown ktx format: " << glFormat << " " << glBaseInternalFormat << " " << glInternalFormat;
     return 0;
-    //throw std::runtime_error("Unknown format");
 }
 
 size_t Header::evalRowSize(uint32_t level) const {
@@ -284,7 +284,6 @@ Image ImageDescriptor::toImage(const ktx::StoragePointer& storage) const {
     FaceBytes faces;
     faces.resize(_faceOffsets.size());
     for (size_t face = 0; face < _numFaces; ++face) {
-        // TODO Should we be storing pointers to unowned data?
         faces[face] = storage->data() + _faceOffsets[face];
     }
     // Note, implicit cast of *this to const ImageHeader&
diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp
index 27aee8771c..b22f262e85 100644
--- a/libraries/ktx/src/ktx/Reader.cpp
+++ b/libraries/ktx/src/ktx/Reader.cpp
@@ -50,7 +50,7 @@ namespace ktx {
 
     bool checkIdentifier(const Byte* identifier) {
         if (!(0 == memcmp(identifier, Header::IDENTIFIER.data(), Header::IDENTIFIER_LENGTH))) {
-            //throw ReaderException("identifier field invalid");
+            throw ReaderException("identifier field invalid");
             return false;
         }
         return true;
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index fc662e32a3..623832aaa8 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -364,7 +364,6 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
 
 bool Geometry::areTexturesLoaded() const {
     if (!_areTexturesLoaded) {
-        //qDebug() << "Textures not loaded for " << _fbxGeometry->originalURL;
         for (auto& material : _materials) {
             // Check if material textures are loaded
             bool materialMissingTexture = std::any_of(material->_textures.cbegin(), material->_textures.cend(),
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 05cfc7c06e..761f55068b 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -281,11 +281,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
         _loaded = true;
     }
 
-    //if (_sourceIsKTX) {
-        //_requestByteRange.fromInclusive = 0;
-        //_requestByteRange.toExclusive = 1000;
-    //}
-
     // if we have content, load it after we have our self pointer
     if (!content.isEmpty()) {
         _startedLoading = true;
@@ -364,12 +359,9 @@ void NetworkTexture::makeRequest() {
         range.toExclusive = 1000;
         _ktxHeaderRequest->setByteRange(range);
 
-        //qCDebug(resourceLog).noquote() << "Starting request for:" << _url.toDisplayString();
         emit loading();
 
         connect(_ktxHeaderRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxHeaderRequestProgress);
-        //connect(this, &Resource::onProgress, this, &NetworkTexture::ktxHeaderRequestFinished);
-
         connect(_ktxHeaderRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxHeaderRequestFinished);
 
         _bytesReceived = _bytesTotal = _bytes = 0;
@@ -389,7 +381,6 @@ void NetworkTexture::makeRequest() {
 }
 
 void NetworkTexture::handleMipInterestCallback(uint16_t level) {
-    //qDebug(networking) << "++++ Got request for mip level: " << _url << " " << level;
     QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(int, level));
 }
 
@@ -426,7 +417,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
 
     if (!_ktxMipRequest) {
-        qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
+        qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
 
         PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
         return;
@@ -439,7 +430,6 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
         range.fromInclusive = -15000;
         _ktxMipRequest->setByteRange(range);
     } else {
-        qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
         ByteRange range;
         range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
                               + _originalKtxDescriptor->images[low]._imageOffset + 4;
@@ -478,8 +468,6 @@ void NetworkTexture::ktxMipRequestFinished() {
 
             auto texture = _textureSource->getGPUTexture();
             if (texture) {
-                //_lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
-                //qDebug() << "Writing mip for " << _url;
                 texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
                     _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
                 _lowestKnownPopulatedMip = _textureSource->getGPUTexture()->minAvailableMipLevel();
@@ -496,7 +484,7 @@ void NetworkTexture::ktxMipRequestFinished() {
                 _ktxResourceState = PENDING_MIP_REQUEST;
             }
             else {
-                qWarning() << "Failed to load mip: " << _url;
+                qWarning(networking) << "Failed to load mip: " << _url;
                 _ktxResourceState = FAILED_TO_LOAD;
             }
         }
@@ -540,14 +528,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
 
             auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
 
-            qDebug() << "Creating KTX";
-            qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
-            qDebug() << "Type:" << header->glType;
-            qDebug() << "TypeSize:" << header->glTypeSize;
-            qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
-            qDebug() << "numberOfFaces:" << header->numberOfFaces;
-            qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
-
             if (!ktx::checkIdentifier(header->identifier)) {
                 qWarning() << "Cannot load " << _url << ", invalid header identifier";
                 _ktxResourceState = FAILED_TO_LOAD;
@@ -586,7 +566,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 return;
             } else {
                 hash = filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
-                //hash = filename = _url.path().replace("/", "_").toStdString();
             }
 
             auto textureCache = DependencyManager::get<TextureCache>();
@@ -629,7 +608,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
 
                 auto newKtxDescriptor = memKtx->toDescriptor();
 
-                //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues);
                 texture = gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor);
                 texture->setKtxBacking(file->getFilepath());
                 texture->setSource(filename);
@@ -645,7 +623,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                     if (image._imageSize > imageSizeRemaining) {
                         break;
                     }
-                    //qDebug() << "Transferring " << level;
                     ktxData -= image._imageSize;
                     texture->assignStoredMip(level, image._imageSize, ktxData);
                     ktxData -= 4;
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 7045894633..52f5ee43f5 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -132,7 +132,6 @@ void HTTPResourceRequest::onRequestFinished() {
                     uint64_t size;
                     std::tie(success, size) = parseContentRangeHeader(contentRangeHeader);
                     if (success) {
-                        //qWarning(networking) << "Total http resource size is: " << size;
                         _totalSizeOfResource = size;
                     } else {
                         qWarning(networking) << "Error parsing content-range header: " << contentRangeHeader;
diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 95bfd0e24d..56897ca4cd 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -667,7 +667,6 @@ void Resource::makeRequest() {
     }
 
     PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
-    qDebug() << "Making request to " << _url << " for byte range " << _requestByteRange.fromInclusive << "-" << _requestByteRange.toExclusive;
 
     _request = ResourceManager::createResourceRequest(this, _activeUrl);
 
@@ -700,7 +699,6 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota
 }
 
 void Resource::handleReplyFinished() {
-    qDebug() << "Got response for " << _activeUrl;
     Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished");
 
     PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index e6ae6d21a1..9232564514 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -544,7 +544,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
     }
 
     if (_fadeState == FADE_WAITING_TO_START) {
-        //if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
         if (_model->isLoaded()) {
             if (EntityItem::getEntitiesShouldFadeFunction()()) {
                 _fadeStartTime = usecTimestampNow();
diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp
index 6eb311fa60..aae1f8455f 100644
--- a/libraries/shared/src/shared/Storage.cpp
+++ b/libraries/shared/src/shared/Storage.cpp
@@ -67,10 +67,8 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
     return std::make_shared<FileStorage>(filename);
 }
 
-// Represents a memory mapped file
 FileStorage::FileStorage(const QString& filename) : _file(filename) {
     if (_file.open(QFile::ReadWrite)) {
-        //qDebug() << ">>> Opening mmapped file: " << filename;
         _mapped = _file.map(0, _file.size());
         if (_mapped) {
             _valid = true;
@@ -83,7 +81,6 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) {
 }
 
 FileStorage::~FileStorage() {
-    //qDebug() << ">>> Closing mmapped file: " << _file.fileName();
     if (_mapped) {
         if (!_file.unmap(_mapped)) {
             throw std::runtime_error("Unable to unmap file");

From 970be9d2c567a904f0527c304ae893d2aa5ca053 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 21 Apr 2017 01:10:54 -0700
Subject: [PATCH 30/85] Add check for canPopulate to gpu backend

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp   | 6 ++++--
 libraries/gpu-gl/src/gpu/gl/GLTexture.h     | 4 ++--
 libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 2 +-
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index 1179403bff..f1bf76b39d 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -437,6 +437,7 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
     size_t idealMemoryAllocation = 0;
     bool canDemote = false;
     bool canPromote = false;
+    bool canPopulate = false;
     bool hasTransfers = false;
     for (const auto& texture : strongTextures) {
         // Race conditions can still leave nulls in the list, so we need to check
@@ -450,7 +451,8 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
         // Track how much we're actually using
         totalVariableMemoryAllocation += gltexture->size();
         canDemote |= vartexture->canDemote();
-        canPromote |= vartexture->canPromote() || (texture->minAvailableMipLevel() < vartexture->_allocatedMip);
+        canPromote |= vartexture->canPromote();
+        canPopulate |= vartexture->canPopulate();
         hasTransfers |= vartexture->hasPendingTransfers();
     }
 
@@ -464,7 +466,7 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
     } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
         qDebug() << "Demoting";
         newState = MemoryPressureState::Oversubscribed;
-    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && unallocated != 0 && canPromote) {
+    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && ((unallocated != 0 && canPromote) || canPopulate)) {
         qDebug() << "Promoting";
         newState = MemoryPressureState::Undersubscribed;
     }
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
index bc8467b808..9aad49546e 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
@@ -112,11 +112,11 @@ protected:
     static void manageMemory();
 
     //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
-    bool canPromote() const { return _allocatedMip > 0 || _populatedMip > 0; }
+    virtual bool canPopulate() const = 0;
+    bool canPromote() const { return _allocatedMip > 0; }
     bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
     bool hasPendingTransfers() const { return _pendingTransfers.size() > 0; }
     void executeNextTransfer(const TexturePointer& currentTexture);
-    virtual bool canPopulate() const = 0;
     virtual void populateTransferQueue() = 0;
     virtual void promote() = 0;
     virtual void demote() = 0;
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
index c0b9ea0e45..dc6d2b3aa7 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
@@ -100,7 +100,7 @@ public:
         GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
         ~GL41VariableAllocationTexture();
 
-        bool canPopulate() const override { return _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
+        bool canPopulate() const override { return _populatedMip > _allocatedMip && _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
         void allocateStorage(uint16 allocatedMip);
         void syncSampler() const override;
         void promote() override;

From 14f8c91e23442b48bd108dfa5aab4f6113cbe1a1 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 21 Apr 2017 01:11:30 -0700
Subject: [PATCH 31/85] Adjust gl45 backend to request interest in mips and
 keep track of min requested

---
 libraries/gpu-gl/src/gpu/gl45/GL45Backend.h   |  2 ++
 .../src/gpu/gl45/GL45BackendTexture.cpp       |  2 ++
 .../gpu/gl45/GL45BackendVariableTexture.cpp   | 23 +++++++++++--------
 libraries/gpu/src/gpu/Texture.cpp             |  1 -
 4 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
index 15e98c3af7..21c429c577 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
@@ -117,6 +117,8 @@ public:
 
         void allocateStorage(uint16 mip);
         void copyMipsFromTexture();
+
+        uint16 _lowestRequestedMip { 0 };
     };
 
 #if 0
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index 761cd305bb..299a52eddd 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -119,8 +119,10 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
 GLuint GL45Texture::allocate(const Texture& texture) {
     GLuint result;
     glCreateTextures(getGLTextureType(texture), 1, &result);
+#ifdef DEBUG
     auto source = texture.source();
     glObjectLabel(GL_TEXTURE, result, source.length(), source.data());
+#endif
     return result;
 }
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 77e42e7fdb..5dfbd6e727 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -75,6 +75,11 @@ void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) {
     for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) {
         _size += _gpuObject.evalMipSize(mip);
     }
+
+    if (!_gpuObject.isStoredMipFaceAvailable(allocatedMip, 0)) {
+        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(allocatedMip);
+    }
+
     Backend::updateTextureGPUMemoryUsage(0, _size);
 
 }
@@ -98,9 +103,11 @@ void GL45ResourceTexture::syncSampler() const {
 
 void GL45ResourceTexture::promote() {
     PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
-    //Q_ASSERT(_allocatedMip > 0);
-    if (!_gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0)) {
-        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(_populatedMip - 1);
+    Q_ASSERT(_allocatedMip > 0);
+    uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
+    if (!_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
+        _lowestRequestedMip = targetAllocatedMip;
+        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(targetAllocatedMip);
         return;
     }
     GLuint oldId = _id;
@@ -109,7 +116,7 @@ void GL45ResourceTexture::promote() {
     const_cast<GLuint&>(_id) = allocate(_gpuObject);
     uint16_t oldAllocatedMip = _allocatedMip;
     // allocate storage for new level
-    allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
+    allocateStorage(targetAllocatedMip);
     uint16_t mips = _gpuObject.getNumMips();
     // copy pre-existing mips
     for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
@@ -176,23 +183,21 @@ void GL45ResourceTexture::populateTransferQueue() {
 
     const uint8_t maxFace = GLTexture::getFaceCount(_target);
     uint16_t sourceMip = _populatedMip;
-    //qDebug() << "populateTransferQueue info : " << _populatedMip << " " << _maxAllocatedMip << " " << _allocatedMip;
     do {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
         bool transferQueued = false;
-        //qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
-                _minRequestedMip = sourceMip;
+                _lowestRequestedMip = sourceMip;
                 continue;
             }
+            _lowestRequestedMip = sourceMip;
 
             // If the mip is less than the max transfer size, then just do it in one transfer
             if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
-                qDebug() << "mip is less than max transfer size";
                 // Can the mip be transferred in one go
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
                 transferQueued = true;
@@ -207,14 +212,12 @@ void GL45ResourceTexture::populateTransferQueue() {
             Q_ASSERT(0 == (mipSize % lines));
             uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine);
             uint32_t lineOffset = 0;
-            qDebug() << "queing up single line transfers " << linesPerTransfer << " " << lineOffset;
             while (lineOffset < lines) {
                 uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
                 lineOffset += linesToCopy;
                 transferQueued = true;
             }
-            _minRequestedMip = std::min(_minRequestedMip, sourceMip);
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp
index bed01a805d..1dbe9db2b4 100755
--- a/libraries/gpu/src/gpu/Texture.cpp
+++ b/libraries/gpu/src/gpu/Texture.cpp
@@ -397,7 +397,6 @@ Size Texture::evalTotalSize(uint16 startingMip) const {
     Size size = 0;
     uint16 minMipLevel = std::max(getMinMip(), startingMip);
     uint16 maxMipLevel = getMaxMip();
-    qDebug() << " min: " << minMipLevel << " " << maxMipLevel;
     for (uint16 level = minMipLevel; level <= maxMipLevel; level++) {
         size += evalMipSize(level);
     }

From 6e307dd6ebac8a82fe30d3e05306725d24c8e551 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Fri, 21 Apr 2017 01:17:59 -0700
Subject: [PATCH 32/85] Remove debug logging

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index f1bf76b39d..ef938ecd6d 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -461,13 +461,10 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
 
     auto newState = MemoryPressureState::Idle;
     if (hasTransfers) {
-        qDebug() << "Transferring";
         newState = MemoryPressureState::Transfer;
     } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
-        qDebug() << "Demoting";
         newState = MemoryPressureState::Oversubscribed;
     } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && ((unallocated != 0 && canPromote) || canPopulate)) {
-        qDebug() << "Promoting";
         newState = MemoryPressureState::Undersubscribed;
     }
 

From 927efc88f0de332e36c39a96d61ab02f2b854f09 Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Thu, 20 Apr 2017 16:41:35 -0700
Subject: [PATCH 33/85] Fix GPUKTXPayload conflict across platforms

---
 libraries/gpu/src/gpu/Texture.h       |   6 +-
 libraries/gpu/src/gpu/Texture_ktx.cpp | 116 +++++++++++++++++++-------
 2 files changed, 88 insertions(+), 34 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 146615d631..5dc8871e9d 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -154,7 +154,7 @@ protected:
     Desc _desc;
 };
 
-enum class TextureUsageType {
+enum class TextureUsageType : uint8 {
     RENDERBUFFER,       // Used as attachments to a framebuffer
     RESOURCE,           // Resource textures, like materials... subject to memory manipulation
     STRICT_RESOURCE,    // Resource textures not subject to manipulation, like the normal fitting texture
@@ -527,8 +527,8 @@ public:
     // Serialize a texture into a KTX file
     static ktx::KTXUniquePointer serialize(const Texture& texture);
 
-    static TexturePointer unserialize(const std::string& ktxFile, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
-    static TexturePointer unserialize(const std::string& ktxFile, const ktx::KTXDescriptor& descriptor, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
+    static TexturePointer unserialize(const std::string& ktxFile);
+    static TexturePointer unserialize(const std::string& ktxFile, const ktx::KTXDescriptor& descriptor);
 
     static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
     static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 8e30645837..89b7b86768 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -20,12 +20,66 @@ using PixelsPointer = Texture::PixelsPointer;
 using KtxStorage = Texture::KtxStorage;
 
 struct GPUKTXPayload {
+    using Version = uint8;
+
+    static const std::string KEY;
+    static const Version CURRENT_VERSION { 1 };
+    static const size_t PADDING { 2 };
+    static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint32) + sizeof(TextureUsageType) + PADDING };
+    static_assert(GPUKTXPayload::SIZE == 36, "Packing size may differ between platforms");
+    static_assert(GPUKTXPayload::SIZE % 4 == 0, "GPUKTXPayload is not 4 bytes aligned");
+
     Sampler::Desc _samplerDesc;
     Texture::Usage _usage;
     TextureUsageType _usageType;
 
+    Byte* serialize(Byte* data) const {
+        *(Version*)data = CURRENT_VERSION;
+        data += sizeof(Version);
+
+        memcpy(data, &_samplerDesc, sizeof(Sampler::Desc));
+        data += sizeof(Sampler::Desc);
+        
+        // We can't copy the bitset in Texture::Usage in a crossplateform manner
+        // So serialize it manually
+        *(uint32*)data = _usage._flags.to_ulong();
+        data += sizeof(uint32);
+
+        *(TextureUsageType*)data = _usageType;
+        data += sizeof(TextureUsageType);
+
+        return data + PADDING;
+    }
+
+    bool unserialize(const Byte* data, size_t size) {
+        if (size != SIZE) {
+            return false;
+        }
+
+        Version version = *(const Version*)data;
+        if (version != CURRENT_VERSION) {
+            glm::vec4 borderColor(1.0f);
+            if (memcmp(&borderColor, data, sizeof(glm::vec4)) == 0) {
+                memcpy(this, data, sizeof(GPUKTXPayload));
+                return true;
+            } else {
+                return false;
+            }
+        }
+        data += sizeof(Version);
+
+        memcpy(&_samplerDesc, data, sizeof(Sampler::Desc));
+        data += sizeof(Sampler::Desc);
+        
+        // We can't copy the bitset in Texture::Usage in a crossplateform manner
+        // So unserialize it manually
+        _usage = Texture::Usage(*(const uint32*)data);
+        data += sizeof(uint32);
+
+        _usageType = *(const TextureUsageType*)data;
+        return true;
+    }
 
-    static std::string KEY;
     static bool isGPUKTX(const ktx::KeyValue& val) {
         return (val._key.compare(KEY) == 0);
     }
@@ -33,17 +87,14 @@ struct GPUKTXPayload {
     static bool findInKeyValues(const ktx::KeyValues& keyValues, GPUKTXPayload& payload) {
         auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX);
         if (found != keyValues.end()) {
-            if ((*found)._value.size() == sizeof(GPUKTXPayload)) {
-                memcpy(&payload, (*found)._value.data(), sizeof(GPUKTXPayload));
-                return true;
-            }
+            auto value = found->_value;
+            return payload.unserialize(value.data(), value.size());
         }
         return false;
     }
 };
-
 const std::string gpu::SOURCE_HASH_KEY { "hifi.sourceHash" };
-std::string GPUKTXPayload::KEY{ "hifi.gpu" };
+const std::string GPUKTXPayload::KEY { "hifi.gpu" };
 
 KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
     {
@@ -253,12 +304,15 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
     keyval._samplerDesc = texture.getSampler().getDesc();
     keyval._usage = texture.getUsage();
     keyval._usageType = texture.getUsageType();
+    Byte keyvalPayload[GPUKTXPayload::SIZE];
+    keyval.serialize(keyvalPayload);
+
     ktx::KeyValues keyValues;
-    keyValues.emplace_back(ktx::KeyValue(GPUKTXPayload::KEY, sizeof(GPUKTXPayload), (ktx::Byte*) &keyval));
+    keyValues.emplace_back(GPUKTXPayload::KEY, (uint32)GPUKTXPayload::SIZE, (ktx::Byte*) &keyvalPayload);
 
     auto hash = texture.sourceHash();
     if (!hash.empty()) {
-        keyValues.emplace_back(ktx::KeyValue(SOURCE_HASH_KEY, static_cast<uint32>(hash.size()), (ktx::Byte*) hash.c_str()));
+        keyValues.emplace_back(SOURCE_HASH_KEY, static_cast<uint32>(hash.size()), (ktx::Byte*) hash.c_str());
     }
 
     auto ktxBuffer = ktx::KTX::create(header, images, keyValues);
@@ -291,17 +345,17 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
     return ktxBuffer;
 }
 
-TexturePointer Texture::unserialize(const std::string& ktxfile, TextureUsageType usageType, Usage usage, const Sampler::Desc& sampler) {
-    std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(ktx::StoragePointer { new storage::FileStorage(ktxfile.c_str()) });
+TexturePointer Texture::unserialize(const std::string& ktxfile) {
+    std::unique_ptr<ktx::KTX> ktxPointer = ktx::KTX::create(std::make_shared<storage::FileStorage>(ktxfile.c_str()));
     if (!ktxPointer) {
         return nullptr;
     }
 
     ktx::KTXDescriptor descriptor { ktxPointer->toDescriptor() };
-    return unserialize(ktxfile, ktxPointer->toDescriptor(), usageType, usage, sampler);
+    return unserialize(ktxfile, ktxPointer->toDescriptor());
 }
 
-TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDescriptor& descriptor, TextureUsageType usageType, Usage usage, const Sampler::Desc& sampler) {
+TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDescriptor& descriptor) {
     const auto& header = descriptor.header;
 
     Format mipFormat = Format::COLOR_BGRA_32;
@@ -327,28 +381,28 @@ TexturePointer Texture::unserialize(const std::string& ktxfile, const ktx::KTXDe
         type = TEX_3D;
     }
 
-    
-    // If found, use the 
     GPUKTXPayload gpuktxKeyValue;
-    bool isGPUKTXPayload = GPUKTXPayload::findInKeyValues(descriptor.keyValues, gpuktxKeyValue);
+    if (!GPUKTXPayload::findInKeyValues(descriptor.keyValues, gpuktxKeyValue)) {
+        qCWarning(gpulogging) << "Could not find GPUKTX key values.";
+        return TexturePointer();
+    }
 
-    auto tex = Texture::create( (isGPUKTXPayload ? gpuktxKeyValue._usageType : usageType),
-                                type,
-                                texelFormat,
-                                header.getPixelWidth(),
-                                header.getPixelHeight(),
-                                header.getPixelDepth(),
-                                1, // num Samples
-                                header.getNumberOfSlices(),
-                                header.getNumberOfLevels(),
-                                (isGPUKTXPayload ? gpuktxKeyValue._samplerDesc : sampler));
-
-    tex->setUsage((isGPUKTXPayload ? gpuktxKeyValue._usage : usage));
+    auto texture = create(gpuktxKeyValue._usageType,
+                          type,
+                          texelFormat,
+                          header.getPixelWidth(),
+                          header.getPixelHeight(),
+                          header.getPixelDepth(),
+                          1, // num Samples
+                          header.getNumberOfSlices(),
+                          header.getNumberOfLevels(),
+                          gpuktxKeyValue._samplerDesc);
+    texture->setUsage(gpuktxKeyValue._usage);
 
     // Assing the mips availables
-    tex->setStoredMipFormat(mipFormat);
-    tex->setKtxBacking(ktxfile);
-    return tex;
+    texture->setStoredMipFormat(mipFormat);
+    texture->setKtxBacking(ktxfile);
+    return texture;
 }
 
 bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header) {

From 3c5754282f99d467d2f7c688f53f3c4d94838462 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Thu, 20 Apr 2017 16:18:51 -0700
Subject: [PATCH 34/85] write the source hash in ktx header in binary

---
 libraries/gpu/src/gpu/Texture.h       | 1 +
 libraries/gpu/src/gpu/Texture_ktx.cpp | 8 +++++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 5dc8871e9d..3c82f67c86 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -35,6 +35,7 @@ namespace ktx {
 namespace gpu {
 
 extern const std::string SOURCE_HASH_KEY;
+const uint8 SOURCE_HASH_BYTES = 16;
 
 // THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
 // with the cube texture
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 89b7b86768..6828af4cc4 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -13,7 +13,10 @@
 #include "Texture.h"
 #include <qdebug.h>
 
+#include <QtCore/QByteArray>
+
 #include <ktx/KTX.h>
+
 using namespace gpu;
 
 using PixelsPointer = Texture::PixelsPointer;
@@ -312,7 +315,10 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
 
     auto hash = texture.sourceHash();
     if (!hash.empty()) {
-        keyValues.emplace_back(SOURCE_HASH_KEY, static_cast<uint32>(hash.size()), (ktx::Byte*) hash.c_str());
+        // the sourceHash is an std::string in hex
+        // we use QByteArray to take the hex and turn it into the smaller binary representation (16 bytes)
+        auto binaryHash = QByteArray::fromHex(QByteArray::fromStdString(hash));
+        keyValues.emplace_back(SOURCE_HASH_KEY, static_cast<uint32>(binaryHash.size()), (ktx::Byte*) binaryHash.data());
     }
 
     auto ktxBuffer = ktx::KTX::create(header, images, keyValues);

From e708a71b6482b4baddfa96e3b1ade51f8450e471 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Thu, 20 Apr 2017 18:19:29 -0700
Subject: [PATCH 35/85] handle 16 byte hash while reading in TextureCache

---
 .../model-networking/src/model-networking/TextureCache.cpp | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 761f55068b..c8765e0a52 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -559,13 +559,16 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             });
             std::string filename;
             std::string hash;
-            if (found == keyValues.end() || found->_value.size() != 32) {
+            if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
                 qWarning("Invalid source hash key found, bailing");
                 _ktxResourceState = FAILED_TO_LOAD;
                 finishedLoading(false);
                 return;
             } else {
-                hash = filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
+                // at this point the source hash is in binary 16-byte form
+                // and we need it in a hexadecimal string
+                auto binaryHash = QByteArray(reinterpret_cast<char*>(found->_value.data()), gpu::SOURCE_HASH_BYTES);
+                hash = filename = binaryHash.toHex().toStdString();
             }
 
             auto textureCache = DependencyManager::get<TextureCache>();

From 397a29039effa0768fba2220488144d58d05d524 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Mon, 17 Apr 2017 19:08:06 -0700
Subject: [PATCH 36/85] add support for byte range requests to ATP

---
 .../src/assets/SendAssetTask.cpp              |  62 ++++++--
 libraries/networking/src/AssetClient.cpp      |   5 +-
 libraries/networking/src/AssetClient.h        |   3 +-
 libraries/networking/src/AssetRequest.cpp     | 133 +++++++-----------
 libraries/networking/src/AssetRequest.h       |  10 +-
 libraries/networking/src/ByteRange.h          |  24 ++++
 libraries/networking/src/ResourceRequest.h    |   7 +-
 7 files changed, 132 insertions(+), 112 deletions(-)
 create mode 100644 libraries/networking/src/ByteRange.h

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index ca8733d660..4a49deabde 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -11,6 +11,8 @@
 
 #include "SendAssetTask.h"
 
+#include <cmath>
+
 #include <QFile>
 
 #include <DependencyManager.h>
@@ -21,6 +23,7 @@
 #include <udt/Packet.h>
 
 #include "AssetUtils.h"
+#include "ByteRange.h"
 #include "ClientServerUtils.h"
 
 SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
@@ -34,20 +37,21 @@ SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const Shar
 
 void SendAssetTask::run() {
     MessageID messageID;
-    DataOffset start, end;
-    
+    ByteRange byteRange;
+
     _message->readPrimitive(&messageID);
     QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
 
     // `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
     // `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
     // starting at index 1.
-    _message->readPrimitive(&start);
-    _message->readPrimitive(&end);
+    _message->readPrimitive(&byteRange.fromInclusive);
+    _message->readPrimitive(&byteRange.toExclusive);
     
     QString hexHash = assetHash.toHex();
     
-    qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from " << start << " to " << end;
+    qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from "
+        << byteRange.fromInclusive << " to " << byteRange.toExclusive;
     
     qDebug() << "Starting task to send asset: " << hexHash << " for messageID " << messageID;
     auto replyPacketList = NLPacketList::create(PacketType::AssetGetReply, QByteArray(), true, true);
@@ -56,7 +60,7 @@ void SendAssetTask::run() {
 
     replyPacketList->writePrimitive(messageID);
 
-    if (end <= start) {
+    if (byteRange.toExclusive < byteRange.fromInclusive) {
         replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
     } else {
         QString filePath = _resourcesDir.filePath(QString(hexHash));
@@ -64,15 +68,47 @@ void SendAssetTask::run() {
         QFile file { filePath };
 
         if (file.open(QIODevice::ReadOnly)) {
-            if (file.size() < end) {
+            if (byteRange.isSet()) {
+                // if the byte range is not set, force it to be from 0 to the end of the file
+                byteRange.fromInclusive = 0;
+                byteRange.toExclusive = file.size();
+            }
+
+            if (file.size() < std::abs(byteRange.toExclusive)) {
                 replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
-                qCDebug(networking) << "Bad byte range: " << hexHash << " " << start << ":" << end;
+                qCDebug(networking) << "Bad byte range: " << hexHash << " "
+                    << byteRange.fromInclusive << ":" << byteRange.toExclusive;
             } else {
-                auto size = end - start;
-                file.seek(start);
-                replyPacketList->writePrimitive(AssetServerError::NoError);
-                replyPacketList->writePrimitive(size);
-                replyPacketList->write(file.read(size));
+                // we have a valid byte range, handle it and send the asset
+                auto size = byteRange.size();
+
+                if (byteRange.fromInclusive > 0) {
+                    // this range is positive, meaning we just need to seek into the file and then read from there
+                    file.seek(byteRange.fromInclusive);
+                    replyPacketList->writePrimitive(AssetServerError::NoError);
+                    replyPacketList->writePrimitive(size);
+                    replyPacketList->write(file.read(size));
+                } else {
+                    // this range is negative, at least the first part of the read will be back into the end of the file
+
+                    // seek to the part of the file where the negative range begins
+                    file.seek(file.size() + byteRange.fromInclusive);
+
+                    replyPacketList->writePrimitive(AssetServerError::NoError);
+                    replyPacketList->writePrimitive(size);
+
+                    // first write everything from the negative range to the end of the file
+                    replyPacketList->write(file.read(-byteRange.fromInclusive));
+
+                    if (byteRange.toExclusive != 0) {
+                        // this range has a portion that is at the front of the file
+
+                        // seek to the beginning and read what is left over
+                        file.seek(0);
+                        replyPacketList->write(file.read(byteRange.toExclusive));
+                    }
+                }
+
                 qCDebug(networking) << "Sending asset: " << hexHash;
             }
             file.close();
diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp
index 37b1af0996..48f8bb87f9 100644
--- a/libraries/networking/src/AssetClient.cpp
+++ b/libraries/networking/src/AssetClient.cpp
@@ -67,7 +67,6 @@ void AssetClient::init() {
     }
 }
 
-
 void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) {
     if (QThread::currentThread() != thread()) {
         QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection,
@@ -182,8 +181,8 @@ RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& o
     return request;
 }
 
-AssetRequest* AssetClient::createRequest(const AssetHash& hash) {
-    auto request = new AssetRequest(hash);
+AssetRequest* AssetClient::createRequest(const AssetHash& hash, ByteRange byteRange) {
+    auto request = new AssetRequest(hash, byteRange);
 
     // Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
     request->moveToThread(thread());
diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h
index c0d58cd8e6..b204fab47e 100644
--- a/libraries/networking/src/AssetClient.h
+++ b/libraries/networking/src/AssetClient.h
@@ -21,6 +21,7 @@
 #include <DependencyManager.h>
 
 #include "AssetUtils.h"
+#include "ByteRange.h"
 #include "ClientServerUtils.h"
 #include "LimitedNodeList.h"
 #include "Node.h"
@@ -55,7 +56,7 @@ public:
     Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths);
     Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash);
     Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
-    Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash);
+    Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash, ByteRange byteRange = ByteRange());
     Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
     Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
 
diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp
index 8d663933ca..e54a058ac2 100644
--- a/libraries/networking/src/AssetRequest.cpp
+++ b/libraries/networking/src/AssetRequest.cpp
@@ -23,10 +23,12 @@
 
 static int requestID = 0;
 
-AssetRequest::AssetRequest(const QString& hash) :
+AssetRequest::AssetRequest(const QString& hash, ByteRange byteRange) :
     _requestID(++requestID),
-    _hash(hash)
+    _hash(hash),
+    _byteRange(byteRange)
 {
+    
 }
 
 AssetRequest::~AssetRequest() {
@@ -34,9 +36,6 @@ AssetRequest::~AssetRequest() {
     if (_assetRequestID) {
         assetClient->cancelGetAssetRequest(_assetRequestID);
     }
-    if (_assetInfoRequestID) {
-        assetClient->cancelGetAssetInfoRequest(_assetInfoRequestID);
-    }
 }
 
 void AssetRequest::start() {
@@ -62,108 +61,74 @@ void AssetRequest::start() {
     // Try to load from cache
     _data = loadFromCache(getUrl());
     if (!_data.isNull()) {
-        _info.hash = _hash;
-        _info.size = _data.size();
         _error = NoError;
         
         _state = Finished;
         emit finished(this);
         return;
     }
-    
-    _state = WaitingForInfo;
-    
+
+    _state = WaitingForData;
+
     auto assetClient = DependencyManager::get<AssetClient>();
-    _assetInfoRequestID = assetClient->getAssetInfo(_hash,
-            [this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
+    auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
+    auto hash = _hash;
 
-        _assetInfoRequestID = INVALID_MESSAGE_ID;
+    _assetRequestID = assetClient->getAsset(_hash, _byteRange.fromInclusive, _byteRange.toExclusive,
+        [this, that, hash](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
 
-        _info = info;
+            if (!that) {
+            qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
+            // If the request is dead, return
+            return;
+        }
+        _assetRequestID = INVALID_MESSAGE_ID;
 
         if (!responseReceived) {
             _error = NetworkError;
         } else if (serverError != AssetServerError::NoError) {
-            switch(serverError) {
+            switch (serverError) {
                 case AssetServerError::AssetNotFound:
                     _error = NotFound;
                     break;
+                case AssetServerError::InvalidByteRange:
+                    _error = InvalidByteRange;
+                    break;
                 default:
                     _error = UnknownError;
                     break;
             }
-        }
+        } else {
+            if (_byteRange.isSet()) {
+                // we had a byte range, the size of the data does not match what we expect, so we return an error
+                if (data.size() != _byteRange.size()) {
+                    _error = SizeVerificationFailed;
+                }
+            } else if (hashData(data).toHex() != _hash) {
+                // the hash of the received data does not match what we expect, so we return an error
+                _error = HashVerificationFailed;
+            }
 
+            if (_error == NoError) {
+                _data = data;
+                _totalReceived += data.size();
+                emit progress(_totalReceived, data.size());
+                
+                saveToCache(getUrl(), data);
+            }
+        }
+        
         if (_error != NoError) {
-            qCWarning(asset_client) << "Got error retrieving asset info for" << _hash;
-            _state = Finished;
-            emit finished(this);
-            
+            qCWarning(asset_client) << "Got error retrieving asset" << _hash << "- error code" << _error;
+        }
+        
+        _state = Finished;
+        emit finished(this);
+    }, [this, that](qint64 totalReceived, qint64 total) {
+        if (!that) {
+            // If the request is dead, return
             return;
         }
-        
-        _state = WaitingForData;
-        _data.resize(info.size);
-        
-        qCDebug(asset_client) << "Got size of " << _hash << " : " << info.size << " bytes";
-        
-        int start = 0, end = _info.size;
-        
-        auto assetClient = DependencyManager::get<AssetClient>();
-        auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
-        auto hash = _hash;
-        _assetRequestID = assetClient->getAsset(_hash, start, end,
-                [this, that, hash, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
-            if (!that) {
-                qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
-                // If the request is dead, return
-                return;
-            }
-            _assetRequestID = INVALID_MESSAGE_ID;
-
-            if (!responseReceived) {
-                _error = NetworkError;
-            } else if (serverError != AssetServerError::NoError) {
-                switch (serverError) {
-                    case AssetServerError::AssetNotFound:
-                        _error = NotFound;
-                        break;
-                    case AssetServerError::InvalidByteRange:
-                        _error = InvalidByteRange;
-                        break;
-                    default:
-                        _error = UnknownError;
-                        break;
-                }
-            } else {
-                Q_ASSERT(data.size() == (end - start));
-                
-                // we need to check the hash of the received data to make sure it matches what we expect
-                if (hashData(data).toHex() == _hash) {
-                    memcpy(_data.data() + start, data.constData(), data.size());
-                    _totalReceived += data.size();
-                    emit progress(_totalReceived, _info.size);
-                    
-                    saveToCache(getUrl(), data);
-                } else {
-                    // hash doesn't match - we have an error
-                    _error = HashVerificationFailed;
-                }
-                
-            }
-            
-            if (_error != NoError) {
-                qCWarning(asset_client) << "Got error retrieving asset" << _hash << "- error code" << _error;
-            }
-            
-            _state = Finished;
-            emit finished(this);
-        }, [this, that](qint64 totalReceived, qint64 total) {
-            if (!that) {
-                // If the request is dead, return
-                return;
-            }
-            emit progress(totalReceived, total);
-        });
+        emit progress(totalReceived, total);
     });
 }
diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h
index 1632a55336..5120b8066e 100644
--- a/libraries/networking/src/AssetRequest.h
+++ b/libraries/networking/src/AssetRequest.h
@@ -17,15 +17,15 @@
 #include <QString>
 
 #include "AssetClient.h"
-
 #include "AssetUtils.h"
 
+#include "ByteRange.h"
+
 class AssetRequest : public QObject {
    Q_OBJECT
 public:
     enum State {
         NotStarted = 0,
-        WaitingForInfo,
         WaitingForData,
         Finished
     };
@@ -36,11 +36,12 @@ public:
         InvalidByteRange,
         InvalidHash,
         HashVerificationFailed,
+        SizeVerificationFailed,
         NetworkError,
         UnknownError
     };
 
-    AssetRequest(const QString& hash);
+    AssetRequest(const QString& hash, ByteRange byteRange);
     virtual ~AssetRequest() override;
 
     Q_INVOKABLE void start();
@@ -59,13 +60,12 @@ private:
     int _requestID;
     State _state = NotStarted;
     Error _error = NoError;
-    AssetInfo _info;
     uint64_t _totalReceived { 0 };
     QString _hash;
     QByteArray _data;
     int _numPendingRequests { 0 };
     MessageID _assetRequestID { INVALID_MESSAGE_ID };
-    MessageID _assetInfoRequestID { INVALID_MESSAGE_ID };
+    ByteRange _byteRange;
 };
 
 #endif
diff --git a/libraries/networking/src/ByteRange.h b/libraries/networking/src/ByteRange.h
new file mode 100644
index 0000000000..2fe5264a49
--- /dev/null
+++ b/libraries/networking/src/ByteRange.h
@@ -0,0 +1,24 @@
+//
+//  ByteRange.h
+//  libraries/networking/src
+//
+//  Created by Stephen Birarda on 4/17/17.
+//  Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_ByteRange_h
+#define hifi_ByteRange_h
+
+struct ByteRange {
+    int64_t fromInclusive { 0 };
+    int64_t toExclusive { 0 };
+
+    bool isSet() { return fromInclusive < 0 || fromInclusive < toExclusive; }
+    int64_t size() { return toExclusive - fromInclusive; }
+};
+
+
+#endif // hifi_ByteRange_h
diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h
index 01ca62cf05..77fc8d2591 100644
--- a/libraries/networking/src/ResourceRequest.h
+++ b/libraries/networking/src/ResourceRequest.h
@@ -17,12 +17,7 @@
 
 #include <cstdint>
 
-struct ByteRange {
-    int64_t fromInclusive { 0 };
-    int64_t toExclusive { 0 };
-
-    bool isSet() { return fromInclusive < 0 || fromInclusive < toExclusive; }
-};
+#include "ByteRange.h"
 
 class ResourceRequest : public QObject {
     Q_OBJECT

From 8145e416f9edcd428374c4da29731f838ebc631a Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Mon, 17 Apr 2017 19:14:24 -0700
Subject: [PATCH 37/85] check both sides of range for invalid byte range

---
 assignment-client/src/assets/SendAssetTask.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index 4a49deabde..3196c12d97 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -74,7 +74,7 @@ void SendAssetTask::run() {
                 byteRange.toExclusive = file.size();
             }
 
-            if (file.size() < std::abs(byteRange.toExclusive)) {
+            if (file.size() < std::abs(byteRange.fromInclusive) || file.size() < byteRange.toExclusive) {
                 replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
                 qCDebug(networking) << "Bad byte range: " << hexHash << " "
                     << byteRange.fromInclusive << ":" << byteRange.toExclusive;

From 229a481232d85877fd9f8b91e6d30be5b7586893 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Mon, 17 Apr 2017 19:17:45 -0700
Subject: [PATCH 38/85] add byte range handling to FileResourceRequest

---
 .../networking/src/FileResourceRequest.cpp    | 32 ++++++++++++++++++-
 libraries/networking/src/ResourceRequest.h    |  1 +
 2 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp
index 58a2074103..3895fbc34d 100644
--- a/libraries/networking/src/FileResourceRequest.cpp
+++ b/libraries/networking/src/FileResourceRequest.cpp
@@ -11,6 +11,8 @@
 
 #include "FileResourceRequest.h"
 
+#include <cstdlib>
+
 #include <QFile>
 
 void FileResourceRequest::doSend() {
@@ -25,7 +27,35 @@ void FileResourceRequest::doSend() {
     QFile file(filename);
     if (file.exists()) {
         if (file.open(QFile::ReadOnly)) {
-            _data = file.readAll();
+
+            if (!_byteRange.isSet()) {
+                // no byte range, read the whole file
+                _data = file.readAll();
+            } else {
+                if (file.size() < std::abs(_byteRange.fromInclusive) || file.size() < _byteRange.toExclusive) {
+                    _result = ResourceRequest::InvalidByteRange;
+                } else {
+                    // we have a byte range to handle
+                    if (_byteRange.fromInclusive > 0) {
+                        // this is a positive byte range, simply skip to that part of the file and read from there
+                        file.seek(_byteRange.fromInclusive);
+                        _data = file.read(_byteRange.size());
+                    } else {
+                        // this is a negative byte range, we'll need to grab data from the end of the file first
+                        file.seek(file.size() + _byteRange.fromInclusive);
+                        _data = file.read(-_byteRange.fromInclusive);
+
+                        if (_byteRange.toExclusive > 0) {
+                            // there is additional data to read from the front of the file
+                            // handle that now
+                            file.seek(0);
+                            _data.append(file.read(_byteRange.toExclusive));
+                        }
+                    }
+                }
+
+            }
+
             _result = ResourceRequest::Success;
         } else {
             _result = ResourceRequest::AccessDenied;
diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h
index 77fc8d2591..ef40cb3455 100644
--- a/libraries/networking/src/ResourceRequest.h
+++ b/libraries/networking/src/ResourceRequest.h
@@ -37,6 +37,7 @@ public:
         Timeout,
         ServerUnavailable,
         AccessDenied,
+        InvalidByteRange,
         InvalidURL,
         NotFound
     };

From 7ae8c741c5793366e946f001332fdbc65d159d1c Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Mon, 17 Apr 2017 19:30:04 -0700
Subject: [PATCH 39/85] pass the byte range to asset client from
 AssetResourceRequest

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

diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp
index 540fb4767f..092e0ccb3d 100644
--- a/libraries/networking/src/AssetResourceRequest.cpp
+++ b/libraries/networking/src/AssetResourceRequest.cpp
@@ -114,7 +114,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
 void AssetResourceRequest::requestHash(const AssetHash& hash) {
     // Make request to atp
     auto assetClient = DependencyManager::get<AssetClient>();
-    _assetRequest = assetClient->createRequest(hash);
+    _assetRequest = assetClient->createRequest(hash, _byteRange);
 
     connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress);
     connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {

From fab1bdaeb7a148f79181a41b0b460e4eb8d27d8d Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Mon, 17 Apr 2017 19:44:52 -0700
Subject: [PATCH 40/85] push asset server packet versions for range requests

---
 libraries/networking/src/udt/PacketHeaders.cpp | 2 +-
 libraries/networking/src/udt/PacketHeaders.h   | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp
index 3ad4dbf28d..863f1bfda6 100644
--- a/libraries/networking/src/udt/PacketHeaders.cpp
+++ b/libraries/networking/src/udt/PacketHeaders.cpp
@@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
         case PacketType::AssetGetInfo:
         case PacketType::AssetGet:
         case PacketType::AssetUpload:
-            return static_cast<PacketVersion>(AssetServerPacketVersion::VegasCongestionControl);
+            return static_cast<PacketVersion>(AssetServerPacketVersion::RangeRequestSupport);
         case PacketType::NodeIgnoreRequest:
             return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
 
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 074876862f..87af3513b5 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -214,7 +214,8 @@ enum class EntityQueryPacketVersion: PacketVersion {
 };
 
 enum class AssetServerPacketVersion: PacketVersion {
-    VegasCongestionControl = 19
+    VegasCongestionControl = 19,
+    RangeRequestSupport
 };
 
 enum class AvatarMixerPacketVersion : PacketVersion {

From 06ce63f421221d06a71e15de623f9b6c9a5d8d6f Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Tue, 18 Apr 2017 10:34:40 -0700
Subject: [PATCH 41/85] fix byte range set check in SendAssetTask

---
 assignment-client/src/assets/SendAssetTask.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index 3196c12d97..89d59360b9 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -68,7 +68,7 @@ void SendAssetTask::run() {
         QFile file { filePath };
 
         if (file.open(QIODevice::ReadOnly)) {
-            if (byteRange.isSet()) {
+            if (!byteRange.isSet()) {
                 // if the byte range is not set, force it to be from 0 to the end of the file
                 byteRange.fromInclusive = 0;
                 byteRange.toExclusive = file.size();

From 7a3219d8f99f7bf352c3ddcde2f36e0be8d0c1c3 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Tue, 18 Apr 2017 10:37:15 -0700
Subject: [PATCH 42/85] force a negative to in ATP byte range to be invalid

---
 assignment-client/src/assets/SendAssetTask.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index 89d59360b9..0222849915 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -60,7 +60,7 @@ void SendAssetTask::run() {
 
     replyPacketList->writePrimitive(messageID);
 
-    if (byteRange.toExclusive < byteRange.fromInclusive) {
+    if (byteRange.toExclusive < byteRange.fromInclusive || byteRange.toExclusive < 0) {
         replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
     } else {
         QString filePath = _resourcesDir.filePath(QString(hexHash));

From 814970c4e277c9e577f92c7e75321d2a4a5be901 Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Tue, 18 Apr 2017 12:36:38 -0700
Subject: [PATCH 43/85] cleanup invalid byte range handling

---
 .../src/assets/SendAssetTask.cpp              | 21 ++++---
 libraries/networking/src/ByteRange.h          | 23 ++++++++
 .../networking/src/FileResourceRequest.cpp    | 55 ++++++++++---------
 3 files changed, 61 insertions(+), 38 deletions(-)

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index 0222849915..cb99636e83 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -60,7 +60,7 @@ void SendAssetTask::run() {
 
     replyPacketList->writePrimitive(messageID);
 
-    if (byteRange.toExclusive < byteRange.fromInclusive || byteRange.toExclusive < 0) {
+    if (!byteRange.isValid()) {
         replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
     } else {
         QString filePath = _resourcesDir.filePath(QString(hexHash));
@@ -74,15 +74,22 @@ void SendAssetTask::run() {
                 byteRange.toExclusive = file.size();
             }
 
-            if (file.size() < std::abs(byteRange.fromInclusive) || file.size() < byteRange.toExclusive) {
+            // check if we're being asked to read data that we just don't have
+            // because of the file size
+            if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) {
                 replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
                 qCDebug(networking) << "Bad byte range: " << hexHash << " "
                     << byteRange.fromInclusive << ":" << byteRange.toExclusive;
             } else {
                 // we have a valid byte range, handle it and send the asset
+
+                // first fixup the range based on the now known file size
+                byteRange.fixupRange(file.size());
+
                 auto size = byteRange.size();
 
                 if (byteRange.fromInclusive > 0) {
+
                     // this range is positive, meaning we just need to seek into the file and then read from there
                     file.seek(byteRange.fromInclusive);
                     replyPacketList->writePrimitive(AssetServerError::NoError);
@@ -98,15 +105,7 @@ void SendAssetTask::run() {
                     replyPacketList->writePrimitive(size);
 
                     // first write everything from the negative range to the end of the file
-                    replyPacketList->write(file.read(-byteRange.fromInclusive));
-
-                    if (byteRange.toExclusive != 0) {
-                        // this range has a portion that is at the front of the file
-
-                        // seek to the beginning and read what is left over
-                        file.seek(0);
-                        replyPacketList->write(file.read(byteRange.toExclusive));
-                    }
+                    replyPacketList->write(file.read(size));
                 }
 
                 qCDebug(networking) << "Sending asset: " << hexHash;
diff --git a/libraries/networking/src/ByteRange.h b/libraries/networking/src/ByteRange.h
index 2fe5264a49..3993bac324 100644
--- a/libraries/networking/src/ByteRange.h
+++ b/libraries/networking/src/ByteRange.h
@@ -18,6 +18,29 @@ struct ByteRange {
 
     bool isSet() { return fromInclusive < 0 || fromInclusive < toExclusive; }
     int64_t size() { return toExclusive - fromInclusive; }
+
+    // byte ranges are invalid if:
+    // (1) the toExclusive of the range is negative
+    // (2) the toExclusive of the range is less than the fromInclusive, and isn't zero
+    // (3) the fromExclusive of the range is negative, and the toExclusive isn't zero
+    bool isValid() {
+        return toExclusive < 0
+            || (toExclusive < fromInclusive && toExclusive != 0)
+            || (fromInclusive < 0 && toExclusive != 0);
+    }
+
+    void fixupRange(int64_t fileSize) {
+        if (fromInclusive > 0 && toExclusive == 0) {
+            // we have a left side of the range that is non-zero
+            // if the RHS of the range is zero, set it to the end of the file now
+            toExclusive = fileSize;
+        } else if (-fromInclusive >= fileSize) {
+            // we have a negative range that is equal or greater than the full size of the file
+            // so we just set this to be a range across the entire file, from 0
+            fromInclusive = 0;
+            toExclusive = fileSize;
+        }
+    }
 };
 
 
diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp
index 3895fbc34d..9e2b8f4ab1 100644
--- a/libraries/networking/src/FileResourceRequest.cpp
+++ b/libraries/networking/src/FileResourceRequest.cpp
@@ -23,45 +23,46 @@ void FileResourceRequest::doSend() {
     if (filename.isEmpty()) {
         filename = _url.toString();
     }
-    
-    QFile file(filename);
-    if (file.exists()) {
-        if (file.open(QFile::ReadOnly)) {
 
-            if (!_byteRange.isSet()) {
-                // no byte range, read the whole file
-                _data = file.readAll();
-            } else {
-                if (file.size() < std::abs(_byteRange.fromInclusive) || file.size() < _byteRange.toExclusive) {
+    if (!_byteRange.isValid()) {
+        _result = ResourceRequest::InvalidByteRange;
+    } else {
+        QFile file(filename);
+        if (file.exists()) {
+            if (file.open(QFile::ReadOnly)) {
+
+                if (file.size() < _byteRange.fromInclusive || file.size() < _byteRange.toExclusive) {
                     _result = ResourceRequest::InvalidByteRange;
                 } else {
-                    // we have a byte range to handle
-                    if (_byteRange.fromInclusive > 0) {
-                        // this is a positive byte range, simply skip to that part of the file and read from there
-                        file.seek(_byteRange.fromInclusive);
-                        _data = file.read(_byteRange.size());
+                    if (!_byteRange.isSet()) {
+                        // no byte range, read the whole file
+                        _data = file.readAll();
                     } else {
-                        // this is a negative byte range, we'll need to grab data from the end of the file first
-                        file.seek(file.size() + _byteRange.fromInclusive);
-                        _data = file.read(-_byteRange.fromInclusive);
+                        // we have a byte range to handle
 
-                        if (_byteRange.toExclusive > 0) {
-                            // there is additional data to read from the front of the file
-                            // handle that now
-                            file.seek(0);
-                            _data.append(file.read(_byteRange.toExclusive));
+                        // fix it up based on the known size of the file
+                        _byteRange.fixupRange(file.size());
+
+                        if (_byteRange.fromInclusive > 0) {
+                            // this is a positive byte range, simply skip to that part of the file and read from there
+                            file.seek(_byteRange.fromInclusive);
+                            _data = file.read(_byteRange.size());
+                        } else {
+                            // this is a negative byte range, we'll need to grab data from the end of the file first
+                            file.seek(file.size() + _byteRange.fromInclusive);
+                            _data = file.read(_byteRange.size());
                         }
                     }
+
+                    _result = ResourceRequest::Success;
                 }
 
+            } else {
+                _result = ResourceRequest::AccessDenied;
             }
-
-            _result = ResourceRequest::Success;
         } else {
-            _result = ResourceRequest::AccessDenied;
+            _result = ResourceRequest::NotFound;
         }
-    } else {
-        _result = ResourceRequest::NotFound;
     }
     
     _state = Finished;

From d9c5997b638ba80b27cfa67f5453664e07d9d2ed Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Tue, 18 Apr 2017 13:10:45 -0700
Subject: [PATCH 44/85] fix references to TextureCache in NetworkTexture

---
 .../model-networking/src/model-networking/TextureCache.cpp     | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index c8765e0a52..0eda44019b 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -458,8 +458,7 @@ void NetworkTexture::ktxMipRequestFinished() {
     if (_ktxResourceState == LOADING_INITIAL_DATA) {
         _ktxHighMipRequestFinished = true;
         maybeHandleFinishedInitialLoad();
-    }
-    else if (_ktxResourceState == REQUESTING_MIP) {
+    } else if (_ktxResourceState == REQUESTING_MIP) {
         Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
         TextureCache::requestCompleted(_self);
 

From 3928e116117606fac9d677d0ad8f54ec5038f3ec Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Tue, 18 Apr 2017 13:51:08 -0700
Subject: [PATCH 45/85] fix valid byte range check and send asset from 0

---
 assignment-client/src/assets/SendAssetTask.cpp | 2 +-
 libraries/networking/src/ByteRange.h           | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index cb99636e83..5a394eaa07 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -88,7 +88,7 @@ void SendAssetTask::run() {
 
                 auto size = byteRange.size();
 
-                if (byteRange.fromInclusive > 0) {
+                if (byteRange.fromInclusive >= 0) {
 
                     // this range is positive, meaning we just need to seek into the file and then read from there
                     file.seek(byteRange.fromInclusive);
diff --git a/libraries/networking/src/ByteRange.h b/libraries/networking/src/ByteRange.h
index 3993bac324..b5e214605d 100644
--- a/libraries/networking/src/ByteRange.h
+++ b/libraries/networking/src/ByteRange.h
@@ -24,9 +24,9 @@ struct ByteRange {
     // (2) the toExclusive of the range is less than the fromInclusive, and isn't zero
     // (3) the fromExclusive of the range is negative, and the toExclusive isn't zero
     bool isValid() {
-        return toExclusive < 0
-            || (toExclusive < fromInclusive && toExclusive != 0)
-            || (fromInclusive < 0 && toExclusive != 0);
+        return toExclusive >= 0
+                && (toExclusive >= fromInclusive || toExclusive == 0)
+                && (fromInclusive >= 0 || toExclusive == 0);
     }
 
     void fixupRange(int64_t fileSize) {

From 6fb074715e575a530548ed106341989542be9525 Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Fri, 21 Apr 2017 15:28:29 -0700
Subject: [PATCH 46/85] Fix alpha rendering bug

---
 libraries/gpu/src/gpu/Texture_ktx.cpp          | 3 ++-
 libraries/render-utils/src/MeshPartPayload.cpp | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 6828af4cc4..b2b08ca170 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -11,12 +11,13 @@
 
 
 #include "Texture.h"
-#include <qdebug.h>
 
 #include <QtCore/QByteArray>
 
 #include <ktx/KTX.h>
 
+#include "GPULogging.h"
+
 using namespace gpu;
 
 using PixelsPointer = Texture::PixelsPointer;
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 9232564514..51ce0fffa7 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -544,7 +544,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
     }
 
     if (_fadeState == FADE_WAITING_TO_START) {
-        if (_model->isLoaded()) {
+        if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
             if (EntityItem::getEntitiesShouldFadeFunction()()) {
                 _fadeStartTime = usecTimestampNow();
                 _fadeState = FADE_IN_PROGRESS;

From 8269e01474da975b48a35db8afaca3a261644b5d Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Fri, 21 Apr 2017 17:08:50 -0700
Subject: [PATCH 47/85] Fix atp-get compile error

---
 libraries/networking/src/AssetRequest.h  | 2 +-
 tests/render-texture-load/CMakeLists.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h
index 5120b8066e..e617d75157 100644
--- a/libraries/networking/src/AssetRequest.h
+++ b/libraries/networking/src/AssetRequest.h
@@ -41,7 +41,7 @@ public:
         UnknownError
     };
 
-    AssetRequest(const QString& hash, ByteRange byteRange);
+    AssetRequest(const QString& hash, ByteRange byteRange = ByteRange());
     virtual ~AssetRequest() override;
 
     Q_INVOKABLE void start();
diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt
index b73b67f56c..1f0c0a069a 100644
--- a/tests/render-texture-load/CMakeLists.txt
+++ b/tests/render-texture-load/CMakeLists.txt
@@ -10,7 +10,7 @@ setup_hifi_project(Quick Gui OpenGL)
 set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
 
 # link in the shared libraries
-link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image)
+link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics ktx image)
 
 package_libraries_for_deployment()
 

From 194541b2d0758e419e1b2408689925f708219be1 Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Fri, 21 Apr 2017 17:30:59 -0700
Subject: [PATCH 48/85] remove temp code

---
 libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index 20e4f4bc18..0d286c46eb 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -31,7 +31,7 @@
 const float METERS_TO_INCHES = 39.3701f;
 static uint32_t _currentWebCount { 0 };
 // Don't allow more than 100 concurrent web views
-static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 0;
+static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20;
 // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
 static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
 
@@ -71,7 +71,7 @@ RenderableWebEntityItem::~RenderableWebEntityItem() {
 
 bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer> renderer) {
     if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
-        //qWarning() << "Too many concurrent web views to create new view";
+        qWarning() << "Too many concurrent web views to create new view";
         return false;
     }
     QString javaScriptToInject;

From 043c587395cf0672d7a66fc97984c64cd6d049a1 Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Fri, 21 Apr 2017 19:16:56 -0700
Subject: [PATCH 49/85] Fix byte range for file resource requests

---
 .../src/assets/SendAssetTask.cpp              | 12 +++------
 libraries/networking/src/ByteRange.h          |  6 +++++
 .../networking/src/FileResourceRequest.cpp    | 27 +++++++------------
 3 files changed, 19 insertions(+), 26 deletions(-)

diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp
index 5a394eaa07..eab88e0d46 100644
--- a/assignment-client/src/assets/SendAssetTask.cpp
+++ b/assignment-client/src/assets/SendAssetTask.cpp
@@ -68,11 +68,9 @@ void SendAssetTask::run() {
         QFile file { filePath };
 
         if (file.open(QIODevice::ReadOnly)) {
-            if (!byteRange.isSet()) {
-                // if the byte range is not set, force it to be from 0 to the end of the file
-                byteRange.fromInclusive = 0;
-                byteRange.toExclusive = file.size();
-            }
+
+            // first fixup the range based on the now known file size
+            byteRange.fixupRange(file.size());
 
             // check if we're being asked to read data that we just don't have
             // because of the file size
@@ -82,10 +80,6 @@ void SendAssetTask::run() {
                     << byteRange.fromInclusive << ":" << byteRange.toExclusive;
             } else {
                 // we have a valid byte range, handle it and send the asset
-
-                // first fixup the range based on the now known file size
-                byteRange.fixupRange(file.size());
-
                 auto size = byteRange.size();
 
                 if (byteRange.fromInclusive >= 0) {
diff --git a/libraries/networking/src/ByteRange.h b/libraries/networking/src/ByteRange.h
index b5e214605d..77932a8c28 100644
--- a/libraries/networking/src/ByteRange.h
+++ b/libraries/networking/src/ByteRange.h
@@ -30,6 +30,12 @@ struct ByteRange {
     }
 
     void fixupRange(int64_t fileSize) {
+        if (!isSet()) {
+            // if the byte range is not set, force it to be from 0 to the end of the file
+            fromInclusive = 0;
+            toExclusive = fileSize;
+        }
+
         if (fromInclusive > 0 && toExclusive == 0) {
             // we have a left side of the range that is non-zero
             // if the RHS of the range is zero, set it to the end of the file now
diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp
index 9e2b8f4ab1..1e549e5fa3 100644
--- a/libraries/networking/src/FileResourceRequest.cpp
+++ b/libraries/networking/src/FileResourceRequest.cpp
@@ -34,24 +34,17 @@ void FileResourceRequest::doSend() {
                 if (file.size() < _byteRange.fromInclusive || file.size() < _byteRange.toExclusive) {
                     _result = ResourceRequest::InvalidByteRange;
                 } else {
-                    if (!_byteRange.isSet()) {
-                        // no byte range, read the whole file
-                        _data = file.readAll();
+                    // fix it up based on the known size of the file
+                    _byteRange.fixupRange(file.size());
+
+                    if (_byteRange.fromInclusive >= 0) {
+                        // this is a positive byte range, simply skip to that part of the file and read from there
+                        file.seek(_byteRange.fromInclusive);
+                        _data = file.read(_byteRange.size());
                     } else {
-                        // we have a byte range to handle
-
-                        // fix it up based on the known size of the file
-                        _byteRange.fixupRange(file.size());
-
-                        if (_byteRange.fromInclusive > 0) {
-                            // this is a positive byte range, simply skip to that part of the file and read from there
-                            file.seek(_byteRange.fromInclusive);
-                            _data = file.read(_byteRange.size());
-                        } else {
-                            // this is a negative byte range, we'll need to grab data from the end of the file first
-                            file.seek(file.size() + _byteRange.fromInclusive);
-                            _data = file.read(_byteRange.size());
-                        }
+                        // this is a negative byte range, we'll need to grab data from the end of the file first
+                        file.seek(file.size() + _byteRange.fromInclusive);
+                        _data = file.read(_byteRange.size());
                     }
 
                     _result = ResourceRequest::Success;

From e768b720ea0c407919296304635fdb76d20765bd Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 24 Apr 2017 10:51:54 -0700
Subject: [PATCH 50/85] Fix load priority for ktx loading

---
 .../model-networking/src/model-networking/TextureCache.cpp      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 0eda44019b..02e1427ddb 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -399,7 +399,7 @@ void NetworkTexture::startRequestForNextMipLevel() {
     if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
         _ktxResourceState = PENDING_MIP_REQUEST;
 
-        setLoadPriority(this, -_originalKtxDescriptor->header.numberOfMipmapLevels + _lowestKnownPopulatedMip);
+        setLoadPriority(this, -static_cast<int>(_originalKtxDescriptor->header.numberOfMipmapLevels) + _lowestKnownPopulatedMip);
 
         init();
         TextureCache::attemptRequest(_self);

From 430edb1560dcaefb3d3505dadbe999f6286aa596 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 24 Apr 2017 10:52:16 -0700
Subject: [PATCH 51/85] Fix loss-of-precision warning in TextureCache

---
 .../model-networking/src/model-networking/TextureCache.cpp   | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 02e1427ddb..d42cb51b6e 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -619,14 +619,13 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
                 ktxData += ktxHighMipData.size();
                 // TODO Move image offset calculation to ktx ImageDescriptor
-                int level;
-                for (level = images.size() - 1; level >= 0; --level) {
+                for (auto level = images.size() - 1; level >= 0; --level) {
                     auto& image = images[level];
                     if (image._imageSize > imageSizeRemaining) {
                         break;
                     }
                     ktxData -= image._imageSize;
-                    texture->assignStoredMip(level, image._imageSize, ktxData);
+                    texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
                     ktxData -= 4;
                     imageSizeRemaining -= (image._imageSize + 4);
                 }

From c10e394ad7311dd0c60cbd1542a695c952cfc824 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 24 Apr 2017 12:07:40 -0700
Subject: [PATCH 52/85] Fix unfailable condition in for loop

---
 .../model-networking/src/model-networking/TextureCache.cpp      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index d42cb51b6e..f9ee9efe3f 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -619,7 +619,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
                 ktxData += ktxHighMipData.size();
                 // TODO Move image offset calculation to ktx ImageDescriptor
-                for (auto level = images.size() - 1; level >= 0; --level) {
+                for (int level = static_cast<int>(images.size()) - 1; level >= 0; --level) {
                     auto& image = images[level];
                     if (image._imageSize > imageSizeRemaining) {
                         break;

From e4c21627f92533ef9de141db1cc26e089400abbb Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Mon, 24 Apr 2017 12:05:34 -0700
Subject: [PATCH 53/85] Remove unused newHeader from Writer.cpp

---
 libraries/ktx/src/ktx/Writer.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 0d3d4e1d60..4226b8fa84 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -43,12 +43,9 @@ namespace ktx {
     std::unique_ptr<KTX> KTX::createBare(const Header& header, const KeyValues& keyValues) {
         auto descriptors = header.generateImageDescriptors();
 
-        auto newHeader = header;
-
         Byte minMip = header.numberOfMipmapLevels;
         auto newKeyValues = keyValues;
         newKeyValues.emplace_back(KeyValue(HIFI_MIN_POPULATED_MIP_KEY, sizeof(Byte), &minMip));
-        newHeader.bytesOfKeyValueData = KeyValue::serializedKeyValuesByteSize(newKeyValues);
 
         StoragePointer storagePointer;
         {

From a2f7a8843392f8ccbf2b3bf6a99326285b914cf1 Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Tue, 25 Apr 2017 16:29:36 -0700
Subject: [PATCH 54/85] Start drawing models before we get the textures

---
 libraries/render-utils/src/MeshPartPayload.cpp | 18 ++++++++++++------
 libraries/render-utils/src/MeshPartPayload.h   | 17 +++++++++--------
 2 files changed, 21 insertions(+), 14 deletions(-)

diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 51ce0fffa7..9485790463 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -118,7 +118,7 @@ void MeshPartPayload::drawCall(gpu::Batch& batch) const {
     batch.drawIndexed(gpu::TRIANGLES, _drawPart._numIndices, _drawPart._startIndex);
 }
 
-void MeshPartPayload::bindMesh(gpu::Batch& batch) const {
+void MeshPartPayload::bindMesh(gpu::Batch& batch) {
     batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
 
     batch.setInputFormat((_drawMesh->getVertexFormat()));
@@ -255,7 +255,7 @@ void MeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::Loca
 }
 
 
-void MeshPartPayload::render(RenderArgs* args) const {
+void MeshPartPayload::render(RenderArgs* args) {
     PerformanceTimer perfTimer("MeshPartPayload::render");
 
     gpu::Batch& batch = *(args->_batch);
@@ -485,7 +485,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
     return builder.build();
 }
 
-void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
+void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) {
     if (!_isBlendShaped) {
         batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
 
@@ -517,7 +517,7 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline:
     batch.setModelTransform(_transform);
 }
 
-float ModelMeshPartPayload::computeFadeAlpha() const {
+float ModelMeshPartPayload::computeFadeAlpha() {
     if (_fadeState == FADE_WAITING_TO_START) {
         return 0.0f;
     }
@@ -536,7 +536,7 @@ float ModelMeshPartPayload::computeFadeAlpha() const {
     return Interpolate::simpleNonLinearBlend(fadeAlpha);
 }
 
-void ModelMeshPartPayload::render(RenderArgs* args) const {
+void ModelMeshPartPayload::render(RenderArgs* args) {
     PerformanceTimer perfTimer("ModelMeshPartPayload::render");
 
     if (!_model->addedToScene() || !_model->isVisible()) {
@@ -544,7 +544,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
     }
 
     if (_fadeState == FADE_WAITING_TO_START) {
-        if (_model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
+        if (_model->isLoaded()) {
             if (EntityItem::getEntitiesShouldFadeFunction()()) {
                 _fadeStartTime = usecTimestampNow();
                 _fadeState = FADE_IN_PROGRESS;
@@ -557,6 +557,12 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
         }
     }
 
+    if (_materialNeedsUpdate && _model->getGeometry()->areTexturesLoaded()) {
+        qDebug() << "Updating for textures";
+        _model->setRenderItemsNeedUpdate();
+        _materialNeedsUpdate = false;
+    }
+
     if (!args) {
         return;
     }
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index ef74011c40..11d1bbf6a7 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -46,11 +46,11 @@ public:
     virtual render::ItemKey getKey() const;
     virtual render::Item::Bound getBound() const;
     virtual render::ShapeKey getShapeKey() const; // shape interface
-    virtual void render(RenderArgs* args) const;
+    virtual void render(RenderArgs* args);
 
     // ModelMeshPartPayload functions to perform render
     void drawCall(gpu::Batch& batch) const;
-    virtual void bindMesh(gpu::Batch& batch) const;
+    virtual void bindMesh(gpu::Batch& batch);
     virtual void bindMaterial(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool enableTextures) const;
     virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const;
 
@@ -93,16 +93,16 @@ public:
             const Transform& boundTransform,
             const gpu::BufferPointer& buffer);
 
-    float computeFadeAlpha() const;
+    float computeFadeAlpha();
 
     // Render Item interface
     render::ItemKey getKey() const override;
     int getLayer() const;
     render::ShapeKey getShapeKey() const override; // shape interface
-    void render(RenderArgs* args) const override;
+    void render(RenderArgs* args) override;
 
     // ModelMeshPartPayload functions to perform render
-    void bindMesh(gpu::Batch& batch) const override;
+    void bindMesh(gpu::Batch& batch) override;
     void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
 
     void initCache();
@@ -116,11 +116,12 @@ public:
     int _shapeID;
 
     bool _isSkinned{ false };
-    bool _isBlendShaped{ false };
+    bool _isBlendShaped { false };
+    bool _materialNeedsUpdate { true };
 
 private:
-    mutable quint64 _fadeStartTime { 0 };
-    mutable uint8_t _fadeState { FADE_WAITING_TO_START };
+    quint64 _fadeStartTime { 0 };
+    uint8_t _fadeState { FADE_WAITING_TO_START };
 };
 
 namespace render {

From d338ccac26afcc7ccc924fc82b55add4ca879078 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 16:32:06 -0700
Subject: [PATCH 55/85] Update NetworkTexture to automatically download all
 mips

---
 libraries/model-networking/src/model-networking/TextureCache.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index f9ee9efe3f..e04e002784 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -276,6 +276,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
     _maxNumPixels(maxNumPixels)
 {
     _textureSource = std::make_shared<gpu::TextureSource>();
+    _lowestRequestedMipLevel = 0;
 
     if (!url.isValid()) {
         _loaded = true;

From 382fe5d38d9719bbe4c694922a097692c044f023 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 16:31:42 -0700
Subject: [PATCH 56/85] Update gpu backend to only transfer if it can populate

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

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index ef938ecd6d..e0c6d86502 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -464,7 +464,7 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
         newState = MemoryPressureState::Transfer;
     } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
         newState = MemoryPressureState::Oversubscribed;
-    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && ((unallocated != 0 && canPromote) || canPopulate)) {
+    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && ((unallocated != 0 && canPromote) && canPopulate)) {
         newState = MemoryPressureState::Undersubscribed;
     }
 

From 51ee058c0b7b8d3ff2b37ec9c9278eab8e40a6e2 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 16:43:03 -0700
Subject: [PATCH 57/85] Update GLTexture to not promote if we can't populate

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index e0c6d86502..fceed7c2eb 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -521,8 +521,7 @@ void GLVariableAllocationSupport::processWorkQueues() {
             vartexture->demote();
             _memoryPressureStateStale = true;
         } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) {
-            if (!vartexture->canPromote()) {
-                vartexture->populateTransferQueue();
+            if (!vartexture->canPromote() || !vartexture->canPopulate()) {
                 continue;
             }
             vartexture->promote();

From 65d30d1d0bd5793e5205de297db7cb1b5a2e83f9 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 16:47:38 -0700
Subject: [PATCH 58/85] Remove lowestRequestMip from gltexture

---
 .../gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp    |  1 -
 libraries/gpu-gl/src/gpu/gl45/GL45Backend.h       |  2 --
 .../src/gpu/gl45/GL45BackendVariableTexture.cpp   | 15 +++++----------
 3 files changed, 5 insertions(+), 13 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 04c2201c75..7c43a585b7 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -395,7 +395,6 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
         bool didQueueTransfer = false;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
-                const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
                 continue;
             }
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
index 21c429c577..15e98c3af7 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
@@ -117,8 +117,6 @@ public:
 
         void allocateStorage(uint16 mip);
         void copyMipsFromTexture();
-
-        uint16 _lowestRequestedMip { 0 };
     };
 
 #if 0
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 5dfbd6e727..39c8c4a7f5 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -50,7 +50,7 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
     for (uint16_t mip = 0; mip < mipLevels; ++mip) {
         if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
             && texture.isStoredMipFaceAvailable(mip)) {
-            _lowestRequestedMip = _maxAllocatedMip = _populatedMip = mip;
+            _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
@@ -106,8 +106,6 @@ void GL45ResourceTexture::promote() {
     Q_ASSERT(_allocatedMip > 0);
     uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
     if (!_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
-        _lowestRequestedMip = targetAllocatedMip;
-        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(targetAllocatedMip);
         return;
     }
     GLuint oldId = _id;
@@ -187,20 +185,17 @@ void GL45ResourceTexture::populateTransferQueue() {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
-        bool transferQueued = false;
+        bool didQueueTransfer = false;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
-                const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(sourceMip);
-                _lowestRequestedMip = sourceMip;
                 continue;
             }
-            _lowestRequestedMip = sourceMip;
 
             // If the mip is less than the max transfer size, then just do it in one transfer
             if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
                 // Can the mip be transferred in one go
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
-                transferQueued = true;
+                didQueueTransfer = true;
                 continue;
             }
 
@@ -216,12 +211,12 @@ void GL45ResourceTexture::populateTransferQueue() {
                 uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
                 lineOffset += linesToCopy;
-                transferQueued = true;
+                didQueueTransfer = true;
             }
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
-        if (transferQueued) {
+        if (didQueueTransfer) {
             _pendingTransfers.emplace(new TransferJob(*this, [=] {
                 _populatedMip = sourceMip;
                 syncSampler();

From f5bb42b19fa7ccd2c4594aa6a7374aa622f2063d Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 16:52:21 -0700
Subject: [PATCH 59/85] Remove request of mip from GL45BackendVariableTexture

---
 libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 39c8c4a7f5..2a02f9b87b 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -76,10 +76,6 @@ void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) {
         _size += _gpuObject.evalMipSize(mip);
     }
 
-    if (!_gpuObject.isStoredMipFaceAvailable(allocatedMip, 0)) {
-        const_cast<gpu::Texture&>(_gpuObject).requestInterestInMip(allocatedMip);
-    }
-
     Backend::updateTextureGPUMemoryUsage(0, _size);
 
 }

From 4d48cb2e801ee93b3328719bb139c81dfc17fdd5 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 16:52:34 -0700
Subject: [PATCH 60/85] Remove extraneous glTextureParameteri logging

---
 libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 2a02f9b87b..e3f694f551 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -93,7 +93,6 @@ void GL45ResourceTexture::copyMipsFromTexture() {
 
 void GL45ResourceTexture::syncSampler() const {
     Parent::syncSampler();
-    qDebug() << "glTextureParameteri " << QString::fromStdString(_source) << _populatedMip << _populatedMip - _allocatedMip;
     glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
 }
 

From 9505bf746cbd2a17b3d3c8f4d12e444b99d504d9 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 25 Apr 2017 21:12:14 -0700
Subject: [PATCH 61/85] Fix lower mips not being downloaded after initial mips

---
 .../model-networking/src/model-networking/TextureCache.cpp     | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index e04e002784..0e9ce46912 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -635,8 +635,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 // images with the same hash being loaded concurrently.  Only one of them will make it into the cache by hash first and will
                 // be the winner
                 texture = textureCache->cacheTextureByHash(filename, texture);
-
-
             }
 
             _lowestKnownPopulatedMip = _originalKtxDescriptor->header.numberOfMipmapLevels;
@@ -660,6 +658,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
             _ktxMipRequest->deleteLater();
             _ktxMipRequest = nullptr;
         }
+        startRequestForNextMipLevel();
     }
 }
 

From 674e767513a1086c052d77acee36cab14eba1cb2 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 08:42:21 -0700
Subject: [PATCH 62/85] Remove MipInterestListener

---
 libraries/gpu/src/gpu/Texture.cpp             | 22 -------------------
 libraries/gpu/src/gpu/Texture.h               | 11 ----------
 .../src/model-networking/TextureCache.cpp     | 18 ---------------
 .../src/model-networking/TextureCache.h       |  5 +----
 4 files changed, 1 insertion(+), 55 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp
index 1dbe9db2b4..0ede0406c3 100755
--- a/libraries/gpu/src/gpu/Texture.cpp
+++ b/libraries/gpu/src/gpu/Texture.cpp
@@ -476,32 +476,10 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
     }
 }
 
-void Texture::requestInterestInMip(uint16 level) {
-    if (!_storage->isMipAvailable(level, 0)) {
-        std::lock_guard<std::mutex> lock(_mipInterestListenersMutex);
-        for (auto& callback : _mipInterestListeners) {
-            callback->handleMipInterestCallback(level);
-        }
-    }
-}
-
 bool Texture::isStoredMipFaceAvailable(uint16 level, uint8 face) const {
     return _storage->isMipAvailable(level, face);
 }
 
-void Texture::registerMipInterestListener(MipInterestListener* listener) {
-    std::lock_guard<std::mutex> lock(_mipInterestListenersMutex);
-    _mipInterestListeners.push_back(listener);
-}
-
-void Texture::unregisterMipInterestListener(MipInterestListener* listener) {
-    std::lock_guard<std::mutex> lock(_mipInterestListenersMutex);
-    auto it = find(_mipInterestListeners.begin(), _mipInterestListeners.end(), listener);
-    if (it != _mipInterestListeners.end()) {
-        _mipInterestListeners.erase(it);
-    }
-}
-
 void Texture::setAutoGenerateMips(bool enable) {
     bool changed = false;
     if (!_autoGenerateMips) {
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 3c82f67c86..a79e679f15 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -488,17 +488,6 @@ public:
     void setStorage(std::unique_ptr<Storage>& newStorage);
     void setKtxBacking(const std::string& filename);
 
-    class MipInterestListener {
-        public:
-            virtual void handleMipInterestCallback(uint16 level) = 0;
-    };
-    void registerMipInterestListener(MipInterestListener* listener);
-    void unregisterMipInterestListener(MipInterestListener* listener);
-    std::vector<MipInterestListener*> _mipInterestListeners;
-    std::mutex _mipInterestListenersMutex;
-
-    void requestInterestInMip(uint16 level);
-
     // Usage is a a set of flags providing Semantic about the usage of the Texture.
     void setUsage(const Usage& usage) { _usage = usage; }
     Usage getUsage() const { return _usage; }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 0e9ce46912..1d37a26f37 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -245,13 +245,6 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
     return result;
 }
 
-NetworkTexture::~NetworkTexture() {
-    auto texture = _textureSource->getGPUTexture();
-    if (texture) {
-        texture->unregisterMipInterestListener(this);
-    }
-}
-
 /// Returns a texture version of an image file
 gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) {
     QImage image = QImage(path);
@@ -381,15 +374,6 @@ void NetworkTexture::makeRequest() {
 
 }
 
-void NetworkTexture::handleMipInterestCallback(uint16_t level) {
-    QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(int, level));
-}
-
-void NetworkTexture::handleMipInterestLevel(int level) {
-    _lowestRequestedMipLevel = std::min((uint16_t)level, _lowestRequestedMipLevel);
-    startRequestForNextMipLevel();
-}
-
 void NetworkTexture::startRequestForNextMipLevel() {
     if (_lowestKnownPopulatedMip == 0) {
         qWarning(networking) << "Requesting next mip level but all have been fulfilled: " << _lowestKnownPopulatedMip
@@ -647,8 +631,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
 
             _lowestKnownPopulatedMip = texture->minAvailableMipLevel();
 
-
-            texture->registerMipInterestListener(this);
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;
             setImage(texture, header->getPixelWidth(), header->getPixelHeight());
             qDebug() << "Loaded KTX: " << QString::fromStdString(hash) << " : " << _url;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index 5fb23c0d2d..c7a7799216 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -41,7 +41,7 @@ public:
 };
 
 /// A texture loaded from the network.
-class NetworkTexture : public Resource, public Texture, public gpu::Texture::MipInterestListener {
+class NetworkTexture : public Resource, public Texture {
     Q_OBJECT
 
 public:
@@ -58,9 +58,6 @@ public:
 
     gpu::TexturePointer getFallbackTexture() const;
 
-    void handleMipInterestCallback(uint16_t level) override;
-    Q_INVOKABLE void handleMipInterestLevel(int level);
-
 signals:
     void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
 

From b9ec573c8b2765e5bec4a393499c1565f86a0e48 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 10:04:52 -0700
Subject: [PATCH 63/85] Update gl41 an gl45 texture backends to take into
 account min avail mip

---
 libraries/gpu-gl/src/gpu/gl41/GL41Backend.h   |  2 +-
 .../src/gpu/gl41/GL41BackendTexture.cpp       | 19 ++++++++++++++++---
 .../gpu/gl45/GL45BackendVariableTexture.cpp   | 13 ++++++++++---
 .../src/model-networking/TextureCache.cpp     |  3 +--
 .../src/model-networking/TextureCache.h       |  1 -
 5 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
index dc6d2b3aa7..c0b9ea0e45 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
@@ -100,7 +100,7 @@ public:
         GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
         ~GL41VariableAllocationTexture();
 
-        bool canPopulate() const override { return _populatedMip > _allocatedMip && _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
+        bool canPopulate() const override { return _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
         void allocateStorage(uint16 allocatedMip);
         void syncSampler() const override;
         void promote() override;
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 7c43a585b7..51307511c2 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -231,15 +231,20 @@ using GL41VariableAllocationTexture = GL41Backend::GL41VariableAllocationTexture
 GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41Texture(backend, texture) {
     auto mipLevels = texture.getNumMips();
     _allocatedMip = mipLevels;
+    _maxAllocatedMip = _populatedMip = mipLevels;
+    uint16_t minAvailableMip = texture.minAvailableMipLevel();
     uvec3 mipDimensions;
     for (uint16_t mip = 0; mip < mipLevels; ++mip) {
-        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
+        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
+            && mip >= minAvailableMip) {
             _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
 
-    uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
+    auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
+    uint16_t allocatedMip = std::max<uint16_t>(minAvailableMip, targetMip);
+
     allocateStorage(allocatedMip);
     _memoryPressureStateStale = true;
     size_t maxFace = GLTexture::getFaceCount(_target);
@@ -292,6 +297,14 @@ void GL41VariableAllocationTexture::syncSampler() const {
 void GL41VariableAllocationTexture::promote() {
     PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
     Q_ASSERT(_allocatedMip > 0);
+
+    uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
+    targetAllocatedMip = std::max<uint16_t>(_gpuObject.minAvailableMipLevel(), targetAllocatedMip);
+
+    if (targetAllocatedMip >= _allocatedMip || !_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
+        return;
+    }
+
     GLuint oldId = _id;
     auto oldSize = _size;
     // create new texture
@@ -299,7 +312,7 @@ void GL41VariableAllocationTexture::promote() {
     uint16_t oldAllocatedMip = _allocatedMip;
 
     // allocate storage for new level
-    allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
+    allocateStorage(targetAllocatedMip);
 
     withPreservedTexture([&] {
         GLuint fbo { 0 };
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index e3f694f551..9b35f35934 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -46,16 +46,20 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
 
     _maxAllocatedMip = _populatedMip = mipLevels;
 
+    uint16_t minAvailableMip = texture.minAvailableMipLevel();
+
     uvec3 mipDimensions;
     for (uint16_t mip = 0; mip < mipLevels; ++mip) {
         if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
-            && texture.isStoredMipFaceAvailable(mip)) {
+            && mip >= minAvailableMip) {
             _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
 
-    uint16_t allocatedMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
+    auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
+    uint16_t allocatedMip = std::max<uint16_t>(minAvailableMip, targetMip);
+
     allocateStorage(allocatedMip);
     _memoryPressureStateStale = true;
     copyMipsFromTexture();
@@ -99,8 +103,11 @@ void GL45ResourceTexture::syncSampler() const {
 void GL45ResourceTexture::promote() {
     PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
     Q_ASSERT(_allocatedMip > 0);
+
     uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
-    if (!_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
+    targetAllocatedMip = std::max<uint16_t>(_gpuObject.minAvailableMipLevel(), targetAllocatedMip);
+
+    if (targetAllocatedMip >= _allocatedMip || !_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
         return;
     }
     GLuint oldId = _id;
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 1d37a26f37..62b94ed455 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -588,8 +588,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                     _ktxResourceState = FAILED_TO_LOAD;
                     finishedLoading(false);
                     return;
-                }
-                else {
+                } else {
                     _file = file;
                 }
 
diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h
index c7a7799216..1e61b9ecee 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.h
+++ b/libraries/model-networking/src/model-networking/TextureCache.h
@@ -46,7 +46,6 @@ class NetworkTexture : public Resource, public Texture {
 
 public:
     NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels);
-    ~NetworkTexture() override;
 
     QString getType() const override { return "NetworkTexture"; }
 

From 4f16eb9bccc7f846257828483593d8bf41bc873d Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 10:21:27 -0700
Subject: [PATCH 64/85] Cleanup KTX logging and add fragment to resource url to
 indicate mip level

---
 .../src/model-networking/TextureCache.cpp            | 12 +++++++++---
 libraries/networking/src/HTTPResourceRequest.cpp     |  5 -----
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 62b94ed455..48999c21f3 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -338,7 +338,9 @@ void NetworkTexture::makeRequest() {
     if (_ktxResourceState == PENDING_INITIAL_LOAD) {
         _ktxResourceState = LOADING_INITIAL_DATA;
 
-        qDebug() << ">>> Making request to " << _url << " for header";
+        // Add a fragment to the base url so we can identify the section of the ktx being requested when debugging
+        // The actual requested url is _activeUrl and will not contain the fragment
+        _url.setFragment("head");
         _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
 
         if (!_ktxHeaderRequest) {
@@ -366,7 +368,12 @@ void NetworkTexture::makeRequest() {
     } else if (_ktxResourceState == PENDING_MIP_REQUEST) {
         if (_lowestKnownPopulatedMip > 0) {
             _ktxResourceState = REQUESTING_MIP;
-            startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1);
+
+            // Add a fragment to the base url so we can identify the section of the ktx being requested when debugging
+            // The actual requested url is _activeUrl and will not contain the fragment
+            uint16_t nextMip = _lowestKnownPopulatedMip - 1;
+            _url.setFragment(QString::number(nextMip));
+            startMipRangeRequest(nextMip, nextMip);
         }
     } else {
         qWarning(networking) << "NetworkTexture::makeRequest() called while not in a valid state: " << _ktxResourceState;
@@ -632,7 +639,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
 
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;
             setImage(texture, header->getPixelWidth(), header->getPixelHeight());
-            qDebug() << "Loaded KTX: " << QString::fromStdString(hash) << " : " << _url;
 
             _ktxHeaderRequest->deleteLater();
             _ktxHeaderRequest = nullptr;
diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp
index 52f5ee43f5..c6a4b93e51 100644
--- a/libraries/networking/src/HTTPResourceRequest.cpp
+++ b/libraries/networking/src/HTTPResourceRequest.cpp
@@ -22,7 +22,6 @@
 #include "NetworkLogging.h"
 
 HTTPResourceRequest::~HTTPResourceRequest() {
-    qDebug() << "Cleaning up:" << _url << " " << _byteRange.fromInclusive << "-" << _byteRange.toExclusive;
     if (_reply) {
         _reply->disconnect(this);
         _reply->deleteLater();
@@ -68,7 +67,6 @@ void HTTPResourceRequest::doSend() {
             // HTTP byte ranges are inclusive on the `to` end: [from, to]
             byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive - 1);
         }
-        qDebug() << "Setting http range to " << byteRange;
         networkRequest.setRawHeader("Range", byteRange.toLatin1());
     }
     networkRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, false);
@@ -79,12 +77,9 @@ void HTTPResourceRequest::doSend() {
     connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress);
 
     setupTimer();
-    qDebug() << "Sent: " << _url;
 }
 
 void HTTPResourceRequest::onRequestFinished() {
-    qDebug() << "On request finished: " << _url;
-
     Q_ASSERT(_state == InProgress);
     Q_ASSERT(_reply);
 

From 29641ba69a2454e01d736eb3fbd4bcc02c319bbc Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 10:55:55 -0700
Subject: [PATCH 65/85] Fix min mip not being written to ktx correctly

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

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index b2b08ca170..6fe07d4241 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -207,7 +207,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
         memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
         _minMipLevelAvailable = level;
         if (offset > 0) {
-            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + offset, (void*)&_minMipLevelAvailable, 1);
+            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + 4 + offset, (void*)&_minMipLevelAvailable, 1);
         }
     }
 }

From 3184ddb29f61d0f3f4edd89c95b789bb0ec083fc Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 11:00:16 -0700
Subject: [PATCH 66/85] Fix duplicate low mip calculation in NetworkTexture

---
 .../src/model-networking/TextureCache.cpp                 | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 48999c21f3..011b3f4ed9 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -627,14 +627,6 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                 texture = textureCache->cacheTextureByHash(filename, texture);
             }
 
-            _lowestKnownPopulatedMip = _originalKtxDescriptor->header.numberOfMipmapLevels;
-            for (uint16_t l = 0; l < 200; l++) {
-                if (texture->isStoredMipFaceAvailable(l)) {
-                    _lowestKnownPopulatedMip = l;
-                    break;
-                }
-            }
-
             _lowestKnownPopulatedMip = texture->minAvailableMipLevel();
 
             _ktxResourceState = WAITING_FOR_MIP_REQUEST;

From 0f461a2188f4e471dbc83c4ea0ef896f74a6e72d Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 11:34:27 -0700
Subject: [PATCH 67/85] Fix priority being reset when requesting low mips

---
 .../model-networking/src/model-networking/TextureCache.cpp    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 011b3f4ed9..c367ad4289 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -391,9 +391,9 @@ void NetworkTexture::startRequestForNextMipLevel() {
     if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
         _ktxResourceState = PENDING_MIP_REQUEST;
 
-        setLoadPriority(this, -static_cast<int>(_originalKtxDescriptor->header.numberOfMipmapLevels) + _lowestKnownPopulatedMip);
-
         init();
+        setLoadPriority(this, -static_cast<int>(_originalKtxDescriptor->header.numberOfMipmapLevels) + _lowestKnownPopulatedMip);
+        _url.setFragment(QString::number(_lowestKnownPopulatedMip - 1));
         TextureCache::attemptRequest(_self);
     }
 }

From 01724c9c907b89503933cf1af4671eba8495cf13 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 11:34:42 -0700
Subject: [PATCH 68/85] Update default load priority to be 0

---
 libraries/networking/src/ResourceCache.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 56897ca4cd..0a24cb4d91 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -555,6 +555,10 @@ void Resource::clearLoadPriority(const QPointer<QObject>& owner) {
 }
 
 float Resource::getLoadPriority() {
+    if (_loadPriorities.size() == 0) {
+        return 0;
+    }
+
     float highestPriority = -FLT_MAX;
     for (QHash<QPointer<QObject>, float>::iterator it = _loadPriorities.begin(); it != _loadPriorities.end(); ) {
         if (it.key().isNull()) {

From 1555fc308861645e1f9746feec4b661a4b7a0a35 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 11:36:33 -0700
Subject: [PATCH 69/85] Update Resource to only clear load priorities if
 successful

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

diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp
index 0a24cb4d91..7ae75b9538 100644
--- a/libraries/networking/src/ResourceCache.cpp
+++ b/libraries/networking/src/ResourceCache.cpp
@@ -643,12 +643,12 @@ void Resource::attemptRequest() {
 void Resource::finishedLoading(bool success) {
     if (success) {
         qCDebug(networking).noquote() << "Finished loading:" << _url.toDisplayString();
+        _loadPriorities.clear();
         _loaded = true;
     } else {
         qCDebug(networking).noquote() << "Failed to load:" << _url.toDisplayString();
         _failedToLoad = true;
     }
-    _loadPriorities.clear();
     emit finished(success);
 }
 

From 70ece9f0fdfcf0b6f20a2b2f9068f6f312b038c8 Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Wed, 26 Apr 2017 13:56:26 -0700
Subject: [PATCH 70/85] Load skybox first and sounds later

---
 libraries/audio/src/SoundCache.cpp                          | 6 +++++-
 .../model-networking/src/model-networking/TextureCache.cpp  | 5 +++++
 libraries/render-utils/src/MeshPartPayload.cpp              | 1 -
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp
index 6b34c68959..1646540da6 100644
--- a/libraries/audio/src/SoundCache.cpp
+++ b/libraries/audio/src/SoundCache.cpp
@@ -14,6 +14,8 @@
 #include "AudioLogging.h"
 #include "SoundCache.h"
 
+static const int SOUNDS_LOADING_PRIORITY { -7 }; // Make sure sounds load after the low rez texture mips
+
 int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
 
 SoundCache::SoundCache(QObject* parent) :
@@ -37,5 +39,7 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) {
 QSharedPointer<Resource> SoundCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
     const void* extra) {
     qCDebug(audio) << "Requesting sound at" << url.toString();
-    return QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
+    auto resource = QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
+    resource->setLoadPriority(this, SOUNDS_LOADING_PRIORITY);
+    return resource;
 }
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index c367ad4289..4597304550 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -50,6 +50,8 @@ Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.k
 const std::string TextureCache::KTX_DIRNAME { "ktx_cache" };
 const std::string TextureCache::KTX_EXT { "ktx" };
 
+static const int SKYBOX_LOAD_PRIORITY { 10 }; // Make sure skybox loads first
+
 TextureCache::TextureCache() :
     _ktxCache(KTX_DIRNAME, KTX_EXT) {
     setUnusedResourceCacheSize(0);
@@ -259,6 +261,9 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSh
     auto content = textureExtra ? textureExtra->content : QByteArray();
     auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS;
     NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels);
+    if (type == image::TextureUsage::CUBE_TEXTURE) {
+        texture->setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
+    }
     return QSharedPointer<Resource>(texture, &Resource::deleter);
 }
 
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 9485790463..2e08420073 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -558,7 +558,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
     }
 
     if (_materialNeedsUpdate && _model->getGeometry()->areTexturesLoaded()) {
-        qDebug() << "Updating for textures";
         _model->setRenderItemsNeedUpdate();
         _materialNeedsUpdate = false;
     }

From a71d246e754bb93a358d2ee3bde55c7808f003a0 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 12:50:42 -0700
Subject: [PATCH 71/85] Replace canPopulate with _minAllocatedMip

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp            |  6 ++----
 libraries/gpu-gl/src/gpu/gl/GLTexture.h              |  6 ++++--
 libraries/gpu-gl/src/gpu/gl41/GL41Backend.h          |  1 -
 libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp | 12 ++++++++++++
 libraries/gpu-gl/src/gpu/gl45/GL45Backend.h          |  1 -
 libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp |  9 ++++++---
 6 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index fceed7c2eb..dc4b828fbb 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -437,7 +437,6 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
     size_t idealMemoryAllocation = 0;
     bool canDemote = false;
     bool canPromote = false;
-    bool canPopulate = false;
     bool hasTransfers = false;
     for (const auto& texture : strongTextures) {
         // Race conditions can still leave nulls in the list, so we need to check
@@ -452,7 +451,6 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
         totalVariableMemoryAllocation += gltexture->size();
         canDemote |= vartexture->canDemote();
         canPromote |= vartexture->canPromote();
-        canPopulate |= vartexture->canPopulate();
         hasTransfers |= vartexture->hasPendingTransfers();
     }
 
@@ -464,7 +462,7 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
         newState = MemoryPressureState::Transfer;
     } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
         newState = MemoryPressureState::Oversubscribed;
-    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && ((unallocated != 0 && canPromote) && canPopulate)) {
+    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) {
         newState = MemoryPressureState::Undersubscribed;
     }
 
@@ -521,7 +519,7 @@ void GLVariableAllocationSupport::processWorkQueues() {
             vartexture->demote();
             _memoryPressureStateStale = true;
         } else if (MemoryPressureState::Undersubscribed == _memoryPressureState) {
-            if (!vartexture->canPromote() || !vartexture->canPopulate()) {
+            if (!vartexture->canPromote()) {
                 continue;
             }
             vartexture->promote();
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
index 9aad49546e..3e944ed70d 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
@@ -112,8 +112,7 @@ protected:
     static void manageMemory();
 
     //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
-    virtual bool canPopulate() const = 0;
-    bool canPromote() const { return _allocatedMip > 0; }
+    bool canPromote() const { return _allocatedMip > _minAllocatedMip; }
     bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
     bool hasPendingTransfers() const { return _pendingTransfers.size() > 0; }
     void executeNextTransfer(const TexturePointer& currentTexture);
@@ -131,6 +130,9 @@ protected:
     // The highest (lowest resolution) mip that we will support, relative to the number 
     // of mips in the gpu::Texture object
     uint16 _maxAllocatedMip { 0 };
+    // The lowest (highest resolution) mip that we will support, relative to the number
+    // of mips in the gpu::Texture object
+    uint16 _minAllocatedMip { 0 };
     // Contains a series of lambdas that when executed will transfer data to the GPU, modify 
     // the _populatedMip and update the sampler in order to fully populate the allocated texture 
     // until _populatedMip == _allocatedMip
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
index c0b9ea0e45..19979a1778 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h
@@ -100,7 +100,6 @@ public:
         GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
         ~GL41VariableAllocationTexture();
 
-        bool canPopulate() const override { return _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
         void allocateStorage(uint16 allocatedMip);
         void syncSampler() const override;
         void promote() override;
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 51307511c2..9fa873f6e2 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -55,6 +55,18 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
             default:
                 Q_UNREACHABLE();
         }
+    } else {
+        if (texture.getUsageType() == TextureUsageType::RESOURCE) {
+            auto varTex = static_cast<GL41VariableAllocationTexture*> (object);
+
+            if (varTex->_minAllocatedMip > 0) {
+                auto minAvailableMip = texture.minAvailableMipLevel();
+                if (minAvailableMip < varTex->_minAllocatedMip) {
+                    varTex->_minAllocatedMip = minAvailableMip;
+                    GL41VariableAllocationTexture::_memoryPressureStateStale = true;
+                }
+            }
+        }
     }
 
     return object;
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
index 15e98c3af7..dbedd81c76 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h
@@ -100,7 +100,6 @@ public:
         GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
         ~GL45VariableAllocationTexture();
         Size size() const override { return _size; }
-        bool canPopulate() const override { return _gpuObject.isStoredMipFaceAvailable(_populatedMip - 1, 0); }
         Size _size { 0 };
     };
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index 299a52eddd..120be923f5 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -85,10 +85,13 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
         if (texture.getUsageType() == TextureUsageType::RESOURCE) {
             auto varTex = static_cast<GL45VariableAllocationTexture*> (object);
 
-            if (varTex->canPromote() && varTex->canPopulate()) {
-                GL45VariableAllocationTexture::_memoryPressureStateStale = true;
+            if (varTex->_minAllocatedMip > 0) {
+                auto minAvailableMip = texture.minAvailableMipLevel();
+                if (minAvailableMip < varTex->_minAllocatedMip) {
+                    varTex->_minAllocatedMip = minAvailableMip;
+                    GL45VariableAllocationTexture::_memoryPressureStateStale = true;
+                }
             }
-
         }
     }
 

From 6a0474934c286db44415ba0bed7344c728da435e Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 13:36:42 -0700
Subject: [PATCH 72/85] Fixup variabletexture ctors

---
 libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp     | 9 ++++-----
 .../gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp   | 9 ++++-----
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 9fa873f6e2..14c7148b3b 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -244,18 +244,17 @@ GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr
     auto mipLevels = texture.getNumMips();
     _allocatedMip = mipLevels;
     _maxAllocatedMip = _populatedMip = mipLevels;
-    uint16_t minAvailableMip = texture.minAvailableMipLevel();
+    _minAllocatedMip = texture.minAvailableMipLevel();
     uvec3 mipDimensions;
-    for (uint16_t mip = 0; mip < mipLevels; ++mip) {
-        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
-            && mip >= minAvailableMip) {
+    for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) {
+        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
             _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
 
     auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
-    uint16_t allocatedMip = std::max<uint16_t>(minAvailableMip, targetMip);
+    uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
 
     allocateStorage(allocatedMip);
     _memoryPressureStateStale = true;
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 9b35f35934..c25d011073 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -46,19 +46,18 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend
 
     _maxAllocatedMip = _populatedMip = mipLevels;
 
-    uint16_t minAvailableMip = texture.minAvailableMipLevel();
+    _minAllocatedMip = texture.minAvailableMipLevel();
 
     uvec3 mipDimensions;
-    for (uint16_t mip = 0; mip < mipLevels; ++mip) {
-        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))
-            && mip >= minAvailableMip) {
+    for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) {
+        if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
             _maxAllocatedMip = _populatedMip = mip;
             break;
         }
     }
 
     auto targetMip = _populatedMip - std::min<uint16_t>(_populatedMip, 2);
-    uint16_t allocatedMip = std::max<uint16_t>(minAvailableMip, targetMip);
+    uint16_t allocatedMip = std::max<uint16_t>(_minAllocatedMip, targetMip);
 
     allocateStorage(allocatedMip);
     _memoryPressureStateStale = true;

From 1bd95ee19fdb79a28e34f6a3ca3815df45e68e6b Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 14:26:09 -0700
Subject: [PATCH 73/85] Remove didQueueTransfer check from
 populateTransferQueue

---
 .../gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp      | 13 ++++---------
 .../src/gpu/gl45/GL45BackendVariableTexture.cpp     | 13 ++++---------
 2 files changed, 8 insertions(+), 18 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 14c7148b3b..948381f288 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -416,7 +416,6 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
-        bool didQueueTransfer = false;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 continue;
@@ -426,7 +425,6 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
             if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
                 // Can the mip be transferred in one go
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
-                didQueueTransfer = true;
                 continue;
             }
 
@@ -442,17 +440,14 @@ void GL41VariableAllocationTexture::populateTransferQueue() {
                 uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
                 lineOffset += linesToCopy;
-                didQueueTransfer = true;
             }
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
-        if (didQueueTransfer) {
-            _pendingTransfers.emplace(new TransferJob(*this, [=] {
-                _populatedMip = sourceMip;
-                syncSampler();
-            }));
-        }
+        _pendingTransfers.emplace(new TransferJob(*this, [=] {
+            _populatedMip = sourceMip;
+            syncSampler();
+        }));
     } while (sourceMip != _allocatedMip);
 }
 
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index c25d011073..6e00159a2e 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -186,7 +186,6 @@ void GL45ResourceTexture::populateTransferQueue() {
         --sourceMip;
         auto targetMip = sourceMip - _allocatedMip;
         auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip);
-        bool didQueueTransfer = false;
         for (uint8_t face = 0; face < maxFace; ++face) {
             if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) {
                 continue;
@@ -196,7 +195,6 @@ void GL45ResourceTexture::populateTransferQueue() {
             if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) {
                 // Can the mip be transferred in one go
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face));
-                didQueueTransfer = true;
                 continue;
             }
 
@@ -212,17 +210,14 @@ void GL45ResourceTexture::populateTransferQueue() {
                 uint32_t linesToCopy = std::min<uint32_t>(lines - lineOffset, linesPerTransfer);
                 _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset));
                 lineOffset += linesToCopy;
-                didQueueTransfer = true;
             }
         }
 
         // queue up the sampler and populated mip change for after the transfer has completed
-        if (didQueueTransfer) {
-            _pendingTransfers.emplace(new TransferJob(*this, [=] {
-                _populatedMip = sourceMip;
-                syncSampler();
-            }));
-        }
+        _pendingTransfers.emplace(new TransferJob(*this, [=] {
+            _populatedMip = sourceMip;
+            syncSampler();
+        }));
     } while (sourceMip != _allocatedMip);
 }
 

From 7bb6010149700be77c9e1337fca131f183bc87be Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 14:37:40 -0700
Subject: [PATCH 74/85] Fixup targetAllocatedMip inside gl textures

---
 libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp       | 7 ++-----
 .../gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp     | 7 +------
 2 files changed, 3 insertions(+), 11 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 948381f288..5db924dd5c 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -245,6 +245,7 @@ GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr
     _allocatedMip = mipLevels;
     _maxAllocatedMip = _populatedMip = mipLevels;
     _minAllocatedMip = texture.minAvailableMipLevel();
+
     uvec3 mipDimensions;
     for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) {
         if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) {
@@ -310,11 +311,7 @@ void GL41VariableAllocationTexture::promote() {
     Q_ASSERT(_allocatedMip > 0);
 
     uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
-    targetAllocatedMip = std::max<uint16_t>(_gpuObject.minAvailableMipLevel(), targetAllocatedMip);
-
-    if (targetAllocatedMip >= _allocatedMip || !_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
-        return;
-    }
+    targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip);
 
     GLuint oldId = _id;
     auto oldSize = _size;
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
index 6e00159a2e..92d820e5f0 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp
@@ -43,9 +43,7 @@ using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
 GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {
     auto mipLevels = texture.getNumMips();
     _allocatedMip = mipLevels;
-
     _maxAllocatedMip = _populatedMip = mipLevels;
-
     _minAllocatedMip = texture.minAvailableMipLevel();
 
     uvec3 mipDimensions;
@@ -104,11 +102,8 @@ void GL45ResourceTexture::promote() {
     Q_ASSERT(_allocatedMip > 0);
 
     uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
-    targetAllocatedMip = std::max<uint16_t>(_gpuObject.minAvailableMipLevel(), targetAllocatedMip);
+    targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip);
 
-    if (targetAllocatedMip >= _allocatedMip || !_gpuObject.isStoredMipFaceAvailable(targetAllocatedMip, 0)) {
-        return;
-    }
     GLuint oldId = _id;
     auto oldSize = _size;
     // create new texture

From e4e554aaeaf7c3f79ebdd5394ea6011010dc2021 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 14:38:13 -0700
Subject: [PATCH 75/85] Remove dead code from Texture.h

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

diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index a79e679f15..825e9237f5 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -480,7 +480,7 @@ public:
 
     // Access the stored mips and faces
     const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); }
-    bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const;// { return _storage->isMipAvailable(level, face); }
+    bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const;
     Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); }
     Size getStoredMipSize(uint16 level) const;
     Size getStoredSize() const;

From c7ac82b4e2b0eb167a88af1aeac6d4595020bdc3 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 14:43:25 -0700
Subject: [PATCH 76/85] Remove magic number for kv and image sizes

---
 libraries/gpu/src/gpu/Texture_ktx.cpp                       | 6 +++---
 libraries/ktx/src/ktx/KTX.h                                 | 2 ++
 .../model-networking/src/model-networking/TextureCache.cpp  | 6 +++---
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 6fe07d4241..207de61b35 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -193,7 +193,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
 
     auto data = file->mutableData();
     data += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
-    data += 4;
+    data += ktx::IMAGE_SIZE_WIDTH;
 
     auto offset = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
     {
@@ -207,7 +207,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
         memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
         _minMipLevelAvailable = level;
         if (offset > 0) {
-            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + 4 + offset, (void*)&_minMipLevelAvailable, 1);
+            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + ktx::KV_SIZE_WIDTH + offset, (void*)&_minMipLevelAvailable, 1);
         }
     }
 }
@@ -300,7 +300,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
                 }
                 images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces));
             }
-            imageOffset += static_cast<uint32_t>(mip->getSize()) + 4;
+            imageOffset += static_cast<uint32_t>(mip->getSize()) + ktx::IMAGE_SIZE_WIDTH;
         }
     }
 
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index e84cba085d..5a3167cf53 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -390,6 +390,8 @@ namespace ktx {
     };
     static const size_t KTX_HEADER_SIZE = 64;
     static_assert(sizeof(Header) == KTX_HEADER_SIZE, "KTX Header size is static and should not change from the spec");
+    static const size_t KV_SIZE_WIDTH = 4; // Number of bytes for keyAndValueByteSize
+    static const size_t IMAGE_SIZE_WIDTH = 4; // Number of bytes for imageSize
 
     // Key Values
     struct KeyValue {
diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 4597304550..74ddf1ae1a 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -429,7 +429,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
     } else {
         ByteRange range;
         range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
-                              + _originalKtxDescriptor->images[low]._imageOffset + 4;
+                              + _originalKtxDescriptor->images[low]._imageOffset + ktx::IMAGE_SIZE_WIDTH;
         range.toExclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
                               + _originalKtxDescriptor->images[high + 1]._imageOffset;
         _ktxMipRequest->setByteRange(range);
@@ -622,8 +622,8 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
                     }
                     ktxData -= image._imageSize;
                     texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
-                    ktxData -= 4;
-                    imageSizeRemaining -= (image._imageSize + 4);
+                    ktxData -= ktx::IMAGE_SIZE_WIDTH;
+                    imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
                 }
 
                 // We replace the texture with the one stored in the cache.  This deals with the possible race condition of two different 

From 19d5414c30be0ba212a568c49aa74ae8f7231f4c Mon Sep 17 00:00:00 2001
From: Atlante45 <clement.brisset@gmail.com>
Date: Wed, 26 Apr 2017 16:23:09 -0700
Subject: [PATCH 77/85] Reduce High Mips request size

---
 .../model-networking/src/model-networking/TextureCache.cpp     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp
index 74ddf1ae1a..55704236e3 100644
--- a/libraries/model-networking/src/model-networking/TextureCache.cpp
+++ b/libraries/model-networking/src/model-networking/TextureCache.cpp
@@ -422,9 +422,10 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
 
     _ktxMipLevelRangeInFlight = { low, high };
     if (isHighMipRequest) {
+        static const int HIGH_MIP_MAX_SIZE = 5516;
         // This is a special case where we load the high 7 mips
         ByteRange range;
-        range.fromInclusive = -15000;
+        range.fromInclusive = -HIGH_MIP_MAX_SIZE;
         _ktxMipRequest->setByteRange(range);
     } else {
         ByteRange range;

From f15a34e145fd1ea64e391d5c4dde3b7d92868147 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 15:41:49 -0700
Subject: [PATCH 78/85] Cleanup data pointer calculations in assignMipData

---
 libraries/gpu/src/gpu/Texture_ktx.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 207de61b35..e3c4b325fb 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -191,9 +191,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
 
     auto file = maybeOpenFile();
 
-    auto data = file->mutableData();
-    data += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
-    data += ktx::IMAGE_SIZE_WIDTH;
+    auto imageData = file->mutableData();
+    imageData += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
+    imageData += ktx::IMAGE_SIZE_WIDTH;
 
     auto offset = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
     {
@@ -204,10 +204,11 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
             return;
         }
 
-        memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize);
+        memcpy(imageData, storage->data(), _ktxDescriptor->images[level]._imageSize);
         _minMipLevelAvailable = level;
         if (offset > 0) {
-            memcpy(file->mutableData() + ktx::KTX_HEADER_SIZE + ktx::KV_SIZE_WIDTH + offset, (void*)&_minMipLevelAvailable, 1);
+            auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + ktx::KV_SIZE_WIDTH + offset;
+            memcpy(minMipKeyData, (void*)&_minMipLevelAvailable, 1);
         }
     }
 }

From f509403b25c3169966918fe2b93ae2dec3a08c96 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 16:15:19 -0700
Subject: [PATCH 79/85] Update prioritization of memory pressure states

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index dc4b828fbb..5bd5ac8db1 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -458,12 +458,12 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
     float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
 
     auto newState = MemoryPressureState::Idle;
-    if (hasTransfers) {
-        newState = MemoryPressureState::Transfer;
+    if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) {
+        newState = MemoryPressureState::Undersubscribed;
     } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
         newState = MemoryPressureState::Oversubscribed;
-    } else if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) {
-        newState = MemoryPressureState::Undersubscribed;
+    } else if (hasTransfers) {
+        newState = MemoryPressureState::Transfer;
     }
 
     if (newState != _memoryPressureState) {
@@ -539,6 +539,7 @@ void GLVariableAllocationSupport::processWorkQueues() {
     }
 
     if (workQueue.empty()) {
+        _memoryPressureState = MemoryPressureState::Idle;
         _memoryPressureStateStale = true;
     }
 }

From 4395cd7ee18f42d1bbc106f5fd925a24f5fa9ad5 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 16:30:17 -0700
Subject: [PATCH 80/85] Update hasPendingTransfers to not touch
 _pendingTransfers

---
 libraries/gpu-gl/src/gpu/gl/GLTexture.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
index 3e944ed70d..cd7b30b961 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h
@@ -114,7 +114,7 @@ protected:
     //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; }
     bool canPromote() const { return _allocatedMip > _minAllocatedMip; }
     bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
-    bool hasPendingTransfers() const { return _pendingTransfers.size() > 0; }
+    bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
     void executeNextTransfer(const TexturePointer& currentTexture);
     virtual void populateTransferQueue() = 0;
     virtual void promote() = 0;

From 47cf44dc60819842bd86816d989891490cf0e4a7 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 17:11:19 -0700
Subject: [PATCH 81/85] Store offset to min mip kv in KtxStorage

---
 libraries/gpu/src/gpu/Texture.h       |  1 +
 libraries/gpu/src/gpu/Texture_ktx.cpp | 17 ++++++++---------
 libraries/ktx/src/ktx/KTX.cpp         |  2 +-
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 825e9237f5..502ad33143 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -330,6 +330,7 @@ public:
 
         std::string _filename;
         std::atomic<uint8_t> _minMipLevelAvailable;
+        size_t _offsetToMinMipKV;
 
         ktx::KTXDescriptorPointer _ktxDescriptor;
         friend class Texture;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index e3c4b325fb..8181bb21ae 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -109,18 +109,18 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
         if (_ktxDescriptor->images.size() < _ktxDescriptor->header.numberOfMipmapLevels) {
             qWarning() << "Bad images found in ktx";
         }
-        auto& keyValues = _ktxDescriptor->keyValues;
-        auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
-            return val._key.compare(ktx::HIFI_MIN_POPULATED_MIP_KEY) == 0;
-        });
-        if (found != keyValues.end()) {
-            _minMipLevelAvailable = found->_value[0];
+
+        _offsetToMinMipKV = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
+        if (_offsetToMinMipKV) {
+            auto data = storage->data() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
+            _minMipLevelAvailable = *data;
         } else {
             // Assume all mip levels are available
             _minMipLevelAvailable = 0;
         }
     }
 
+
     // now that we know the ktx, let's get the header info to configure this Texture::Storage:
     Format mipFormat = Format::COLOR_BGRA_32;
     Format texelFormat = Format::COLOR_SRGBA_32;
@@ -195,7 +195,6 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     imageData += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
     imageData += ktx::IMAGE_SIZE_WIDTH;
 
-    auto offset = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
     {
         std::lock_guard<std::mutex> lock { _cacheFileWriteMutex };
 
@@ -206,8 +205,8 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
 
         memcpy(imageData, storage->data(), _ktxDescriptor->images[level]._imageSize);
         _minMipLevelAvailable = level;
-        if (offset > 0) {
-            auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + ktx::KV_SIZE_WIDTH + offset;
+        if (_offsetToMinMipKV > 0) {
+            auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
             memcpy(minMipKeyData, (void*)&_minMipLevelAvailable, 1);
         }
     }
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index 0dbc2e720f..cea397927a 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -126,7 +126,7 @@ size_t KTXDescriptor::getValueOffsetForKey(const std::string& key) const {
     size_t offset { 0 };
     for (auto& kv : keyValues) {
         if (kv._key == key) {
-            return offset + kv._key.size() + 1;
+            return offset + ktx::KV_SIZE_WIDTH + kv._key.size() + 1;
         }
         offset += kv.serializedByteSize();
     }

From cb299695f461a3ca29dee4858bea330f2607531e Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 17:52:53 -0700
Subject: [PATCH 82/85] Update imageOffset to size_t

---
 libraries/gpu/src/gpu/Texture_ktx.cpp | 1 -
 libraries/ktx/src/ktx/KTX.cpp         | 4 ++--
 libraries/ktx/src/ktx/KTX.h           | 8 ++++----
 3 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 8181bb21ae..efff6c7afe 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -97,7 +97,6 @@ struct GPUKTXPayload {
         return false;
     }
 };
-const std::string gpu::SOURCE_HASH_KEY { "hifi.sourceHash" };
 const std::string GPUKTXPayload::KEY { "hifi.gpu" };
 
 KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index cea397927a..38bb91e5c2 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -136,7 +136,7 @@ size_t KTXDescriptor::getValueOffsetForKey(const std::string& key) const {
 ImageDescriptors Header::generateImageDescriptors() const {
     ImageDescriptors descriptors;
 
-    uint32_t imageOffset = 0;
+    size_t imageOffset = 0;
     for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) {
         auto imageSize = static_cast<uint32_t>(evalImageSize(level));
         if (imageSize == 0) {
@@ -149,7 +149,7 @@ ImageDescriptors Header::generateImageDescriptors() const {
             0
         };
 
-        imageOffset += (imageSize * numberOfFaces) + 4;
+        imageOffset += (imageSize * numberOfFaces) + ktx::IMAGE_SIZE_WIDTH;
 
         ImageHeader::FaceOffsets offsets;
         // TODO Add correct face offsets
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 5a3167cf53..42ff4644ca 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -422,11 +422,11 @@ namespace ktx {
         // This is the byte offset from the _start_ of the image region. For example, level 0
         // will have a byte offset of 0.
         const uint32_t _numFaces;
-        const uint32_t _imageOffset;
+        const size_t _imageOffset;
         const uint32_t _imageSize;
         const uint32_t _faceSize;
         const uint32_t _padding;
-        ImageHeader(bool cube, uint32_t imageOffset, uint32_t imageSize, uint32_t padding) :
+        ImageHeader(bool cube, size_t imageOffset, uint32_t imageSize, uint32_t padding) :
             _numFaces(cube ? NUM_CUBEMAPFACES : 1),
             _imageOffset(imageOffset),
             _imageSize(imageSize * _numFaces),
@@ -448,10 +448,10 @@ namespace ktx {
     struct Image : public ImageHeader {
         FaceBytes _faceBytes;
         Image(const ImageHeader& header, const FaceBytes& faces) : ImageHeader(header), _faceBytes(faces) {}
-        Image(uint32_t imageOffset, uint32_t imageSize, uint32_t padding, const Byte* bytes) :
+        Image(size_t imageOffset, uint32_t imageSize, uint32_t padding, const Byte* bytes) :
             ImageHeader(false, imageOffset, imageSize, padding),
             _faceBytes(1, bytes) {}
-        Image(uint32_t imageOffset, uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) :
+        Image(size_t imageOffset, uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) :
             ImageHeader(true, imageOffset, pageSize, padding)
             {
                 if (cubeFaceBytes.size() == NUM_CUBEMAPFACES) {

From fee36453a3d699fd29b70182ba7873eb98ccfd8b Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 17:53:13 -0700
Subject: [PATCH 83/85] Move hifi.minMip

---
 libraries/gpu/src/gpu/Texture.h | 4 +++-
 libraries/ktx/src/ktx/KTX.h     | 5 ++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 502ad33143..9b23b4e695 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -34,7 +34,9 @@ namespace ktx {
 
 namespace gpu {
 
-extern const std::string SOURCE_HASH_KEY;
+
+const std::string SOURCE_HASH_KEY { "hifi.sourceHash" };
+
 const uint8 SOURCE_HASH_BYTES = 16;
 
 // THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index 42ff4644ca..e8fa019a07 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -70,10 +70,9 @@ end
 
 
 namespace ktx {
-    const std::string HIFI_MIN_POPULATED_MIP_KEY = "hifi.minMip";
-
-
     const uint32_t PACKING_SIZE { sizeof(uint32_t) };
+    const std::string HIFI_MIN_POPULATED_MIP_KEY{ "hifi.minMip" };
+
     using Byte = uint8_t;
 
     enum class GLType : uint32_t {

From f5b7feccde699e0986ac3dfc0577b601e1683dcc Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 18:01:56 -0700
Subject: [PATCH 84/85] Update AssetClient/Request to use const byteRange

---
 libraries/networking/src/AssetClient.cpp  | 2 +-
 libraries/networking/src/AssetClient.h    | 2 +-
 libraries/networking/src/AssetRequest.cpp | 2 +-
 libraries/networking/src/AssetRequest.h   | 4 ++--
 libraries/networking/src/ByteRange.h      | 4 ++--
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp
index 48f8bb87f9..15e0b8c9b5 100644
--- a/libraries/networking/src/AssetClient.cpp
+++ b/libraries/networking/src/AssetClient.cpp
@@ -181,7 +181,7 @@ RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& o
     return request;
 }
 
-AssetRequest* AssetClient::createRequest(const AssetHash& hash, ByteRange byteRange) {
+AssetRequest* AssetClient::createRequest(const AssetHash& hash, const ByteRange& byteRange) {
     auto request = new AssetRequest(hash, byteRange);
 
     // Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h
index b204fab47e..6f9cc3cd31 100644
--- a/libraries/networking/src/AssetClient.h
+++ b/libraries/networking/src/AssetClient.h
@@ -56,7 +56,7 @@ public:
     Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths);
     Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash);
     Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
-    Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash, ByteRange byteRange = ByteRange());
+    Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash, const ByteRange& byteRange = ByteRange());
     Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
     Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
 
diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp
index e54a058ac2..341c3b45da 100644
--- a/libraries/networking/src/AssetRequest.cpp
+++ b/libraries/networking/src/AssetRequest.cpp
@@ -23,7 +23,7 @@
 
 static int requestID = 0;
 
-AssetRequest::AssetRequest(const QString& hash, ByteRange byteRange) :
+AssetRequest::AssetRequest(const QString& hash, const ByteRange& byteRange) :
     _requestID(++requestID),
     _hash(hash),
     _byteRange(byteRange)
diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h
index e617d75157..b808ae0ca6 100644
--- a/libraries/networking/src/AssetRequest.h
+++ b/libraries/networking/src/AssetRequest.h
@@ -41,7 +41,7 @@ public:
         UnknownError
     };
 
-    AssetRequest(const QString& hash, ByteRange byteRange = ByteRange());
+    AssetRequest(const QString& hash, const ByteRange& byteRange = ByteRange());
     virtual ~AssetRequest() override;
 
     Q_INVOKABLE void start();
@@ -65,7 +65,7 @@ private:
     QByteArray _data;
     int _numPendingRequests { 0 };
     MessageID _assetRequestID { INVALID_MESSAGE_ID };
-    ByteRange _byteRange;
+    const ByteRange _byteRange;
 };
 
 #endif
diff --git a/libraries/networking/src/ByteRange.h b/libraries/networking/src/ByteRange.h
index 77932a8c28..6fd3559154 100644
--- a/libraries/networking/src/ByteRange.h
+++ b/libraries/networking/src/ByteRange.h
@@ -16,8 +16,8 @@ struct ByteRange {
     int64_t fromInclusive { 0 };
     int64_t toExclusive { 0 };
 
-    bool isSet() { return fromInclusive < 0 || fromInclusive < toExclusive; }
-    int64_t size() { return toExclusive - fromInclusive; }
+    bool isSet() const { return fromInclusive < 0 || fromInclusive < toExclusive; }
+    int64_t size() const { return toExclusive - fromInclusive; }
 
     // byte ranges are invalid if:
     // (1) the toExclusive of the range is negative

From 60ba874e2e423301d0116019842282d6322ac54a Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 26 Apr 2017 18:02:23 -0700
Subject: [PATCH 85/85] Fix implementation of mutableData in non-File Storage
 classes

---
 libraries/shared/src/shared/Storage.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h
index 3983387c15..da5b773d52 100644
--- a/libraries/shared/src/shared/Storage.h
+++ b/libraries/shared/src/shared/Storage.h
@@ -43,7 +43,7 @@ namespace storage {
         MemoryStorage(size_t size, const uint8_t* data = nullptr);
         const uint8_t* data() const override { return _data.data(); }
         uint8_t* data() { return _data.data(); }
-        uint8_t* mutableData() override { return 0; }
+        uint8_t* mutableData() override { return _data.data(); }
         size_t size() const override { return _data.size(); }
         operator bool() const override { return true; }
     private:
@@ -73,7 +73,7 @@ namespace storage {
     public:
         ViewStorage(const storage::StoragePointer& owner, size_t size, const uint8_t* data);
         const uint8_t* data() const override { return _data; }
-        uint8_t* mutableData() override { return 0; }
+        uint8_t* mutableData() override { throw std::runtime_error("Cannot modify ViewStorage");  }
         size_t size() const override { return _size; }
         operator bool() const override { return *_owner; }
     private: