From 4665f6027f1e1636245fe2d8edb09be9c6c0d61b Mon Sep 17 00:00:00 2001
From: Philip Rideout <philiprideout@gmail.com>
Date: Mon, 10 Feb 2020 09:24:48 -0800
Subject: [PATCH] Add dynamic raster states to MaterialInstance.

Fulfills a request from tirichards@.

This is similar in some ways to dynamic backface culling but simpler
because there is no interaction with double-sided lighting.

For reference, dynamic backface culling was implemented with #1936, #1877, #1641.
---
 RELEASE_NOTES.md                              |  1 +
 .../src/main/cpp/MaterialInstance.cpp         | 24 ++++++++++++
 .../com/google/android/filament/Material.java |  6 +--
 .../android/filament/MaterialInstance.java    | 37 ++++++++++++++++++-
 filament/include/filament/Material.h          |  6 +--
 filament/include/filament/MaterialInstance.h  | 17 ++++++++-
 filament/src/MaterialInstance.cpp             | 33 ++++++++++++++++-
 filament/src/RenderPass.cpp                   |  9 +++--
 filament/src/details/MaterialInstance.h       | 15 ++++++++
 .../filamat/include/filamat/MaterialBuilder.h |  6 +--
 web/filament-js/filament.d.ts                 |  3 ++
 web/filament-js/jsbindings.cpp                |  5 ++-
 12 files changed, 146 insertions(+), 16 deletions(-)

diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 993d5abc3905..976e43a2943e 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -10,6 +10,7 @@ A new header is inserted each time a *tag* is created.
 - Removed depth-prepass related APIs.
 - gltfio: add asynchronous API to ResourceLoader.
 - gltfio: generate normals for flat-shaded models that do not have normals.
+- Material instances now allow dynamic depth testing and other rasterization state.
 
 ## v1.4.5
 
diff --git a/android/filament-android/src/main/cpp/MaterialInstance.cpp b/android/filament-android/src/main/cpp/MaterialInstance.cpp
index ca75e94f4c4c..57ac24385570 100644
--- a/android/filament-android/src/main/cpp/MaterialInstance.cpp
+++ b/android/filament-android/src/main/cpp/MaterialInstance.cpp
@@ -324,3 +324,27 @@ Java_com_google_android_filament_MaterialInstance_nSetCullingMode(JNIEnv*,
     MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
     instance->setCullingMode((MaterialInstance::CullingMode) cullingMode);
 }
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_google_android_filament_MaterialInstance_nSetColorWrite(JNIEnv*,
+        jclass, jlong nativeMaterialInstance, jboolean enable) {
+    MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
+    instance->setColorWrite(enable);
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_google_android_filament_MaterialInstance_nSetDepthWrite(JNIEnv*,
+        jclass, jlong nativeMaterialInstance, jboolean enable) {
+    MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
+    instance->setDepthWrite(enable);
+}
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_google_android_filament_MaterialInstance_nSetDepthCulling(JNIEnv*,
+        jclass, jlong nativeMaterialInstance, jboolean enable) {
+    MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
+    instance->setDepthCulling(enable);
+}
diff --git a/android/filament-android/src/main/java/com/google/android/filament/Material.java b/android/filament-android/src/main/java/com/google/android/filament/Material.java
index 2e77d0ca70b1..b82e4c830901 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/Material.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/Material.java
@@ -422,7 +422,7 @@ public CullingMode getCullingMode() {
     }
 
     /**
-     * Indicates whether this material will write to the color buffer.
+     * Indicates whether instances of this material will, by default, write to the color buffer.
      *
      * @see
      * <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:colorwrite">
@@ -433,7 +433,7 @@ public boolean isColorWriteEnabled() {
     }
 
     /**
-     * Indicates whether this material will write to the depth buffer.
+     * Indicates whether instances of this material will, by default, write to the depth buffer.
      *
      * @see
      * <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:depthwrite">
@@ -444,7 +444,7 @@ public boolean isDepthWriteEnabled() {
     }
 
     /**
-     * Indicates whether this material will use depth testing.
+     * Indicates whether instances of this material will, by default, use depth testing.
      *
      * @see
      * <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:depthculling">
diff --git a/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java b/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java
index 2f18ec2c4fe4..d82b4714bafb 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java
@@ -430,6 +430,39 @@ public void setCullingMode(Material.CullingMode mode) {
         nSetCullingMode(getNativeObject(), mode.ordinal());
     }
 
+    /**
+     * Overrides the default color-buffer write state that was set on the material.
+     *
+     * @see
+     * <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:colorWrite">
+     * Rasterization: colorWrite</a>
+     */
+    void setColorWrite(boolean enable) {
+        nSetColorWrite(getNativeObject(), enable);
+    }
+
+    /**
+     * Overrides the default depth-buffer write state that was set on the material.
+     *
+     * @see
+     * <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:depthWrite">
+     * Rasterization: depthWrite</a>
+     */
+    void setDepthWrite(boolean enable) {
+        nSetDepthWrite(getNativeObject(), enable);
+    }
+
+    /**
+     * Overrides the default depth testing state that was set on the material.
+     *
+     * @see
+     * <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:depthCulling">
+     * Rasterization: depthCulling</a>
+     */
+    void setDepthCulling(boolean enable) {
+        nSetDepthCulling(getNativeObject(), enable);
+    }
+
     public long getNativeObject() {
         if (mNativeObject == 0) {
             throw new IllegalStateException("Calling method on destroyed MaterialInstance");
@@ -499,6 +532,8 @@ private static native void nSetSpecularAntiAliasingThreshold(long nativeMaterial
             float threshold);
 
     private static native void nSetDoubleSided(long nativeMaterialInstance, boolean doubleSided);
-
     private static native void nSetCullingMode(long nativeMaterialInstance, long mode);
+    private static native void nSetColorWrite(long nativeMaterialInstance, boolean enable);
+    private static native void nSetDepthWrite(long nativeMaterialInstance, boolean enable);
+    private static native void nSetDepthCulling(long nativeMaterialInstance, boolean enable);
 }
diff --git a/filament/include/filament/Material.h b/filament/include/filament/Material.h
index ba82945a0420..b4b502d2f0e7 100644
--- a/filament/include/filament/Material.h
+++ b/filament/include/filament/Material.h
@@ -153,13 +153,13 @@ class UTILS_PUBLIC Material : public FilamentAPI {
     //! This value only makes sense when the blending mode is transparent or fade.
     TransparencyMode getTransparencyMode() const noexcept;
 
-    //! Indicates whether this material will write to the color buffer.
+    //! Indicates whether instances of this material will, by default, write to the color buffer.
     bool isColorWriteEnabled() const noexcept;
 
-    //! Indicates whether this material will write to the depth buffer.
+    //! Indicates whether instances of this material will, by default, write to the depth buffer.
     bool isDepthWriteEnabled() const noexcept;
 
-    //! Indicates whether this material will use depth testing.
+    //! Indicates whether instances of this material will, by default, use depth testing.
     bool isDepthCullingEnabled() const noexcept;
 
     //! Indicates whether this material is double-sided.
diff --git a/filament/include/filament/MaterialInstance.h b/filament/include/filament/MaterialInstance.h
index ac004d05267f..51cb814c0e95 100644
--- a/filament/include/filament/MaterialInstance.h
+++ b/filament/include/filament/MaterialInstance.h
@@ -125,7 +125,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
 
     /**
      * Set up a custom scissor rectangle; by default this encompasses the View.
-     * 
+     *
      * @param left      left coordinate of the scissor box
      * @param bottom    bottom coordinate of the scissor box
      * @param width     width of the scissor box
@@ -188,6 +188,21 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
      * Overrides the default triangle culling state that was set on the material.
      */
     void setCullingMode(CullingMode culling) noexcept;
+
+    /**
+     * Overrides the default color-buffer write state that was set on the material.
+     */
+    void setColorWrite(bool enable) noexcept;
+
+    /**
+     * Overrides the default depth-buffer write state that was set on the material.
+     */
+    void setDepthWrite(bool enable) noexcept;
+
+    /**
+     * Overrides the default depth testing state that was set on the material.
+     */
+    void setDepthCulling(bool enable) noexcept;
 };
 
 } // namespace filament
diff --git a/filament/src/MaterialInstance.cpp b/filament/src/MaterialInstance.cpp
index 740f80c777da..d9b4a3cfaa78 100644
--- a/filament/src/MaterialInstance.cpp
+++ b/filament/src/MaterialInstance.cpp
@@ -44,9 +44,15 @@ FMaterialInstance::FMaterialInstance() noexcept = default;
 FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material) {
     mMaterial = material;
 
+    const RasterState& rasterState = mMaterial->getRasterState();
+
     // We inherit the resolved culling mode rather than the builder-set culling mode.
     // This preserves the property whereby double-sidedness automatically disables culling.
-    mCulling = mMaterial->getRasterState().culling;
+    mCulling = rasterState.culling;
+
+    mColorWrite = rasterState.colorWrite;
+    mDepthWrite = rasterState.depthWrite;
+    mDepthFunc = rasterState.depthFunc;
 
     mMaterialSortingKey = RenderPass::makeMaterialSortingKey(
             material->getId(), material->generateMaterialInstanceId());
@@ -172,6 +178,18 @@ void FMaterialInstance::setCullingMode(CullingMode culling) noexcept {
     mCulling = culling;
 }
 
+void FMaterialInstance::setColorWrite(bool enable) noexcept {
+    mColorWrite = enable;
+}
+
+void FMaterialInstance::setDepthWrite(bool enable) noexcept {
+    mDepthWrite = enable;
+}
+
+void FMaterialInstance::setDepthCulling(bool enable) noexcept {
+    mDepthFunc = enable ? RasterState::DepthFunc::LE : RasterState::DepthFunc::A;
+}
+
 // explicit template instantiation of our supported types
 template UTILS_NOINLINE void FMaterialInstance::setParameter<bool>    (const char* name, bool     v) noexcept;
 template UTILS_NOINLINE void FMaterialInstance::setParameter<float>   (const char* name, float    v) noexcept;
@@ -312,4 +330,17 @@ void MaterialInstance::setCullingMode(CullingMode culling) noexcept {
     upcast(this)->setCullingMode(culling);
 }
 
+void MaterialInstance::setColorWrite(bool enable) noexcept {
+    upcast(this)->setColorWrite(enable);
+}
+
+void MaterialInstance::setDepthWrite(bool enable) noexcept {
+    upcast(this)->setDepthWrite(enable);
+}
+
+void MaterialInstance::setDepthCulling(bool enable) noexcept {
+    upcast(this)->setDepthCulling(enable);
+}
+
+
 } // namespace filament
diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp
index 37c4cd1f4d54..68cbecbc992c 100644
--- a/filament/src/RenderPass.cpp
+++ b/filament/src/RenderPass.cpp
@@ -210,7 +210,7 @@ void RenderPass::recordDriverCommands(FEngine::DriverApi& driver, const Command*
         FMaterial const* UTILS_RESTRICT ma = nullptr;
         auto const& customCommands = mCustomCommands;
 
-        auto updateMaterial = [&](FMaterialInstance const* materialInstance) {
+        auto useMaterialInstance = [&](FMaterialInstance const* materialInstance) {
             mi = materialInstance;
             ma = mi->getMaterial();
             pipeline.scissor = mi->getScissor();
@@ -220,7 +220,7 @@ void RenderPass::recordDriverCommands(FEngine::DriverApi& driver, const Command*
 
         FMaterialInstance const * const materialInstanceOverride = mMaterialInstanceOverride;
         if (UTILS_UNLIKELY(materialInstanceOverride)) {
-            updateMaterial(materialInstanceOverride);
+            useMaterialInstance(materialInstanceOverride);
         }
 
         first--;
@@ -240,7 +240,7 @@ void RenderPass::recordDriverCommands(FEngine::DriverApi& driver, const Command*
             pipeline.rasterState = info.rasterState;
             if (UTILS_UNLIKELY(!materialInstanceOverride && mi != info.mi)) {
                 // this is always taken the first time
-                updateMaterial(info.mi);
+                useMaterialInstance(info.mi);
             }
 
             pipeline.program = ma->getProgram(info.materialVariant.key);
@@ -289,6 +289,9 @@ void RenderPass::setupColorCommand(Command& cmdDraw, bool hasDepthPass,
     cmdDraw.primitive.rasterState = ma->getRasterState();
     cmdDraw.primitive.rasterState.inverseFrontFaces = inverseFrontFaces;
     cmdDraw.primitive.rasterState.culling = mi->getCullingMode();
+    cmdDraw.primitive.rasterState.colorWrite = mi->getColorWrite();
+    cmdDraw.primitive.rasterState.depthWrite = mi->getDepthWrite();
+    cmdDraw.primitive.rasterState.depthFunc = mi->getDepthFunc();
     cmdDraw.primitive.mi = mi;
     cmdDraw.primitive.materialVariant.key = variant;
 
diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h
index b2edc77cb9fe..40a3440d0a38 100644
--- a/filament/src/details/MaterialInstance.h
+++ b/filament/src/details/MaterialInstance.h
@@ -98,6 +98,12 @@ class FMaterialInstance : public MaterialInstance {
 
     backend::CullingMode getCullingMode() const noexcept { return mCulling; }
 
+    bool getColorWrite() const noexcept { return mColorWrite; }
+
+    bool getDepthWrite() const noexcept { return mDepthWrite; }
+
+    backend::RasterState::DepthFunc getDepthFunc() const noexcept { return mDepthFunc; }
+
     void setPolygonOffset(float scale, float constant) noexcept {
         mPolygonOffset = { scale, constant };
     }
@@ -120,6 +126,12 @@ class FMaterialInstance : public MaterialInstance {
 
     void setCullingMode(CullingMode culling) noexcept;
 
+    void setColorWrite(bool enable) noexcept;
+
+    void setDepthWrite(bool enable) noexcept;
+
+    void setDepthCulling(bool enable) noexcept;
+
 private:
     friend class FMaterial;
     friend class MaterialInstance;
@@ -139,6 +151,9 @@ class FMaterialInstance : public MaterialInstance {
     backend::SamplerGroup mSamplers;
     backend::PolygonOffset mPolygonOffset;
     backend::CullingMode mCulling;
+    bool mColorWrite;
+    bool mDepthWrite;
+    backend::RasterState::DepthFunc mDepthFunc;
 
     uint64_t mMaterialSortingKey = 0;
 
diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h
index fda8d86f7e2f..6c88c01df54a 100644
--- a/libs/filamat/include/filamat/MaterialBuilder.h
+++ b/libs/filamat/include/filamat/MaterialBuilder.h
@@ -318,13 +318,13 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase {
      */
     MaterialBuilder& culling(CullingMode culling) noexcept;
 
-    //! Enable / disable color-buffer write (enabled by default).
+    //! Enable / disable color-buffer write (enabled by default, material instances can override).
     MaterialBuilder& colorWrite(bool enable) noexcept;
 
-    //! Enable / disable depth-buffer write (enabled by default for opaque, disabled for others).
+    //! Enable / disable depth-buffer write (enabled by default for opaque, disabled for others, material instances can override).
     MaterialBuilder& depthWrite(bool enable) noexcept;
 
-    //! Enable / disable depth based culling (enabled by default).
+    //! Enable / disable depth based culling (enabled by default, material instances can override).
     MaterialBuilder& depthCulling(bool enable) noexcept;
 
     /**
diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts
index c174fe1f783f..e6d0f6ca040a 100644
--- a/web/filament-js/filament.d.ts
+++ b/web/filament-js/filament.d.ts
@@ -85,6 +85,9 @@ export class MaterialInstance {
     public setMaskThreshold(threshold: number): void;
     public setDoubleSided(doubleSided: boolean): void;
     public setCullingMode(mode: CullingMode): void;
+    public setColorWrite(enable: boolean): void;
+    public setDepthWrite(enable: boolean): void;
+    public setDepthCulling(enable: boolean): void;
 }
 
 export class EntityManager {
diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp
index ffde6039801e..2360cc35502f 100644
--- a/web/filament-js/jsbindings.cpp
+++ b/web/filament-js/jsbindings.cpp
@@ -958,7 +958,10 @@ class_<MaterialInstance>("MaterialInstance")
     .function("setPolygonOffset", &MaterialInstance::setPolygonOffset)
     .function("setMaskThreshold", &MaterialInstance::setMaskThreshold)
     .function("setDoubleSided", &MaterialInstance::setDoubleSided)
-    .function("setCullingMode", &MaterialInstance::setCullingMode);
+    .function("setCullingMode", &MaterialInstance::setCullingMode)
+    .function("setColorWrite", &MaterialInstance::setColorWrite)
+    .function("setDepthWrite", &MaterialInstance::setDepthWrite)
+    .function("setDepthCulling", &MaterialInstance::setDepthCulling);
 
 class_<TextureSampler>("TextureSampler")
     .constructor<backend::SamplerMinFilter, backend::SamplerMagFilter, backend::SamplerWrapMode>();