diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 1ec6ea8f23..7c3c2dc1ca 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -20,6 +20,10 @@ #include "Model.h" #include "world.h" +GeometryCache::GeometryCache() : + _pendingBlenders(0) { +} + GeometryCache::~GeometryCache() { foreach (const VerticesIndices& vbo, _hemisphereVBOs) { glDeleteBuffers(1, &vbo.first); @@ -296,10 +300,30 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons return getResource(url, fallback, delayLoad).staticCast(); } -void GeometryCache::setBlendedVertices(const QPointer& model, const QWeakPointer& geometry, - const QVector& vertices, const QVector& normals) { +void GeometryCache::noteRequiresBlend(Model* model) { + if (_pendingBlenders < QThread::idealThreadCount()) { + if (model->maybeStartBlender()) { + _pendingBlenders++; + } + return; + } + if (!_modelsRequiringBlends.contains(model)) { + _modelsRequiringBlends.append(model); + } +} + +void GeometryCache::setBlendedVertices(const QPointer& model, int blendNumber, + const QWeakPointer& geometry, const QVector& vertices, const QVector& normals) { if (!model.isNull()) { - model->setBlendedVertices(geometry, vertices, normals); + model->setBlendedVertices(blendNumber, geometry, vertices, normals); + } + _pendingBlenders--; + while (!_modelsRequiringBlends.isEmpty()) { + Model* nextModel = _modelsRequiringBlends.takeFirst(); + if (nextModel && nextModel->maybeStartBlender()) { + _pendingBlenders++; + return; + } } } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 2e5725e1b1..647e0592fd 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -35,6 +35,7 @@ class GeometryCache : public ResourceCache { public: + GeometryCache(); virtual ~GeometryCache(); void renderHemisphere(int slices, int stacks); @@ -47,9 +48,12 @@ public: /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); + /// Adds the specified model to the list requiring vertex blends. + void noteRequiresBlend(Model* model); + public slots: - void setBlendedVertices(const QPointer& model, const QWeakPointer& geometry, + void setBlendedVertices(const QPointer& model, int blendNumber, const QWeakPointer& geometry, const QVector& vertices, const QVector& normals); protected: @@ -68,6 +72,9 @@ private: QHash _gridBuffers; QHash > _networkGeometry; + + QList > _modelsRequiringBlends; + int _pendingBlenders; }; /// Geometry loaded from the network. diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7f83147bb8..19d711a69d 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -62,8 +62,8 @@ Model::Model(QObject* parent) : _lodDistance(0.0f), _pupilDilation(0.0f), _url("http://invalid.com"), - _blenderPending(false), - _blendRequired(false) { + _blendNumber(0), + _appliedBlendNumber(0) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -826,7 +826,7 @@ void Model::updateShapePositions() { class Blender : public QRunnable { public: - Blender(Model* model, const QWeakPointer& geometry, + Blender(Model* model, int blendNumber, const QWeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients); virtual void run(); @@ -834,55 +834,55 @@ public: private: QPointer _model; + int _blendNumber; QWeakPointer _geometry; QVector _meshes; QVector _blendshapeCoefficients; }; -Blender::Blender(Model* model, const QWeakPointer& geometry, +Blender::Blender(Model* model, int blendNumber, const QWeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients) : _model(model), + _blendNumber(blendNumber), _geometry(geometry), _meshes(meshes), _blendshapeCoefficients(blendshapeCoefficients) { } void Blender::run() { - // make sure the model still exists - if (_model.isNull()) { - return; - } QVector vertices, normals; - int offset = 0; - foreach (const FBXMesh& mesh, _meshes) { - if (mesh.blendshapes.isEmpty()) { - continue; - } - vertices += mesh.vertices; - normals += mesh.normals; - glm::vec3* meshVertices = vertices.data() + offset; - glm::vec3* meshNormals = normals.data() + offset; - offset += mesh.vertices.size(); - const float NORMAL_COEFFICIENT_SCALE = 0.01f; - for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { - float vertexCoefficient = _blendshapeCoefficients.at(i); - if (vertexCoefficient < EPSILON) { + if (!_model.isNull()) { + int offset = 0; + foreach (const FBXMesh& mesh, _meshes) { + if (mesh.blendshapes.isEmpty()) { continue; } - float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; - const FBXBlendshape& blendshape = mesh.blendshapes.at(i); - for (int j = 0; j < blendshape.indices.size(); j++) { - int index = blendshape.indices.at(j); - meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; - meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; + vertices += mesh.vertices; + normals += mesh.normals; + glm::vec3* meshVertices = vertices.data() + offset; + glm::vec3* meshNormals = normals.data() + offset; + offset += mesh.vertices.size(); + const float NORMAL_COEFFICIENT_SCALE = 0.01f; + for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { + float vertexCoefficient = _blendshapeCoefficients.at(i); + if (vertexCoefficient < EPSILON) { + continue; + } + float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; + const FBXBlendshape& blendshape = mesh.blendshapes.at(i); + for (int j = 0; j < blendshape.indices.size(); j++) { + int index = blendshape.indices.at(j); + meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; + meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; + } } } } - // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices", - Q_ARG(const QPointer&, _model), Q_ARG(const QWeakPointer&, _geometry), - Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); + Q_ARG(const QPointer&, _model), Q_ARG(int, _blendNumber), + Q_ARG(const QWeakPointer&, _geometry), Q_ARG(const QVector&, vertices), + Q_ARG(const QVector&, normals)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { @@ -1020,14 +1020,9 @@ void Model::simulateInternal(float deltaTime) { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes()) { - if (_blenderPending) { - _blendRequired = true; - } else { - _blendRequired = false; - _blenderPending = true; - QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients)); - } + if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + _blendedBlendshapeCoefficients = _blendshapeCoefficients; + Application::getInstance()->getGeometryCache()->noteRequiresBlend(this); } } @@ -1290,22 +1285,23 @@ void Model::renderJointCollisionShapes(float alpha) { // implement this when we have shapes for regular models } -void Model::setBlendedVertices(const QWeakPointer& geometry, const QVector& vertices, - const QVector& normals) { - _blenderPending = false; - - // start the next blender if required +bool Model::maybeStartBlender() { const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry(); - if (_blendRequired) { - _blendRequired = false; - if (fbxGeometry.hasBlendedMeshes()) { - _blenderPending = true; - QThreadPool::globalInstance()->start(new Blender(this, _geometry, fbxGeometry.meshes, _blendshapeCoefficients)); - } + if (fbxGeometry.hasBlendedMeshes()) { + QThreadPool::globalInstance()->start(new Blender(this, ++_blendNumber, _geometry, + fbxGeometry.meshes, _blendshapeCoefficients)); + return true; } - if (_geometry != geometry || _blendedVertexBuffers.isEmpty()) { + return false; +} + +void Model::setBlendedVertices(int blendNumber, const QWeakPointer& geometry, + const QVector& vertices, const QVector& normals) { + if (_geometry != geometry || _blendedVertexBuffers.isEmpty() || blendNumber < _appliedBlendNumber) { return; } + _appliedBlendNumber = blendNumber; + const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry(); int index = 0; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); @@ -1358,6 +1354,8 @@ void Model::deleteGeometry() { if (_geometry) { _geometry->clearLoadPriority(this); } + + _blendedBlendshapeCoefficients.clear(); } void Model::renderMeshes(RenderMode mode, bool translucent, bool receiveShadows) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index f2d98ac589..63b2058a20 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -165,9 +165,11 @@ public: virtual void renderJointCollisionShapes(float alpha); + bool maybeStartBlender(); + /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(const QWeakPointer& geometry, const QVector& vertices, - const QVector& normals); + void setBlendedVertices(int blendNumber, const QWeakPointer& geometry, + const QVector& vertices, const QVector& normals); class LocalLight { public: @@ -285,8 +287,9 @@ private: glm::vec4 _localLightColors[MAX_LOCAL_LIGHTS]; glm::vec4 _localLightDirections[MAX_LOCAL_LIGHTS]; - bool _blenderPending; - bool _blendRequired; + QVector _blendedBlendshapeCoefficients; + int _blendNumber; + int _appliedBlendNumber; static ProgramObject _program; static ProgramObject _normalMapProgram;