add runtime color filter support to platform

Flag: com.android.graphics.hwui.flags.runtime_color_filters_blenders

Test: atest CtsUiRenderingTestCases:RuntimeColorFilterTests
Bug: b/358126864

Change-Id: If569957489f5352455c419074219125f6e9cc43e
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 0b95148..20301d2 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -18,7 +18,9 @@
 #include "ColorFilter.h"
 
 #include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
 #include "SkBlendMode.h"
+#include "include/effects/SkRuntimeEffect.h"
 
 namespace android {
 
@@ -89,6 +91,78 @@
             filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
         }
     }
+
+    static jlong RuntimeColorFilter_createColorFilter(JNIEnv* env, jobject, jstring agsl) {
+        ScopedUtfChars strSksl(env, agsl);
+        auto result = SkRuntimeEffect::MakeForColorFilter(SkString(strSksl.c_str()),
+                                                          SkRuntimeEffect::Options{});
+        if (result.effect.get() == nullptr) {
+            doThrowIAE(env, result.errorText.c_str());
+            return 0;
+        }
+        auto builder = new SkRuntimeEffectBuilder(std::move(result.effect));
+        auto* runtimeColorFilter = new RuntimeColorFilter(builder);
+        runtimeColorFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(runtimeColorFilter));
+    }
+
+    static void RuntimeColorFilter_updateUniformsFloatArray(JNIEnv* env, jobject,
+                                                            jlong colorFilterPtr,
+                                                            jstring uniformName,
+                                                            jfloatArray uniforms,
+                                                            jboolean isColor) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        AutoJavaFloatArray autoValues(env, uniforms, 0, kRO_JNIAccess);
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length(),
+                                   isColor);
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsFloats(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                                        jstring uniformName, jfloat value1,
+                                                        jfloat value2, jfloat value3, jfloat value4,
+                                                        jint count) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        const float values[4] = {value1, value2, value3, value4};
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), values, count, false);
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsIntArray(JNIEnv* env, jobject,
+                                                          jlong colorFilterPtr, jstring uniformName,
+                                                          jintArray uniforms) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        AutoJavaIntArray autoValues(env, uniforms, 0);
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length());
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsInts(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                                      jstring uniformName, jint value1, jint value2,
+                                                      jint value3, jint value4, jint count) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        const int values[4] = {value1, value2, value3, value4};
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), values, count);
+        }
+    }
+
+    static void RuntimeColorFilter_updateChild(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                               jstring childName, jlong childPtr) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, childName);
+        auto* child = reinterpret_cast<SkFlattenable*>(childPtr);
+        if (filter && child) {
+            filter->updateChild(env, name.c_str(), child);
+        }
+    }
 };
 
 static const JNINativeMethod colorfilter_methods[] = {
@@ -107,6 +181,20 @@
         {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
         {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
 
+static const JNINativeMethod runtime_color_filter_methods[] = {
+        {"nativeCreateRuntimeColorFilter", "(Ljava/lang/String;)J",
+         (void*)ColorFilterGlue::RuntimeColorFilter_createColorFilter},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloatArray},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloats},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsIntArray},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsInts},
+        {"nativeUpdateChild", "(JLjava/lang/String;J)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateChild}};
+
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
                                   NELEM(colorfilter_methods));
@@ -118,7 +206,10 @@
                                   NELEM(lighting_methods));
     android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter",
                                   colormatrix_methods, NELEM(colormatrix_methods));
-    
+    android::RegisterMethodsOrDie(env, "android/graphics/RuntimeColorFilter",
+                                  runtime_color_filter_methods,
+                                  NELEM(runtime_color_filter_methods));
+
     return 0;
 }
 
diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
new file mode 100644
index 0000000..46db863
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RuntimeEffectUtils.h"
+
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                         const float values[], int count, bool isColor) {
+    SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                       const int values[], int count) {
+    SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+                 SkFlattenable* childEffect) {
+    SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
+    if (builderChild.fChild == nullptr) {
+        ThrowIAEFmt(env, "unable to find shader named %s", childName);
+        return;
+    }
+
+    builderChild = sk_ref_sp(childEffect);
+}
+
+}  // namespace uirenderer
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/jni/RuntimeEffectUtils.h b/libs/hwui/jni/RuntimeEffectUtils.h
new file mode 100644
index 0000000..75623c0
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RUNTIMEEFFECTUTILS_H
+#define RUNTIMEEFFECTUTILS_H
+
+#include "GraphicsJNI.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                         const float values[], int count, bool isColor);
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                       const int values[], int count);
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+                 SkFlattenable* childEffect);
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // MAIN_RUNTIMEEFFECTUTILS_H