Add padding to properly align mixed-size uniforms.

The UniformManager would previously assert if it detected an alignment
error, but did not actually have any mechanism for automatically adding
padding between misaligned elements. If a user specifies a uniform
layout like `uniform float a; uniform float4 b;`, we now insert padding
between `a` and `b` so that `b` will be properly aligned. (This reuses
the logic that was originally used to trigger the alignment assertion.)

This CL fixes the bug and adds a test. The test is only active for
elements of matching size; mixed half- and full-precision elements
will need improved test logic.

Change-Id: I8c04eb6350fa73bdbcd1a08e1a45b17fee0d4194
Bug: skia:13478
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/555440
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/src/gpu/graphite/UniformManager.cpp b/src/gpu/graphite/UniformManager.cpp
index cddb62b..d9145c7 100644
--- a/src/gpu/graphite/UniformManager.cpp
+++ b/src/gpu/graphite/UniformManager.cpp
@@ -329,7 +329,6 @@
     }
 };
 
-#ifdef SK_DEBUG
 // To determine whether a current offset is aligned, we can just 'and' the lowest bits with the
 // alignment mask. A value of 0 means aligned, any other value is how many bytes past alignment we
 // are. This works since all alignments are powers of 2. The mask is always (alignment - 1).
@@ -468,19 +467,20 @@
 // taking into consideration all alignment requirements. The uniformOffset is set to the offset for
 // the new uniform, and currentOffset is updated to be the offset to the end of the new uniform.
 static uint32_t get_ubo_aligned_offset(uint32_t* currentOffset,
-                                       uint32_t* maxAlignment,
                                        SkSLType type,
                                        int arrayCount) {
+    // TODO(skia:13478): these alignments are currently Metal-specific; we are likely to need
+    // fixes for other Layout types.
     uint32_t alignmentMask = sksltype_to_alignment_mask(type);
-    if (alignmentMask > *maxAlignment) {
-        *maxAlignment = alignmentMask;
-    }
     uint32_t offsetDiff = *currentOffset & alignmentMask;
     if (offsetDiff != 0) {
         offsetDiff = alignmentMask - offsetDiff + 1;
     }
     uint32_t uniformOffset = *currentOffset + offsetDiff;
     SkASSERT(sizeof(float) == 4);
+
+    // TODO(skia:13478): these size calculations are currently Metal-specific; we are likely to need
+    // fixes for other Layout types.
     if (arrayCount) {
         *currentOffset = uniformOffset + sksltype_to_mtl_size(type) * arrayCount;
     } else {
@@ -488,7 +488,6 @@
     }
     return uniformOffset;
 }
-#endif // SK_DEBUG
 
 SkSLType UniformManager::getUniformTypeForLayout(SkSLType type) {
     if (fLayout != Layout::kMetal) {
@@ -543,17 +542,13 @@
 }
 
 void UniformManager::reset() {
-#ifdef SK_DEBUG
     fCurUBOOffset = 0;
-    fCurUBOMaxAlignment = 0;
-#endif
     fOffset = 0;
     fStorage.rewind();
 }
 
 void UniformManager::checkReset() const {
     SkASSERT(fCurUBOOffset == 0);
-    SkASSERT(fCurUBOMaxAlignment == 0);
     SkASSERT(fOffset == 0);
     SkASSERT(fStorage.empty());
 }
@@ -570,17 +565,7 @@
     SkASSERT(fExpectedUniforms[fExpectedUniformIndex].type() == type);
     SkASSERT((fExpectedUniforms[fExpectedUniformIndex].count() == 0 && count == 1) ||
              fExpectedUniforms[fExpectedUniformIndex].count() == count);
-#ifdef SK_DEBUG
-    fExpectedUniformIndex++;
-
-    SkSLType revisedType = this->getUniformTypeForLayout(type);
-
-    uint32_t debugOffset = get_ubo_aligned_offset(&fCurUBOOffset,
-                                                  &fCurUBOMaxAlignment,
-                                                  revisedType,
-                                                  count);
-    SkASSERT(debugOffset == fOffset);
-#endif
+    SkDEBUGCODE(fExpectedUniformIndex++;)
 }
 
 void UniformManager::doneWithExpectedUniforms() {
@@ -589,10 +574,18 @@
 }
 
 void UniformManager::write(SkSLType type, unsigned int count, const void* src) {
-    SkDEBUGCODE(this->checkExpected(type, (count == SkUniform::kNonArray) ? 1 : count);)
+    this->checkExpected(type, (count == SkUniform::kNonArray) ? 1 : count);
 
     SkSLType revisedType = this->getUniformTypeForLayout(type);
 
+    // Insert padding as needed to get the correct uniform alignment.
+    uint32_t alignedOffset = get_ubo_aligned_offset(&fCurUBOOffset, revisedType, count);
+    SkASSERT(alignedOffset >= fOffset);
+    if (alignedOffset > fOffset) {
+        fStorage.append(alignedOffset - fOffset);
+        fOffset = alignedOffset;
+    }
+
     uint32_t bytesNeeded = fWriteUniform(revisedType, CType::kDefault,
                                          /*dest=*/nullptr, count, /*src=*/nullptr);
     char* dst = fStorage.append(bytesNeeded);
diff --git a/src/gpu/graphite/UniformManager.h b/src/gpu/graphite/UniformManager.h
index 7d6d08e..02fe70c 100644
--- a/src/gpu/graphite/UniformManager.h
+++ b/src/gpu/graphite/UniformManager.h
@@ -71,14 +71,12 @@
 
     WriteUniformFn fWriteUniform;
     Layout fLayout;  // TODO: eventually 'fLayout' will not need to be stored
+    uint32_t fCurUBOOffset = 0;
 #ifdef SK_DEBUG
-    uint32_t fCurUBOOffset;
-    uint32_t fCurUBOMaxAlignment;
-
     SkSpan<const SkUniform> fExpectedUniforms;
     int fExpectedUniformIndex = 0;
 #endif // SK_DEBUG
-    uint32_t fOffset;
+    uint32_t fOffset = 0;
 
     SkTDArray<char> fStorage;
 };
diff --git a/tests/graphite/UniformManagerTest.cpp b/tests/graphite/UniformManagerTest.cpp
index 1fcf6c4..0f1b8af 100644
--- a/tests/graphite/UniformManagerTest.cpp
+++ b/tests/graphite/UniformManagerTest.cpp
@@ -193,3 +193,56 @@
         }
     }
 }
+
+DEF_TEST(UniformManagerCheckPaddingScalarVector, r) {
+    // Verify that the uniform manager properly adds padding between pairs of scalar/vector.
+    // This test doesn't check mixed 16/32-bit padding yet.
+    for (Layout layout : kLayouts) {
+        UniformManager mgr(layout);
+
+        for (SkSLType type1 : kTypes) {
+            const int vecLength1 = SkSLTypeVecLength(type1);
+            if (vecLength1 < 1) {
+                continue;
+            }
+
+            for (SkSLType type2 : kTypes) {
+                const int vecLength2 = SkSLTypeVecLength(type2);
+                if (vecLength2 < 1) {
+                    continue;
+                }
+
+                // Write two scalar/vector uniforms.
+                const SkUniform expectations[] = {{"a", type1}, {"b", type2}};
+                mgr.setExpectedUniforms(SkSpan(expectations));
+                mgr.write(type1, SkUniform::kNonArray, kFloats);
+                mgr.write(type2, SkUniform::kNonArray, kFloats);
+                mgr.doneWithExpectedUniforms();
+
+                // Verify that all elements are the same size.
+                const size_t elementSize1 = element_size(layout, type1);
+                const size_t elementSize2 = element_size(layout, type2);
+                if (elementSize1 == elementSize2) {
+                    // The expected uniform layout is listed as strings below.
+                    // A/B: uniform values.
+                    // a/b: padding as part of the uniform type (vec3 takes 4 slots)
+                    // _  : padding between uniforms for alignment
+                    static constexpr const char* kExpectedLayout[5][5] = {
+                        { "", "",      "",       "",         ""         },
+                        { "", "AB",    "A_BB",   "A___BBBb", "A___BBBB" },
+                        { "", "AAB",   "AABB",   "AA__BBBb", "AA__BBBB" },
+                        { "", "AAAaB", "AAAaBB", "AAAaBBBb", "AAAaBBBB" },
+                        { "", "AAAAB", "AAAABB", "AAAABBBb", "AAAABBBB" },
+                    };
+                    const size_t size = strlen(kExpectedLayout[vecLength1][vecLength2]) *
+                                        elementSize1;
+                    SkUniformDataBlock uniformData = mgr.peekData();
+                    REPORTER_ASSERT(r, uniformData.size() == size,
+                                    "Layout:%d Types:%d %d padding test failed",
+                                    (int)layout, (int)type1, (int)type2);
+                }
+                mgr.reset();
+            }
+        }
+    }
+}