diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index c9b976cc14..b4a37519a6 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -4441,7 +4441,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
                     static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR)
                         ? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR)
                         : "hifiFrames";
-                    static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}.hfb";
+                    static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}";
                     QString fullPath = FileUtils::computeDocumentPath(FileUtils::replaceDateTimeTokens(GPU_FRAME_TEMPLATE));
                     if (FileUtils::canCreateFile(fullPath)) {
                         getActiveDisplayPlugin()->captureFrame(fullPath.toStdString());
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
index e69791e73d..6ca11688a2 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
@@ -37,6 +37,7 @@
 #include <shaders/Shaders.h>
 #include <gpu/gl/GLShared.h>
 #include <gpu/gl/GLBackend.h>
+#include <gpu/gl/GLTexelFormat.h>
 #include <GeometryCache.h>
 
 #include <CursorManager.h>
@@ -476,34 +477,50 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) {
     });
 }
 
+ktx::StoragePointer textureToKtx(const gpu::Texture& texture) {
+    ktx::Header header;
+    {
+        auto gpuDims = texture.getDimensions();
+        header.pixelWidth = gpuDims.x;
+        header.pixelHeight = gpuDims.y;
+        header.pixelDepth = 0;
+    }
+
+    {
+        auto gltexelformat = gpu::gl::GLTexelFormat::evalGLTexelFormat(texture.getStoredMipFormat());
+        header.glInternalFormat = gltexelformat.internalFormat;
+        header.glFormat = gltexelformat.format;
+        header.glBaseInternalFormat = gltexelformat.format;
+        header.glType = gltexelformat.type;
+        header.glTypeSize = 1;
+        header.numberOfMipmapLevels = 1 + texture.getMaxMip();
+    }
+
+    auto memKtx = ktx::KTX::createBare(header);
+    auto storage = memKtx->_storage;
+    uint32_t faceCount = std::max(header.numberOfFaces, 1u);
+    uint32_t mipCount = std::max(header.numberOfMipmapLevels, 1u);
+    for (uint32_t mip = 0; mip < mipCount; ++mip) {
+        for (uint32_t face = 0; face < faceCount; ++face) {
+            const auto& image = memKtx->_images[mip];
+            auto& faceBytes = const_cast<gpu::Byte*&>(image._faceBytes[face]);
+            if (texture.isStoredMipFaceAvailable(mip, face)) {
+                auto storedImage = texture.accessStoredMipFace(mip, face);
+                auto storedSize = storedImage->size();
+                memcpy(faceBytes, storedImage->data(), storedSize);
+            }
+        }
+    }
+    return storage;
+}
+
 void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const {
     withOtherThreadContext([&] {
         using namespace gpu;
         auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
         FramebufferPointer framebuffer{ Framebuffer::create("captureFramebuffer") };
-        TextureCapturer captureLambda = [&](std::vector<uint8_t>& outputBuffer, const gpu::TexturePointer& texture, uint16 layer) {
-            QImage image;
-            if (texture->getUsageType() == TextureUsageType::STRICT_RESOURCE) {
-                image = QImage{ 1, 1, QImage::Format_ARGB32 };
-                auto storedImage = texture->accessStoredMipFace(0, 0);
-                memcpy(image.bits(), storedImage->data(), image.sizeInBytes());
-            //if (texture == textureCache->getWhiteTexture()) {
-            //} else if (texture == textureCache->getBlackTexture()) {
-            //} else if (texture == textureCache->getBlueTexture()) {
-            //} else if (texture == textureCache->getGrayTexture()) {
-            } else {
-                ivec4 rect = { 0, 0, texture->getWidth(), texture->getHeight() };
-                framebuffer->setRenderBuffer(0, texture, layer);
-                glBackend->syncGPUObject(*framebuffer);
-
-                image = QImage{ rect.z, rect.w, QImage::Format_ARGB32 };
-                glBackend->downloadFramebuffer(framebuffer, rect, image);
-            }
-            QBuffer buffer;
-            QImageWriter(&buffer, "png").write(image);
-            const auto& data = buffer.data();
-            outputBuffer.resize(data.size());
-            memcpy(outputBuffer.data(), data.constData(), data.size());
+        TextureCapturer captureLambda = [&](const gpu::TexturePointer& texture)->storage::StoragePointer {
+            return textureToKtx(*texture);
         };
 
         if (_currentFrame) {
diff --git a/libraries/gpu/src/gpu/FrameIO.cpp b/libraries/gpu/src/gpu/FrameIO.cpp
index 12e07b6e2a..a0f21df881 100644
--- a/libraries/gpu/src/gpu/FrameIO.cpp
+++ b/libraries/gpu/src/gpu/FrameIO.cpp
@@ -8,6 +8,7 @@
 
 #include "FrameIO.h"
 #include <shared/Storage.h>
+#include <stdexcept>
 
 using namespace gpu::hfb;
 
@@ -30,30 +31,44 @@ static bool read(const uint8_t*& ptr, size_t& remaining, T& output) {
     return skip(ptr, remaining, readSize);
 }
 
-Descriptor Descriptor::parse(const uint8_t* const data, size_t size) {
-    const auto* ptr = data;
-    auto remaining = size;
-    Descriptor result;
-    if (!read(ptr, remaining, result.header)) {
-        return {};
-    }
-    if (result.header.length != size) {
-        return {};
-    }
+Descriptor::Descriptor(const StoragePointer& storage) : storage(storage) {
+    const auto* const start = storage->data();
+    const auto* ptr = storage->data();
+    auto remaining = storage->size();
 
-    while (remaining != 0) {
-        result.chunks.emplace_back();
-        auto& chunk = result.chunks.back();
-        ChunkHeader& chunkHeader = chunk;
-        if (!read(ptr, remaining, chunkHeader)) {
-            return {};
+    try {
+        // Can't parse files more than 4GB
+        if (remaining > UINT32_MAX) {
+            throw std::runtime_error("File too large");
         }
-        chunk.offset = ptr - data;
-        if (!skip(ptr, remaining, chunk.length)) {
-            return {};
+
+        if (!read(ptr, remaining, header)) {
+            throw std::runtime_error("Couldn't read binary header");
         }
+
+        if (header.length != storage->size()) {
+            throw std::runtime_error("Header/Actual size mismatch");
+        }
+
+        while (remaining != 0) {
+            chunks.emplace_back();
+            auto& chunk = chunks.back();
+            ChunkHeader& chunkHeader = chunk;
+            if (!read(ptr, remaining, chunkHeader)) {
+                throw std::runtime_error("Coulnd't read chunk header");
+            }
+            chunk.offset = (uint32_t)(ptr - start);
+            if (chunk.end() > storage->size()) {
+                throw std::runtime_error("Chunk too large for file");
+            }
+            if (!skip(ptr, remaining, chunk.length)) {
+                throw std::runtime_error("Skip chunk data failed");
+            }
+        }
+    } catch (const std::runtime_error&) {
+        // LOG somnething
+        header.magic = 0;
     }
-    return result;
 }
 
 size_t Chunk::end() const {
@@ -62,30 +77,15 @@ size_t Chunk::end() const {
     return result;
 }
 
-
-bool Descriptor::getChunkString(std::string& output, size_t chunkIndex, const uint8_t* const data, size_t size) {
+StoragePointer Descriptor::getChunk(uint32_t chunkIndex) const {
     if (chunkIndex >= chunks.size()) {
-        return false;
+        return {};
     }
     const auto& chunk = chunks[chunkIndex];
-    if (chunk.end() > size) {
-        return false;
+    if (chunk.end() > storage->size()) {
+        return {};
     }
-    output = std::string{ (const char*)(data + chunk.offset), chunk.length };
-    return true;
-}
-
-bool Descriptor::getChunkBuffer(Buffer& output, size_t chunkIndex, const uint8_t* const data, size_t size) {
-    if (chunkIndex >= chunks.size()) {
-        return false;
-    }
-    const auto& chunk = chunks[chunkIndex];
-    if (chunk.end() > size) {
-        return false;
-    }
-    output.resize(chunk.length);
-    memcpy(output.data(), data + chunk.offset, chunk.length);
-    return true;
+    return storage->createView(chunk.length, chunk.offset);
 }
 
 static void writeUint(uint8_t*& dest, uint32_t value) {
@@ -105,12 +105,15 @@ static void writeChunk(uint8_t*& dest, uint32_t chunkType, const T& chunkData) {
 void gpu::hfb::writeFrame(const std::string& filename,
                           const std::string& json,
                           const Buffer& binaryBuffer,
-                          const Buffers& pngBuffers) {
+                          const StorageBuilders& ktxBuilders) {
     uint32_t strLen = (uint32_t)json.size();
     uint32_t size = gpu::hfb::HEADER_SIZE + gpu::hfb::CHUNK_HEADER_SIZE + strLen;
     size += gpu::hfb::CHUNK_HEADER_SIZE + (uint32_t)binaryBuffer.size();
-    for (const auto& pngBuffer : pngBuffers) {
-        size += gpu::hfb::CHUNK_HEADER_SIZE + (uint32_t)pngBuffer.size();
+    for (const auto& builder : ktxBuilders) {
+        auto storage = builder();
+        if (storage) {
+            size += gpu::hfb::CHUNK_HEADER_SIZE + (uint32_t)storage->size();
+        }
     }
 
     auto outputConst = storage::FileStorage::create(filename.c_str(), size, nullptr);
@@ -121,8 +124,13 @@ void gpu::hfb::writeFrame(const std::string& filename,
     writeUint(ptr, size);
     writeChunk(ptr, gpu::hfb::CHUNK_TYPE_JSON, json);
     writeChunk(ptr, gpu::hfb::CHUNK_TYPE_BIN, binaryBuffer);
-    for (const auto& png : pngBuffers) {
-        writeChunk(ptr, gpu::hfb::CHUNK_TYPE_PNG, png);
+    for (const auto& builder : ktxBuilders) {
+        static StoragePointer EMPTY_STORAGE{ std::make_shared<storage::MemoryStorage>(0, nullptr) };
+        auto storage = builder();
+        if (!storage) {
+            storage = EMPTY_STORAGE;
+        }
+        writeChunk(ptr, gpu::hfb::CHUNK_TYPE_KTX, *storage);
     }
     assert((ptr - output->data()) == size);
 }
diff --git a/libraries/gpu/src/gpu/FrameIO.h b/libraries/gpu/src/gpu/FrameIO.h
index bb1dfa0f8c..77602ef4e8 100644
--- a/libraries/gpu/src/gpu/FrameIO.h
+++ b/libraries/gpu/src/gpu/FrameIO.h
@@ -12,28 +12,34 @@
 #include "Forward.h"
 #include "Format.h"
 
+#include <shared/Storage.h>
+
 #include <functional>
 
 namespace gpu {
 
-using TextureCapturer = std::function<void(std::vector<uint8_t>&, const TexturePointer&, uint16 layer)>;
-using TextureLoader = std::function<void(const std::vector<uint8_t>&, const TexturePointer&, uint16 layer)>;
+using TextureCapturer = std::function<storage::StoragePointer(const TexturePointer&)>;
+//using TextureLoader = std::function<void(const storage::StoragePointer& storage, const TexturePointer&)>;
 void writeFrame(const std::string& filename, const FramePointer& frame, const TextureCapturer& capturer = nullptr);
-FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader = nullptr);
+FramePointer readFrame(const std::string& filename, uint32_t externalTexture);
 
 namespace hfb {
 
+using Storage = storage::Storage;
+using StoragePointer = storage::Pointer;
+using StorageBuilders = storage::Builders;
+
 constexpr char* const EXTENSION{ ".hfb" };
 constexpr uint32_t HEADER_SIZE{ sizeof(uint32_t) * 3 };
 constexpr uint32_t CHUNK_HEADER_SIZE = sizeof(uint32_t) * 2;
 constexpr uint32_t MAGIC{ 0x49464948 };
 constexpr uint32_t VERSION{ 0x01 };
 constexpr uint32_t CHUNK_TYPE_JSON{ 0x4E4F534A };
+constexpr uint32_t CHUNK_TYPE_KTX{ 0x0058544b };
 constexpr uint32_t CHUNK_TYPE_BIN{ 0x004E4942 };
 constexpr uint32_t CHUNK_TYPE_PNG{ 0x00474E50 };
 
 using Buffer = std::vector<uint8_t>;
-using Buffers = std::vector<Buffer>;
 
 struct Header {
     uint32_t magic{ 0 };
@@ -55,16 +61,21 @@ struct Chunk : public ChunkHeader {
 using Chunks = std::vector<Chunk>;
 
 struct Descriptor {
+    using Pointer = std::shared_ptr<Descriptor>;
+
     Header header;
     Chunks chunks;
+    StoragePointer storage;
 
+    Descriptor(const StoragePointer& storage);
     operator bool() const { return header.magic == MAGIC; }
-    bool getChunkString(std::string& output, size_t chunk, const uint8_t* const data, size_t size);
-    bool getChunkBuffer(Buffer& output, size_t chunk, const uint8_t* const data, size_t size);
-    static Descriptor parse(const uint8_t* const data, size_t size);
+    StoragePointer getChunk(uint32_t chunk) const;
 };
 
-void writeFrame(const std::string& filename, const std::string& json, const Buffer& binaryBuffer, const Buffers& pngBuffers);
+void writeFrame(const std::string& filename,
+                const std::string& json,
+                const Buffer& binaryBuffer,
+                const StorageBuilders& pngBuffers);
 
 }  // namespace hfb
 
diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp
index 1f94828119..d636c6aaca 100644
--- a/libraries/gpu/src/gpu/FrameReader.cpp
+++ b/libraries/gpu/src/gpu/FrameReader.cpp
@@ -10,9 +10,7 @@
 #include <nlohmann/json.hpp>
 #include <unordered_map>
 
-#include <QtCore/QFileInfo>
-#include <QtCore/QDir>
-
+#include <shared/FileUtils.h>
 #include <ktx/KTX.h>
 #include "Frame.h"
 #include "Batch.h"
@@ -33,7 +31,7 @@ public:
             auto lastSlash = filename.rfind('/');
             result = filename.substr(0, lastSlash + 1);
         } else {
-            result = QFileInfo(filename.c_str()).absoluteDir().canonicalPath().toStdString();
+            result = FileUtils::getParentPath(filename.c_str()).toStdString();
             if (*result.rbegin() != '/') {
                 result += '/';
             }
@@ -41,18 +39,17 @@ public:
         return result;
     }
 
-    Deserializer(const std::string& filename_, uint32_t externalTexture = 0, const TextureLoader& loader = {}) :
+    Deserializer(const std::string& filename_, uint32_t externalTexture = 0) :
         filename(filename_), basedir(getBaseDir(filename_)), mappedFile(std::make_shared<FileStorage>(filename.c_str())),
-        externalTexture(externalTexture), textureLoader(loader) {
-        descriptor = hfb::Descriptor::parse(mappedFile->data(), (uint32_t)mappedFile->size());
+        externalTexture(externalTexture) {
+        descriptor = std::make_shared<hfb::Descriptor>(mappedFile);
     }
 
     const std::string filename;
     const std::string basedir;
     const StoragePointer mappedFile;
     const uint32_t externalTexture;
-    hfb::Descriptor descriptor;
-    TextureLoader textureLoader;
+    hfb::Descriptor::Pointer descriptor;
     std::vector<ShaderPointer> shaders;
     std::vector<ShaderPointer> programs;
     std::vector<TexturePointer> textures;
@@ -70,19 +67,8 @@ public:
     FramePointer deserializeFrame();
 
     std::string getStringChunk(size_t chunkIndex) {
-        std::string result;
-        if (!descriptor.getChunkString(result, chunkIndex, mappedFile->data(), mappedFile->size())) {
-            return {};
-        }
-        return result;
-    }
-
-    hfb::Buffer getBufferChunk(size_t chunkIndex) {
-        hfb::Buffer result;
-        if (!descriptor.getChunkBuffer(result, chunkIndex, mappedFile->data(), mappedFile->size())) {
-            return {};
-        }
-        return result;
+        auto storage = descriptor->getChunk((uint32_t)chunkIndex);
+        return std::string{ (const char*)storage->data(), storage->size() };
     }
 
     void readBuffers(const json& node);
@@ -240,8 +226,8 @@ public:
     static void readCommand(const json& node, Batch& batch);
 };
 
-FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) {
-    return Deserializer(filename, externalTexture, loader).readFrame();
+FramePointer readFrame(const std::string& filename, uint32_t externalTexture) {
+    return Deserializer(filename, externalTexture).readFrame();
 }
 
 }  // namespace gpu
@@ -249,7 +235,7 @@ FramePointer readFrame(const std::string& filename, uint32_t externalTexture, co
 using namespace gpu;
 
 void Deserializer::readBuffers(const json& buffersNode) {
-    const auto& binaryChunk = descriptor.chunks[1];
+    const auto& binaryChunk = descriptor->chunks[1];
     const auto* mapped = mappedFile->data() + binaryChunk.offset;
     const auto mappedSize = binaryChunk.length;
     size_t bufferCount = buffersNode.size();
@@ -333,7 +319,7 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
     std::string ktxFile;
     readOptional(ktxFile, node, keys::ktxFile);
     if (!ktxFile.empty()) {
-        if (!QFileInfo(ktxFile.c_str()).exists()) {
+        if (!FileUtils::exists(ktxFile.c_str())) {
             qDebug() << "Warning" << ktxFile.c_str() << " not found, ignoring";
             ktxFile = {};
         }
@@ -347,8 +333,8 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
             frameReaderPath.replace("libraries/gpu/src/gpu/framereader.cpp", "interface/resources", Qt::CaseInsensitive);
             ktxFile.replace(0, 1, frameReaderPath.toStdString());
         }
-        if (QFileInfo(ktxFile.c_str()).isRelative()) {
-            ktxFile = basedir + ktxFile;
+        if (FileUtils::isRelative(ktxFile.c_str())) {
+            ktxFile = basedir + "/" + ktxFile;
         }
         ktx::StoragePointer ktxStorage{ new storage::FileStorage(ktxFile.c_str()) };
         auto ktxObject = ktx::KTX::create(ktxStorage);
@@ -381,19 +367,14 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
     auto& texture = *result;
     readOptional(texture._source, node, keys::source);
 
-    if (!ktxFile.empty()) {
-        if (QFileInfo(ktxFile.c_str()).isRelative()) {
-            ktxFile = basedir + "/" + ktxFile;
-        }
+
+    if (chunkIndex != INVALID_CHUNK_INDEX) {
+        auto ktxChunk = descriptor->getChunk(chunkIndex);
+        texture.setKtxBacking(ktxChunk);
+    } else if (!ktxFile.empty()) {
         texture.setSource(ktxFile);
         texture.setKtxBacking(ktxFile);
-    } else if (chunkIndex != INVALID_CHUNK_INDEX) {
-        if (textureLoader) {
-            texture.setSource("Chunk " + std::to_string(chunkIndex));
-            textureLoader(getBufferChunk(chunkIndex), result, 0);
-        }
-    }
-
+    } 
     return result;
 }
 
diff --git a/libraries/gpu/src/gpu/FrameWriter.cpp b/libraries/gpu/src/gpu/FrameWriter.cpp
index 85397f149a..761f37a620 100644
--- a/libraries/gpu/src/gpu/FrameWriter.cpp
+++ b/libraries/gpu/src/gpu/FrameWriter.cpp
@@ -34,7 +34,7 @@ public:
     std::unordered_map<QueryPointer, uint32_t> queryMap;
     std::unordered_set<TexturePointer> captureTextures;
     hfb::Buffer binaryBuffer;
-    hfb::Buffers pngBuffers;
+    hfb::StorageBuilders ktxBuilders;
 
     Serializer(const std::string& basename, const TextureCapturer& capturer) : filename(basename + hfb::EXTENSION), textureCapturer(capturer) {}
 
@@ -392,13 +392,17 @@ json Serializer::writeTexture(const TexturePointer& texturePointer) {
         const auto* storage = texture._storage.get();
         const auto* ktxStorage = dynamic_cast<const Texture::KtxStorage*>(storage);
         if (ktxStorage) {
-            result[keys::ktxFile] = ktxStorage->_filename;
+            result[keys::chunk] = 2 + ktxBuilders.size();
+            auto filename = ktxStorage->_filename;
+            ktxBuilders.push_back([=] {
+                return std::make_shared<storage::FileStorage>(filename.c_str());
+            });
         } else if (textureCapturer && captureTextures.count(texturePointer) != 0) {
-            auto layers = std::max<uint16>(texture.getNumSlices(), 1);
-            result[keys::chunk] = 2 + pngBuffers.size();
-            pngBuffers.push_back({});
-            hfb::Buffer& pngBuffer = pngBuffers.back();
-            textureCapturer(pngBuffer, texturePointer, 0);
+            result[keys::chunk] = 2 + ktxBuilders.size();
+            auto storage = textureCapturer(texturePointer);
+            ktxBuilders.push_back([=] {
+                return storage;
+            });
         }
     }
     return result;
@@ -790,7 +794,7 @@ void Serializer::writeFrame(const Frame& frame) {
 
     writeBinaryBlob();
 
-    hfb::writeFrame(filename, frameNode.dump(), binaryBuffer, pngBuffers);
+    hfb::writeFrame(filename, frameNode.dump(), binaryBuffer, ktxBuilders);
 }
 
 void Serializer::writeBinaryBlob() {
diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h
index 5e2485941d..debedf02a5 100755
--- a/libraries/gpu/src/gpu/Texture.h
+++ b/libraries/gpu/src/gpu/Texture.h
@@ -343,6 +343,7 @@ public:
 
     class KtxStorage : public Storage {
     public:
+        KtxStorage(const storage::StoragePointer& storage);
         KtxStorage(const std::string& filename);
         KtxStorage(const cache::FilePointer& file);
         PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override;
@@ -366,6 +367,7 @@ public:
         static std::vector<std::pair<std::shared_ptr<storage::FileStorage>, std::shared_ptr<std::mutex>>> _cachedKtxFiles;
         static std::mutex _cachedKtxFilesMutex;
 
+        storage::StoragePointer _storage;
         std::string _filename;
         cache::FilePointer _cacheEntry;
         std::atomic<uint8_t> _minMipLevelAvailable;
@@ -543,6 +545,7 @@ public:
     Size getStoredSize() const;
 
     void setStorage(std::unique_ptr<Storage>& newStorage);
+    void setKtxBacking(const storage::StoragePointer& storage);
     void setKtxBacking(const std::string& filename);
     void setKtxBacking(const cache::FilePointer& cacheEntry);
 
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index f471baf2c7..a5cea3e60e 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -159,7 +159,31 @@ struct IrradianceKTXPayload {
 };
 const std::string IrradianceKTXPayload::KEY{ "hifi.irradianceSH" };
 
-KtxStorage::KtxStorage(const cache::FilePointer& cacheEntry) : KtxStorage(cacheEntry->getFilepath())  {
+KtxStorage::KtxStorage(const storage::StoragePointer& storage) : _storage(storage) {
+    auto ktxPointer = ktx::KTX::create(storage);
+    _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor()));
+    if (_ktxDescriptor->images.size() < _ktxDescriptor->header.numberOfMipmapLevels) {
+        qWarning() << "Bad images found in ktx";
+    }
+
+    _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;
+    if (Texture::evalTextureFormat(_ktxDescriptor->header, mipFormat, texelFormat)) {
+        _format = mipFormat;
+    }
+}
+
+KtxStorage::KtxStorage(const cache::FilePointer& cacheEntry) : KtxStorage(cacheEntry->getFilepath()) {
     _cacheEntry = cacheEntry;
 }
 
@@ -228,28 +252,31 @@ void KtxStorage::releaseOpenKtxFiles() {
 PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
     auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
     auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
+    storage::StoragePointer storageView;
     if (faceSize != 0 && faceOffset != 0) {
-        std::lock_guard<std::mutex> lock(*_cacheFileMutex);
-        auto file = maybeOpenFile();
-        if (file) {
-            auto storageView = file->createView(faceSize, faceOffset);
-            if (storageView) {
-                return storageView->toMemoryStorage();
-            } else {
-                qWarning() << "Failed to get a valid storageView for faceSize=" << faceSize << "  faceOffset=" << faceOffset << "out of valid file " << QString::fromStdString(_filename);
-            }
+        if (_storage) {
+            storageView = _storage->createView(faceSize, faceOffset);
         } else {
-            qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename);
+            std::lock_guard<std::mutex> lock(*_cacheFileMutex);
+            auto file = maybeOpenFile();
+            if (file) {
+                storageView = file->createView(faceSize, faceOffset);
+            } else {
+                qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename);
+            }
         }
     }
-    return nullptr;
+    if (!storageView) {
+        qWarning() << "Failed to get a valid storageView for faceSize=" << faceSize << "  faceOffset=" << faceOffset
+                    << "out of valid file " << QString::fromStdString(_filename);
+    }
+    return storageView->toMemoryStorage();
 }
 
 Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
     return _ktxDescriptor->getMipFaceTexelsSize(level, face);
 }
 
-
 bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const {
     return level >= _minMipLevelAvailable;
 }
@@ -271,7 +298,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     auto& imageDesc = _ktxDescriptor->images[level];
     if (storage->size() != imageDesc._imageSize) {
         qWarning() << "Invalid image size: " << storage->size() << ", expected: " << imageDesc._imageSize
-            << ", level: " << level << ", filename: " << QString::fromStdString(_filename);
+                   << ", level: " << level << ", filename: " << QString::fromStdString(_filename);
         return;
     }
 
@@ -311,8 +338,7 @@ void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::Stor
     throw std::runtime_error("Invalid call");
 }
 
-bool validKtx(const std::string& filename) {
-    ktx::StoragePointer storage { new storage::FileStorage(filename.c_str()) };
+bool validKtx(const storage::StoragePointer& storage) {
     auto ktxPointer = ktx::KTX::create(storage);
     if (!ktxPointer) {
         return false;
@@ -320,6 +346,21 @@ bool validKtx(const std::string& filename) {
     return true;
 }
 
+bool validKtx(const std::string& filename) {
+    ktx::StoragePointer storage{ new storage::FileStorage(filename.c_str()) };
+    return validKtx(storage);
+}
+
+void Texture::setKtxBacking(const storage::StoragePointer& storage) {
+    // Check the KTX file for validity before using it as backing storage
+    if (!validKtx(storage)) {
+        return;
+    }
+
+    auto newBacking = std::unique_ptr<Storage>(new KtxStorage(storage));
+    setStorage(newBacking);
+}
+
 void Texture::setKtxBacking(const std::string& filename) {
     // Check the KTX file for validity before using it as backing storage
     if (!validKtx(filename)) {
@@ -355,7 +396,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
     // Set Dimensions
     uint32_t numFaces = 1;
     switch (texture.getType()) {
-    case TEX_1D: {
+        case TEX_1D: {
             if (texture.isArray()) {
                 header.set1DArray(texture.getWidth(), texture.getNumSlices());
             } else {
@@ -363,7 +404,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
             }
             break;
         }
-    case TEX_2D: {
+        case TEX_2D: {
             if (texture.isArray()) {
                 header.set2DArray(texture.getWidth(), texture.getHeight(), texture.getNumSlices());
             } else {
@@ -371,7 +412,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
             }
             break;
         }
-    case TEX_3D: {
+        case TEX_3D: {
             if (texture.isArray()) {
                 header.set3DArray(texture.getWidth(), texture.getHeight(), texture.getDepth(), texture.getNumSlices());
             } else {
@@ -379,7 +420,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
             }
             break;
         }
-    case TEX_CUBE: {
+        case TEX_CUBE: {
             if (texture.isArray()) {
                 header.setCubeArray(texture.getWidth(), texture.getHeight(), texture.getNumSlices());
             } else {
@@ -388,8 +429,8 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
             numFaces = Texture::CUBE_FACE_COUNT;
             break;
         }
-    default:
-        return nullptr;
+        default:
+            return nullptr;
     }
 
     // Number level of mips coming
diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h
index d755a482e3..0165113ec5 100644
--- a/libraries/ktx/src/ktx/KTX.h
+++ b/libraries/ktx/src/ktx/KTX.h
@@ -96,7 +96,7 @@ namespace ktx {
     using GLBaseInternalFormat = khronos::gl::texture::BaseInternalFormat;
 
     using Storage = storage::Storage;
-    using StoragePointer = std::shared_ptr<Storage>;
+    using StoragePointer = std::shared_ptr<const Storage>;
 
     struct ImageDescriptor;
     using ImageDescriptors = std::vector<ImageDescriptor>;
diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp
index f2a4925351..164af091de 100644
--- a/libraries/shared/src/shared/FileUtils.cpp
+++ b/libraries/shared/src/shared/FileUtils.cpp
@@ -21,6 +21,7 @@
 #include <QtCore/QUrl>
 #include <QtCore/QTextStream>
 #include <QtCore/QRegularExpression>
+#include <QtCore/QFileSelector>
 #include <QtGui/QDesktopServices>
 
 
@@ -176,3 +177,15 @@ bool FileUtils::canCreateFile(const QString& fullPath) {
     }
     return true;
 }
+
+QString FileUtils::getParentPath(const QString& fullPath) {
+    return QFileInfo(fullPath).absoluteDir().canonicalPath();
+}
+
+bool FileUtils::exists(const QString& fileName) {
+    return QFileInfo(fileName).exists();
+}
+
+bool FileUtils::isRelative(const QString& fileName) {
+    return QFileInfo(fileName).isRelative();
+}
diff --git a/libraries/shared/src/shared/FileUtils.h b/libraries/shared/src/shared/FileUtils.h
index 2f5e11f005..d4ff819e75 100644
--- a/libraries/shared/src/shared/FileUtils.h
+++ b/libraries/shared/src/shared/FileUtils.h
@@ -12,20 +12,23 @@
 #ifndef hifi_FileUtils_h
 #define hifi_FileUtils_h
 
-#include <QString>
-#include <QtCore/QFileSelector>
+#include <QtCore/QString>
+
 class FileUtils {
 
 public:
     static const QStringList& getFileSelectors();
     static QString selectFile(const QString& fileName);
     static void locateFile(const QString& fileName);
+    static bool exists(const QString& fileName);
+    static bool isRelative(const QString& fileName);
     static QString standardPath(QString subfolder);
     static QString readFile(const QString& filename);
     static QStringList readLines(const QString& filename, QString::SplitBehavior splitBehavior = QString::KeepEmptyParts);
     static QString replaceDateTimeTokens(const QString& path);
     static QString computeDocumentPath(const QString& path);
     static bool canCreateFile(const QString& fullPath);
+    static QString getParentPath(const QString& fullPath);
 };
 
 #endif // hifi_FileUtils_h
diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h
index 0e5032bb62..6a2cecf8b9 100644
--- a/libraries/shared/src/shared/Storage.h
+++ b/libraries/shared/src/shared/Storage.h
@@ -10,15 +10,22 @@
 #ifndef hifi_Storage_h
 #define hifi_Storage_h
 
-#include <stdint.h>
+#include <cstdint>
 #include <vector>
 #include <memory>
-#include <QFile>
-#include <QString>
+#include <functional>
+
+#include <QtCore/QFile>
+#include <QtCore/QString>
 
 namespace storage {
     class Storage;
-    using StoragePointer = std::shared_ptr<const Storage>;
+    using Pointer = std::shared_ptr<const Storage>;
+    using StoragePointer = Pointer;
+    // A function that can construct storage, useful for creating a list of
+    // things that can become storage without requiring that they all be instantiated at once
+    using Builder = std::function<StoragePointer()>;
+    using Builders = std::vector<Builder>;
 
     // 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> {
diff --git a/tools/gpu-frame-player/src/PlayerWindow.cpp b/tools/gpu-frame-player/src/PlayerWindow.cpp
index db8d8e0f4d..8e7f730181 100644
--- a/tools/gpu-frame-player/src/PlayerWindow.cpp
+++ b/tools/gpu-frame-player/src/PlayerWindow.cpp
@@ -106,19 +106,8 @@ void PlayerWindow::resizeEvent(QResizeEvent* ev) {
     _renderThread.resize(ev->size());
 }
 
-void PlayerWindow::textureLoader(const std::vector<uint8_t>& imageBytes, const gpu::TexturePointer& texture, uint16_t layer) {
-    QImage image;
-    QByteArray bytes{ (const char*)imageBytes.data(), (int)imageBytes.size() };
-    QBuffer bytesBuffer(&bytes);
-    QImageReader(&bytesBuffer).read(&image);
-    if (layer > 0) {
-        return;
-    }
-    texture->assignStoredMip(0, image.byteCount(), image.constBits());
-}
-
 void PlayerWindow::loadFrame(const QString& path) {
-    auto frame = gpu::readFrame(path.toStdString(), _renderThread._externalTexture, &PlayerWindow::textureLoader);
+    auto frame = gpu::readFrame(path.toStdString(), _renderThread._externalTexture);
     if (frame) {
         _renderThread.submitFrame(frame);
         if (!_renderThread.isThreaded()) {