diff --git a/interface/resources/meshes/defaultAvatar_body.fbx b/interface/resources/meshes/defaultAvatar/body.fbx
similarity index 100%
rename from interface/resources/meshes/defaultAvatar_body.fbx
rename to interface/resources/meshes/defaultAvatar/body.fbx
diff --git a/interface/resources/meshes/body.jpg b/interface/resources/meshes/defaultAvatar/body.jpg
similarity index 100%
rename from interface/resources/meshes/body.jpg
rename to interface/resources/meshes/defaultAvatar/body.jpg
diff --git a/interface/resources/meshes/defaultAvatar_head.fbx b/interface/resources/meshes/defaultAvatar/head.fbx
similarity index 100%
rename from interface/resources/meshes/defaultAvatar_head.fbx
rename to interface/resources/meshes/defaultAvatar/head.fbx
diff --git a/interface/resources/meshes/tail.jpg b/interface/resources/meshes/defaultAvatar/tail.jpg
similarity index 100%
rename from interface/resources/meshes/tail.jpg
rename to interface/resources/meshes/defaultAvatar/tail.jpg
diff --git a/interface/resources/meshes/visor.png b/interface/resources/meshes/defaultAvatar/visor.png
similarity index 100%
rename from interface/resources/meshes/visor.png
rename to interface/resources/meshes/defaultAvatar/visor.png
diff --git a/interface/resources/meshes/defaultAvatar_body.fst b/interface/resources/meshes/defaultAvatar_body.fst
index 3e8fa3ef45..5874c206db 100644
--- a/interface/resources/meshes/defaultAvatar_body.fst
+++ b/interface/resources/meshes/defaultAvatar_body.fst
@@ -1,3 +1,5 @@
+filename=defaultAvatar/body.fbx
+texdir=defaultAvatar
 scale=130
 joint = jointRoot = jointRoot
 joint = jointLean = jointSpine
diff --git a/interface/resources/meshes/defaultAvatar_head.fst b/interface/resources/meshes/defaultAvatar_head.fst
index 1352652efc..34cf44f0e4 100644
--- a/interface/resources/meshes/defaultAvatar_head.fst
+++ b/interface/resources/meshes/defaultAvatar_head.fst
@@ -1,7 +1,7 @@
 # faceshift target mapping file
 name= defaultAvatar_head
-filename=../../../Avatars/Jelly/jellyrob_blue.fbx
-texdir=../../../Avatars/Jelly
+filename=defaultAvatar/head.fbx
+texdir=defaultAvatar
 scale=80
 rx=0
 ry=0
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 761ed59db9..c56830de9f 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -345,13 +345,13 @@ bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, floa
 
 void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
     AvatarData::setFaceModelURL(faceModelURL);
-    const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
+    const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
     _head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
 }
 
 void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
     AvatarData::setSkeletonModelURL(skeletonModelURL);
-    const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
+    const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
     _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
 }
 
diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp
index b4e7c4abc5..a0d5f03ae9 100644
--- a/interface/src/renderer/FBXReader.cpp
+++ b/interface/src/renderer/FBXReader.cpp
@@ -1568,14 +1568,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
     return geometry;
 }
 
-FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
-    QBuffer modelBuffer(const_cast<QByteArray*>(&model));
-    modelBuffer.open(QIODevice::ReadOnly);
+QVariantHash readMapping(const QByteArray& data) {
+    QBuffer buffer(const_cast<QByteArray*>(&data));
+    buffer.open(QIODevice::ReadOnly);
+    return parseMapping(&buffer);
+}
 
-    QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
-    mappingBuffer.open(QIODevice::ReadOnly);
-
-    return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
+FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
+    QBuffer buffer(const_cast<QByteArray*>(&model));
+    buffer.open(QIODevice::ReadOnly);
+    return extractFBXGeometry(parseFBX(&buffer), mapping);
 }
 
 bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h
index d700439460..5e2b77035f 100644
--- a/interface/src/renderer/FBXReader.h
+++ b/interface/src/renderer/FBXReader.h
@@ -163,9 +163,12 @@ public:
     QVector<FBXAttachment> attachments;
 };
 
+/// Reads an FST mapping from the supplied data.
+QVariantHash readMapping(const QByteArray& data);
+
 /// Reads FBX geometry from the supplied model and mapping data.
 /// \exception QString if an error occurs in parsing
-FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
+FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
 
 /// Reads SVO geometry from the supplied model data.
 FBXGeometry readSVO(const QByteArray& model);
diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp
index 3526fa5050..dfe6949438 100644
--- a/interface/src/renderer/GeometryCache.cpp
+++ b/interface/src/renderer/GeometryCache.cpp
@@ -7,11 +7,7 @@
 
 #include <cmath>
 
-// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
-#include "InterfaceConfig.h"
-
 #include <QNetworkReply>
-#include <QOpenGLBuffer>
 #include <QTimer>
 
 #include "Application.h"
@@ -304,40 +300,23 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
 }
 
 NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback) :
-    _modelRequest(url),
-    _modelReply(NULL),
-    _mappingReply(NULL),
+    _request(url),
+    _reply(NULL),
+    _textureBase(url),
     _fallback(fallback),
-    _attempts(0)
-{
+    _attempts(0) {
+    
     if (!url.isValid()) {
         return;
     }
-    _modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
-    makeModelRequest();
-    
-    QUrl mappingURL = url;
-    QString path = url.path();
-    mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
-    QNetworkRequest mappingRequest(mappingURL);
-    mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
-    _mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
-    
-    connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
-    connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
+    _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
+    makeRequest();
 }
 
 NetworkGeometry::~NetworkGeometry() {
-    if (_modelReply != NULL) {
-        delete _modelReply;
+    if (_reply != NULL) {
+        delete _reply;
     }
-    if (_mappingReply != NULL) {
-        delete _mappingReply;
-    }
-    foreach (const NetworkMesh& mesh, _meshes) {
-        glDeleteBuffers(1, &mesh.indexBufferID);
-        glDeleteBuffers(1, &mesh.vertexBufferID);
-    }    
 }
 
 glm::vec4 NetworkGeometry::computeAverageColor() const {
@@ -364,20 +343,155 @@ glm::vec4 NetworkGeometry::computeAverageColor() const {
     return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
 }
 
-void NetworkGeometry::makeModelRequest() {
-    _modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest);
+void NetworkGeometry::makeRequest() {
+    _reply = Application::getInstance()->getNetworkAccessManager()->get(_request);
     
-    connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
-    connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
+    connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
+    connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
 }
 
-void NetworkGeometry::handleModelReplyError() {
-    QDebug debug = qDebug() << _modelReply->errorString();
+void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
+    if (!_reply->isFinished()) {
+        return;
+    }
     
-    QNetworkReply::NetworkError error = _modelReply->error();
-    _modelReply->disconnect(this);
-    _modelReply->deleteLater();
-    _modelReply = NULL;
+    QUrl url = _reply->url();
+    QByteArray data = _reply->readAll();
+    _reply->disconnect(this);
+    _reply->deleteLater();
+    _reply = NULL;
+    
+    if (url.path().toLower().endsWith(".fst")) {
+        // it's a mapping file; parse it and get the mesh filename
+        _mapping = readMapping(data);
+        QString filename = _mapping.value("filename").toString();
+        if (filename.isNull()) {
+            qDebug() << "Mapping file " << url << " has no filename.";
+            maybeLoadFallback();
+        } else {
+            QString texdir = _mapping.value("texdir").toString();
+            if (!texdir.isNull()) {
+                if (!texdir.endsWith('/')) {
+                    texdir += '/';
+                }
+                _textureBase = url.resolved(texdir);
+            }
+            _request.setUrl(url.resolved(filename));
+            makeRequest();
+        }
+        return;
+    }
+    
+    try {
+        _geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
+        
+    } catch (const QString& error) {
+        qDebug() << "Error reading " << url << ": " << error;
+        maybeLoadFallback();
+        return;
+    }
+    
+    foreach (const FBXMesh& mesh, _geometry.meshes) {
+        NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) };
+        
+        int totalIndices = 0;
+        foreach (const FBXMeshPart& part, mesh.parts) {
+            NetworkMeshPart networkPart;
+            if (!part.diffuseFilename.isEmpty()) {
+                networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
+                    _textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
+            }
+            if (!part.normalFilename.isEmpty()) {
+                networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
+                    _textureBase.resolved(QUrl(part.normalFilename)), true);
+            }
+            networkMesh.parts.append(networkPart);
+                        
+            totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
+        }
+        
+        networkMesh.indexBuffer.create();
+        networkMesh.indexBuffer.bind();
+        networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
+        networkMesh.indexBuffer.allocate(totalIndices * sizeof(int));
+        int offset = 0;
+        foreach (const FBXMeshPart& part, mesh.parts) {
+            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
+                part.quadIndices.constData());
+            offset += part.quadIndices.size() * sizeof(int);
+            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
+                part.triangleIndices.constData());
+            offset += part.triangleIndices.size() * sizeof(int);
+        }
+        networkMesh.indexBuffer.release();
+        
+        networkMesh.vertexBuffer.create();
+        networkMesh.vertexBuffer.bind();
+        networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
+        
+        // if we don't need to do any blending or springing, then the positions/normals can be static
+        if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
+            int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
+            int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
+            int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
+            int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
+            int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
+            int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
+            
+            networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
+            networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
+            networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3));
+            networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(),
+                mesh.tangents.size() * sizeof(glm::vec3));
+            networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
+            networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
+                mesh.texCoords.size() * sizeof(glm::vec2));
+            networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
+                mesh.clusterIndices.size() * sizeof(glm::vec4));
+            networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
+                mesh.clusterWeights.size() * sizeof(glm::vec4));
+        
+        // if there's no springiness, then the cluster indices/weights can be static
+        } else if (mesh.springiness == 0.0f) {
+            int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
+            int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
+            int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
+            int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
+            networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
+            networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));        
+            networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));    
+            networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
+                mesh.texCoords.size() * sizeof(glm::vec2));
+            networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
+                mesh.clusterIndices.size() * sizeof(glm::vec4));
+            networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
+                mesh.clusterWeights.size() * sizeof(glm::vec4));
+            
+        } else {
+            int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
+            int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
+            networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
+            networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
+            networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
+            networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
+                mesh.texCoords.size() * sizeof(glm::vec2));
+        }
+        
+        networkMesh.vertexBuffer.release();
+        
+        _meshes.append(networkMesh);
+    }
+    
+    emit loaded();
+}
+
+void NetworkGeometry::handleReplyError() {
+    QDebug debug = qDebug() << _reply->errorString();
+    
+    QNetworkReply::NetworkError error = _reply->error();
+    _reply->disconnect(this);
+    _reply->deleteLater();
+    _reply = NULL;
     
     // retry for certain types of failures
     switch (error) {
@@ -394,7 +508,7 @@ void NetworkGeometry::handleModelReplyError() {
             const int MAX_ATTEMPTS = 8;
             const int BASE_DELAY_MS = 1000;
             if (++_attempts < MAX_ATTEMPTS) {
-                QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
+                QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
                 debug << " -- retrying...";
                 return;
             }
@@ -407,135 +521,6 @@ void NetworkGeometry::handleModelReplyError() {
     
 }
 
-void NetworkGeometry::handleMappingReplyError() {
-    _mappingReply->disconnect(this);
-    _mappingReply->deleteLater();
-    _mappingReply = NULL;
-    
-    maybeReadModelWithMapping();
-}
-
-void NetworkGeometry::maybeReadModelWithMapping() {
-    if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
-        return;
-    }
-    
-    QUrl url = _modelReply->url();
-    QByteArray model = _modelReply->readAll();
-    _modelReply->disconnect(this);
-    _modelReply->deleteLater();
-    _modelReply = NULL;
-    
-    QByteArray mapping;
-    if (_mappingReply != NULL) {
-        mapping = _mappingReply->readAll();
-        _mappingReply->disconnect(this);
-        _mappingReply->deleteLater();
-        _mappingReply = NULL;
-    }
-    
-    try {
-        _geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping);
-        
-    } catch (const QString& error) {
-        qDebug() << "Error reading " << url << ": " << error;
-        maybeLoadFallback();
-        return;
-    }
-    
-    foreach (const FBXMesh& mesh, _geometry.meshes) {
-        NetworkMesh networkMesh;
-        
-        int totalIndices = 0;
-        foreach (const FBXMeshPart& part, mesh.parts) {
-            NetworkMeshPart networkPart;
-            QString basePath = url.path();
-            basePath = basePath.left(basePath.lastIndexOf('/') + 1);
-            if (!part.diffuseFilename.isEmpty()) {
-                url.setPath(basePath + part.diffuseFilename);
-                networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, false, mesh.isEye);
-            }
-            if (!part.normalFilename.isEmpty()) {
-                url.setPath(basePath + part.normalFilename);
-                networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true);
-            }
-            networkMesh.parts.append(networkPart);
-                        
-            totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
-        }
-                        
-        glGenBuffers(1, &networkMesh.indexBufferID);
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
-        glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
-        int offset = 0;
-        foreach (const FBXMeshPart& part, mesh.parts) {
-            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
-                part.quadIndices.constData());
-            offset += part.quadIndices.size() * sizeof(int);
-            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
-                part.triangleIndices.constData());
-            offset += part.triangleIndices.size() * sizeof(int);
-        }
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-        
-        glGenBuffers(1, &networkMesh.vertexBufferID);
-        glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
-            
-        // if we don't need to do any blending or springing, then the positions/normals can be static
-        if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
-            int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
-            int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
-            int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
-            int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
-            int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
-            int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
-            glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
-                NULL, GL_STATIC_DRAW);
-            glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
-                mesh.texCoords.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
-                mesh.clusterIndices.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
-                mesh.clusterWeights.constData());
-        
-        // if there's no springiness, then the cluster indices/weights can be static
-        } else if (mesh.springiness == 0.0f) {
-            int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
-            int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
-            int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
-            int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
-            glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
-                NULL, GL_STATIC_DRAW);
-            glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());        
-            glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());    
-            glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
-                mesh.clusterIndices.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
-                mesh.clusterWeights.constData());
-            
-        } else {
-            int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
-            int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
-            glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
-            glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
-            glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
-                mesh.texCoords.constData());
-        }
-        
-        glBindBuffer(GL_ARRAY_BUFFER, 0);
-        
-        _meshes.append(networkMesh);
-    }
-    
-    emit loaded();
-}
-
 void NetworkGeometry::loadFallback() {
     _geometry = _fallback->_geometry;
     _meshes = _fallback->_meshes;
diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h
index 618796e907..0587831721 100644
--- a/interface/src/renderer/GeometryCache.h
+++ b/interface/src/renderer/GeometryCache.h
@@ -9,17 +9,19 @@
 #ifndef __interface__GeometryCache__
 #define __interface__GeometryCache__
 
+// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
+#include "InterfaceConfig.h"
+
 #include <QHash>
 #include <QNetworkRequest>
 #include <QObject>
+#include <QOpenGLBuffer>
 #include <QSharedPointer>
 #include <QWeakPointer>
 
 #include "FBXReader.h"
-#include "InterfaceConfig.h"
 
 class QNetworkReply;
-class QOpenGLBuffer;
 
 class NetworkGeometry;
 class NetworkMesh;
@@ -76,19 +78,19 @@ signals:
 
 private slots:
     
-    void makeModelRequest();
-    void handleModelReplyError();    
-    void handleMappingReplyError();
-    void maybeReadModelWithMapping();
+    void makeRequest();
+    void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+    void handleReplyError();
     void loadFallback();
     
 private:
     
     void maybeLoadFallback();
     
-    QNetworkRequest _modelRequest;
-    QNetworkReply* _modelReply;
-    QNetworkReply* _mappingReply;
+    QNetworkRequest _request;
+    QNetworkReply* _reply;
+    QVariantHash _mapping;
+    QUrl _textureBase;
     QSharedPointer<NetworkGeometry> _fallback;
     
     int _attempts;
@@ -110,8 +112,8 @@ public:
 class NetworkMesh {
 public:
     
-    GLuint indexBufferID;
-    GLuint vertexBufferID;
+    QOpenGLBuffer indexBuffer;
+    QOpenGLBuffer vertexBuffer;
     
     QVector<NetworkMeshPart> parts;
     
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index b14ed1036d..095429f201 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -768,13 +768,13 @@ void Model::renderMeshes(float alpha, bool translucent) {
                 (networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
             continue;
         }
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
-        
+        const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
+
         const FBXMesh& mesh = geometry.meshes.at(i);    
         int vertexCount = mesh.vertices.size();
         
-        glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
-      
+        const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
+        
         ProgramObject* program = &_program;
         ProgramObject* skinProgram = &_skinProgram;
         SkinLocations* skinLocations = &_skinLocations;