Add integer uniforms to runtime effects

Bug: skia:11803
Change-Id: I925f14be282b96355721986de6049090b35adf3d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/391856
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 4fc3718..2699db1 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -12,6 +12,13 @@
 
   * Skia's GPU backend no longer supports NVPR. Our more recent path renderers are more
     performant and are not limited to nVidia hardware.
+
+  * SkRuntimeEffect now supports uniforms of type int, int2, int3, and int4. Per the OpenGL ES
+    Shading Language Version 1.00 specification, there are few guarantees about the representation
+    or range of integral types, and operations that assume integral representation (eg, bitwise),
+    are not supported.
+    https://review.skia.org/391856
+
 * * *
 
 Milestone 90
diff --git a/gm/runtimeintrinsics.cpp b/gm/runtimeintrinsics.cpp
index fef08fe..378c589 100644
--- a/gm/runtimeintrinsics.cpp
+++ b/gm/runtimeintrinsics.cpp
@@ -444,34 +444,42 @@
 /*
   Specialized shader for testing relational operators.
 */
-static SkString make_bvec_sksl(const char* fn) {
+static SkString make_bvec_sksl(const char* type, const char* fn) {
     // We use negative floats, to ensure that the integer variants are working with the correct
     // interpretation of the data.
     return SkStringPrintf(
+            "uniform %s2 v1;"
             "half4 main(float2 p) {"
-            "    float2 v1 = float2(-2.0);"
             "    p.x = p.x < 0.33 ? -3.0 : (p.x < 0.66 ? -2.0 : -1.0);"
             "    p.y = p.y < 0.33 ? -3.0 : (p.y < 0.66 ? -2.0 : -1.0);"
             "    bool2 cmp = %s;"
             "    return half4(cmp.x ? 1.0 : 0.0, cmp.y ? 1.0 : 0.0, 0, 1);"
             "}",
-            fn);
+            type, fn);
 }
 
+template <typename T = float>
 static void plot_bvec(SkCanvas* canvas, const char* fn, const char* label) {
     canvas->save();
 
     draw_label(canvas, label);
 
-    auto [effect, error] = SkRuntimeEffect::Make(make_bvec_sksl(fn));
+    const char* type = std::is_integral_v<T> ? "int" : "float";
+    auto [effect, error] = SkRuntimeEffect::Make(make_bvec_sksl(type, fn));
     if (!effect) {
         SkDebugf("Error: %s\n", error.c_str());
         return;
     }
 
+    T uniformData[2] = { -2, -2 };
+    sk_sp<SkData> uniforms = SkData::MakeWithCopy(uniformData, sizeof(uniformData));
+
     draw_shader(canvas,
-                effect->makeShader(/*uniforms=*/nullptr, /*children=*/nullptr, /*childCount=*/0,
-                                   /*localMatrix=*/nullptr, /*isOpaque=*/false));
+                effect->makeShader(uniforms,
+                                   /*children=*/nullptr,
+                                   /*childCount=*/0,
+                                   /*localMatrix=*/nullptr,
+                                   /*isOpaque=*/false));
 
     canvas->restore();
     next_column(canvas);
@@ -485,22 +493,22 @@
     canvas->translate(kPadding, kPadding);
     canvas->save();
 
-    plot_bvec(canvas, "lessThan(p, v1)",                  "lessThan");
-    plot_bvec(canvas, "lessThan(int2(p), int2(v1))",      "lessThan(int)");
-    plot_bvec(canvas, "lessThanEqual(p, v1)",             "lessThanEqual");
-    plot_bvec(canvas, "lessThanEqual(int2(p), int2(v1))", "lessThanEqual(int)");
+    plot_bvec<float>(canvas, "lessThan(p, v1)",            "lessThan");
+    plot_bvec<int>  (canvas, "lessThan(int2(p), v1)",      "lessThan(int)");
+    plot_bvec<float>(canvas, "lessThanEqual(p, v1)",       "lessThanEqual");
+    plot_bvec<int>  (canvas, "lessThanEqual(int2(p), v1)", "lessThanEqual(int)");
     next_row(canvas);
 
-    plot_bvec(canvas, "greaterThan(p, v1)",                  "greaterThan");
-    plot_bvec(canvas, "greaterThan(int2(p), int2(v1))",      "greaterThan(int)");
-    plot_bvec(canvas, "greaterThanEqual(p, v1)",             "greaterThanEqual");
-    plot_bvec(canvas, "greaterThanEqual(int2(p), int2(v1))", "greaterThanEqual(int)");
+    plot_bvec<float>(canvas, "greaterThan(p, v1)",            "greaterThan");
+    plot_bvec<int>  (canvas, "greaterThan(int2(p), v1)",      "greaterThan(int)");
+    plot_bvec<float>(canvas, "greaterThanEqual(p, v1)",       "greaterThanEqual");
+    plot_bvec<int>  (canvas, "greaterThanEqual(int2(p), v1)", "greaterThanEqual(int)");
     next_row(canvas);
 
-    plot_bvec(canvas, "equal(p, v1)",                "equal");
-    plot_bvec(canvas, "equal(int2(p), int2(v1))",    "equal(int)");
-    plot_bvec(canvas, "notEqual(p, v1)",             "notEqual");
-    plot_bvec(canvas, "notEqual(int2(p), int2(v1))", "notEqual(int)");
+    plot_bvec<float>(canvas, "equal(p, v1)",          "equal");
+    plot_bvec<int>  (canvas, "equal(int2(p), v1)",    "equal(int)");
+    plot_bvec<float>(canvas, "notEqual(p, v1)",       "notEqual");
+    plot_bvec<int>  (canvas, "notEqual(int2(p), v1)", "notEqual(int)");
     next_row(canvas);
 
     plot_bvec(canvas, "equal(   lessThanEqual(p, v1), greaterThanEqual(p, v1))", "equal(bvec)");
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index 76763d9..9ae7374 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -50,6 +50,10 @@
             kFloat2x2,
             kFloat3x3,
             kFloat4x4,
+            kInt,
+            kInt2,
+            kInt3,
+            kInt4,
         };
 
         enum Flags {
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index be38e28..62bd8a6 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -125,6 +125,11 @@
     if (type == ctx.fTypes.fFloat4x4.get()) { v->type = Type::kFloat4x4; return true; }
     if (type == ctx.fTypes.fHalf4x4.get())  { v->type = Type::kFloat4x4; return true; }
 
+    if (type == ctx.fTypes.fInt.get())  { v->type = Type::kInt;  return true; }
+    if (type == ctx.fTypes.fInt2.get()) { v->type = Type::kInt2; return true; }
+    if (type == ctx.fTypes.fInt3.get()) { v->type = Type::kInt3; return true; }
+    if (type == ctx.fTypes.fInt4.get()) { v->type = Type::kInt4; return true; }
+
     return false;
 }
 
@@ -297,6 +302,7 @@
 }
 
 size_t SkRuntimeEffect::Uniform::sizeInBytes() const {
+    static_assert(sizeof(int) == sizeof(float));
     auto element_size = [](Type type) -> size_t {
         switch (type) {
             case Type::kFloat:  return sizeof(float);
@@ -307,6 +313,11 @@
             case Type::kFloat2x2: return sizeof(float) * 4;
             case Type::kFloat3x3: return sizeof(float) * 9;
             case Type::kFloat4x4: return sizeof(float) * 16;
+
+            case Type::kInt:  return sizeof(int);
+            case Type::kInt2: return sizeof(int) * 2;
+            case Type::kInt3: return sizeof(int) * 3;
+            case Type::kInt4: return sizeof(int) * 4;
             default: SkUNREACHABLE;
         }
     };
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index 8ec0a4f..b79936a 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -128,33 +128,29 @@
 
     void onSetData(const GrGLSLProgramDataManager& pdman,
                    const GrFragmentProcessor& _proc) override {
+        using Type = SkRuntimeEffect::Uniform::Type;
         size_t uniIndex = 0;
         const GrSkSLFP& outer = _proc.cast<GrSkSLFP>();
         const uint8_t* uniformData = outer.fUniforms->bytes();
         for (const auto& v : outer.fEffect->uniforms()) {
-            const float* data = reinterpret_cast<const float*>(uniformData + v.offset);
+            const UniformHandle handle = fUniformHandles[uniIndex++];
+            auto floatData = [=] { return SkTAddOffset<const float>(uniformData, v.offset); };
+            auto intData = [=] { return SkTAddOffset<const int>(uniformData, v.offset); };
             switch (v.type) {
-                case SkRuntimeEffect::Uniform::Type::kFloat:
-                    pdman.set1fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
-                case SkRuntimeEffect::Uniform::Type::kFloat2:
-                    pdman.set2fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
-                case SkRuntimeEffect::Uniform::Type::kFloat3:
-                    pdman.set3fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
-                case SkRuntimeEffect::Uniform::Type::kFloat4:
-                    pdman.set4fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
-                case SkRuntimeEffect::Uniform::Type::kFloat2x2:
-                    pdman.setMatrix2fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
-                case SkRuntimeEffect::Uniform::Type::kFloat3x3:
-                    pdman.setMatrix3fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
-                case SkRuntimeEffect::Uniform::Type::kFloat4x4:
-                    pdman.setMatrix4fv(fUniformHandles[uniIndex++], v.count, data);
-                    break;
+                case Type::kFloat:  pdman.set1fv(handle, v.count, floatData()); break;
+                case Type::kFloat2: pdman.set2fv(handle, v.count, floatData()); break;
+                case Type::kFloat3: pdman.set3fv(handle, v.count, floatData()); break;
+                case Type::kFloat4: pdman.set4fv(handle, v.count, floatData()); break;
+
+                case Type::kFloat2x2: pdman.setMatrix2fv(handle, v.count, floatData()); break;
+                case Type::kFloat3x3: pdman.setMatrix3fv(handle, v.count, floatData()); break;
+                case Type::kFloat4x4: pdman.setMatrix4fv(handle, v.count, floatData()); break;
+
+                case Type::kInt:  pdman.set1iv(handle, v.count, intData()); break;
+                case Type::kInt2: pdman.set2iv(handle, v.count, intData()); break;
+                case Type::kInt3: pdman.set3iv(handle, v.count, intData()); break;
+                case Type::kInt4: pdman.set4iv(handle, v.count, intData()); break;
+
                 default:
                     SkDEBUGFAIL("Unsupported uniform type");
                     break;
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index b295e93..9019a64 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -43,9 +43,8 @@
 }
 
 DEF_TEST(SkRuntimeEffectInvalid_LimitedUniformTypes, r) {
-    // Runtime SkSL supports a limited set of uniform types. No bool, or int, for example:
+    // Runtime SkSL supports a limited set of uniform types. No bool, for example:
     test_invalid_effect(r, "uniform bool b;" EMPTY_MAIN, "uniform");
-    test_invalid_effect(r, "uniform int i;"  EMPTY_MAIN, "uniform");
 }
 
 DEF_TEST(SkRuntimeEffectInvalid_NoInVariables, r) {
@@ -214,6 +213,7 @@
     TestEffect effect(r, surface);
 
     using float4 = std::array<float, 4>;
+    using int4 = std::array<int, 4>;
 
     // Local coords
     effect.build("half4 main(float2 p) { return half4(half2(p - 0.5), 0, 1); }");
@@ -226,6 +226,13 @@
     effect.uniform("gColor") = float4{ 1.0f, 0.0f, 0.0f, 0.498f };
     effect.test(0x7F00007F);  // Tests that we clamp to valid premul
 
+    // Same, with integer uniforms
+    effect.build("uniform int4 gColor; half4 main() { return half4(gColor) / 255.0; }");
+    effect.uniform("gColor") = int4{ 0x00, 0x40, 0xBF, 0xFF };
+    effect.test(0xFFBF4000);
+    effect.uniform("gColor") = int4{ 0xFF, 0x00, 0x00, 0x7F };
+    effect.test(0x7F00007F);  // Tests that we clamp to valid premul
+
     // Test sk_FragCoord (device coords). Rotate the canvas to be sure we're seeing device coords.
     // Since the surface is 2x2, we should see (0,0), (1,0), (0,1), (1,1). Multiply by 0.498 to
     // make sure we're not saturating unexpectedly.
diff --git a/tools/viewer/SkSLSlide.cpp b/tools/viewer/SkSLSlide.cpp
index 58de5aa..8fa3bfc 100644
--- a/tools/viewer/SkSLSlide.cpp
+++ b/tools/viewer/SkSLSlide.cpp
@@ -199,6 +199,21 @@
                 }
                 break;
             }
+            case SkRuntimeEffect::Uniform::Type::kInt:
+            case SkRuntimeEffect::Uniform::Type::kInt2:
+            case SkRuntimeEffect::Uniform::Type::kInt3:
+            case SkRuntimeEffect::Uniform::Type::kInt4: {
+                int rows = ((int)v.type - (int)SkRuntimeEffect::Uniform::Type::kInt) + 1;
+                int* i = reinterpret_cast<int*>(data);
+                for (int c = 0; c < v.count; ++c, i += rows) {
+                    SkString name = v.isArray() ? SkStringPrintf("%s[%d]", v.name.c_str(), c)
+                                                : v.name;
+                    ImGui::PushID(c);
+                    ImGui::DragScalarN(name.c_str(), ImGuiDataType_S32, i, rows, 1.0f);
+                    ImGui::PopID();
+                }
+                break;
+            }
         }
     }