From b73a608094520b6e07574a61853438f25f37fed4 Mon Sep 17 00:00:00 2001
From: HifiExperiments <thingsandstuffblog@gmail.com>
Date: Fri, 15 Nov 2019 00:18:24 -0800
Subject: [PATCH 1/2] procedural vertex shaders

---
 interface/src/Application.cpp                 |  17 +++
 .../src/RenderableMaterialEntityItem.cpp      |  15 ++-
 .../src/RenderableMaterialEntityItem.h        |   2 +-
 .../src/RenderableShapeEntityItem.cpp         |  12 ++
 .../src/RenderableShapeEntityItem.h           |   1 +
 libraries/entities/src/EntityTree.cpp         |   8 ++
 libraries/entities/src/EntityTree.h           |   4 +
 libraries/entities/src/MaterialEntityItem.cpp |  26 +++-
 libraries/entities/src/MaterialEntityItem.h   |   6 +
 .../procedural/src/procedural/Procedural.cpp  | 111 ++++++++++++++----
 .../procedural/src/procedural/Procedural.h    |  22 +++-
 .../src/procedural/ProceduralCommon.slh       |  11 ++
 .../render-utils/src/MeshPartPayload.cpp      |  10 ++
 .../render-utils/src/simple_procedural.slv    |  44 ++++++-
 14 files changed, 253 insertions(+), 36 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index a1c6efa32d..f9ea7fa666 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2107,6 +2107,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         }
         return false;
     });
+    EntityTree::setGetUnscaledDimensionsForEntityIDOperator([this](const QUuid& id) {
+        if (_aboutToQuit) {
+            return glm::vec3(1.0f);
+        }
+
+        auto entity = getEntities()->getEntity(id);
+        if (entity) {
+            return entity->getUnscaledDimensions();
+        }
+
+        auto avatarManager = DependencyManager::get<AvatarManager>();
+        auto avatar = static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(id));
+        if (avatar) {
+            return avatar->getSNScale();
+        }
+        return glm::vec3(1.0f);
+    });
     Procedural::opaqueStencil = [](gpu::StatePointer state) { PrepareStencil::testMaskDrawShape(*state); };
     Procedural::transparentStencil = [](gpu::StatePointer state) { PrepareStencil::testMask(*state); };
 
diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
index a013ff75b7..af121f2957 100644
--- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
@@ -153,13 +153,13 @@ void MaterialEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo
 
         if (urlChanged && !usingMaterialData) {
             _networkMaterial = DependencyManager::get<MaterialCache>()->getMaterial(_materialURL);
-            auto onMaterialRequestFinished = [this, oldParentID, oldParentMaterialName, newCurrentMaterialName](bool success) {
+            auto onMaterialRequestFinished = [this, entity, oldParentID, oldParentMaterialName, newCurrentMaterialName](bool success) {
                 if (success) {
                     deleteMaterial(oldParentID, oldParentMaterialName);
                     _texturesLoaded = false;
                     _parsedMaterials = _networkMaterial->parsedMaterials;
                     setCurrentMaterialName(newCurrentMaterialName);
-                    applyMaterial();
+                    applyMaterial(entity);
                 } else {
                     deleteMaterial(oldParentID, oldParentMaterialName);
                     _retryApply = false;
@@ -183,13 +183,13 @@ void MaterialEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo
             _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(_materialData.toUtf8()), _materialURL);
             // Since our material changed, the current name might not be valid anymore, so we need to update
             setCurrentMaterialName(newCurrentMaterialName);
-            applyMaterial();
+            applyMaterial(entity);
         } else {
             if (deleteNeeded) {
                 deleteMaterial(oldParentID, oldParentMaterialName);
             }
             if (addNeeded) {
-                applyMaterial();
+                applyMaterial(entity);
             }
         }
 
@@ -382,7 +382,7 @@ void MaterialEntityRenderer::applyTextureTransform(std::shared_ptr<NetworkMateri
     material->setTextureTransforms(textureTransform, _materialMappingMode, _materialRepeat);
 }
 
-void MaterialEntityRenderer::applyMaterial() {
+void MaterialEntityRenderer::applyMaterial(const TypedEntityPointer& entity) {
     _retryApply = false;
 
     std::shared_ptr<NetworkMaterial> material = getMaterial();
@@ -396,6 +396,11 @@ void MaterialEntityRenderer::applyMaterial() {
 
     graphics::MaterialLayer materialLayer = graphics::MaterialLayer(material, _priority);
 
+    if (auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(material)) {
+        procedural->setBoundOperator([this] { return getBound(); });
+        entity->setHasVertexShader(procedural->hasVertexShader());
+    }
+
     // Our parent could be an entity or an avatar
     std::string parentMaterialName = _parentMaterialName.toStdString();
     if (EntityTreeRenderer::addMaterialToEntity(parentID, materialLayer, parentMaterialName)) {
diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h
index ff7367a44e..3a73c988eb 100644
--- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h
@@ -56,7 +56,7 @@ private:
     void setCurrentMaterialName(const std::string& currentMaterialName);
 
     void applyTextureTransform(std::shared_ptr<NetworkMaterial>& material);
-    void applyMaterial();
+    void applyMaterial(const TypedEntityPointer& entity);
     void deleteMaterial(const QUuid& oldParentID, const QString& oldParentMaterialName);
 
     NetworkMaterialResourcePointer _networkMaterial;
diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
index 88cc78b6b6..497294e45a 100644
--- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
@@ -207,6 +207,18 @@ ShapeKey ShapeEntityRenderer::getShapeKey() {
     return builder.build();
 }
 
+Item::Bound ShapeEntityRenderer::getBound() {
+    auto mat = _materials.find("0");
+    if (mat != _materials.end() && mat->second.top().material && mat->second.top().material->isProcedural() &&
+        mat->second.top().material->isReady()) {
+        auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(mat->second.top().material);
+        if (procedural->hasVertexShader() && procedural->hasBoundOperator()) {
+           return procedural->getBound();
+        }
+    }
+    return Parent::getBound();
+}
+
 void ShapeEntityRenderer::doRender(RenderArgs* args) {
     PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
     Q_ASSERT(args->_batch);
diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h
index 6061526f75..5bc61606ad 100644
--- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h
@@ -26,6 +26,7 @@ public:
 
 protected:
     ShapeKey getShapeKey() override;
+    Item::Bound getBound() override;
 
 private:
     virtual bool needsRenderUpdate() const override;
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index fed9090ddb..01962bb79d 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -3154,6 +3154,7 @@ std::function<QObject*(const QUuid&)> EntityTree::_getEntityObjectOperator = nul
 std::function<QSizeF(const QUuid&, const QString&)> EntityTree::_textSizeOperator = nullptr;
 std::function<bool()> EntityTree::_areEntityClicksCapturedOperator = nullptr;
 std::function<void(const QUuid&, const QVariant&)> EntityTree::_emitScriptEventOperator = nullptr;
+std::function<glm::vec3(const QUuid&)> EntityTree::_getUnscaledDimensionsForEntityIDOperator = nullptr;
 
 QObject* EntityTree::getEntityObject(const QUuid& id) {
     if (_getEntityObjectOperator) {
@@ -3182,6 +3183,13 @@ void EntityTree::emitScriptEvent(const QUuid& id, const QVariant& message) {
     }
 }
 
+glm::vec3 EntityTree::getUnscaledDimensionsForEntityID(const QUuid& id) {
+    if (_getUnscaledDimensionsForEntityIDOperator) {
+        return _getUnscaledDimensionsForEntityIDOperator(id);
+    }
+    return glm::vec3(1.0f);
+}
+
 void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
                                                MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
     // if the queryBox has changed, tell the entity-server
diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h
index 7b84e40ddd..4c942bf40a 100644
--- a/libraries/entities/src/EntityTree.h
+++ b/libraries/entities/src/EntityTree.h
@@ -273,6 +273,9 @@ public:
     static void setEmitScriptEventOperator(std::function<void(const QUuid&, const QVariant&)> emitScriptEventOperator) { _emitScriptEventOperator = emitScriptEventOperator; }
     static void emitScriptEvent(const QUuid& id, const QVariant& message);
 
+    static void setGetUnscaledDimensionsForEntityIDOperator(std::function<glm::vec3(const QUuid&)> getUnscaledDimensionsForEntityIDOperator) { _getUnscaledDimensionsForEntityIDOperator = getUnscaledDimensionsForEntityIDOperator; }
+    static glm::vec3 getUnscaledDimensionsForEntityID(const QUuid& id);
+
     std::map<QString, QString> getNamedPaths() const { return _namedPaths; }
 
     void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
@@ -389,6 +392,7 @@ private:
     static std::function<QSizeF(const QUuid&, const QString&)> _textSizeOperator;
     static std::function<bool()> _areEntityClicksCapturedOperator;
     static std::function<void(const QUuid&, const QVariant&)> _emitScriptEventOperator;
+    static std::function<glm::vec3(const QUuid&)> _getUnscaledDimensionsForEntityIDOperator;
 
     std::vector<int32_t> _staleProxies;
 
diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp
index 1a7c3c601b..22143e88ba 100644
--- a/libraries/entities/src/MaterialEntityItem.cpp
+++ b/libraries/entities/src/MaterialEntityItem.cpp
@@ -139,10 +139,10 @@ void MaterialEntityItem::debugDump() const {
 
 void MaterialEntityItem::setUnscaledDimensions(const glm::vec3& value) {
     _desiredDimensions = value;
-    if (_materialMappingMode == MaterialMappingMode::UV) {
-        EntityItem::setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS);
-    } else if (_materialMappingMode == MaterialMappingMode::PROJECTED) {
+    if (_hasVertexShader || _materialMappingMode == MaterialMappingMode::PROJECTED) {
         EntityItem::setUnscaledDimensions(value);
+    } else if (_materialMappingMode == MaterialMappingMode::UV) {
+        EntityItem::setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS);
     }
 }
 
@@ -264,6 +264,13 @@ void MaterialEntityItem::setMaterialRepeat(bool value) {
     });
 }
 
+void MaterialEntityItem::setParentID(const QUuid& parentID) {
+    if (parentID != getParentID()) {
+        EntityItem::setParentID(parentID);
+        _hasVertexShader = false;
+    }
+}
+
 AACube MaterialEntityItem::calculateInitialQueryAACube(bool& success) {
     AACube aaCube = EntityItem::calculateInitialQueryAACube(success);
     // A Material entity's queryAACube contains its parent's queryAACube
@@ -278,3 +285,16 @@ AACube MaterialEntityItem::calculateInitialQueryAACube(bool& success) {
     }
     return aaCube;
 }
+
+void MaterialEntityItem::setHasVertexShader(bool hasVertexShader) {
+    bool prevHasVertexShader = _hasVertexShader;
+    _hasVertexShader = hasVertexShader;
+
+    if (hasVertexShader && !prevHasVertexShader) {
+        setLocalPosition(glm::vec3(0.0f));
+        setLocalOrientation(glm::quat());
+        setUnscaledDimensions(EntityTree::getUnscaledDimensionsForEntityID(getParentID()));
+    } else if (!hasVertexShader && prevHasVertexShader) {
+        setUnscaledDimensions(_desiredDimensions);
+    }
+}
\ No newline at end of file
diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h
index 3f32486f0b..d8de8c3bc6 100644
--- a/libraries/entities/src/MaterialEntityItem.h
+++ b/libraries/entities/src/MaterialEntityItem.h
@@ -64,6 +64,8 @@ public:
     QString getParentMaterialName() const;
     void setParentMaterialName(const QString& parentMaterialName);
 
+    void setParentID(const QUuid& parentID) override;
+
     glm::vec2 getMaterialMappingPos() const;
     void setMaterialMappingPos(const glm::vec2& materialMappingPos);
     glm::vec2 getMaterialMappingScale() const;
@@ -73,6 +75,8 @@ public:
 
     AACube calculateInitialQueryAACube(bool& success) override;
 
+    void setHasVertexShader(bool hasVertexShader);
+
 private:
     // URL for this material.  Currently, only JSON format is supported.  Set to "materialData" to use the material data to live edit a material.
     // The following fields are supported in the JSON:
@@ -108,6 +112,8 @@ private:
     float _materialMappingRot { 0 };
     QString _materialData;
 
+    bool _hasVertexShader { false };
+
 };
 
 #endif // hifi_MaterialEntityItem_h
diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp
index 43c6b25dcb..22bfc3f1ad 100644
--- a/libraries/procedural/src/procedural/Procedural.cpp
+++ b/libraries/procedural/src/procedural/Procedural.cpp
@@ -29,6 +29,7 @@ Q_LOGGING_CATEGORY(proceduralLog, "hifi.gpu.procedural")
 
 // User-data parsing constants
 static const QString PROCEDURAL_USER_DATA_KEY = "ProceduralEntity";
+static const QString VERTEX_URL_KEY = "vertexShaderURL";
 static const QString FRAGMENT_URL_KEY = "fragmentShaderURL";
 static const QString URL_KEY = "shaderUrl";
 static const QString VERSION_KEY = "version";
@@ -42,6 +43,7 @@ static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION";
 bool operator==(const ProceduralData& a, const ProceduralData& b) {
     return ((a.version == b.version) &&
             (a.fragmentShaderUrl == b.fragmentShaderUrl) &&
+            (a.vertexShaderUrl == b.vertexShaderUrl) &&
             (a.uniforms == b.uniforms) &&
             (a.channels == b.channels));
 }
@@ -101,9 +103,9 @@ void ProceduralData::parse(const QJsonObject& proceduralData) {
         }
     }
 
-    // Empty shader URL isn't valid
-    if (fragmentShaderUrl.isEmpty()) {
-        return;
+    {  // Vertex shader URL
+        auto rawShaderUrl = proceduralData[VERTEX_URL_KEY].toString();
+        vertexShaderUrl = DependencyManager::get<ResourceManager>()->normalizeURL(rawShaderUrl);
     }
 
     uniforms = proceduralData[UNIFORMS_KEY].toObject();
@@ -172,29 +174,57 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) {
 
     if (proceduralData.fragmentShaderUrl != _data.fragmentShaderUrl) {
         _data.fragmentShaderUrl = proceduralData.fragmentShaderUrl;
-        const auto& shaderUrl = _data.fragmentShaderUrl;
 
         _shaderDirty = true;
         _networkFragmentShader.reset();
         _fragmentShaderPath.clear();
         _fragmentShaderSource.clear();
 
-        if (shaderUrl.isEmpty() || !shaderUrl.isValid()) {
+        if (!_data.fragmentShaderUrl.isValid()) {
+            qCWarning(proceduralLog) << "Invalid fragment shader URL: " << _data.fragmentShaderUrl;
             return;
         }
 
-        if (shaderUrl.isLocalFile()) {
-            if (!QFileInfo(shaderUrl.toLocalFile()).exists()) {
+        if (_data.fragmentShaderUrl.isLocalFile()) {
+            if (!QFileInfo(_data.fragmentShaderUrl.toLocalFile()).exists()) {
+                qCWarning(proceduralLog) << "Invalid fragment shader URL, missing local file: " << _data.fragmentShaderUrl;
                 return;
             }
-            _fragmentShaderPath = shaderUrl.toLocalFile();
-        } else if (shaderUrl.scheme() == URL_SCHEME_QRC) {
-            _fragmentShaderPath = ":" + shaderUrl.path();
+            _fragmentShaderPath = _data.fragmentShaderUrl.toLocalFile();
+        } else if (_data.fragmentShaderUrl.scheme() == URL_SCHEME_QRC) {
+            _fragmentShaderPath = ":" + _data.fragmentShaderUrl.path();
         } else {
-            _networkFragmentShader = ShaderCache::instance().getShader(shaderUrl);
+            _networkFragmentShader = ShaderCache::instance().getShader(_data.fragmentShaderUrl);
         }
     }
 
+    if (proceduralData.vertexShaderUrl != _data.vertexShaderUrl) {
+        _data.vertexShaderUrl = proceduralData.vertexShaderUrl;
+
+        _shaderDirty = true;
+        _networkVertexShader.reset();
+        _vertexShaderPath.clear();
+        _vertexShaderSource.clear();
+
+        if (!_data.vertexShaderUrl.isValid()) {
+            qCWarning(proceduralLog) << "Invalid vertex shader URL: " << _data.vertexShaderUrl;
+            return;
+        }
+
+        if (_data.vertexShaderUrl.isLocalFile()) {
+            if (!QFileInfo(_data.vertexShaderUrl.toLocalFile()).exists()) {
+                qCWarning(proceduralLog) << "Invalid vertex shader URL, missing local file: " << _data.vertexShaderUrl;
+                return;
+            }
+            _vertexShaderPath = _data.vertexShaderUrl.toLocalFile();
+        } else if (_data.vertexShaderUrl.scheme() == URL_SCHEME_QRC) {
+            _vertexShaderPath = ":" + _data.vertexShaderUrl.path();
+        } else {
+            _networkVertexShader = ShaderCache::instance().getShader(_data.vertexShaderUrl);
+        }
+
+    }
+
     _enabled = true;
 }
 
@@ -213,8 +243,12 @@ bool Procedural::isReady() const {
         _fadeStartTime = usecTimestampNow();
     }
 
-    // Do we have a network or local shader, and if so, is it loaded?
-    if (_fragmentShaderPath.isEmpty() && (!_networkFragmentShader || !_networkFragmentShader->isLoaded())) {
+    // We need to have at least one shader, and whichever ones we have need to be loaded
+    bool hasFragmentShader = !_fragmentShaderPath.isEmpty() || _networkFragmentShader;
+    bool fragmentShaderLoaded = !_fragmentShaderPath.isEmpty() || (_networkFragmentShader && _networkFragmentShader->isLoaded());
+    bool hasVertexShader = !_vertexShaderPath.isEmpty() || _networkVertexShader;
+    bool vertexShaderLoaded = !_vertexShaderPath.isEmpty() || (_networkVertexShader && _networkVertexShader->isLoaded());
+    if ((!hasFragmentShader && !hasVertexShader) || (hasFragmentShader && !fragmentShaderLoaded) || (hasVertexShader && !vertexShaderLoaded)) {
         return false;
     }
 
@@ -258,6 +292,20 @@ void Procedural::prepare(gpu::Batch& batch,
         _shaderDirty = true;
     }
 
+    if (!_vertexShaderPath.isEmpty()) {
+        auto lastModified = (uint64_t)QFileInfo(_vertexShaderPath).lastModified().toMSecsSinceEpoch();
+        if (lastModified > _vertexShaderModified) {
+            QFile file(_vertexShaderPath);
+            file.open(QIODevice::ReadOnly);
+            _vertexShaderSource = QTextStream(&file).readAll();
+            _shaderDirty = true;
+            _vertexShaderModified = lastModified;
+        }
+    } else if (_vertexShaderSource.isEmpty() && _networkVertexShader && _networkVertexShader->isLoaded()) {
+        _vertexShaderSource = _networkVertexShader->_source;
+        _shaderDirty = true;
+    }
+
     if (_shaderDirty) {
         _proceduralPipelines.clear();
     }
@@ -276,25 +324,42 @@ void Procedural::prepare(gpu::Batch& batch,
 
         gpu::Shader::Source& fragmentSource = (key.isTransparent() && _transparentFragmentSource.valid()) ? _transparentFragmentSource : _opaqueFragmentSource;
 
-        // Build the fragment shader
+        // Build the fragment and vertex shaders
+        auto versionDefine = "#define PROCEDURAL_V" + std::to_string(_data.version);
         fragmentSource.replacements.clear();
-        fragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V" + std::to_string(_data.version);
-        fragmentSource.replacements[PROCEDURAL_BLOCK] = _fragmentShaderSource.toStdString();
+        fragmentSource.replacements[PROCEDURAL_VERSION] = versionDefine;
+        if (!_fragmentShaderSource.isEmpty()) {
+            fragmentSource.replacements[PROCEDURAL_BLOCK] = _fragmentShaderSource.toStdString();
+        }
+        vertexSource.replacements.clear();
+        vertexSource.replacements[PROCEDURAL_VERSION] = versionDefine;
+        if (!_vertexShaderSource.isEmpty()) {
+            vertexSource.replacements[PROCEDURAL_BLOCK] = _vertexShaderSource.toStdString();
+        }
 
         // Set any userdata specified uniforms (if any)
         if (!_data.uniforms.empty()) {
-            // First grab all the possible dialect/variant/Reflections
-            std::vector<shader::Reflection*> allReflections;
+            // First grab all the possible dialect/variant/reflections
+            std::vector<shader::Reflection*> allFragmentReflections;
             for (auto dialectIt = fragmentSource.dialectSources.begin(); dialectIt != fragmentSource.dialectSources.end(); ++dialectIt) {
                 for (auto variantIt = (*dialectIt).second.variantSources.begin(); variantIt != (*dialectIt).second.variantSources.end(); ++variantIt) {
-                    allReflections.push_back(&(*variantIt).second.reflection);
+                    allFragmentReflections.push_back(&(*variantIt).second.reflection);
+                }
+            }
+            std::vector<shader::Reflection*> allVertexReflections;
+            for (auto dialectIt = vertexSource.dialectSources.begin(); dialectIt != vertexSource.dialectSources.end(); ++dialectIt) {
+                for (auto variantIt = (*dialectIt).second.variantSources.begin(); variantIt != (*dialectIt).second.variantSources.end(); ++variantIt) {
+                    allVertexReflections.push_back(&(*variantIt).second.reflection);
                 }
             }
             // Then fill in every reflections the new custom bindings
             int customSlot = procedural::slot::uniform::Custom;
             for (const auto& key : _data.uniforms.keys()) {
                 std::string uniformName = key.toLocal8Bit().data();
-                for (auto reflection : allReflections) {
+                for (auto reflection : allFragmentReflections) {
+                    reflection->uniforms[uniformName] = customSlot;
+                }
+                for (auto reflection : allVertexReflections) {
                     reflection->uniforms[uniformName] = customSlot;
                 }
                 ++customSlot;
@@ -303,6 +368,7 @@ void Procedural::prepare(gpu::Batch& batch,
 
         // Leave this here for debugging
         //qCDebug(proceduralLog) << "FragmentShader:\n" << fragmentSource.getSource(shader::Dialect::glsl450, shader::Variant::Mono).c_str();
+        //qCDebug(proceduralLog) << "VertexShader:\n" << vertexSource.getSource(shader::Dialect::glsl450, shader::Variant::Mono).c_str();
 
         gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(vertexSource);
         gpu::ShaderPointer fragmentShader = gpu::Shader::createPixel(fragmentSource);
@@ -453,6 +519,11 @@ glm::vec4 Procedural::getColor(const glm::vec4& entityColor) const {
     return entityColor;
 }
 
+bool Procedural::hasVertexShader() const {
+    std::lock_guard<std::mutex> lock(_mutex);
+    return !_data.vertexShaderUrl.isEmpty();
+}
+
 void graphics::ProceduralMaterial::initializeProcedural() {
     _procedural._vertexSource = gpu::Shader::getVertexShaderSource(shader::render_utils::vertex::simple_procedural);
     _procedural._vertexSourceSkinned = gpu::Shader::getVertexShaderSource(shader::render_utils::vertex::simple_procedural_deformed);
diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h
index aac353bf7c..89f21218e6 100644
--- a/libraries/procedural/src/procedural/Procedural.h
+++ b/libraries/procedural/src/procedural/Procedural.h
@@ -36,6 +36,8 @@ const size_t MAX_PROCEDURAL_TEXTURE_CHANNELS{ 4 };
  * The data used to define a Procedural shader material.
  * @typedef {object} ProceduralData
  * @property {number} version=1 - The version of the procedural shader.
+ * @property {string} vertexShaderURL - A link to a vertex shader.  Currently, only GLSL shaders are supported.  The shader must implement a different method depending on the version.
+ *     If a procedural material contains a vertex shader, the bounding box of the material entity is used to cull the object to which the material is applied.
  * @property {string} fragmentShaderURL - A link to a fragment shader.  Currently, only GLSL shaders are supported.  The shader must implement a different method depending on the version.
  *     <code>shaderUrl</code> is an alias.
  * @property {string[]} channels=[] - An array of input texture URLs.  Currently, up to 4 are supported.
@@ -50,6 +52,7 @@ struct ProceduralData {
     // Rendering object descriptions, from userData
     uint8_t version { 0 };
     QUrl fragmentShaderUrl;
+    QUrl vertexShaderUrl;
     QJsonObject uniforms;
     QJsonArray channels;
 };
@@ -110,6 +113,11 @@ public:
     void setIsFading(bool isFading) { _isFading = isFading; }
     void setDoesFade(bool doesFade) { _doesFade = doesFade; }
 
+    bool hasVertexShader() const;
+    void setBoundOperator(const std::function<AABox()>& boundOperator) { _boundOperator = boundOperator; }
+    bool hasBoundOperator() const { return (bool)_boundOperator; }
+    AABox getBound() { return _boundOperator(); }
+
     gpu::Shader::Source _vertexSource;
     gpu::Shader::Source _vertexSourceSkinned;
     gpu::Shader::Source _vertexSourceSkinnedDQ;
@@ -156,7 +164,11 @@ protected:
     uint64_t _firstCompile { 0 };
     int32_t _frameCount { 0 };
 
-    // Rendering object descriptions, from userData
+    // Rendering object descriptions
+    QString _vertexShaderSource;
+    QString _vertexShaderPath;
+    uint64_t _vertexShaderModified { 0 };
+    NetworkShaderPointer _networkVertexShader;
     QString _fragmentShaderSource;
     QString _fragmentShaderPath;
     uint64_t _fragmentShaderModified { 0 };
@@ -187,6 +199,9 @@ private:
     mutable bool _isFading { false };
     bool _doesFade { true };
     ProceduralProgramKey _prevKey;
+
+    std::function<AABox()> _boundOperator { nullptr };
+
     mutable std::mutex _mutex;
 };
 
@@ -210,6 +225,7 @@ public:
     bool isFading() const { return _procedural.isFading(); }
     void setIsFading(bool isFading) { _procedural.setIsFading(isFading); }
     uint64_t getFadeStartTime() const { return _procedural.getFadeStartTime(); }
+    bool hasVertexShader() const { return _procedural.hasVertexShader(); }
     void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation,
                  const uint64_t& created, const ProceduralProgramKey key = ProceduralProgramKey()) {
         _procedural.prepare(batch, position, size, orientation, created, key);
@@ -217,6 +233,10 @@ public:
 
     void initializeProcedural();
 
+    void setBoundOperator(const std::function<AABox()>& boundOperator) { _procedural.setBoundOperator(boundOperator); }
+    bool hasBoundOperator() const { return _procedural.hasBoundOperator(); }
+    AABox getBound() { return _procedural.getBound(); }
+
 private:
     QString _proceduralString;
     Procedural _procedural;
diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh
index 2915f096e6..e2344dc14e 100644
--- a/libraries/procedural/src/procedural/ProceduralCommon.slh
+++ b/libraries/procedural/src/procedural/ProceduralCommon.slh
@@ -60,6 +60,17 @@ LAYOUT_STD140(binding=PROCEDURAL_BUFFER_INPUTS) uniform standardInputsBuffer {
 #define iChannelResolution standardInputs.channelResolution
 #define iWorldOrientation standardInputs.worldOrientation
 
+struct ProceduralVertexData {
+    vec4 position;
+    vec4 nonSkinnedPosition;    // input only
+    vec3 normal;
+    vec3 nonSkinnedNormal;      // input only
+    vec3 tangent;               // input only
+    vec3 nonSkinnedTangent;     // input only
+    vec4 color;
+    vec2 texCoord0;
+};
+
 struct ProceduralFragment {
     vec3 normal;
     vec3 diffuse;
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 9b42139892..713e3f4f93 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -110,6 +110,13 @@ ItemKey MeshPartPayload::getKey() const {
 }
 
 Item::Bound MeshPartPayload::getBound() const {
+    graphics::MaterialPointer material = _drawMaterials.empty() ? nullptr : _drawMaterials.top().material;
+    if (material && material->isProcedural() && material->isReady()) {
+        auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(_drawMaterials.top().material);
+        if (procedural->hasVertexShader() && procedural->hasBoundOperator()) {
+           return procedural->getBound();
+        }
+    }
     return _worldBound;
 }
 
@@ -175,6 +182,9 @@ void MeshPartPayload::render(RenderArgs* args) {
 
     if (!_drawMaterials.empty() && _drawMaterials.top().material && _drawMaterials.top().material->isProcedural() &&
             _drawMaterials.top().material->isReady()) {
+        if (!(enableMaterialProceduralShaders && ENABLE_MATERIAL_PROCEDURAL_SHADERS)) {
+            return;
+        }
         auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(_drawMaterials.top().material);
         auto& schema = _drawMaterials.getSchemaBuffer().get<graphics::MultiMaterial::Schema>();
         glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity);
diff --git a/libraries/render-utils/src/simple_procedural.slv b/libraries/render-utils/src/simple_procedural.slv
index a8d494f72d..70bce451d3 100644
--- a/libraries/render-utils/src/simple_procedural.slv
+++ b/libraries/render-utils/src/simple_procedural.slv
@@ -20,9 +20,9 @@
 <@if HIFI_USE_DEFORMED or HIFI_USE_DEFORMEDDQ@>
     <@include MeshDeformer.slh@>
     <@if HIFI_USE_DEFORMED@>
-        <$declareMeshDeformer(1, _SCRIBE_NULL, 1, _SCRIBE_NULL, 1)$>
+        <$declareMeshDeformer(1, 1, 1, _SCRIBE_NULL, 1)$>
     <@else@>
-        <$declareMeshDeformer(1, _SCRIBE_NULL, 1, 1, 1)$>
+        <$declareMeshDeformer(1, 1, 1, 1, 1)$>
     <@endif@>
     <$declareMeshDeformerActivation(1, 1)$>
 <@endif@>
@@ -34,24 +34,56 @@ layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
 layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color;
 layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
 
+<@include procedural/ProceduralCommon.slh@>
+
+#line 1001
+//PROCEDURAL_BLOCK_BEGIN
+
+void getProceduralVertex(inout ProceduralVertexData proceduralData) {}
+
+//PROCEDURAL_BLOCK_END
+
+#line 2030
 void main(void) {
     vec4 positionMS = inPosition;
     vec3 normalMS = inNormal.xyz;
+    vec3 tangentMS = inTangent.xyz;
+    vec4 color = color_sRGBAToLinear(inColor);
+    vec2 texCoord0 = inTexCoord0.st;
 
 <@if HIFI_USE_DEFORMED or HIFI_USE_DEFORMEDDQ@>
-        evalMeshDeformer(inPosition, positionMS, inNormal.xyz, normalMS,
+        evalMeshDeformer(inPosition, positionMS, inNormal.xyz, normalMS, inTangent.xyz, tangentMS,
                          meshDeformer_doSkinning(_drawCallInfo.y), inSkinClusterIndex, inSkinClusterWeight,
                          meshDeformer_doBlendshape(_drawCallInfo.y), gl_VertexID);
 <@endif@>
 
+#if defined(PROCEDURAL_V1) || defined(PROCEDURAL_V2) || defined(PROCEDURAL_V3)
+    ProceduralVertexData proceduralData = ProceduralVertexData(
+        positionMS,
+        inPosition,
+        normalMS,
+        inNormal.xyz,
+        tangentMS,
+        inTangent.xyz,
+        color,
+        texCoord0
+    );
+
+    getProceduralVertex(proceduralData);
+
+    positionMS = proceduralData.position;
+    normalMS = proceduralData.normal;
+    color = proceduralData.color;
+    texCoord0 = proceduralData.texCoord0;
+#endif
+
     _positionMS = positionMS;
     _normalMS = normalMS;
+    _color = color;
+    _texCoord01 = vec4(texCoord0, 0.0, 0.0);
 
     TransformCamera cam = getTransformCamera();
     TransformObject obj = getTransformObject();
     <$transformModelToEyeAndClipPos(cam, obj, positionMS, _positionES, gl_Position)$>
     <$transformModelToWorldDir(cam, obj, normalMS, _normalWS)$>
-
-    _color = color_sRGBAToLinear(inColor);
-    _texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0);
 }
\ No newline at end of file

From 2eb0ec18fc5d4dfbf6bf8ea5f40f043fa3185bfc Mon Sep 17 00:00:00 2001
From: HifiExperiments <thingsandstuffblog@gmail.com>
Date: Wed, 11 Dec 2019 00:42:59 -0800
Subject: [PATCH 2/2] fix non-procedural material entity crash

---
 interface/src/Application.cpp                             | 2 +-
 .../src/RenderableMaterialEntityItem.cpp                  | 3 ++-
 libraries/entities/src/EntityTree.cpp                     | 8 ++++----
 libraries/entities/src/EntityTree.h                       | 6 +++---
 libraries/entities/src/MaterialEntityItem.cpp             | 2 +-
 5 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index f9ea7fa666..6a6f624c8f 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2107,7 +2107,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         }
         return false;
     });
-    EntityTree::setGetUnscaledDimensionsForEntityIDOperator([this](const QUuid& id) {
+    EntityTree::setGetUnscaledDimensionsForIDOperator([this](const QUuid& id) {
         if (_aboutToQuit) {
             return glm::vec3(1.0f);
         }
diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
index af121f2957..7fa3a57804 100644
--- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
@@ -396,7 +396,8 @@ void MaterialEntityRenderer::applyMaterial(const TypedEntityPointer& entity) {
 
     graphics::MaterialLayer materialLayer = graphics::MaterialLayer(material, _priority);
 
-    if (auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(material)) {
+    if (material->isProcedural()) {
+        auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(material);
         procedural->setBoundOperator([this] { return getBound(); });
         entity->setHasVertexShader(procedural->hasVertexShader());
     }
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index 01962bb79d..c43b543125 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -3154,7 +3154,7 @@ std::function<QObject*(const QUuid&)> EntityTree::_getEntityObjectOperator = nul
 std::function<QSizeF(const QUuid&, const QString&)> EntityTree::_textSizeOperator = nullptr;
 std::function<bool()> EntityTree::_areEntityClicksCapturedOperator = nullptr;
 std::function<void(const QUuid&, const QVariant&)> EntityTree::_emitScriptEventOperator = nullptr;
-std::function<glm::vec3(const QUuid&)> EntityTree::_getUnscaledDimensionsForEntityIDOperator = nullptr;
+std::function<glm::vec3(const QUuid&)> EntityTree::_getUnscaledDimensionsForIDOperator = nullptr;
 
 QObject* EntityTree::getEntityObject(const QUuid& id) {
     if (_getEntityObjectOperator) {
@@ -3183,9 +3183,9 @@ void EntityTree::emitScriptEvent(const QUuid& id, const QVariant& message) {
     }
 }
 
-glm::vec3 EntityTree::getUnscaledDimensionsForEntityID(const QUuid& id) {
-    if (_getUnscaledDimensionsForEntityIDOperator) {
-        return _getUnscaledDimensionsForEntityIDOperator(id);
+glm::vec3 EntityTree::getUnscaledDimensionsForID(const QUuid& id) {
+    if (_getUnscaledDimensionsForIDOperator) {
+        return _getUnscaledDimensionsForIDOperator(id);
     }
     return glm::vec3(1.0f);
 }
diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h
index 4c942bf40a..3662f6acf0 100644
--- a/libraries/entities/src/EntityTree.h
+++ b/libraries/entities/src/EntityTree.h
@@ -273,8 +273,8 @@ public:
     static void setEmitScriptEventOperator(std::function<void(const QUuid&, const QVariant&)> emitScriptEventOperator) { _emitScriptEventOperator = emitScriptEventOperator; }
     static void emitScriptEvent(const QUuid& id, const QVariant& message);
 
-    static void setGetUnscaledDimensionsForEntityIDOperator(std::function<glm::vec3(const QUuid&)> getUnscaledDimensionsForEntityIDOperator) { _getUnscaledDimensionsForEntityIDOperator = getUnscaledDimensionsForEntityIDOperator; }
-    static glm::vec3 getUnscaledDimensionsForEntityID(const QUuid& id);
+    static void setGetUnscaledDimensionsForIDOperator(std::function<glm::vec3(const QUuid&)> getUnscaledDimensionsForIDOperator) { _getUnscaledDimensionsForIDOperator = getUnscaledDimensionsForIDOperator; }
+    static glm::vec3 getUnscaledDimensionsForID(const QUuid& id);
 
     std::map<QString, QString> getNamedPaths() const { return _namedPaths; }
 
@@ -392,7 +392,7 @@ private:
     static std::function<QSizeF(const QUuid&, const QString&)> _textSizeOperator;
     static std::function<bool()> _areEntityClicksCapturedOperator;
     static std::function<void(const QUuid&, const QVariant&)> _emitScriptEventOperator;
-    static std::function<glm::vec3(const QUuid&)> _getUnscaledDimensionsForEntityIDOperator;
+    static std::function<glm::vec3(const QUuid&)> _getUnscaledDimensionsForIDOperator;
 
     std::vector<int32_t> _staleProxies;
 
diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp
index 22143e88ba..73bebfc403 100644
--- a/libraries/entities/src/MaterialEntityItem.cpp
+++ b/libraries/entities/src/MaterialEntityItem.cpp
@@ -293,7 +293,7 @@ void MaterialEntityItem::setHasVertexShader(bool hasVertexShader) {
     if (hasVertexShader && !prevHasVertexShader) {
         setLocalPosition(glm::vec3(0.0f));
         setLocalOrientation(glm::quat());
-        setUnscaledDimensions(EntityTree::getUnscaledDimensionsForEntityID(getParentID()));
+        setUnscaledDimensions(EntityTree::getUnscaledDimensionsForID(getParentID()));
     } else if (!hasVertexShader && prevHasVertexShader) {
         setUnscaledDimensions(_desiredDimensions);
     }